commit dee9e248421ccc00499440865aced2ef7f240b2b Author: JBuildr <8625880+JBuildr@users.noreply.github.com> Date: Wed Oct 7 18:21:13 2026 +0200 Nubix 0.3.0 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ddf8f1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Keep Unix line endings for everything that runs on Linux/macOS, even when committed from Windows. +* text=auto eol=lf +*.sh text eol=lf +*.png binary +*.ttf binary diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c3b014d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +# Nubix — build and unit-test the host (macOS) version on every push and pull request. +# Copyright (C) 2026 Nubix contributors +# SPDX-License-Identifier: GPL-3.0-only +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + host-macos: + name: Host build + tests (macOS) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies (Homebrew) + run: brew install cmake pkg-config sdl2 sdl2_ttf ffmpeg opus openssl@3 + + - name: Build and run unit tests + run: bash scripts/build-host.sh --test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..09f67fe --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,82 @@ +# Nubix — build the PS5 release on GitHub and publish it. +# +# Push a tag like v0.3.0 (it must match project(nubix VERSION ...) in CMakeLists.txt). The job +# builds the PS5 homebrew zip inside the ps5dev Docker image, packs the GPL "Corresponding Source" +# of everything linked into the binary, and attaches both to a GitHub release for that tag. +# Started by hand (Actions tab -> Release -> Run workflow) it only builds and keeps the files as a +# workflow artifact, without creating a release. +# Copyright (C) 2026 Nubix contributors +# SPDX-License-Identifier: GPL-3.0-only +name: Release + +on: + push: + tags: ['v*'] + workflow_dispatch: + +permissions: + contents: write + +jobs: + ps5: + name: PS5 build + corresponding source + runs-on: ubuntu-24.04 # Docker is only available on Linux runners + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + + - name: Read version + id: ver + run: | + ver="$(sed -n 's/^project(nubix VERSION \([0-9.]*\).*/\1/p' CMakeLists.txt)" + [ -n "$ver" ] || { echo "version not found in CMakeLists.txt"; exit 1; } + echo "version=$ver" >> "$GITHUB_OUTPUT" + if [ "${GITHUB_REF_TYPE}" = "tag" ] && [ "${GITHUB_REF_NAME}" != "v$ver" ]; then + echo "::error::tag ${GITHUB_REF_NAME} does not match CMakeLists.txt version v$ver"; exit 1 + fi + + - name: Point SOURCE.txt at this repository + run: sed -i "s#/nubix#${GITHUB_REPOSITORY}#g" SOURCE.txt + + # First, while dist/ still belongs to the runner user (the Docker steps below write as root). + - name: Corresponding source bundle + run: bash scripts/make-source-bundle.sh + + - name: Build libdatachannel for PS5 + run: bash scripts/build-deps.sh ps5 + + - name: Build PS5 homebrew + run: bash scripts/build-ps5.sh + + - name: Collect release files + run: | + sudo chown -R "$(id -u):$(id -g)" . + v="${{ steps.ver.outputs.version }}" + mkdir -p release + cp "build-ps5/nubix-v$v-websrv.zip" release/ + cp "dist/nubix-v$v-corresponding-source.tar.xz" "dist/nubix-v$v-corresponding-source.MANIFEST" release/ + (cd release && sha256sum * > SHA256SUMS.txt) + ls -la release + + - name: Upload as workflow artifact + uses: actions/upload-artifact@v4 + with: + name: nubix-v${{ steps.ver.outputs.version }} + path: release/ + retention-days: 14 + + - name: Publish GitHub release + if: github.ref_type == 'tag' + uses: softprops/action-gh-release@v2 + with: + name: Nubix ${{ github.ref_name }} + prerelease: ${{ startsWith(steps.ver.outputs.version, '0.') }} + files: release/* + body: | + **Before your jailbroken PS5 goes online, block Sony's servers with a DNS server** (only Sony's domains, see the README section "Before you go online"). + + **Install:** download `nubix-${{ github.ref_name }}-websrv.zip`, unzip it and copy the `nubix` folder to `/data/homebrew/` on your PS5, then start **Nubix** from the websrv Homebrew Launcher. See the README for details. + + `nubix-${{ github.ref_name }}-corresponding-source.tar.xz` is the complete source code of everything in the binary (GPL-3.0 requirement); you do not need it to use the app. + + > Unofficial, non-commercial project. Not affiliated with Microsoft or Sony. Using third-party clients may violate their terms of service; use at your own risk. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2809f85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build*/ +deps/ +dist/ +.cache/ +.DS_Store +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3e461c0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,131 @@ +# Nubix — native Xbox Cloud Gaming client for PS5 (homebrew payload) + host build. +# Copyright (C) 2026 Nubix contributors +# SPDX-License-Identifier: GPL-3.0-only +# +# Host: scripts/build-host.sh (or: cmake -S . -B build-host && cmake --build build-host) +# PS5: scripts/build-ps5.sh (inside docker "ps5dev" with the prospero toolchain) +cmake_minimum_required(VERSION 3.20) +project(nubix VERSION 0.3.0 LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) +endif() + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +include(XcPlatform) + +option(XC_BUILD_TESTS "Build unit tests (host only)" ON) +option(XC_BUILD_CLI "Build the xc-cli tool (host only)" ON) +option(XC_BUILD_LINKCHECK "Build xc-linkcheck (links every third-party dependency)" OFF) +option(XC_WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF) + +include(XcDeps) + +# ---- version --------------------------------------------------------------- +set(XC_VERSION "${PROJECT_VERSION}") +find_package(Git QUIET) +if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") + execute_process(COMMAND "${GIT_EXECUTABLE}" describe --always --dirty --tags + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE _git_desc + OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET RESULT_VARIABLE _git_rc) + if(_git_rc EQUAL 0 AND _git_desc) + set(XC_VERSION "${PROJECT_VERSION}-${_git_desc}") + endif() +endif() + +# ---- common compile settings ---------------------------------------------- +add_library(xc_options INTERFACE) +target_include_directories(xc_options INTERFACE "${CMAKE_SOURCE_DIR}/src") +target_include_directories(xc_options SYSTEM INTERFACE "${CMAKE_SOURCE_DIR}/third_party") +target_compile_definitions(xc_options INTERFACE XC_VERSION="${XC_VERSION}") +target_compile_options(xc_options INTERFACE -Wall -Wextra -Wno-unused-parameter) +if(XC_WARNINGS_AS_ERRORS) + target_compile_options(xc_options INTERFACE -Werror) +endif() +if(XC_PS5) + target_compile_definitions(xc_options INTERFACE XC_PS5=1) +else() + target_compile_definitions(xc_options INTERFACE XC_SOURCE_ASSETS_DIR="${CMAKE_SOURCE_DIR}/assets") +endif() + +# ---- xc_core: auth, http, config, catalog, gssv, protocol, log ------------- +file(GLOB XC_CORE_SOURCES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/src/core/*.cpp") +add_library(xc_core STATIC ${XC_CORE_SOURCES}) +target_link_libraries(xc_core PUBLIC xc_options xc::curl xc::openssl Threads::Threads) + +# ---- xc_platform: paths, notify, gamepad, PS5 shims ------------------------ +set(XC_PLATFORM_SOURCES + src/platform/common.cpp + src/platform/gamepad.cpp + src/platform/net_compat.c) +if(XC_PS5) + list(APPEND XC_PLATFORM_SOURCES src/platform/ps5.cpp src/platform/ps5_shims.c) +else() + list(APPEND XC_PLATFORM_SOURCES src/platform/host.cpp) +endif() +add_library(xc_platform STATIC ${XC_PLATFORM_SOURCES}) +target_link_libraries(xc_platform PUBLIC xc_core xc::sdl2) + +# ---- xc_stream: webrtc, rtp, video, audio, streamer ------------------------- +file(GLOB XC_STREAM_SOURCES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/src/stream/*.cpp") +add_library(xc_stream STATIC ${XC_STREAM_SOURCES}) +target_link_libraries(xc_stream PUBLIC xc_core xc_platform xc::datachannel xc::avcodec xc::avutil xc::opus xc::sdl2) + +# ---- xc_ui: drawing, screens, app, QR --------------------------------------- +file(GLOB XC_UI_SOURCES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/src/ui/*.cpp") +add_library(xc_ui STATIC ${XC_UI_SOURCES}) +target_link_libraries(xc_ui PUBLIC xc_stream xc_platform xc::sdl2_ttf xc::sdl2) +# vendored Nayuki qrcodegen: keep its warnings out of our output +set_source_files_properties("${CMAKE_SOURCE_DIR}/src/ui/qrcodegen.cpp" PROPERTIES COMPILE_OPTIONS "-w") + +# ---- app ---------------------------------------------------------------------- +add_executable(nubix src/main.cpp) +target_link_libraries(nubix PRIVATE xc_ui) + +if(XC_PS5) + # Unstripped ELF keeps symbols for crash analysis; the stripped one is what ships. + set_target_properties(nubix PROPERTIES OUTPUT_NAME "nubix-debug" SUFFIX ".elf") + # SDL2 PS5 port + payload system libraries (most already come from sdl2.pc --static) + target_link_libraries(nubix PRIVATE + SceSystemService SceUserService ScePad SceAudioOut SceVideoOut SceKeyboard SceImeDialog SceNet) + add_custom_command(TARGET nubix POST_BUILD + COMMAND "${CMAKE_STRIP}" -o "${CMAKE_BINARY_DIR}/nubix.elf" "$" + COMMENT "Stripping -> nubix.elf") +else() + # host: make assets reachable next to the binary + add_custom_command(TARGET nubix POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E create_symlink "${CMAKE_SOURCE_DIR}/assets" "$/assets" + COMMENT "Linking assets/ next to nubix") +endif() + +# ---- dependency link check (any target) --------------------------------------------- +if(XC_BUILD_LINKCHECK) + add_executable(xc-linkcheck tools/linkcheck.cpp) + target_link_libraries(xc-linkcheck PRIVATE xc_platform xc::datachannel xc::avcodec xc::avutil xc::opus + xc::curl xc::openssl xc::sdl2_ttf xc::sdl2) + if(XC_PS5) + set_target_properties(xc-linkcheck PROPERTIES SUFFIX ".elf") + target_link_libraries(xc-linkcheck PRIVATE + SceSystemService SceUserService ScePad SceAudioOut SceVideoOut SceKeyboard SceImeDialog SceNet) + endif() +endif() + +# ---- host tools / tests ------------------------------------------------------------ +if(NOT XC_PS5 AND XC_BUILD_CLI) + add_executable(xc-cli tools/xc-cli.cpp tools/cli_gssv.cpp) + target_link_libraries(xc-cli PRIVATE xc_stream xc_platform) +endif() + +if(NOT XC_PS5 AND XC_BUILD_TESTS) + enable_testing() + file(GLOB XC_TEST_SOURCES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/tests/*.cpp") + add_executable(xc-tests ${XC_TEST_SOURCES}) + target_link_libraries(xc-tests PRIVATE xc_ui) + foreach(suite smoke protocol sdp rtp config ratelimit net) + add_test(NAME ${suite} COMMAND xc-tests ${suite}) + endforeach() +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7dca3a7 --- /dev/null +++ b/README.md @@ -0,0 +1,218 @@ +# Nubix + +**Play Xbox Cloud Gaming online on your jailbroken PS5.** + +An **unofficial**, open-source client for **Xbox Cloud Gaming** on jailbroken PS5 consoles. +It runs as a homebrew app (ELF payload) started from the +[websrv](https://github.com/ps5-payload-dev/websrv) Homebrew Launcher and streams cloud games +to your TV with a DualSense as the controller. + +> **Not affiliated with, endorsed by, or sponsored by Microsoft or Sony.** "Xbox", "Xbox Cloud +> Gaming" and "Game Pass" are trademarks of Microsoft; "PlayStation" and "PS5" are trademarks of +> Sony Interactive Entertainment. They are used here only to describe what this software works with. +> Read [Disclaimer](#disclaimer) before you sign in. + +## Status + +| Area | State | +|---|---| +| Microsoft sign-in (code + QR, no browser on the console) | ✅ works on PS5 | +| Cloud library (Game Pass, owned, free-to-play), filters, box art | ✅ works on PS5 | +| Queue / waiting for a server | ✅ works on PS5 | +| Cloud streaming (video, audio, controller, rumble) | ✅ works on PS5 | +| Remote play of your own Xbox console (xHome) | ⚠️ implemented, not tested | +| Native PS5 title / home-screen tile | ❌ not supported (start it from the websrv launcher) | + +Video is decoded in software (FFmpeg). If the picture stutters, set the resolution to 720p. + +## Requirements + +- A jailbroken PS5 that can run ELF payloads (`elfldr` on port 9021) and the + [websrv](https://github.com/ps5-payload-dev/websrv) Homebrew Launcher. +- A Microsoft account with a Game Pass plan that includes cloud gaming (Essential, Premium or + Ultimate, depending on your region), or access to the free, ad-supported cloud offering. +- A wired connection or good 5 GHz Wi-Fi. +- A protective DNS that blocks Sony's servers (see [below](#before-you-go-online-protective-dns)). + +## Before you go online: protective DNS + +**Do this before your jailbroken PS5 connects to the internet for the first time.** A jailbroken +console must never reach Sony's servers: it could download a system update (which can remove the +jailbreak for good) and it reports to Sony. Nubix itself needs the internet, so you cannot simply +cut the connection. Instead, point the PS5 at a DNS server that only lets through what you allow. + +### The included DNS server: `tools/sony_dns.py` + +[`tools/sony_dns.py`](tools/sony_dns.py) (Python 3.8+, no extra packages) works in **allowlist mode**: + +- **Everything is blocked by default.** +- Allowed are only the domains Nubix needs for Xbox Cloud Gaming: + `login.microsoftonline.com`, `login.live.com`, `xboxlive.com`, `displaycatalog.mp.microsoft.com`, + `store-images.s-microsoft.com` and `stun.l.google.com` (each with its subdomains). +- **Sony / PlayStation domains are always blocked**, even if you allow them by mistake: + `playstation.com`, `playstation.net`, `playstation.org`, `sonyentertainmentnetwork.com`, + `scea.com`, `sbdnpd.com`, `ribob01.net`. +- If you want the PS5 to reach anything else, add the domain to + [`tools/allowlist.txt`](tools/allowlist.txt) (one per line). **You are responsible for every + domain you add.** + +Run it on a PC in the same network as the PS5: + +``` +python tools\sony_dns.py (Windows, terminal opened with "Run as administrator") +sudo python3 tools/sony_dns.py (macOS / Linux) +``` + +It prints the IP address to enter on the PS5. On Windows, allow Python through the firewall for +**private networks** when asked, otherwise the PS5's queries never arrive. The PC has to stay on +while the PS5 is online, so give it a fixed IP in your router. + +Useful options: +- `--log-all` shows every query the PS5 makes (ALLOW / BLOCK), so you can see what to add. +- `--allow example.com` allows a domain without editing the file. +- `--map host=IP` answers a name with a fixed address, for example if your jailbreak method + redirects `manuals.playstation.net` to your own exploit host. This works even for Sony names. +- `--upstream 9.9.9.9` uses another upstream DNS (default `1.1.1.1`). + +### On the PS5 + +*Settings → Network → Settings → Set Up Internet Connection* → your connection → +*Advanced Settings* → *DNS Settings: Manual* → **Primary DNS** = the IP shown by the script, +**Secondary DNS** = the same IP (a public DNS as secondary would bypass the protection). +Never sign in to PSN on the jailbroken console and ignore any update prompt. The PS5's own +"Test Internet Connection" will report a PSN failure with this setup; that is expected. + +### Using your own DNS server instead + +With Pi-hole, AdGuard Home or NextDNS you can do the same: block all Sony domains listed above +(with subdomains) and ideally allow only the Nubix domains. **Do not** use generic PS5 blocklists +that also block CDNs such as `akamai.net`, `akamaiedge.net`, `akadns.net`, `edgekey.net` or +`edgesuite.net`, and do not set the DNS to `127.0.0.2`: Nubix would no longer be able to sign in, +load your library or stream. + +## Installation + +1. Download `nubix-v-websrv.zip` (e.g. `nubix-v0.3.0-websrv.zip`) from the releases page. +2. Unzip it and copy the `nubix` folder to `/data/homebrew/` on the PS5 (FTP, or a USB drive + under `/mnt/usb0/homebrew/`). You should end up with + `/data/homebrew/nubix/{eboot.elf, homebrew.js, sce_sys/icon0.png, assets/}`. +3. Start websrv, open the Homebrew Launcher (the launcher PKG on the PS5, or + `http://:8080` from a phone or PC) and start **Nubix**. + *Start (verbose log)* writes a detailed log for troubleshooting. + +To update, replace the folder. Your sign-in and settings are kept in `/data/nubix/`. + +## First start + +The app shows a code and a QR code. Open **https://www.microsoft.com/link** on your phone or PC +(or scan the QR code), enter the code and sign in. The app then stores a refresh token, so you +only do this once. + +Library tabs: **Cloud library**, **My consoles**, **Settings** (switch with L1/R1). +△ cycles the filter: *All games*, *Game Pass*, *Owned* and *Free to play*. + +## Controls + +The PS5 system keeps the PS and Create buttons to itself, so the app never sees them. + +| DualSense | Xbox controller | +|---|---| +| ✕ / ○ / □ / △ | A / B / X / Y | +| L1 / R1, L2 / R2, L3 / R3 | LB / RB, LT / RT, LS / RS | +| Options | Menu | +| Touchpad click | View | +| Hold **Options + touchpad** for 1 s | Stream menu (resume, Xbox button, statistics, refresh video, disconnect) | + +In the menus: ✕ = select, ○ = back, L1/R1 = switch tabs, △ = filter. + +## Settings + +- **Stream resolution**: 720p, 1080p, 1080p (high quality). 720p decodes fastest. +- **Maximum bitrate**: 5–20 Mbps. Lower it on Wi-Fi or if the picture stutters. +- **Server region**: automatic or one of the regions Xbox offers your account. +- **Free-to-play fallback**: retry with the ad-supported offering when a title is not in your plan. +- **Extra stick deadzone**, **stream statistics**, **sign out**. + +## Network behaviour + +Nubix talks to the same Microsoft services as the Xbox web player, and paces itself +(`src/core/ratelimit.*`): +- at most 5 requests per second per Microsoft host; +- on HTTP 429 it waits for `Retry-After` (or backs off 2 → 120 s) and only repeats read-only requests; +- at most 4 cloud session starts per 5 minutes; +- the library is cached for 6 hours and box art permanently (`/data/nubix/cache/`); △ on *My consoles* reloads the console list. + +## Troubleshooting + +- Log: `/data/nubix/log.txt` (also viewable at `http://:8080/fs/data/nubix/log.txt`). + Start with *Start (verbose log)* for more detail. +- Stutter or growing delay: 720p and/or a lower bitrate; prefer a wired connection. +- "No cloud gaming access": your account has no plan with cloud gaming in your region. +- Sign-in, library or box art fail only with the protective DNS: start `tools/sony_dns.py --log-all` + and look for BLOCK lines while Nubix loads; please report them so the built-in list can be fixed. +- Close other running homebrew before starting Nubix (websrv can crash otherwise). + +## Privacy and security + +- Your password never reaches the app: sign-in uses Microsoft's device-code flow. +- The refresh token is stored **unencrypted** in `/data/nubix/config.json`. Anyone with + file access to the console (for example an open FTP payload on your network) can copy it. Do not + leave FTP running on untrusted networks. *Settings → Sign out* deletes it; you can also sign out of + all devices in your Microsoft account's security settings. +- The app only contacts Microsoft services (sign-in, Xbox Live, cloud gaming, Microsoft Store catalog) + plus Google's public STUN server (`stun.l.google.com`) to find its public address for WebRTC. + +## Disclaimer + +This is an unofficial, non-commercial hobby project. Microsoft does not authorise third-party clients. Using unofficial clients may violate the Microsoft +Services Agreement and Xbox terms of use, and running homebrew may violate Sony's terms. Your account +or console could be restricted. **Use it at your own risk**, ideally first with a Microsoft account you could afford to lose. The software is provided without any +warranty (see `LICENSE`). Do not use it to circumvent paywalls, regional restrictions or copy protection; +it only streams what your own account is entitled to. + +## Building from source + +Release builds are made by GitHub Actions: pushing a tag `vX.Y.Z` (matching the version in +`CMakeLists.txt`) runs `.github/workflows/release.yml`, which builds the PS5 zip and the +corresponding-source archive and attaches both to the GitHub release. Every push also builds and +tests the host version on macOS (`.github/workflows/ci.yml`). + +To build locally: Docker is required for the PS5 build; the host build (macOS/Linux) is for development and testing. + +```sh +# host (macOS): brew install cmake pkg-config sdl2 sdl2_ttf ffmpeg opus openssl@3 +scripts/build-deps.sh host && scripts/build-host.sh --test # host app, xc-cli, unit tests +scripts/build-deps.sh ps5 && scripts/build-ps5.sh # build-ps5/nubix.elf + dist/nubix/ +PS5_HOST= scripts/deploy.sh install|run|log # FTP install, send to elfldr, fetch log +``` + +Developer tools: +- `nubix --selftest` brings up every subsystem headless + (`SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy`) and exits 0 on success. +- `xc-cli` (host) drives the services without UI: `login`, `status`, `titles`, `consoles`, + `play `, `stream cloud [seconds]`. +- Protocol implementation: `src/core/` (sign-in, catalog, session API, input/message protocol) and + `src/stream/` (WebRTC, RTP/RTCP, decoding, session state machine). + +## Credits + +Nubix stands on the shoulders of the projects that documented the Xbox cloud streaming protocol: +[green-nx](https://github.com/rmrf404/green-nx), [GreenOvercast](https://github.com/Producdevity/GreenOvercast), +[Greenlight](https://github.com/unknownskl/greenlight), [xbox-xcloud-player](https://github.com/unknownskl/xbox-xcloud-player), +[xal-node](https://github.com/unknownskl/xal-node), [XStreaming](https://github.com/Geocld/XStreaming), +[green-vita](https://github.com/Day-OS/green-vita), and on the PS5 homebrew work of +[ps5-payload-dev](https://github.com/ps5-payload-dev) (SDK, websrv, SDL2 port) and +[ProsperoLight](https://github.com/xEasy4Breezy/ProsperoLight). + +## License + +**GPL-3.0-only** (see [`LICENSE`](LICENSE)). Parts of the code are derived from +[green-nx](https://github.com/rmrf404/green-nx) (GPL-3.0) and +[GreenOvercast](https://github.com/Producdevity/GreenOvercast) (MPL-2.0); the affected files say so in +their headers. Third-party components, their licenses and copyright notices: +[`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) and [`THIRD_PARTY_LICENSES.txt`](THIRD_PARTY_LICENSES.txt). + +**Source code for binary releases.** Every release zip is accompanied on the same release page by +`nubix-v-corresponding-source.tar.xz`, which contains the complete corresponding source of +everything linked into `eboot.elf` (built with `scripts/make-source-bundle.sh`). See `SOURCE.txt` in the +zip for exact component versions. diff --git a/SOURCE.txt b/SOURCE.txt new file mode 100644 index 0000000..f828dff --- /dev/null +++ b/SOURCE.txt @@ -0,0 +1,152 @@ +Nubix - Source Code Offer / Corresponding Source +================================================= + +Nubix is free software, licensed under the GNU General Public License, version 3 +only (GPL-3.0-only); see LICENSE. The PS5 binary (nubix.elf / homebrew package) is +statically linked against the third-party libraries listed below. As required by +section 6 of the GPLv3, the complete Corresponding Source for every binary release +is published next to the binary, at the same place and at no further charge: + + https://github.com/JBuildr/nubix/releases + + Release asset: nubix-v-corresponding-source.tar.xz + (e.g. nubix-v0.3.0-corresponding-source.tar.xz for Nubix 0.3.0) + Checksum: nubix-v-corresponding-source.tar.xz.sha256 + Manifest: nubix-v-corresponding-source.MANIFEST + +The archive contains: + - nubix-v-src.tar.xz : the Nubix source tree the binary was built from, + including patches/libdatachannel-*.patch, docker/Dockerfile, CMakeLists.txt, + cmake/ and scripts/ (the complete build and installation scripts); + - upstream/ : the unmodified upstream source of each library in the table below, + as release tarballs (sha256-verified against the pinned sums) or as + `git archive` exports of the exact commits; + - MANIFEST and SHA256SUMS : sha256 and size of every file in the archive. + +The archive is produced by scripts/make-source-bundle.sh (in the Nubix source). +By default that script packages the current working tree +(`git ls-files -co --exclude-standard`, i.e. tracked files including uncommitted +changes plus non-ignored untracked files; docs/ is excluded), or a given git ref +(--nubix-ref ) or directory (--nubix-dir ). + + +How to rebuild the PS5 binary +----------------------------- + 1. Build the toolchain image (Ubuntu 24.04 + clang/lld 18 + the prebuilt + ps5-payload-dev SDK and libraries from pacbrew-repo release v0.40.2): + docker build -t ps5dev docker/ + 2. Build libdatachannel v0.24.6 (+ submodules), with patches/ applied: + scripts/build-deps.sh ps5 + (set LDC_LOCAL_SRC= to use the libdatachannel sources from this archive + instead of cloning from GitHub; extract all libdatachannel/libjuice/libsrtp/ + usrsctp/plog/json archives from upstream/ into one directory first.) + 3. Build Nubix: + scripts/build-ps5.sh + The result is written to build-ps5/. + +The libraries in /opt/ps5-payload-sdk inside the image were built by the +ps5-payload-dev project from the PKGBUILD recipes in pacbrew-repo v0.40.2 +(included in upstream/) with the ps5-payload-sdk toolchain (also included). To +rebuild one of them from the sources in this archive, use its PKGBUILD +(directory named after the port, e.g. ffmpeg/, SDL2/, curl/) with the +ps5-payload-sdk and pacbrew (makepkg) as described in pacbrew-repo/README.md, +pointing source= at the tarball/export from upstream/. + + +Components linked into the PS5 binary +------------------------------------- +Component Version / commit License (SPDX) Upstream source +----------------- --------------------------------------------- --------------------------------- --------------------------------------------------------------- +Nubix 0.3.0 (this release) GPL-3.0-only https://github.com/JBuildr/nubix +nlohmann/json bundled in third_party/ (Nubix source) MIT https://github.com/nlohmann/json +libdatachannel v0.24.6 6b1e2e620f1e37f0eafeee702eaea0043cb305fd MPL-2.0 (+ patches/ from Nubix) https://github.com/paullouisageneau/libdatachannel + libjuice v1.7.4 b89c792e3612faf2f12cf35bcc56857313a06be3 MPL-2.0 https://github.com/paullouisageneau/libjuice + libsrtp 24b3bf8f19b6f5ab4cd2bcceb4f4064efca86fd5 BSD-3-Clause https://github.com/cisco/libsrtp + usrsctp fec583d54493f879d2ae44a743423bf8a04371ab BSD-3-Clause https://github.com/paullouisageneau/usrsctp + plog 94899e0b926ac1b0f4750bfbd495167b4a6ae9ef MIT https://github.com/SergiusTheBest/plog + json (nlohmann) 55f93686c01528224f448c19128836e7df245f72 MIT https://github.com/nlohmann/json +FFmpeg 7.0.1 (libavcodec, libavutil, libswresample) GPL-3.0-or-later (--enable-gpl https://ffmpeg.org/releases/ffmpeg-7.0.1.tar.xz + --enable-version3 --enable-libx264) +x264 0.165.3222 b35605ace3ddf7c1a5d67a2eb553f034aef41d55 GPL-2.0-or-later https://code.videolan.org/videolan/x264 (branch stable) +libiconv 1.17 LGPL-2.1-or-later https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.17.tar.gz +OpenSSL 3.5.2 (libssl, libcrypto) Apache-2.0 https://github.com/openssl/openssl/releases/download/openssl-3.5.2/openssl-3.5.2.tar.gz +curl (libcurl) 8.18.0 curl https://curl.se/download/curl-8.18.0.tar.xz +libpsl 0.21.5 MIT https://github.com/rockdaboot/libpsl/releases/download/0.21.5/libpsl-0.21.5.tar.gz +SDL2 (PS5 fork) 2.30.12, a47182a20e741e4735d1fcd41e8656bf338b0c72 Zlib https://github.com/ps5-payload-dev/SDL (branch release-2.30.x-ps5) +SDL2_ttf 2.22.0 Zlib https://libsdl.org/projects/SDL_ttf/release/SDL2_ttf-2.22.0.tar.gz +FreeType 2.13.2 FTL OR GPL-2.0-or-later https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz +libpng 1.6.43 libpng-2.0 https://download.sourceforge.net/libpng/libpng-1.6.43.tar.xz +zlib 1.3.2 Zlib https://github.com/madler/zlib/releases/download/v1.3.2/zlib-1.3.2.tar.gz +zstd 1.5.6 BSD-3-Clause OR GPL-2.0-only https://github.com/facebook/zstd/releases/download/v1.5.6/zstd-1.5.6.tar.gz +xz (liblzma) 5.4.6 public domain (liblzma 5.4.x) https://github.com/tukaani-project/xz/releases/download/v5.4.6/xz-5.4.6.tar.xz +bzip2 1.0.8 bzip2-1.0.6 https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz +openlibm (libm) 0.8.6 MIT AND BSD-2-Clause AND ISC https://github.com/JuliaMath/openlibm/archive/refs/tags/v0.8.6.tar.gz +Opus 1.5.2 BSD-3-Clause https://downloads.xiph.org/releases/opus/opus-1.5.2.tar.gz +libsamplerate 0.2.2 BSD-2-Clause https://github.com/libsndfile/libsamplerate/releases/download/0.2.2/libsamplerate-0.2.2.tar.xz +ps5-payload-sdk 4eb701204fc3f8d31e84cf8ca272974e2be9c867 GPL-3.0-or-later; include/freebsd https://github.com/ps5-payload-dev/sdk + (crt, libc/libpthread wrappers, headers, under BSD licenses + toolchain scripts) +pacbrew-repo v0.40.2 1687e827675c66e1b040751c7f0811222bd3aea0 GPL-3.0 (build recipes) https://github.com/ps5-payload-dev/pacbrew-repo + (PKGBUILD build scripts of every library above, + incl. FFmpeg's ffplay-with-game-controller-input.diff) + +Not bundled (permissive license without a source-distribution requirement): +LLVM runtimes 18.1.8 (libc++, libc++abi, libunwind; built Apache-2.0 WITH LLVM-exception + by ps5-payload-sdk libcxx.sh) + https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.8/llvm-project-18.1.8.src.tar.xz + sha256 0b58557a6d32ceee97c8d533a59b9212d87e0fc4d2833924eb6c611247db2f2a + +Not part of the Corresponding Source (GPLv3 section 1 "System Libraries" / generally +available tools): the PS5 system modules the binary is dynamically linked to at +runtime (libkernel, libSceLibcInternal, libSceNet, libSceVideoOut, libSceAudioOut, +libScePad, libSceUserService, libSceSystemService, libSceImeDialog, libSceKeyboard, ...; +the SDK only ships link stubs for them), and the compiler/linker used (clang/lld 18 +from Ubuntu 24.04, see docker/Dockerfile). + + +Evidence for commits not pinned by a version tag +------------------------------------------------ +pacbrew-repo v0.40.2 builds the SDK, the SDL2 fork and x264 from git branch heads +(sha256sums=SKIP). The exact commits were determined from the prebuilt +ps5-payload-dev.tar.gz of release v0.40.2 (as installed in the ps5dev image): + +* pacbrew-repo: tag v0.40.2 = commit 1687e82 (2026-08-18); the next commit on the + repository is dated 2026-08-30, i.e. after the build, so the recipes used are + exactly those of the tag. The release asset ps5-payload-dev.tar.gz was + (re)built 2026-08-23 ~14:18-16:11 UTC (file mtimes in the SDK: 2026-08-23 14:18 UTC; + asset upload 16:11:56 UTC; release note "rebuild with latest SDL2 port"). + +* SDL2 PS5 fork: PROVEN. libSDL2.a contains the revision string + "SDL-2.30.12-ga47182a20" (SDL_GetRevision). Commit a47182a20e74... (2026-08-22, + "ps5: adjust remote control keyboard mappings") is on branch release-2.30.x-ps5; + the next commit is dated 2026-09-24, after the build. + +* x264: PROVEN. libx264.a contains "r3222 b35605a" (X264_POINTVER 0.165.3222) and + x264.pc reports Version 0.165.3222; the stable branch head is + b35605ace3ddf7c1a5d67a2eb553f034aef41d55 (abbreviation b35605a is unique). + +* ps5-payload-sdk: INFERRED, strongly supported. The SDK records no commit id. The + SDK's master branch at the build time (2026-08-23) was 4eb701204fc3 (2026-08-02, + "add support for firmware 13.60"); the next commit is 8e125899 (2026-08-28). + The installed crt1.o contains the firmware 13.60 offset added by 4eb7012 + (data offsets 0x28C9E80, 0x0D9C064, 0x314B510, 0x2098228) and lacks kernel_iommu_* symbols introduced + by the 2026-08-28/29 commits; libc.a lacks killpg() (added 2026-09-03). + libc++/libc++abi/libunwind in the SDK are built by that commit's libcxx.sh from + LLVM 18.1.8. + +* Release tarballs: versions from the v0.40.2 PKGBUILDs (pkgver=) and cross-checked + against the installed pkg-config files (FreeType reports libtool version 26.1.20 = + 2.13.2; openlibm.pc reports 0.8.0 because upstream's Make.inc in the v0.8.6 + tag still says VERSION = 0.8.0 - the PKGBUILD pins v0.8.6 with sha256). The archive's tarballs are + verified against the sha256sums in those PKGBUILDs; FFmpeg's PKGBUILD has + sha256sums=SKIP, so its sum (bce9eeb0f17ef8982390b1f37711a61b4290dc8c2a0c1a37b5857e85bfb0e4ff) + was computed on download and recorded in the MANIFEST. + +* libdatachannel: v0.24.6 tag with the submodule commits recorded in that tag + (git submodule status of the build checkout). The Nubix patch + patches/libdatachannel-xbox-pli.patch is applied on top at build time. + +Written offer: if the release page is unavailable, anyone in possession of a Nubix +binary may obtain the Corresponding Source by opening an issue at +https://github.com/JBuildr/nubix/issues; this offer is valid for as long as the +binary is distributed, and at least three years after the last distribution. diff --git a/THIRD_PARTY_LICENSES.txt b/THIRD_PARTY_LICENSES.txt new file mode 100644 index 0000000..1598c95 --- /dev/null +++ b/THIRD_PARTY_LICENSES.txt @@ -0,0 +1,7411 @@ +Nubix - third-party license texts + +Full license texts of the third-party components linked into, or shipped with, the Nubix PS5 +binary. Versions, copyright holders and usage: THIRD_PARTY_NOTICES.md. Each text was taken +unmodified from the upstream project at the version Nubix builds against. + +Not repeated here: + - GNU GPL v3 (Nubix itself, green-nx/ProsperoLight-derived code, the ps5-payload-sdk, FFmpeg + as built): see LICENSE + - DejaVu fonts: see assets/fonts/LICENSE-DejaVu.txt + +Contents: + [ffmpeg] FFmpeg 7.0.1 (GPL-3.0-or-later as built) + [x264] x264 0.165.3222 (b35605a) + [openssl] OpenSSL 3.5.2 + [curl] libcurl 8.18.0 + [libpsl] libpsl 0.21.5 + [libpsl-chromium] libpsl: Chromium-derived code + [SDL2] SDL2 2.30.12 (PS5 fork) + [SDL2_ttf] SDL2_ttf 2.22.0 + [freetype-FTL] FreeType 2.13.2: FreeType License (FTL) + [freetype] FreeType 2.13.2: LICENSE.TXT + [libpng] libpng 1.6.43 + [zlib] zlib 1.3.2 + [zstd] zstd 1.5.6 + [xz] xz / liblzma 5.4.6 + [bzip2] bzip2 1.0.8 + [libiconv] GNU libiconv + libcharset 1.17 + [libiconv-NOTE] GNU libiconv: note + [openlibm] openlibm 0.8.6 + [opus] Opus 1.5.2 + [libsamplerate] libsamplerate 0.2.2 + [libdatachannel] libdatachannel 0.24.6 + [libjuice] libjuice 1.7.4 + [libsrtp] libsrtp 2.8.0 + [usrsctp] usrsctp (fec583d) + [plog] plog (94899e0) + [nlohmann-json] nlohmann/json 3.11.3 + [qrcodegen] QR Code generator (Project Nayuki) + [llvm-libcxx] LLVM libc++ 18.1.8 + [llvm-libcxxabi] LLVM libc++abi 18.1.8 + [llvm-libunwind] LLVM libunwind 18.1.8 + [ps5-payload-sdk] ps5-payload-sdk (crt1.o, libc.a) + [ps5-payload-sdk-libc-notices] ps5-payload-sdk libc: per-file notices + [musl] musl (parts of the SDK libc) + [GPL-2.0] GNU General Public License v2 + [LGPL-2.1] GNU Lesser General Public License v2.1 + [Apache-2.0] Apache License 2.0 + [MPL-2.0] Mozilla Public License 2.0 + +============================================================================== +[ffmpeg] FFmpeg 7.0.1 (GPL-3.0-or-later as built) +============================================================================== + +# License + +Most files in FFmpeg are under the GNU Lesser General Public License version 2.1 +or later (LGPL v2.1+). Read the file `COPYING.LGPLv2.1` for details. Some other +files have MIT/X11/BSD-style licenses. In combination the LGPL v2.1+ applies to +FFmpeg. + +Some optional parts of FFmpeg are licensed under the GNU General Public License +version 2 or later (GPL v2+). See the file `COPYING.GPLv2` for details. None of +these parts are used by default, you have to explicitly pass `--enable-gpl` to +configure to activate them. In this case, FFmpeg's license changes to GPL v2+. + +Specifically, the GPL parts of FFmpeg are: + +- libpostproc +- optional x86 optimization in the files + - `libavcodec/x86/flac_dsp_gpl.asm` + - `libavcodec/x86/idct_mmx.c` + - `libavfilter/x86/vf_removegrain.asm` +- the following building and testing tools + - `compat/solaris/make_sunver.pl` + - `doc/t2h.pm` + - `doc/texi2pod.pl` + - `libswresample/tests/swresample.c` + - `tests/checkasm/*` + - `tests/tiny_ssim.c` +- the following filters in libavfilter: + - `signature_lookup.c` + - `vf_blackframe.c` + - `vf_boxblur.c` + - `vf_colormatrix.c` + - `vf_cover_rect.c` + - `vf_cropdetect.c` + - `vf_delogo.c` + - `vf_eq.c` + - `vf_find_rect.c` + - `vf_fspp.c` + - `vf_histeq.c` + - `vf_hqdn3d.c` + - `vf_kerndeint.c` + - `vf_lensfun.c` (GPL version 3 or later) + - `vf_mcdeint.c` + - `vf_mpdecimate.c` + - `vf_nnedi.c` + - `vf_owdenoise.c` + - `vf_perspective.c` + - `vf_phase.c` + - `vf_pp.c` + - `vf_pp7.c` + - `vf_pullup.c` + - `vf_repeatfields.c` + - `vf_sab.c` + - `vf_signature.c` + - `vf_smartblur.c` + - `vf_spp.c` + - `vf_stereo3d.c` + - `vf_super2xsai.c` + - `vf_tinterlace.c` + - `vf_uspp.c` + - `vf_vaguedenoiser.c` + - `vsrc_mptestsrc.c` + +Should you, for whatever reason, prefer to use version 3 of the (L)GPL, then +the configure parameter `--enable-version3` will activate this licensing option +for you. Read the file `COPYING.LGPLv3` or, if you have enabled GPL parts, +`COPYING.GPLv3` to learn the exact legal terms that apply in this case. + +There are a handful of files under other licensing terms, namely: + +* The files `libavcodec/jfdctfst.c`, `libavcodec/jfdctint_template.c` and + `libavcodec/jrevdct.c` are taken from libjpeg, see the top of the files for + licensing details. Specifically note that you must credit the IJG in the + documentation accompanying your program if you only distribute executables. + You must also indicate any changes including additions and deletions to + those three files in the documentation. +* `tests/reference.pnm` is under the expat license. + + +## External libraries + +FFmpeg can be combined with a number of external libraries, which sometimes +affect the licensing of binaries resulting from the combination. + +### Compatible libraries + +The following libraries are under GPL version 2: +- avisynth +- frei0r +- libcdio +- libdavs2 +- librubberband +- libvidstab +- libx264 +- libx265 +- libxavs +- libxavs2 +- libxvid + +When combining them with FFmpeg, FFmpeg needs to be licensed as GPL as well by +passing `--enable-gpl` to configure. + +The following libraries are under LGPL version 3: +- gmp +- libaribb24 +- liblensfun + +When combining them with FFmpeg, use the configure option `--enable-version3` to +upgrade FFmpeg to the LGPL v3. + +The VMAF, mbedTLS, RK MPI, OpenCORE and VisualOn libraries are under the Apache License +2.0. That license is incompatible with the LGPL v2.1 and the GPL v2, but not with +version 3 of those licenses. So to combine these libraries with FFmpeg, the +license version needs to be upgraded by passing `--enable-version3` to configure. + +The smbclient library is under the GPL v3, to combine it with FFmpeg, +the options `--enable-gpl` and `--enable-version3` have to be passed to +configure to upgrade FFmpeg to the GPL v3. + +### Incompatible libraries + +There are certain libraries you can combine with FFmpeg whose licenses are not +compatible with the GPL and/or the LGPL. If you wish to enable these +libraries, even in circumstances that their license may be incompatible, pass +`--enable-nonfree` to configure. This will cause the resulting binary to be +unredistributable. + +The Fraunhofer FDK AAC and OpenSSL libraries are under licenses which are +incompatible with the GPLv2 and v3. To the best of our knowledge, they are +compatible with the LGPL. + +============================================================================== +[x264] x264 0.165.3222 (b35605a) +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + +============================================================================== +[openssl] OpenSSL 3.5.2 +============================================================================== + + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +============================================================================== +[curl] libcurl 8.18.0 +============================================================================== + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1996 - 2025, Daniel Stenberg, , and many +contributors, see the THANKS file. + +All rights reserved. + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization of the copyright holder. + +============================================================================== +[libpsl] libpsl 0.21.5 +============================================================================== + +Copyright (C) 2014-2024 Tim Rühsen + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +============================================================================== +[libpsl-chromium] libpsl: Chromium-derived code +============================================================================== + +* The following License is for the source code files + psl-make-dafsa and lookup_string_in_fixed_set.c. + +// Copyright 2015 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +============================================================================== +[SDL2] SDL2 2.30.12 (PS5 fork) +============================================================================== + +Copyright (C) 1997-2025 Sam Lantinga + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +============================================================================== +[SDL2_ttf] SDL2_ttf 2.22.0 +============================================================================== + +Copyright (C) 1997-2024 Sam Lantinga + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +============================================================================== +[freetype-FTL] FreeType 2.13.2: FreeType License (FTL) +============================================================================== + + The FreeType Project LICENSE + ---------------------------- + + 2006-Jan-27 + + Copyright 1996-2002, 2006 by + David Turner, Robert Wilhelm, and Werner Lemberg + + + +Introduction +============ + + The FreeType Project is distributed in several archive packages; + some of them may contain, in addition to the FreeType font engine, + various tools and contributions which rely on, or relate to, the + FreeType Project. + + This license applies to all files found in such packages, and + which do not fall under their own explicit license. The license + affects thus the FreeType font engine, the test programs, + documentation and makefiles, at the very least. + + This license was inspired by the BSD, Artistic, and IJG + (Independent JPEG Group) licenses, which all encourage inclusion + and use of free software in commercial and freeware products + alike. As a consequence, its main points are that: + + o We don't promise that this software works. However, we will be + interested in any kind of bug reports. (`as is' distribution) + + o You can use this software for whatever you want, in parts or + full form, without having to pay us. (`royalty-free' usage) + + o You may not pretend that you wrote this software. If you use + it, or only parts of it, in a program, you must acknowledge + somewhere in your documentation that you have used the + FreeType code. (`credits') + + We specifically permit and encourage the inclusion of this + software, with or without modifications, in commercial products. + We disclaim all warranties covering The FreeType Project and + assume no liability related to The FreeType Project. + + + Finally, many people asked us for a preferred form for a + credit/disclaimer to use in compliance with this license. We thus + encourage you to use the following text: + + """ + Portions of this software are copyright © The FreeType + Project (www.freetype.org). All rights reserved. + """ + + Please replace with the value from the FreeType version you + actually use. + + +Legal Terms +=========== + +0. Definitions +-------------- + + Throughout this license, the terms `package', `FreeType Project', + and `FreeType archive' refer to the set of files originally + distributed by the authors (David Turner, Robert Wilhelm, and + Werner Lemberg) as the `FreeType Project', be they named as alpha, + beta or final release. + + `You' refers to the licensee, or person using the project, where + `using' is a generic term including compiling the project's source + code as well as linking it to form a `program' or `executable'. + This program is referred to as `a program using the FreeType + engine'. + + This license applies to all files distributed in the original + FreeType Project, including all source code, binaries and + documentation, unless otherwise stated in the file in its + original, unmodified form as distributed in the original archive. + If you are unsure whether or not a particular file is covered by + this license, you must contact us to verify this. + + The FreeType Project is copyright (C) 1996-2000 by David Turner, + Robert Wilhelm, and Werner Lemberg. All rights reserved except as + specified below. + +1. No Warranty +-------------- + + THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO + USE, OF THE FREETYPE PROJECT. + +2. Redistribution +----------------- + + This license grants a worldwide, royalty-free, perpetual and + irrevocable right and license to use, execute, perform, compile, + display, copy, create derivative works of, distribute and + sublicense the FreeType Project (in both source and object code + forms) and derivative works thereof for any purpose; and to + authorize others to exercise some or all of the rights granted + herein, subject to the following conditions: + + o Redistribution of source code must retain this license file + (`FTL.TXT') unaltered; any additions, deletions or changes to + the original files must be clearly indicated in accompanying + documentation. The copyright notices of the unaltered, + original files must be preserved in all copies of source + files. + + o Redistribution in binary form must provide a disclaimer that + states that the software is based in part of the work of the + FreeType Team, in the distribution documentation. We also + encourage you to put an URL to the FreeType web page in your + documentation, though this isn't mandatory. + + These conditions apply to any software derived from or based on + the FreeType Project, not just the unmodified files. If you use + our work, you must acknowledge us. However, no fee need be paid + to us. + +3. Advertising +-------------- + + Neither the FreeType authors and contributors nor you shall use + the name of the other for commercial, advertising, or promotional + purposes without specific prior written permission. + + We suggest, but do not require, that you use one or more of the + following phrases to refer to this software in your documentation + or advertising materials: `FreeType Project', `FreeType Engine', + `FreeType library', or `FreeType Distribution'. + + As you have not signed this license, you are not required to + accept it. However, as the FreeType Project is copyrighted + material, only this license, or another one contracted with the + authors, grants you the right to use, distribute, and modify it. + Therefore, by using, distributing, or modifying the FreeType + Project, you indicate that you understand and accept all the terms + of this license. + +4. Contacts +----------- + + There are two mailing lists related to FreeType: + + o freetype@nongnu.org + + Discusses general use and applications of FreeType, as well as + future and wanted additions to the library and distribution. + If you are looking for support, start in this list if you + haven't found anything to help you in the documentation. + + o freetype-devel@nongnu.org + + Discusses bugs, as well as engine internals, design issues, + specific licenses, porting, etc. + + Our home page can be found at + + https://www.freetype.org + + +--- end of FTL.TXT --- + +============================================================================== +[freetype] FreeType 2.13.2: LICENSE.TXT +============================================================================== + +FREETYPE LICENSES +----------------- + +The FreeType 2 font engine is copyrighted work and cannot be used +legally without a software license. In order to make this project +usable to a vast majority of developers, we distribute it under two +mutually exclusive open-source licenses. + +This means that *you* must choose *one* of the two licenses described +below, then obey all its terms and conditions when using FreeType 2 in +any of your projects or products. + + - The FreeType License, found in the file `docs/FTL.TXT`, which is + similar to the original BSD license *with* an advertising clause + that forces you to explicitly cite the FreeType project in your + product's documentation. All details are in the license file. + This license is suited to products which don't use the GNU General + Public License. + + Note that this license is compatible to the GNU General Public + License version 3, but not version 2. + + - The GNU General Public License version 2, found in + `docs/GPLv2.TXT` (any later version can be used also), for + programs which already use the GPL. Note that the FTL is + incompatible with GPLv2 due to its advertisement clause. + +The contributed BDF and PCF drivers come with a license similar to +that of the X Window System. It is compatible to the above two +licenses (see files `src/bdf/README` and `src/pcf/README`). The same +holds for the source code files `src/base/fthash.c` and +`include/freetype/internal/fthash.h`; they were part of the BDF driver +in earlier FreeType versions. + +The gzip module uses the zlib license (see `src/gzip/zlib.h`) which +too is compatible to the above two licenses. + +The files `src/autofit/ft-hb.c` and `src/autofit/ft-hb.h` contain code +taken almost verbatim from the HarfBuzz file `hb-ft.cc`, which uses +the 'Old MIT' license, compatible to the above two licenses. + +The MD5 checksum support (only used for debugging in development +builds) is in the public domain. + + +--- end of LICENSE.TXT --- + +============================================================================== +[libpng] libpng 1.6.43 +============================================================================== + +COPYRIGHT NOTICE, DISCLAIMER, and LICENSE +========================================= + +PNG Reference Library License version 2 +--------------------------------------- + + * Copyright (c) 1995-2024 The PNG Reference Library Authors. + * Copyright (c) 2018-2024 Cosmin Truta. + * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. + * Copyright (c) 1996-1997 Andreas Dilger. + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + +The software is supplied "as is", without warranty of any kind, +express or implied, including, without limitation, the warranties +of merchantability, fitness for a particular purpose, title, and +non-infringement. In no event shall the Copyright owners, or +anyone distributing the software, be liable for any damages or +other liability, whether in contract, tort or otherwise, arising +from, out of, or in connection with the software, or the use or +other dealings in the software, even if advised of the possibility +of such damage. + +Permission is hereby granted to use, copy, modify, and distribute +this software, or portions hereof, for any purpose, without fee, +subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you + use this software in a product, an acknowledgment in the product + documentation would be appreciated, but is not required. + + 2. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + + 3. This Copyright notice may not be removed or altered from any + source or altered source distribution. + + +PNG Reference Library License version 1 (for libpng 0.5 through 1.6.35) +----------------------------------------------------------------------- + +libpng versions 1.0.7, July 1, 2000, through 1.6.35, July 15, 2018 are +Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson, are +derived from libpng-1.0.6, and are distributed according to the same +disclaimer and license as libpng-1.0.6 with the following individuals +added to the list of Contributing Authors: + + Simon-Pierre Cadieux + Eric S. Raymond + Mans Rullgard + Cosmin Truta + Gilles Vollant + James Yu + Mandar Sahastrabuddhe + Google Inc. + Vadim Barkov + +and with the following additions to the disclaimer: + + There is no warranty against interference with your enjoyment of + the library or against infringement. There is no warranty that our + efforts or the library will fulfill any of your particular purposes + or needs. This library is provided with all faults, and the entire + risk of satisfactory quality, performance, accuracy, and effort is + with the user. + +Some files in the "contrib" directory and some configure-generated +files that are distributed with libpng have other copyright owners, and +are released under other open source licenses. + +libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are +Copyright (c) 1998-2000 Glenn Randers-Pehrson, are derived from +libpng-0.96, and are distributed according to the same disclaimer and +license as libpng-0.96, with the following individuals added to the +list of Contributing Authors: + + Tom Lane + Glenn Randers-Pehrson + Willem van Schaik + +libpng versions 0.89, June 1996, through 0.96, May 1997, are +Copyright (c) 1996-1997 Andreas Dilger, are derived from libpng-0.88, +and are distributed according to the same disclaimer and license as +libpng-0.88, with the following individuals added to the list of +Contributing Authors: + + John Bowler + Kevin Bracey + Sam Bushell + Magnus Holmgren + Greg Roelofs + Tom Tanner + +Some files in the "scripts" directory have other copyright owners, +but are released under this license. + +libpng versions 0.5, May 1995, through 0.88, January 1996, are +Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + +For the purposes of this copyright and license, "Contributing Authors" +is defined as the following set of individuals: + + Andreas Dilger + Dave Martindale + Guy Eric Schalnat + Paul Schmidt + Tim Wegner + +The PNG Reference Library is supplied "AS IS". The Contributing +Authors and Group 42, Inc. disclaim all warranties, expressed or +implied, including, without limitation, the warranties of +merchantability and of fitness for any purpose. The Contributing +Authors and Group 42, Inc. assume no liability for direct, indirect, +incidental, special, exemplary, or consequential damages, which may +result from the use of the PNG Reference Library, even if advised of +the possibility of such damage. + +Permission is hereby granted to use, copy, modify, and distribute this +source code, or portions hereof, for any purpose, without fee, subject +to the following restrictions: + + 1. The origin of this source code must not be misrepresented. + + 2. Altered versions must be plainly marked as such and must not + be misrepresented as being the original source. + + 3. This Copyright notice may not be removed or altered from any + source or altered source distribution. + +The Contributing Authors and Group 42, Inc. specifically permit, +without fee, and encourage the use of this source code as a component +to supporting the PNG file format in commercial products. If you use +this source code in a product, acknowledgment is not required but would +be appreciated. + +============================================================================== +[zlib] zlib 1.3.2 +============================================================================== + +Copyright notice: + + (C) 1995-2026 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +============================================================================== +[zstd] zstd 1.5.6 +============================================================================== + +BSD License + +For Zstandard software + +Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook, nor Meta, nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +============================================================================== +[xz] xz / liblzma 5.4.6 +============================================================================== + + +XZ Utils Licensing +================== + + Different licenses apply to different files in this package. Here + is a rough summary of which licenses apply to which parts of this + package (but check the individual files to be sure!): + + - liblzma is in the public domain. + + - xz, xzdec, and lzmadec command line tools are in the public + domain unless GNU getopt_long had to be compiled and linked + in from the lib directory. The getopt_long code is under + GNU LGPLv2.1+. + + - The scripts to grep, diff, and view compressed files have been + adapted from gzip. These scripts and their documentation are + under GNU GPLv2+. + + - All the documentation in the doc directory and most of the + XZ Utils specific documentation files in other directories + are in the public domain. + + Note: The JavaScript files (under the MIT license) have + been removed from the Doxygen-generated HTML version of the + liblzma API documentation. Doxygen itself is under the GNU GPL + but the remaining files generated by Doxygen are not affected + by the licenses used in Doxygen because Doxygen licensing has + the following exception: + + "Documents produced by doxygen are derivative works + derived from the input used in their production; + they are not affected by this license." + + - Translated messages are in the public domain. + + - The build system contains public domain files, and files that + are under GNU GPLv2+ or GNU GPLv3+. None of these files end up + in the binaries being built. + + - Test files and test code in the tests directory, and debugging + utilities in the debug directory are in the public domain. + + - The extra directory may contain public domain files, and files + that are under various free software licenses. + + You can do whatever you want with the files that have been put into + the public domain. If you find public domain legally problematic, + take the previous sentence as a license grant. If you still find + the lack of copyright legally problematic, you have too many + lawyers. + + As usual, this software is provided "as is", without any warranty. + + If you copy significant amounts of public domain code from XZ Utils + into your project, acknowledging this somewhere in your software is + polite (especially if it is proprietary, non-free software), but + naturally it is not legally required. Here is an example of a good + notice to put into "about box" or into documentation: + + This software includes code from XZ Utils + . + + The following license texts are included in the following files: + - COPYING.LGPLv2.1: GNU Lesser General Public License version 2.1 + - COPYING.GPLv2: GNU General Public License version 2 + - COPYING.GPLv3: GNU General Public License version 3 + + Note that the toolchain (compiler, linker etc.) may add some code + pieces that are copyrighted. Thus, it is possible that e.g. liblzma + binary wouldn't actually be in the public domain in its entirety + even though it contains no copyrighted code from the XZ Utils source + package. + + If you have questions, don't hesitate to ask the author(s) for more + information. + +============================================================================== +[bzip2] bzip2 1.0.8 +============================================================================== + + +-------------------------------------------------------------------------- + +This program, "bzip2", the associated library "libbzip2", and all +documentation, are copyright (C) 1996-2019 Julian R Seward. All +rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +3. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + +4. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Julian Seward, jseward@acm.org +bzip2/libbzip2 version 1.0.8 of 13 July 2019 + +-------------------------------------------------------------------------- + +============================================================================== +[libiconv] GNU libiconv + libcharset 1.17 +============================================================================== + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + +============================================================================== +[libiconv-NOTE] GNU libiconv: note +============================================================================== + +GNU libiconv 1.17 (libiconv.a and libcharset.a) +================================================ + +libiconv and libcharset are licensed under the GNU Lesser General Public +License, version 2.1 or (at your option) any later version: + + Copyright (C) 1999-2022 Free Software Foundation, Inc. + +The full license text is in libiconv.txt (COPYING.LIB from the libiconv +v1.17 tag). Nubix links these libraries statically (they are pulled in by +SDL2 in the ps5-payload-dev pacbrew build). Because Nubix itself is +GPL-3.0-only, the complete Corresponding Source of each release (see +"Source code" in THIRD_PARTY_NOTICES.md) is available, which includes what +is needed to modify libiconv and relink the binary (LGPL-2.1 section 6). + +============================================================================== +[openlibm] openlibm 0.8.6 +============================================================================== + +## OpenLibm + +OpenLibm contains code that is covered by various licenses. + +The OpenLibm code derives from the FreeBSD msun and OpenBSD libm +implementations, which in turn derives from FDLIBM 5.3. As a result, it +has a number of fixes and updates that have accumulated over the years +in msun, and also optimized assembly versions of many functions. These +improvements are provided under the BSD and ISC licenses. The msun +library also includes work placed under the public domain, which is +noted in the individual files. Further work on making a standalone +OpenLibm library from msun, as part of the Julia project is covered +under the MIT license. The test files, test-double.c and test-float.c +are under the LGPL. + +## Parts copyrighted by the Julia project (MIT License) + +> Copyright (c) 2011-14 The Julia Project. +> https://github.com/JuliaMath/openlibm/graphs/contributors +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## Parts copyrighted by Stephen L. Moshier (ISC License) + +> Copyright (c) 2008 Stephen L. Moshier +> +> Permission to use, copy, modify, and distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +> OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +## FREEBSD MSUN (FreeBSD/2-clause BSD/Simplified BSD License) + +> Copyright 1992-2011 The FreeBSD Project. All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are +> met: +> +> 1. Redistributions of source code must retain the above copyright +> notice, this list of conditions and the following disclaimer. +> +> 2. Redistributions in binary form must reproduce the above copyright +> notice, this list of conditions and the following disclaimer in the +> documentation and/or other materials provided with the distribution. +> THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS IS'' AND ANY +> EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +> PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR +> CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +> EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +> PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +> PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +> LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +> NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +> SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +> +> The views and conclusions contained in the software and documentation +> are those of the authors and should not be interpreted as representing +> official policies, either expressed or implied, of the FreeBSD +> Project. + +## FDLIBM + +> Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. +> +> Developed at SunPro, a Sun Microsystems, Inc. business. +> Permission to use, copy, modify, and distribute this +> software is freely granted, provided that this notice +> is preserved. + +## Tests + +> Copyright (C) 1997, 1999 Free Software Foundation, Inc. +> This file is part of the GNU C Library. +> Contributed by Andreas Jaeger , 1997. +> +> The GNU C Library is free software; you can redistribute it and/or +> modify it under the terms of the GNU Lesser General Public +> License as published by the Free Software Foundation; either +> version 2.1 of the License, or (at your option) any later version. +> +> The GNU C Library is distributed in the hope that it will be useful, +> but WITHOUT ANY WARRANTY; without even the implied warranty of +> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +> Lesser General Public License for more details. +> +> You should have received a copy of the GNU Lesser General Public +> License along with the GNU C Library; if not, write to the Free +> Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +> 02111-1307 USA. + +============================================================================== +[opus] Opus 1.5.2 +============================================================================== + +Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic, + Jean-Marc Valin, Timothy B. Terriberry, + CSIRO, Gregory Maxwell, Mark Borgerding, + Erik de Castro Lopo, Mozilla, Amazon + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Opus is subject to the royalty-free patent licenses which are +specified at: + +Xiph.Org Foundation: +https://datatracker.ietf.org/ipr/1524/ + +Microsoft Corporation: +https://datatracker.ietf.org/ipr/1914/ + +Broadcom Corporation: +https://datatracker.ietf.org/ipr/1526/ + +============================================================================== +[libsamplerate] libsamplerate 0.2.2 +============================================================================== + +Copyright (c) 2012-2016, Erik de Castro Lopo +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +============================================================================== +[libdatachannel] libdatachannel 0.24.6 +============================================================================== + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + +============================================================================== +[libjuice] libjuice 1.7.4 +============================================================================== + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + +============================================================================== +[libsrtp] libsrtp 2.8.0 +============================================================================== + +/* + * + * Copyright (c) 2001-2017 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * Neither the name of the Cisco Systems, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +============================================================================== +[usrsctp] usrsctp (fec583d) +============================================================================== + +Copyright (c) 2015, Randall Stewart and Michael Tuexen +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of usrsctp nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +============================================================================== +[plog] plog (94899e0) +============================================================================== + +MIT License + +Copyright (c) 2022 Sergey Podobry + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +============================================================================== +[nlohmann-json] nlohmann/json 3.11.3 +============================================================================== + +MIT License + +Copyright (c) 2013-2022 Niels Lohmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +============================================================================== +[qrcodegen] QR Code generator (Project Nayuki) +============================================================================== + +QR Code generator library (Project Nayuki) +https://github.com/nayuki/QR-Code-generator -- vendored as src/ui/qrcodegen.cpp / src/ui/qrcodegen.hpp +Upstream has no separate LICENSE file; this is the License section of its Readme.markdown (master): + +License +------- + +Copyright © 2026 Project Nayuki. (MIT License) +[https://www.nayuki.io/page/qr-code-generator-library](https://www.nayuki.io/page/qr-code-generator-library) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +* The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + +* The Software is provided "as is", without warranty of any kind, express or + implied, including but not limited to the warranties of merchantability, + fitness for a particular purpose and noninfringement. In no event shall the + authors or copyright holders be liable for any claim, damages or other + liability, whether in an action of contract, tort or otherwise, arising from, + out of or in connection with the Software or the use or other dealings in the + Software. + +============================================================================== +[llvm-libcxx] LLVM libc++ 18.1.8 +============================================================================== + +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +============================================================================== +Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy): +============================================================================== + +The libc++ library is dual licensed under both the University of Illinois +"BSD-Like" license and the MIT license. As a user of this code you may choose +to use it under either license. As a contributor, you agree to allow your code +to be used under both. + +Full text of the relevant licenses is included below. + +============================================================================== + +University of Illinois/NCSA +Open Source License + +Copyright (c) 2009-2019 by the contributors listed in CREDITS.TXT + +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== + +Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +============================================================================== +[llvm-libcxxabi] LLVM libc++abi 18.1.8 +============================================================================== + +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +============================================================================== +Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy): +============================================================================== + +The libc++abi library is dual licensed under both the University of Illinois +"BSD-Like" license and the MIT license. As a user of this code you may choose +to use it under either license. As a contributor, you agree to allow your code +to be used under both. + +Full text of the relevant licenses is included below. + +============================================================================== + +University of Illinois/NCSA +Open Source License + +Copyright (c) 2009-2019 by the contributors listed in CREDITS.TXT + +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== + +Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +============================================================================== +[llvm-libunwind] LLVM libunwind 18.1.8 +============================================================================== + +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +============================================================================== +Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy): +============================================================================== + +The libunwind library is dual licensed under both the University of Illinois +"BSD-Like" license and the MIT license. As a user of this code you may choose +to use it under either license. As a contributor, you agree to allow your code +to be used under both. + +Full text of the relevant licenses is included below. + +============================================================================== + +University of Illinois/NCSA +Open Source License + +Copyright (c) 2009-2019 by the contributors listed in CREDITS.TXT + +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== + +Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +============================================================================== +[ps5-payload-sdk] ps5-payload-sdk (crt1.o, libc.a) +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + +============================================================================== +[ps5-payload-sdk-libc-notices] ps5-payload-sdk libc: per-file notices +============================================================================== + +ps5-payload-sdk (crt1.o and libc.a) -- copyright and license notices +===================================================================== + +The PS5 binary (eboot.elf) statically links crt1.o and libc.a from the +ps5-payload-sdk (https://github.com/ps5-payload-dev/sdk), as shipped in the +pacbrew-repo "ps5-payload-dev" bundle release v0.40.2 (pacbrew-repo commit +1687e827675c66e1b040751c7f0811222bd3aea0). That bundle builds the SDK from its +git HEAD; at the time the v0.40.2 bundle was built (2026-08-23) this was SDK +tag v0.42 (commit 4eb701204fc3f8d31e84cf8ca272974e2be9c867). The object list +of the shipped libc.a matches libc/Makefile at that commit. + +The SDK as a whole is licensed under the GNU General Public License, +version 3 or (at your option) any later version, with no linking exception: + + Copyright (C) 2023-2026 John Törnblom + Copyright (C) 2026 John Törnblom, idlesauce, and sleirsgoevy (crt/_start.S, crt/kernel_procio.c) + +See ps5-payload-sdk.txt (the SDK's LICENSE file, GPL-3.0 text). + +Several files of libc.a were imported from other C libraries and keep their +original permissive licenses (FreeBSD / University of California BSD +licenses, OpenBSD/NetBSD ISC-style licenses, public domain, musl MIT, and +LLVM compiler-rt Apache-2.0 WITH LLVM-exception for emutls.c). The license +headers of those files, extracted verbatim from the SDK sources, follow. +See also musl.txt (musl COPYRIGHT) and llvm-libcxx.txt (Apache-2.0 WITH +LLVM-exception). + +============================================================================== +Files: crt/_start.S, crt/crt.c, crt/elf.h, crt/kernel.c, crt/kernel.h, crt/kernel_procio.c, crt/klog.c, crt/klog.h, crt/mdbg.c, crt/mdbg.h, crt/nid.c, crt/nid.h, crt/patch.c, crt/patch.h, crt/payload.h, crt/rtld.c, crt/rtld.h, crt/rtld_dlfcn.c, crt/rtld_dlfcn.h, crt/rtld_payload.c, crt/rtld_payload.h, crt/rtld_so.c, crt/rtld_so.h, crt/rtld_sprx.c, crt/rtld_sprx.h, crt/syscall.c, crt/syscall.h +------------------------------------------------------------------------------ +GPL-3.0-or-later (John Törnblom) + +============================================================================== +Files: libc/__flt_rounds.c, libc/asctime_r.c, libc/backtrace.c, libc/ctime_r.c, libc/dirfd.c, libc/dlfcn.c, libc/fflush_unlocked.c, libc/grp.c, libc/isatty.c, libc/localtime.c, libc/mman.c, libc/mount.c, libc/netdb.c, libc/nl_langinfo.c, libc/no-locale.c, libc/passwd.c, libc/pipe2.c, libc/popen.c, libc/qsort_r.c, libc/syscalls.c, libc/syslog.c, libc/tmpfile.c, libc/tmpnam.c +------------------------------------------------------------------------------ +GPL-3.0-or-later (John Törnblom) + +============================================================================== +Files: libc/__xuname.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/_setjmp.S +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/arc4random.c +------------------------------------------------------------------------------ +/* + * Copyright (c) 1996, David Mazieres + * Copyright (c) 2008, Damien Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +============================================================================== +Files: libc/assert.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/call_once.c, libc/cnd.c, libc/mtx.c, libc/thrd.c, libc/tss.c +------------------------------------------------------------------------------ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2011 Ed Schouten + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/cclass.h +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1992, 1993, 1994 Henry Spencer. + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Henry Spencer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)cclass.h 8.3 (Berkeley) 3/20/94 + */ + +============================================================================== +Files: libc/cname.h +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1992, 1993, 1994 Henry Spencer. + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Henry Spencer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)cname.h 8.3 (Berkeley) 3/20/94 + */ + +============================================================================== +Files: libc/emutls.c +------------------------------------------------------------------------------ +//===---------- emutls.c - Implements __emutls_get_address ---------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + + +============================================================================== +Files: libc/engine.c.inc +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1992, 1993, 1994 Henry Spencer. + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Henry Spencer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)engine.c 8.5 (Berkeley) 3/20/94 + */ + +============================================================================== +Files: libc/execlp.c, libc/gmtime.c, libc/mbsnrtowcs.c, libc/strcasecmp.c, libc/strcasestr.c, libc/wcsnrtombs.c +------------------------------------------------------------------------------ +/* +Copyright © 2005-2020 Rich Felker, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +============================================================================== +Files: libc/ffs.c, libc/ffsl.c, libc/ffsll.c, libc/fls.c, libc/flsl.c, libc/flsll.c, libc/memccpy.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/fnmatch.c +------------------------------------------------------------------------------ +/* + * An implementation of what I call the "Sea of Stars" algorithm for + * POSIX fnmatch(). The basic idea is that we factor the pattern into + * a head component (which we match first and can reject without ever + * measuring the length of the string), an optional tail component + * (which only exists if the pattern contains at least one star), and + * an optional "sea of stars", a set of star-separated components + * between the head and tail. After the head and tail matches have + * been removed from the input string, the components in the "sea of + * stars" are matched sequentially by searching for their first + * occurrence past the end of the previous match. + * + * - Rich Felker, April 2012 + */ + +============================================================================== +Files: libc/fts.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $OpenBSD: fts.c,v 1.22 1999/10/03 19:22:22 millert Exp $ + */ + +============================================================================== +Files: libc/getloadavg.c, libc/termios.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/getmntinfo.c +------------------------------------------------------------------------------ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/if_indextoname.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1997, 2000 + * Berkeley Software Design, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * BSDI Id: if_indextoname.c,v 2.3 2000/04/17 22:38:05 dab Exp + */ + +============================================================================== +Files: libc/if_nametoindex.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1997, 2000 + * Berkeley Software Design, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * BSDI Id: if_nametoindex.c,v 2.3 2000/04/17 22:38:05 dab Exp + */ + +============================================================================== +Files: libc/mktemp.c +------------------------------------------------------------------------------ +/* + * Copyright (c) 1987, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/namespace.h, libc/un-namespace.h +------------------------------------------------------------------------------ +/* + * Copyright (c) 2001 Daniel Eischen . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +============================================================================== +Files: libc/nftw.c +------------------------------------------------------------------------------ +/* + * Copyright (c) 2003, 2004 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +============================================================================== +Files: libc/pmadvise.c +------------------------------------------------------------------------------ +/* + * The contents of this file are in the public domain. + * Written by Garrett A. Wollman, 2000-10-07. + * + */ + +============================================================================== +Files: libc/reallocarray.c +------------------------------------------------------------------------------ +/* + * Copyright (c) 2008 Otto Moerbeek + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +============================================================================== +Files: libc/recvmmsg.c, libc/sendmmsg.c +------------------------------------------------------------------------------ +/* + * Copyright (c) 2016 Boris Astardzhiev, Smartcom-Bulgaria AD + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice(s), this list of conditions and the following disclaimer as + * the first lines of this file unmodified other than the possible + * addition of one or more copyright notices. + * 2. Redistributions in binary form must reproduce the above copyright + * notice(s), this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +============================================================================== +Files: libc/regcomp.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1992, 1993, 1994 Henry Spencer. + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Henry Spencer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)regcomp.c 8.5 (Berkeley) 3/20/94 + */ + +============================================================================== +Files: libc/regerror.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1992, 1993, 1994 Henry Spencer. + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Henry Spencer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)regerror.c 8.4 (Berkeley) 3/20/94 + */ + +============================================================================== +Files: libc/regex2.h +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1992, 1993, 1994 Henry Spencer. + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Henry Spencer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)regex2.h 8.4 (Berkeley) 3/20/94 + */ + +============================================================================== +Files: libc/regexec.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1992, 1993, 1994 Henry Spencer. + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Henry Spencer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)regexec.c 8.3 (Berkeley) 3/20/94 + */ + +============================================================================== +Files: libc/regfree.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1992, 1993, 1994 Henry Spencer. + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Henry Spencer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)regfree.c 8.3 (Berkeley) 3/20/94 + */ + +============================================================================== +Files: libc/scandir.c +------------------------------------------------------------------------------ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/setbuffer.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/strsignal.c +------------------------------------------------------------------------------ +/* + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/tdelete.c, libc/tsearch.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 2015 Nuxi, https://nuxi.nl/ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/tfind.c +------------------------------------------------------------------------------ +/* $NetBSD: tfind.c,v 1.2 1999/09/16 11:45:37 lukem Exp $ */ + +============================================================================== +Files: libc/times.c +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +============================================================================== +Files: libc/tsearch_path.h +------------------------------------------------------------------------------ +/*- + * Copyright (c) 2015 Nuxi, https://nuxi.nl/ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +============================================================================== +Files: libc/twalk.c +------------------------------------------------------------------------------ +/* $NetBSD: twalk.c,v 1.4 2012/03/20 16:38:45 matt Exp $ */ + +============================================================================== +Files: libc/utils.h +------------------------------------------------------------------------------ +/*- + * Copyright (c) 1992, 1993, 1994 Henry Spencer. + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Henry Spencer. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)utils.h 8.3 (Berkeley) 3/20/94 + */ + +============================================================================== +[musl] musl (parts of the SDK libc) +============================================================================== + +musl as a whole is licensed under the following standard MIT license: + +---------------------------------------------------------------------- +Copyright © 2005-2020 Rich Felker, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------------------------------------------------------------- + +Authors/contributors include: + +A. Wilcox +Ada Worcester +Alex Dowad +Alex Suykov +Alexander Monakov +Andre McCurdy +Andrew Kelley +Anthony G. Basile +Aric Belsito +Arvid Picciani +Bartosz Brachaczek +Benjamin Peterson +Bobby Bingham +Boris Brezillon +Brent Cook +Chris Spiegel +Clément Vasseur +Daniel Micay +Daniel Sabogal +Daurnimator +David Carlier +David Edelsohn +Denys Vlasenko +Dmitry Ivanov +Dmitry V. Levin +Drew DeVault +Emil Renner Berthing +Fangrui Song +Felix Fietkau +Felix Janda +Gianluca Anzolin +Hauke Mehrtens +He X +Hiltjo Posthuma +Isaac Dunham +Jaydeep Patil +Jens Gustedt +Jeremy Huntwork +Jo-Philipp Wich +Joakim Sindholt +John Spencer +Julien Ramseier +Justin Cormack +Kaarle Ritvanen +Khem Raj +Kylie McClain +Leah Neukirchen +Luca Barbato +Luka Perkov +Lynn Ochs +M Farkas-Dyck (Strake) +Mahesh Bodapati +Markus Wichmann +Masanori Ogino +Michael Clark +Michael Forney +Mikhail Kremnyov +Natanael Copa +Nicholas J. Kain +orc +Pascal Cuoq +Patrick Oppenlander +Petr Hosek +Petr Skocik +Pierre Carrier +Reini Urban +Rich Felker +Richard Pennington +Ryan Fairfax +Samuel Holland +Segev Finer +Shiz +sin +Solar Designer +Stefan Kristiansson +Stefan O'Rear +Szabolcs Nagy +Timo Teräs +Trutz Behn +Will Dietz +William Haddon +William Pitcock + +Portions of this software are derived from third-party works licensed +under terms compatible with the above MIT license: + +The TRE regular expression implementation (src/regex/reg* and +src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed +under a 2-clause BSD license (license text in the source files). The +included version has been heavily modified by Rich Felker in 2012, in +the interests of size, simplicity, and namespace cleanliness. + +Much of the math library code (src/math/* and src/complex/*) is +Copyright © 1993,2004 Sun Microsystems or +Copyright © 2003-2011 David Schultz or +Copyright © 2003-2009 Steven G. Kargl or +Copyright © 2003-2009 Bruce D. Evans or +Copyright © 2008 Stephen L. Moshier or +Copyright © 2017-2018 Arm Limited +and labelled as such in comments in the individual source files. All +have been licensed under extremely permissive terms. + +The ARM memcpy code (src/string/arm/memcpy.S) is Copyright © 2008 +The Android Open Source Project and is licensed under a two-clause BSD +license. It was taken from Bionic libc, used on Android. + +The AArch64 memcpy and memset code (src/string/aarch64/*) are +Copyright © 1999-2019, Arm Limited. + +The implementation of DES for crypt (src/crypt/crypt_des.c) is +Copyright © 1994 David Burren. It is licensed under a BSD license. + +The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was +originally written by Solar Designer and placed into the public +domain. The code also comes with a fallback permissive license for use +in jurisdictions that may not recognize the public domain. + +The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011 +Lynn Ochs and is licensed under an MIT-style license. + +The x86_64 port was written by Nicholas J. Kain and is licensed under +the standard MIT terms. + +The mips and microblaze ports were originally written by Richard +Pennington for use in the ellcc project. The original code was adapted +by Rich Felker for build system and code conventions during upstream +integration. It is licensed under the standard MIT terms. + +The mips64 port was contributed by Imagination Technologies and is +licensed under the standard MIT terms. + +The powerpc port was also originally written by Richard Pennington, +and later supplemented and integrated by John Spencer. It is licensed +under the standard MIT terms. + +All other files which have no copyright comments are original works +produced specifically for use as part of this library, written either +by Rich Felker, the main author of the library, or by one or more +contibutors listed above. Details on authorship of individual files +can be found in the git version control history of the project. The +omission of copyright and license comments in each file is in the +interest of source tree size. + +In addition, permission is hereby granted for all public header files +(include/* and arch/*/bits/*) and crt files intended to be linked into +applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit +the copyright notice and permission notice otherwise required by the +license, and to use these files without any requirement of +attribution. These files include substantial contributions from: + +Bobby Bingham +John Spencer +Nicholas J. Kain +Rich Felker +Richard Pennington +Stefan Kristiansson +Szabolcs Nagy + +all of whom have explicitly granted such permission. + +This file previously contained text expressing a belief that most of +the files covered by the above exception were sufficiently trivial not +to be subject to copyright, resulting in confusion over whether it +negated the permissions granted in the license. In the spirit of +permissive licensing, and of not having licensing issues being an +obstacle to adoption, that text has been removed. + +============================================================================== +[GPL-2.0] GNU General Public License v2 +============================================================================== + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Moe Ghoul, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + +============================================================================== +[LGPL-2.1] GNU Lesser General Public License v2.1 +============================================================================== + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Moe Ghoul, President of Vice + +That's all there is to it! + +============================================================================== +[Apache-2.0] Apache License 2.0 +============================================================================== + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +============================================================================== +[MPL-2.0] Mozilla Public License 2.0 +============================================================================== + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..076579b --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,137 @@ +# Third-party notices + +Nubix is free software, licensed under the **GNU General Public License, version 3 only +(GPL-3.0-only)**; see `LICENSE`. It is an unofficial client and is not affiliated with or +endorsed by Microsoft or Sony. + +The PS5 binary (`eboot.elf` / `nubix.elf`) **statically links** every component listed under +"Linked into the PS5 binary" below. All of them are under licenses compatible with distribution of +the combined work under GPL-3.0. The complete, unmodified license text of each component, taken +from the exact upstream version used, is in `THIRD_PARTY_LICENSES.txt` (shipped next to the binary in +every release; the `[name]` after each component below is its section in that file). + +Because FFmpeg is built with `--enable-gpl --enable-version3` and the ps5-payload-sdk C runtime +is GPL-3.0-or-later, the PS5 binary as a whole can only be distributed under GPL-3.0, which is +Nubix's own license. + +## Linked into the PS5 binary + +All prebuilt components except libdatachannel and its submodules come from the +[ps5-payload-dev pacbrew-repo](https://github.com/ps5-payload-dev/pacbrew-repo) bundle release +**v0.40.2** (commit `1687e827675c66e1b040751c7f0811222bd3aea0`), which `docker/Dockerfile` +installs into the build image. + +### Multimedia + +| Component | Version | License | Copyright | License file | +|---|---|---|---|---| +| [FFmpeg](https://ffmpeg.org) (libavcodec 61.3.100, libavutil, libswresample) | 7.0.1 | **GPL-3.0-or-later as built** (configured `--enable-gpl --enable-version3 --enable-openssl --enable-libx264`; FFmpeg is otherwise LGPL-2.1-or-later) | Copyright (c) 2000-2024 the FFmpeg developers | `THIRD_PARTY_LICENSES.txt` [ffmpeg], `LICENSE` | +| [x264](https://www.videolan.org/developers/x264.html) | 0.165.3222 (commit `b35605ace3ddf7c1a5d67a2eb553f034aef41d55`) | GPL-2.0-or-later | Copyright (C) 2003-2025 x264 project | `THIRD_PARTY_LICENSES.txt` [x264] | +| [Opus](https://opus-codec.org) | 1.5.2 | BSD-3-Clause | Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic, Jean-Marc Valin, Timothy B. Terriberry, CSIRO, Gregory Maxwell, Mark Borgerding, Erik de Castro Lopo, Mozilla, Amazon | `THIRD_PARTY_LICENSES.txt` [opus] | +| [libsamplerate](https://github.com/libsndfile/libsamplerate) | 0.2.2 | BSD-2-Clause | Copyright (c) 2012-2016, Erik de Castro Lopo | `THIRD_PARTY_LICENSES.txt` [libsamplerate] | + +### WebRTC + +Built from source by `scripts/build-deps.sh` (libdatachannel tag `v0.24.6` with its pinned +submodules). + +| Component | Version | License | Copyright | License file | +|---|---|---|---|---| +| [libdatachannel](https://github.com/paullouisageneau/libdatachannel) | v0.24.6, **modified** by `patches/libdatachannel-xbox-pli.patch` (the patch is MPL-2.0) | MPL-2.0 | Copyright (c) 2019-2025 Paul-Louis Ageneau; also Filip Klembara, Staz Modrzynski, Arda Cinar, Eric Gressman, Zita Liao, Robert Edmonds, Sean DuBois, Shigemasa Watanabe, Vladimir Voronin, Alex Potsides | `THIRD_PARTY_LICENSES.txt` [libdatachannel] | +| [libjuice](https://github.com/paullouisageneau/libjuice) | v1.7.4 (`b89c792`) | MPL-2.0 | Copyright (c) 2020-2022 Paul-Louis Ageneau | `THIRD_PARTY_LICENSES.txt` [libjuice] | +| [libsrtp](https://github.com/cisco/libsrtp) | 2.8.0 (commit `24b3bf8`) | BSD-3-Clause | Copyright (c) 2001-2017 Cisco Systems, Inc. | `THIRD_PARTY_LICENSES.txt` [libsrtp] | +| [usrsctp](https://github.com/sctplab/usrsctp) | commit `fec583d` | BSD-3-Clause | Copyright (c) 2015, Randall Stewart and Michael Tuexen; Copyright (c) 2001-2008 Cisco Systems, Inc.; portions Copyright The Regents of the University of California | `THIRD_PARTY_LICENSES.txt` [usrsctp] | +| [plog](https://github.com/SergiusTheBest/plog) (via libdatachannel) | commit `94899e0` | MIT | Copyright (c) 2022 Sergey Podobry | `THIRD_PARTY_LICENSES.txt` [plog] | +| [nlohmann/json](https://github.com/nlohmann/json) (header-only; Nubix compiles the vendored copy in `third_party/nlohmann/`) | 3.11.3 | MIT | Copyright (c) 2013-2022 Niels Lohmann | `THIRD_PARTY_LICENSES.txt` [nlohmann-json] | + +### Networking and TLS + +| Component | Version | License | Copyright | License file | +|---|---|---|---|---| +| [OpenSSL](https://www.openssl.org) (libssl, libcrypto) | 3.5.2 | Apache-2.0 (OpenSSL 3.5.2 ships no NOTICE file) | Copyright 1995-2025 The OpenSSL Project Authors | `THIRD_PARTY_LICENSES.txt` [openssl] | +| [libcurl](https://curl.se) | 8.18.0 | curl | Copyright (c) 1996 - 2025, Daniel Stenberg, , and many contributors | `THIRD_PARTY_LICENSES.txt` [curl] | +| [libpsl](https://github.com/rockdaboot/libpsl) | 0.21.5 | MIT; `psl-make-dafsa` / `lookup_string_in_fixed_set.c` BSD-3-Clause (Chromium) | Copyright (C) 2014-2024 Tim Rühsen; Copyright 2015 The Chromium Authors | `THIRD_PARTY_LICENSES.txt` [libpsl], `THIRD_PARTY_LICENSES.txt` [libpsl-chromium] | +| [Public Suffix List](https://publicsuffix.org) data (compiled into libpsl) | snapshot bundled with libpsl 0.21.5 | MPL-2.0 | Mozilla Foundation and Public Suffix List contributors | `THIRD_PARTY_LICENSES.txt` [MPL-2.0] | + +### Graphics, text and input + +| Component | Version | License | Copyright | License file | +|---|---|---|---|---| +| [SDL2](https://www.libsdl.org) (PS5 port [ps5-payload-dev/SDL](https://github.com/ps5-payload-dev/SDL), branch `release-2.30.x-ps5`) | 2.30.12 | Zlib | Copyright (C) 1997-2025 Sam Lantinga | `THIRD_PARTY_LICENSES.txt` [SDL2] | +| [SDL2_ttf](https://github.com/libsdl-org/SDL_ttf) | 2.22.0 | Zlib | Copyright (C) 1997-2024 Sam Lantinga | `THIRD_PARTY_LICENSES.txt` [SDL2_ttf] | +| [FreeType](https://freetype.org) | 2.13.2 | FreeType License (FTL), chosen from FTL / GPL-2.0 | Copyright 1996-2002, 2006 by David Turner, Robert Wilhelm, and Werner Lemberg. Portions of this software are copyright © 2023 The FreeType Project (www.freetype.org). All rights reserved. | `THIRD_PARTY_LICENSES.txt` [freetype-FTL], `THIRD_PARTY_LICENSES.txt` [freetype] | +| [libpng](http://www.libpng.org) | 1.6.43 | libpng-2.0 | Copyright (c) 1995-2024 The PNG Reference Library Authors; Copyright (c) 2018-2024 Cosmin Truta; Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson; Copyright (c) 1996-1997 Andreas Dilger; Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. | `THIRD_PARTY_LICENSES.txt` [libpng] | +| [QR Code generator library](https://www.nayuki.io/page/qr-code-generator-library) (`src/ui/qrcodegen.*`) | vendored | MIT | Copyright (c) Project Nayuki | `THIRD_PARTY_LICENSES.txt` [qrcodegen] | + +### Compression and character conversion + +| Component | Version | License | Copyright | License file | +|---|---|---|---|---| +| [zlib](https://zlib.net) | 1.3.2 | Zlib | (C) 1995-2026 Jean-loup Gailly and Mark Adler | `THIRD_PARTY_LICENSES.txt` [zlib] | +| [zstd](https://github.com/facebook/zstd) | 1.5.6 | BSD-3-Clause (chosen from BSD-3-Clause / GPL-2.0) | Copyright (c) Meta Platforms, Inc. and affiliates | `THIRD_PARTY_LICENSES.txt` [zstd] | +| [xz / liblzma](https://tukaani.org/xz/) | 5.4.6 | public domain (liblzma) | Lasse Collin, Igor Pavlov and others; placed in the public domain | `THIRD_PARTY_LICENSES.txt` [xz] | +| [bzip2 / libbzip2](https://sourceware.org/bzip2/) | 1.0.8 | bzip2-1.0.6 | Copyright (C) 1996-2019 Julian R Seward | `THIRD_PARTY_LICENSES.txt` [bzip2] | +| [GNU libiconv + libcharset](https://www.gnu.org/software/libiconv/) | 1.17 | LGPL-2.1-or-later | Copyright (C) 1999-2022 Free Software Foundation, Inc. | `THIRD_PARTY_LICENSES.txt` [libiconv], `THIRD_PARTY_LICENSES.txt` [libiconv-NOTE] | + +### C / C++ runtime + +| Component | Version | License | Copyright | License file | +|---|---|---|---|---| +| [ps5-payload-sdk](https://github.com/ps5-payload-dev/sdk) `crt1.o` and `libc.a` (pacbrew v0.40.2; SDK sources at tag v0.42, `4eb7012`) | — | **GPL-3.0-or-later** (no linking exception); individual libc files carry FreeBSD / University of California BSD, OpenBSD / NetBSD ISC-style, musl MIT, public-domain and LLVM (Apache-2.0 WITH LLVM-exception, `emutls.c`) notices | Copyright (C) 2023-2026 John Törnblom (some crt files also idlesauce and sleirsgoevy); Copyright © 2005-2020 Rich Felker, et al.; Copyright The Regents of the University of California; others as listed | `THIRD_PARTY_LICENSES.txt` [ps5-payload-sdk], `THIRD_PARTY_LICENSES.txt` [ps5-payload-sdk-libc-notices], `THIRD_PARTY_LICENSES.txt` [musl] | +| [openlibm](https://github.com/JuliaMath/openlibm) | 0.8.6 | MIT / BSD-2-Clause / ISC / Sun FDLIBM permissive notice | Copyright (c) 2011-14 The Julia Project; Copyright 1992-2011 The FreeBSD Project; Copyright (C) 1993 by Sun Microsystems, Inc.; Copyright (c) 2008 Stephen L. Moshier; others in the file | `THIRD_PARTY_LICENSES.txt` [openlibm] | +| [LLVM](https://llvm.org) libc++, libc++abi, libunwind | 18.1.8 | Apache-2.0 WITH LLVM-exception | Copyright the LLVM Project contributors | `THIRD_PARTY_LICENSES.txt` [llvm-libcxx], `THIRD_PARTY_LICENSES.txt` [llvm-libcxxabi], `THIRD_PARTY_LICENSES.txt` [llvm-libunwind] | + +PS5 system libraries (`libkernel`, `libSceLibcInternal`, `libScePad`, `libSceAudioOut`, ...) are +resolved dynamically at run time from the console's firmware; they are not included in the binary +and fall under the GPL-3.0 "System Libraries" exclusion. + +The host (desktop) build used for development links the operating system's copies of the same +libraries dynamically and is not distributed. + +## Shipped as data + +| Component | License | Notes | +|---|---|---| +| [DejaVu fonts](https://dejavu-fonts.github.io) | Bitstream Vera / Arev fonts license, DejaVu changes public domain | `assets/fonts/`; license in `assets/fonts/LICENSE-DejaVu.txt` (copy in `assets/fonts/LICENSE-DejaVu.txt`). Shipped as separate files (mere aggregation), not compiled into the binary. | +| Mozilla CA certificate bundle `assets/cacert.pem` ([curl.se/ca](https://curl.se/docs/caextract.html)) | MPL-2.0 | Extracted from Mozilla's NSS certdata by the curl project; unmodified. Its source form is the file itself; license in `THIRD_PARTY_LICENSES.txt` [MPL-2.0]. | + +## Code ported or adapted from other projects + +| Project | License | Authors | What | +|---|---|---|---| +| [green-nx](https://github.com/rmrf404/green-nx) | GPL-3.0 | rmrf404, luishidalgoa, praneetreddy017, Dispnser | auth/session/catalog flow, SDP template, stream engine behaviour | +| [GreenOvercast](https://github.com/Producdevity/GreenOvercast) | MPL-2.0 | Producdevity | libdatachannel usage and the keyframe-request (PLI) fix in `patches/libdatachannel-xbox-pli.patch` | +| [ProsperoLight](https://github.com/xEasy4Breezy/ProsperoLight) | GPL-3.0 | xEasy4Breezy / blackbearreloaded | PS5 platform notes | + +**GreenOvercast / MPL-2.0 statement.** Parts of Nubix are derived from GreenOvercast, which is +"Covered Software" under the Mozilla Public License 2.0. As permitted by MPL-2.0 section 3.3, those +parts are distributed as part of a Larger Work under the GNU General Public License v3.0. They +also remain available under the terms of the MPL-2.0 (`THIRD_PARTY_LICENSES.txt` [MPL-2.0]); their source form is +included in the Nubix Corresponding Source. `patches/libdatachannel-xbox-pli.patch` is a +Modification of libdatachannel and is itself licensed under MPL-2.0 only. + +Protocol details were also cross-checked against (no code copied) [Greenlight](https://github.com/unknownskl/greenlight), +[xbox-xcloud-player](https://github.com/unknownskl/xbox-xcloud-player), [xal-node](https://github.com/unknownskl/xal-node), +[XStreaming](https://github.com/Geocld/XStreaming) (all MIT) and [green-vita](https://github.com/Day-OS/green-vita) (MPL-2.0). + +## Source code + +For every binary release, the complete **Corresponding Source** (GPL-3.0 section 6) is published +as a release asset right next to the binary, named +`nubix-v-corresponding-source.tar.xz` (see `SOURCE.txt` for its exact layout). It contains the Nubix source at the release tag, +`patches/`, the build scripts and Dockerfile, and the exact source of every third-party component +listed above (including FFmpeg, x264, the ps5-payload-sdk and the libdatachannel tree with its +submodules) at the versions given here. + +To rebuild the PS5 binary from that source: + +1. Build the toolchain image: `docker build -t ps5dev docker/` — Ubuntu 24.04, clang-18 and the + ps5-payload-dev pacbrew bundle **v0.40.2** (`docker/Dockerfile`). +2. `scripts/build-deps.sh ps5` — fetches libdatachannel v0.24.6 with its submodules, applies + `patches/libdatachannel-*.patch`, and builds it into `deps/ps5/`. +3. `scripts/build-ps5.sh` — builds `build-ps5/nubix.elf` inside the `ps5dev` container, assembles + the `dist/nubix/` homebrew folder (binary, assets, `LICENSE`, this file, `THIRD_PARTY_LICENSES.txt`) and + packages it as `build-ps5/nubix-v-websrv.zip`. + +To replace a library (for example libiconv under LGPL-2.1 section 6), rebuild that library from +its source, install it into the toolchain sysroot and rerun steps 2 and 3. diff --git a/assets/cacert.pem b/assets/cacert.pem new file mode 100644 index 0000000..4b694a5 --- /dev/null +++ b/assets/cacert.pem @@ -0,0 +1,2950 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla as of: Fri Sep 25 03:12:01 2026 GMT +## +## Find updated versions here: https://curl.se/docs/caextract.html +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/release/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl version 1.33. +## SHA256: beb7e6dfe6499926e52c075c27bcfbe4c957f8609c575b3860273ae2806f63eb +## + + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +======================================== +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn +dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ +FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ +5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG +x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX +2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL +OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 +sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C +GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 +WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt +rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ +nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg +tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW +sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp +pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA +zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq +ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 +7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz +0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j +Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn +RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O ++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq +/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE +Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM +lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 +yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ +eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW +FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ +7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ +Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM +8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi +FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi +yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c +J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw +sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GB CA +=============================== +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG +EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw +MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds +b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX +scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP +rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk +9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o +Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg +GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI +hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD +dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 +VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui +HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +SZAFIR ROOT CA2 +=============== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV +BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ +BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD +VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q +qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK +DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE +2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ +ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi +ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC +AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 +O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 +oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul +4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 ++/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +Certum Trusted Network CA 2 +=========================== +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE +BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 +bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y +ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ +TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB +IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 +7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o +CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b +Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p +uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 +GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ +9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB +Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye +hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM +BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW +Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA +L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo +clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM +pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb +w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo +J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm +ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX +is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 +zAYspsbiDrW5viSP +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2015 +======================================================= +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT +BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 +aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx +MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg +QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV +BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw +MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv +bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh +iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ +6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd +FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr +i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F +GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 +fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu +iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI +hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ +D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM +d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y +d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn +82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb +davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F +Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt +J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa +JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q +p/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions ECC RootCA 2015 +=========================================================== +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 +aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw +MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj +IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD +VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 +Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP +dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK +Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA +GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn +dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +ISRG Root X1 +============ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE +BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD +EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG +EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT +DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r +Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 +3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K +b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN +Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ +4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf +1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu +hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH +usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r +OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY +9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV +0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt +hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw +TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx +e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA +JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD +YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n +JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ +m+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM +================ +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT +AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw +MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD +TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf +qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr +btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL +j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou +08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw +WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT +tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ +47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC +ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa +i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o +dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s +D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ +j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT +Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW ++YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 +Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d +8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm +5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG +rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +Amazon Root CA 1 +================ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 +MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH +FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ +gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t +dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce +VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 +DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM +CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy +8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa +2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 +xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +Amazon Root CA 2 +================ +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 +MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 +kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp +N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 +AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd +fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx +kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS +btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 +Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN +c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ +3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw +DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA +A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE +YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW +xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ +gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW +aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV +Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 +KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi +JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= +-----END CERTIFICATE----- + +Amazon Root CA 3 +================ +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB +f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr +Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 +rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc +eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +Amazon Root CA 4 +================ +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN +/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri +83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA +MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 +AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT +D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr +IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g +TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp +ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD +VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt +c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth +bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 +IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 +6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc +wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 +3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 +WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU +ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc +lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R +e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j +q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +GDCA TrustAUTH R5 ROOT +====================== +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw +BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD +DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow +YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs +AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p +OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr +pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ +9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ +xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM +R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ +D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 +oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx +9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 +H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 +6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd ++PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ +HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD +F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ +8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv +/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT +aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +SSL.com Root Certification Authority RSA +======================================== +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM +BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x +MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw +MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM +LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C +Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8 +P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge +oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp +k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z +fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ +gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2 +UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8 +1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s +bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr +dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf +ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl +u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq +erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj +MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ +vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI +Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y +wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI +WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +SSL.com Root Certification Authority ECC +======================================== +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv +BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy +MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO +BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ +8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR +hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT +jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW +e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z +5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority RSA R2 +============================================== +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w +DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u +MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD +VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh +hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w +cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO +Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ +B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh +CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim +9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto +RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm +JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 ++qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp +qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 +++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx +Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G +guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz +OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 +CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq +lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR +rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 +hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX +9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority ECC +=========================================== +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy +BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw +MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM +LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy +3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O +BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe +5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ +N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm +m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +GlobalSign Root CA - R6 +======================= +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX +R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i +YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs +U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss +grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE +3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF +vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM +PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ +azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O +WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy +CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP +0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN +b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV +HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 +lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY +BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym +Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr +3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 +0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T +uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK +oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t +JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GC CA +=============================== +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD +SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo +MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa +Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL +ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr +VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab +NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E +AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk +AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +UCA Global G2 Root +================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x +NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU +cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT +oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV +8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS +h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o +LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ +R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe +KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa +4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc +OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 +8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo +5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A +Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 +yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX +c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo +jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk +bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x +ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn +RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== +-----END CERTIFICATE----- + +UCA Extended Validation Root +============================ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u +IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G +A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs +iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF +Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu +eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR +59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH +0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR +el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv +B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth +WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS +NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS +3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM +aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 +dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb ++7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW +F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi +GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc +GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi +djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr +dhh2n1ax +-----END CERTIFICATE----- + +Certigna Root CA +================ +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE +BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ +MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda +MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz +MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX +stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz +KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 +JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 +XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq +4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej +wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ +lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI +jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ +/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy +dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h +LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl +cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt +OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP +TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq +7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 +4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd +8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS +6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY +tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS +aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde +E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +emSign Root CA - G1 +=================== +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET +MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx +ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk +aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN +LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 +cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW +DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ +6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH +hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 +vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q +NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q ++Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih +U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +emSign ECC Root CA - G3 +======================= +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG +A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg +MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 +MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 +ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc +58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr +MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D +CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 +jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +emSign Root CA - C1 +=================== +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx +EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp +Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD +ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up +ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ +Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX +OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V +I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms +lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ +XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp +/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 +NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 +wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ +BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +emSign ECC Root CA - C3 +======================= +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG +A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF +Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD +ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd +6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 +SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA +B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA +MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU +ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +Hongkong Post Root CA 3 +======================= +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG +A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK +Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2 +MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv +bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX +SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz +iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf +jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim +5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe +sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj +0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/ +JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u +y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h ++bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG +xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID +AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN +AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw +W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld +y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov ++BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc +eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw +9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7 +nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY +hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB +60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq +dBb9HxEGmpv0 +-----END CERTIFICATE----- + +Microsoft ECC Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 +MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 +thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB +eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM ++Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf +Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR +eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +Microsoft RSA Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg +UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw +NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml +7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e +S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 +1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ +dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F +yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS +MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr +lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ +0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ +ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og +6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 +dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk ++ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex +/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy +AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW +ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE +7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT +c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D +5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +e-Szigno Root CA 2017 +===================== +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw +DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt +MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa +Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE +CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp +Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx +s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv +vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA +tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO +svxyqltZ+efcMQ== +-----END CERTIFICATE----- + +certSIGN Root CA G2 +=================== +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw +EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy +MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH +TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 +N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk +abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg +wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp +dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh +ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 +jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf +95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc +z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL +iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB +ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB +/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 +8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 +BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW +atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU +Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M +NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N +0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +NAVER Global Root Certification Authority +========================================= +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG +A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD +DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 +NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT +UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb +UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW ++j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 +XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 +aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 +Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z +VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B +A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai +cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy +YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV +HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK +21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB +jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx +hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg +E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH +D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ +A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY +qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG +I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg +kpzNNIaRkPpkUZ3+/uul9XXeifdy +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM SERVIDORES SEGUROS +=================================== +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF +UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy +NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 +MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt +UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB +QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 +LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG +SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD +zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= +-----END CERTIFICATE----- + +GlobalSign Root R46 +=================== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv +b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX +BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es +CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ +r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje +2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt +bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj +K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 +12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on +ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls +eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 +vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM +BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy +gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 +CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm +OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq +JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye +qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz +nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 +DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 +QEUxeCp6 +-----END CERTIFICATE----- + +GlobalSign Root E46 +=================== +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT +AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg +RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV +BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB +jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj +QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL +gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk +vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ +CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +ANF Secure Server Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4 +NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv +bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg +Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw +MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw +EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz +BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv +T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv +B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse +zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM +VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j +7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z +JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe +8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO +Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ +UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx +j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt +dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM +5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb +5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54 +EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H +hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy +g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3 +r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +Certum EC-384 CA +================ +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ +TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 +MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh +dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq +vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn +iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo +ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 +QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +Certum Trusted Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG +EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew +HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY +QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p +fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52 +HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2 +fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt +g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4 +NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk +fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ +P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY +njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK +HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL +LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s +ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K +h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8 +CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA +4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo +WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj +6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT +OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck +bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- + +TunTrust Root CA +================ +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG +A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj +dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw +NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD +ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz +2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b +bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7 +NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd +gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW +VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f +Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ +juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas +DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS +VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI +04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl +0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd +Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY +YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp +adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x +xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP +jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM +MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z +ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r +AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +HARICA TLS RSA Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG +EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz +OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl +bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB +IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN +JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu +a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y +Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K +5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv +dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR +0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH +GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm +haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ +CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU +EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq +QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD +QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR +j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5 +vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0 +qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6 +Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/ +PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn +kf3/W9b3raYvAwtt41dU63ZTGI0RmLo= +-----END CERTIFICATE----- + +HARICA TLS ECC Root CA 2021 +=========================== +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH +UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD +QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX +DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj +IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv +b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l +AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b +ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW +0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi +rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw +CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud +DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w +gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A +bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL +4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb +LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il +I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP +cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA +LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A +lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH +9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf +NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE +ZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +vTrus ECC Root CA +================= +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE +BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS +b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa +BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c +ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n +TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT +QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL +YgmRWAD5Tfs0aNoJrSEGGJTO +-----END CERTIFICATE----- + +vTrus Root CA +============= +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG +A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv +b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG +A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots +SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI +ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF +XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA +YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70 +kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2 +AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu +/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu +1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO +9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg +scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC +AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr +jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4 +8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn +xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg +icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4 +sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW +nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc +SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H +l3s= +-----END CERTIFICATE----- + +ISRG Root X2 +============ +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV +UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT +UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT +MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS +RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H +ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb +d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF +cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5 +U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +HiPKI Root CA - G1 +================== +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ +IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT +AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg +Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0 +o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k +wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE +YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA +GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd +hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj +1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4 +9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/ +Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF +8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD +AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl +tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE +wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q +JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv +5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz +jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg +hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb +yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/ +yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i +YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW +ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI +KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg +UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +GTS Root R1 +=========== +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM +f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0 +xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w +B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW +nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk +9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq +kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A +K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX +V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW +cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD +ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi +ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar +J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci +NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me +LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF +fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+ +7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3 +FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3 +gm3c +-----END CERTIFICATE----- + +GTS Root R3 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout +736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq +Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT +L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV +11RZt+cRLInUue4X +-----END CERTIFICATE----- + +GTS Root R4 +=========== +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi +MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw +HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ +R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO +PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu +hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA +MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1 +PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C +r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh +4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +Telia Root CA v2 +================ +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT +AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2 +MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK +DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7 +6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q +9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn +pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl +tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW +5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr +RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E +BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4 +M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau +BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W +xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5 +tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H +eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C +y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC +QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15 +h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70 +sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9 +xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ +raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc= +-----END CERTIFICATE----- + +D-TRUST BR Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7 +dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu +QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom +AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +D-TRUST EV Root CA 1 2020 +========================= +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE +RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy +MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV +BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8 +ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ +raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL +MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu +bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP +PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD +AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR +AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +DigiCert TLS ECC P384 Root G5 +============================= +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4 +NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg +Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd +lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj +n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB +/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds +Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx +AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +DigiCert TLS RSA4096 Root G5 +============================ +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG +EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0 +MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2 +IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8 +7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU +AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces +tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa +zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV +DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q +TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy +z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/ +MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk +wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E +FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN +lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN +MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/ +u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G +OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh +47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU +FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ +yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP +bEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +Certainly Root R1 +================= +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE +BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN +MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy +dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O +5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl +8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl +DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI +XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN +KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ +AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb +rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1 +VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS +p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz +HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v +MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB +GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+ +gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH +JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7 +fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw +x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S +X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +Certainly Root E1 +================= +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV +UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0 +MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu +bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4 +fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9 +YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E +AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 +rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +Security Communication ECC RootCA1 +================================== +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD +VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t +dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL +MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV +BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo +5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW +BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L +snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e +N9k= +-----END CERTIFICATE----- + +BJCA Global Root CA1 +==================== +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQG +EwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJK +Q0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAzMTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkG +A1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQD +DBRCSkNBIEdsb2JhbCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFm +CL3ZxRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZspDyRhyS +sTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O558dnJCNPYwpj9mZ9S1Wn +P3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgRat7GGPZHOiJBhyL8xIkoVNiMpTAK+BcW +yqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRj +eulumijWML3mG90Vr4TqnMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNn +MoH1V6XKV0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/pj+b +OT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZOz2nxbkRs1CTqjSSh +GL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXnjSXWgXSHRtQpdaJCbPdzied9v3pK +H9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMB +AAGjQjBAMB0GA1UdDgQWBBTF7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 +YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3KliawLwQ8hOnThJ +dMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u+2D2/VnGKhs/I0qUJDAnyIm8 +60Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuh +TaRjAv04l5U/BXCga99igUOLtFkNSoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW +4AB+dAb/OMRyHdOoP2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmp +GQrI+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRzznfSxqxx +4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9eVzYH6Eze9mCUAyTF6ps +3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4S +SPfSKcOYKMryMguTjClPPGAyzQWWYezyr/6zcCwupvI= +-----END CERTIFICATE----- + +BJCA Global Root CA2 +==================== +-----BEGIN CERTIFICATE----- +MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQswCQYDVQQGEwJD +TjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJKQ0Eg +R2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgyMVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UE +BhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRC +SkNBIEdsb2JhbCBSb290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jl +SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK +/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJKsVF/BvDRgh9Obl+rg/xI +1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8 +W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g +UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root E46 +============================================= +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH +QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2 +ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5 +WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0 +aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr +gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0 +NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud +DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH +lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U +SAGKcw== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root R46 +============================================= +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1 +OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k +1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf +GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP +FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu +ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz +Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A +wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF +plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ +EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW +6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI +IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp +E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4 +exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M +0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI +84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m +pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd +Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b +E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm +J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +SSL.com TLS RSA Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG +EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg +Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC +VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u +9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y +7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac +oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M +R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG +D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW +TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk +8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq +g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk +7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu +N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN +j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by +iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU +o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo +ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib +MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi +vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7 +P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0 +9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +SSL.com TLS ECC Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v +dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx +GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy +JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1 +5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7 +81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG +MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w +7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5 +Zn6g6g== +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA ECC TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB +dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD +VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg +VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT +AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K +DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS +b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX +NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+ +uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY +a3cpetskz2VAv9LcjBHo9H1/IISpQuQo +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA RSA TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD +DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw +CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0 +b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV +BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB +l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG +vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK +ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt +0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK +PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY +sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY +Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+ +rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa +fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G +CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl +Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX +AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G +slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt +afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q +TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj +1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l +PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W +HYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +TrustAsia Global Root CA G3 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEMBQAwWjELMAkG +A1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMM +G1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAeFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEw +MTlaMFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMu +MSQwIgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNST1QY4Sxz +lZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqKAtCWHwDNBSHvBm3dIZwZ +Q0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/V +P68czH5GX6zfZBCK70bwkPAPLfSIC7Epqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1Ag +dB4SQXMeJNnKziyhWTXAyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm +9WAPzJMshH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gXzhqc +D0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAvkV34PmVACxmZySYg +WmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msTf9FkPz2ccEblooV7WIQn3MSAPmea +mseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jAuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCF +TIcQcf+eQxuulXUtgQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj +7zjKsK5Xf/IhMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4wM8zAQLpw6o1 +D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2XFNFV1pF1AWZLy4jVe5jaN/T +G3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNj +duMNhXJEIlU/HHzp/LgV6FL6qj6jITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstl +cHboCoWASzY9M/eVVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys ++TIxxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1onAX1daBli +2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d7XB4tmBZrOFdRWOPyN9y +aFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2NtjjgKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsAS +ZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFR +JQJ6+N1rZdVtTTDIZbpoFGWsJwt0ivKH +-----END CERTIFICATE----- + +TrustAsia Global Root CA G4 +=========================== +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMwWjELMAkGA1UE +BhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMMG1Ry +dXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0yMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJa +MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQw +IgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATxs8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbwLxYI+hW8 +m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJijYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mDpm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/ +pDHel4NZg6ZvccveMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AA +bbd+NvBNEU/zy4k6LHiRUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xk +dUfFVZDj/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +Telekom Security TLS ECC Root 2020 +================================== +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQswCQYDVQQGEwJE +RTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJUZWxl +a29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIwMB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIz +NTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkg +R21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqG +SM49AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/OtdKPD/M1 +2kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDPf8iAC8GXs7s1J8nCG6NC +MEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6fMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMAoGCCqGSM49BAMDA2cAMGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZ +Mo7k+5Dck2TOrbRBR2Diz6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdU +ga/sf+Rn27iQ7t0l +-----END CERTIFICATE----- + +Telekom Security TLS RSA Root 2023 +================================== +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBjMQswCQYDVQQG +EwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJU +ZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAyMDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMy +NzIzNTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJp +dHkgR21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9cUD/h3VC +KSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHVcp6R+SPWcHu79ZvB7JPP +GeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMAU6DksquDOFczJZSfvkgdmOGjup5czQRx +UX11eKvzWarE4GC+j4NSuHUaQTXtvPM6Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWo +l8hHD/BeEIvnHRz+sTugBTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9 +FIS3R/qy8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73Jco4v +zLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg8qKrBC7m8kwOFjQg +rIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8rFEz0ciD0cmfHdRHNCk+y7AO+oML +KFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7S +WWO/gLCMk3PLNaaZlSJhZQNg+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUtqeXgj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQpGv7qHBFfLp+ +sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm9S3ul0A8Yute1hTWjOKWi0Fp +kzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErwM807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy +/SKE8YXJN3nptT+/XOR0so8RYgDdGGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4 +mZqTuXNnQkYRIer+CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtz +aL1txKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+w6jv/naa +oqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aKL4x35bcF7DvB7L6Gs4a8 +wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+ljX273CXE2whJdV/LItM3z7gLfEdxquVeE +HVlNjM7IDiPCtyaaEBRx/pOyiriA8A4QntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0 +o82bNSQ3+pCTE4FCxpgmdTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- + +TWCA CYBER Root CA +================== +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQMQswCQYDVQQG +EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB +IENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQG +EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB +IENZQkVSIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1s +Ts6P40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxFavcokPFh +V8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/34bKS1PE2Y2yHer43CdT +o0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684iJkXXYJndzk834H/nY62wuFm40AZoNWDT +Nq5xQwTxaWV4fPMf88oon1oglWa0zbfuj3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK +/c/WMw+f+5eesRycnupfXtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkH +IuNZW0CP2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDAS9TM +fAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDAoS/xUgXJP+92ZuJF +2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzCkHDXShi8fgGwsOsVHkQGzaRP6AzR +wyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83 +QOGt4A1WNzAdBgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0ttGlTITVX1olN +c79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn68xDiBaiA9a5F/gZbG0jAn/x +X9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNnTKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDR +IG4kqIQnoVesqlVYL9zZyvpoBJ7tRCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq +/p1hvIbZv97Tujqxf36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0R +FxbIQh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz8ppy6rBe +Pm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4NxKfKjLji7gh7MMrZQzv +It6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzXxeSDwWrruoBa3lwtcHb4yOWHh8qgnaHl +IhInD0Q9HWzq1MKLL295q39QpsQZp6F6t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- + +SecureSign Root CA14 +==================== +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEMBQAwUTELMAkG +A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT +ZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgwNzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJ +BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU +U2VjdXJlU2lnbiBSb290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh +1oq/FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOgvlIfX8xn +bacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy6pJxaeQp8E+BgQQ8sqVb +1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa +/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9JkdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOE +kJTRX45zGRBdAuVwpcAQ0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSx +jVIHvXiby8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac18iz +ju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs0Wq2XSqypWa9a4X0 +dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIABSMbHdPTGrMNASRZhdCyvjG817XsY +AFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVLApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeq +YR3r6/wtbyPk86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E +rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ibed87hwriZLoA +ymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopTzfFP7ELyk+OZpDc8h7hi2/Ds +Hzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHSDCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPG +FrojutzdfhrGe0K22VoF3Jpf1d+42kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6q +nsb58Nn4DSEC5MUoFlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/ +OfVyK4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6dB7h7sxa +OgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtlLor6CZpO2oYofaphNdgO +pygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB365jJ6UeTo3cKXhZ+PmhIIynJkBugnLN +eLLIjzwec+fBH7/PzqUqm9tEZDKgu39cJRNItX+S +-----END CERTIFICATE----- + +SecureSign Root CA15 +==================== +-----BEGIN CERTIFICATE----- +MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMwUTELMAkGA1UE +BhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRTZWN1 +cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMyNTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNV +BAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2Vj +dXJlU2lnbiBSb290IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5G +dCx4wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSRZHX+AezB +2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT9DAKBggqhkjOPQQDAwNoADBlAjEA2S6J +fl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJ +SwdLZrWeqrqgHkHZAXQ6bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= +-----END CERTIFICATE----- + +D-TRUST BR Root CA 2 2023 +========================= +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG +EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0Eg +MiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUwOTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTAT +BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCT +cfKri3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNEgXtRr90z +sWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8k12b9py0i4a6Ibn08OhZ +WiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCTRphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6 +++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LUL +QyReS2tNZ9/WtT5PeB+UcSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIv +x9gvdhFP/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bSuREV +MweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+0bpwHJwh5Q8xaRfX +/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4NDfTisl01gLmB1IRpkQLLddCNxbU9 +CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUZ5Dw1t61GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC +MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tIFoE9c+CeJyrr +d6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67nriv6uvw8l5VAk1/DLQOj7aRv +U9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTRVFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4 +nj8+AybmTNudX0KEPUUDAxxZiMrcLmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdij +YQ6qgYF/6FKC0ULn4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff +/vtDhQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsGkoHU6XCP +pz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46ls/pdu4D58JDUjxqgejB +WoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aSEcr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/ +5usWDiJFAbzdNpQ0qTUmiteXue4Icr80knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jt +n/mtd+ArY0+ew+43u3gJhJ65bvspmZDogNOfJA== +-----END CERTIFICATE----- + +TrustAsia TLS ECC Root CA +========================= +-----BEGIN CERTIFICATE----- +MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMwWDELMAkGA1UE +BhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNVBAMTGVRy +dXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBY +MQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAG +A1UEAxMZVHJ1c3RBc2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/ +pVs/AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDpguMqWzJ8 +S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49 +BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15K +eAIxAKORh/IRM4PDwYqROkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ== +-----END CERTIFICATE----- + +TrustAsia TLS RSA Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEMBQAwWDELMAkG +A1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNVBAMT +GVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2 +WjBYMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEi +MCAGA1UEAxMZVHJ1c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+NmDQDIPN +lOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJQ1DNDX3eRA5gEk9bNb2/ +mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fk +zv93uMltrOXVmPGZLmzjyUT5tUMnCE32ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYo +zza/+lcK7Fs/6TAWe8TbxNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyr +z2I8sMeXi9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQUNoy +IBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+jTnhMmCWr8n4uIF6C +FabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DTbE3txci3OE9kxJRMT6DNrqXGJyV1 +J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnT +q1mt1tve1CuBAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZ +ylomkadFK/hTMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3 +Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4iqME3mmL5Dw8 +veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt7DlK9RME7I10nYEKqG/odv6L +TytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHx +tlotJnMnlvm5P1vQiJ3koP26TpUJg3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp +27RIGAAtvKLEiUUjpQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87q +qA8MpugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongPXvPKnbwb +PKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIweSsCI3zWQzj8C9GRh3sfI +B5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNz +FrwFuHnYWa8G5z9nODmxfKuU4CkUpijy323imttUQ/hHWKNddBWcwauwxzQ= +-----END CERTIFICATE----- + +D-TRUST EV Root CA 2 2023 +========================= +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG +EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0Eg +MiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUwOTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTAT +BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1 +sJkKF8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE7CUXFId/ +MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFeEMbsh2aJgWi6zCudR3Mf +vc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6lHPTGGkKSv/BAQP/eX+1SH977ugpbzZM +lWGG2Pmic4ruri+W7mjNPU0oQvlFKzIbRlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3 +YG14C8qKXO0elg6DpkiVjTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq910 +7PncjLgcjmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZxTnXo +nMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ARZZaBhDM7DS3LAa +QzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nkhbDhezGdpn9yo7nELC7MmVcOIQxF +AZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knFNXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUqvyREBuHkV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC +MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14QvBukEdHjqOS +Mo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4pZt+UPJ26oUFKidBK7GB0aL2 +QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xD +UmPBEcrCRbH0O1P1aa4846XerOhUt7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V +4U/M5d40VxDJI3IXcI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuo +dNv8ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT2vFp4LJi +TZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs7dpn1mKmS00PaaLJvOwi +S5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNPgofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/ +HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAstNl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L ++KIkBI3Y4WNeApI02phhXBxvWHZks/wCuPWdCg== +-----END CERTIFICATE----- + +SwissSign RSA TLS Root CA 2022 - 1 +================================== +-----BEGIN CERTIFICATE----- +MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UEAxMiU3dpc3NTaWduIFJTQSBU +TFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgxMTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJ +BgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0Eg +VExTIFJvb3QgQ0EgMjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmji +C8NXvDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7LCTLf5Im +gKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX5XH8irCRIFucdFJtrhUn +WXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyEEPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlf +GUEGjw5NBuBwQCMBauTLE5tzrE0USJIt/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36q +OTw7D59Ke4LKa2/KIj4x0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLO +EGrOyvi5KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM0ZPl +EuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shdOxtYk8EXlFXIC+OC +eYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrtaclXvyFu1cvh43zcgTFeRc5JzrBh3 +Q4IgaezprClG5QtO+DdziZaKHG29777YtvTKwP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQAB +o2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow +4UD2p8P98Q+4DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL +BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO310aewCoSPY6W +lkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgzHqp41eZUBDqyggmNzhYzWUUo +8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQiJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zp +y1FVCypM9fJkT6lc/2cyjlUtMoIcgC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3Cjlvr +zG4ngRhZi0Rjn9UMZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6M +OuhFLhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJpzv1/THfQ +wUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/TdAo9QAwKxuDdollDruF/U +KIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0n +hzck5npgL7XTgwSqT0N1osGDsieYK7EOgLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rw +tnu64ZzZ +-----END CERTIFICATE----- + +OISTE Server Root ECC G1 +======================== +-----BEGIN CERTIFICATE----- +MIICNTCCAbqgAwIBAgIQI/nD1jWvjyhLH/BU6n6XnTAKBggqhkjOPQQDAzBLMQswCQYDVQQGEwJD +SDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwYT0lTVEUgU2VydmVyIFJvb3Qg +RUNDIEcxMB4XDTIzMDUzMTE0NDIyOFoXDTQ4MDUyNDE0NDIyN1owSzELMAkGA1UEBhMCQ0gxGTAX +BgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IEVDQyBH +MTB2MBAGByqGSM49AgEGBSuBBAAiA2IABBcv+hK8rBjzCvRE1nZCnrPoH7d5qVi2+GXROiFPqOuj +vqQycvO2Ackr/XeFblPdreqqLiWStukhEaivtUwL85Zgmjvn6hp4LrQ95SjeHIC6XG4N2xml4z+c +KrhAS93mT6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQ3TYhlz/w9itWj8UnATgwQ +b0K0nDAdBgNVHQ4EFgQUN02IZc/8PYrVo/FJwE4MEG9CtJwwDgYDVR0PAQH/BAQDAgGGMAoGCCqG +SM49BAMDA2kAMGYCMQCpKjAd0MKfkFFRQD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxg +ZzFDJe0CMQCSia7pXGKDYmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c= +-----END CERTIFICATE----- + +OISTE Server Root RSA G1 +======================== +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBLMQswCQYDVQQG +EwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwYT0lTVEUgU2VydmVyIFJv +b3QgUlNBIEcxMB4XDTIzMDUzMTE0MzcxNloXDTQ4MDUyNDE0MzcxNVowSzELMAkGA1UEBhMCQ0gx +GTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IFJT +QSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKqu9KuCz/vlNwvn1ZatkOhLKdxV +YOPMvLO8LZK55KN68YG0nnJyQ98/qwsmtO57Gmn7KNByXEptaZnwYx4M0rH/1ow00O7brEi56rAU +jtgHqSSY3ekJvqgiG1k50SeH3BzN+Puz6+mTeO0Pzjd8JnduodgsIUzkik/HEzxux9UTl7Ko2yRp +g1bTacuCErudG/L4NPKYKyqOBGf244ehHa1uzjZ0Dl4zO8vbUZeUapU8zhhabkvG/AePLhq5Svdk +NCncpo1Q4Y2LS+VIG24ugBA/5J8bZT8RtOpXaZ+0AOuFJJkk9SGdl6r7NH8CaxWQrbueWhl/pIzY ++m0o/DjH40ytas7ZTpOSjswMZ78LS5bOZmdTaMsXEY5Z96ycG7mOaES3GK/m5Q9l3JUJsJMStR8+ +lKXHiHUhsd4JJCpM4rzsTGdHwimIuQq6+cF0zowYJmXa92/GjHtoXAvuY8BeS/FOzJ8vD+HomnqT +8eDI278n5mUpezbgMxVz8p1rhAhoKzYHKyfMeNhqhw5HdPSqoBNdZH702xSu+zrkL8Fl47l6QGzw +Brd7KJvX4V84c5Ss2XCTLdyEr0YconosP4EmQufU2MVshGYRi3drVByjtdgQ8K4p92cIiBdcuJd5 +z+orKu5YM+Vt6SmqZQENghPsJQtdLEByFSnTkCz3GkPVavBpAgMBAAGjYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHwYDVR0jBBgwFoAU8snBDw1jALvsRQ5KH7WxszbNDo0wHQYDVR0OBBYEFPLJwQ8NYwC7 +7EUOSh+1sbM2zQ6NMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEANGd5sjrG5T33 +I3K5Ce+SrScfoE4KsvXaFwyihdJ+klH9FWXXXGtkFu6KRcoMQzZENdl//nk6HOjG5D1rd9QhEOP2 +8yBOqb6J8xycqd+8MDoX0TJD0KqKchxRKEzdNsjkLWd9kYccnbz8qyiWXmFcuCIzGEgWUOrKL+ml +Sdx/PKQZvDatkuK59EvV6wit53j+F8Bdh3foZ3dPAGav9LEDOr4SfEE15fSmG0eLy3n31r8Xbk5l +8PjaV8GUgeV6Vg27Rn9vkf195hfkgSe7BYhW3SCl95gtkRlpMV+bMPKZrXJAlszYd2abtNUOshD+ +FKrDgHGdPY3ofRRsYWSGRqbXVMW215AWRqWFyp464+YTFrYVI8ypKVL9AMb2kI5Wj4kI3Zaq5tNq +qYY19tVFeEJKRvwDyF7YZvZFZSS0vod7VSCd9521Kvy5YhnLbDuv0204bKt7ph6N/Ome/msVuduC +msuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3J8tRd/iWkx7P8nd9H0aT +olkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2wq1yVAb+axj5d9spLFKebXd7Yv0PTY6Y +MjAwcRLWJTXjn/hvnLXrahut6hDTlhZyBiElxky8j3C7DOReIoMt0r7+hVu05L0= +-----END CERTIFICATE----- + +e-Szigno TLS Root CA 2023 +========================= +-----BEGIN CERTIFICATE----- +MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xFzAVBgNVBGEMDlZBVEhV +LTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBUTFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0 +MDAwMFoXDTM4MDcxNzE0MDAwMFowdTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYw +FAYDVQQKDA1NaWNyb3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZ +ZS1Temlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAGgP36J8 +PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFSAL/fjO1ZrTJlqwlZULUZ +wmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/vSzUaQ49CE0y5LBqcvjC2xN7cS53kpDzL +Ltmt3999Cd8ukv+ho2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E +FgQUWYQCYlpGePVd3I8KECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0Uw +CgYIKoZIzj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpty7Ve +7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZlC9p2x1L/Cx6AcCIw +wzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6uWWL +-----END CERTIFICATE----- + +SECOM TLS RSA Root CA 2024 +========================== +-----BEGIN CERTIFICATE----- +MIIFmjCCA4KgAwIBAgIJAO6JNNDLgOCyMA0GCSqGSIb3DQEBDAUAMFoxCzAJBgNVBAYTAkpQMSYw +JAYDVQQKEx1TRUNPTSBUcnVzdCBTeXN0ZW1zIENvLiwgTHRkLjEjMCEGA1UEAxMaU0VDT00gVExT +IFJTQSBSb290IENBIDIwMjQwHhcNMjQwMTMxMDUxMTU1WhcNNDkwMTE0MDUxMTU1WjBaMQswCQYD +VQQGEwJKUDEmMCQGA1UEChMdU0VDT00gVHJ1c3QgU3lzdGVtcyBDby4sIEx0ZC4xIzAhBgNVBAMT +GlNFQ09NIFRMUyBSU0EgUm9vdCBDQSAyMDI0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEA4TjizUwzxbInq8Tx11gaFYNk5fO+34y7TyM4neh0UgL5JIZbJNLTz2x//L/B71+5m6X6nGIr +7d4lFJBGtjO677hXOz93zkcWaUTm3VbOAjBlt4YWxlcccBHXuZ7o3Q+4R+ormrBdHeJ1CTUEG8tt +QbKIl3G7OZYbnH8/pP8cjPub/0kDVNuMzp7xsVRROOisQt53fMoJLlYgoebbuMphOqMCtjkJ7R6e +fEMfLp8UAVi9ZaLRn76ET/CJkk925nduuufC4BatS4mnXFmxN0vUXb0ij9B8O/D8gixQEsVSD4GK +8FWRPh3bVd/6bzdkHGJjy21XI0yejVomZUbRrOfNuz0boPGV1pt18fFC39IHQEth3OFqb5NDO3L+ +A9bNqTgAyUgRmIn4ucgDc/Ri/Km3V51ueZjy1/yk0qwJVadAVVrCt56iNeXOyEvzJADGgDQ8E1Pd +aqct8Cynz/47ReQM62vFYO08wcQkrjmX/tesiko1V1yyaf6EfPzUFzmaGy9xvkCwdbm15EdTolOj +E0H2Vb5/APDOyCFEokiYGmXTLdAUl0wKZ4IyjkHGzy0jhpaXEXE/GJcEvI6VzEchjaBL03EJ0h9p +G4OqeIOycKvAo3A+TbetyfsrgYyHzU0a7/qUjGat1AAq1nVljMpKqpinPTsf/d9H39FTUeJL7Tpz +zjUCAwEAAaNjMGEwHQYDVR0OBBYEFCzrchKOWHdkNRVWNQFXB6l9DTbmMB8GA1UdIwQYMBaAFCzr +chKOWHdkNRVWNQFXB6l9DTbmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBDAUAA4ICAQAVwsvluSafaez5tFPR/hRTBzRxEyMMQF3XJXCVi3yegZyKoec7hmE6jx2Z +M8KgM1kn2yJRwFXHX8zUW9nBLEDWc4wuE8LrlZqhGZM9pJQXGmGzResDJV6JgRBna+j4sA1M7yId +lvL0sAfFXFCTRaWTD4E1V99RLrFzWfTcC+e180hDuNMpqOEo46+lMeW/Wvh7ifOQs+kiK0O2gHxQ +DNxslSavnCs4V7l8HRDJ2La10o70Bo7VLzf1W8MBvv0VTnxB+NjT5qTAbhGFh9Gvp4BaJpmdUf0C +5CEP6dbQlfgxWfzYr69yVT6dPQB+GFEaY03IMY+AcBCs+om1fNxrQXt9zoofMBNFbLhvpNH/JsXW +dGUzfNbO12uswTa5wah8LB18FTQN2/zPHYmvBEoLuyUgZ09VNLJo5YA0kXItVYkLjMe2SixzK4sc +UHv81IK99I91DWx7FwMVKw2xgFp+ZLYB2dnpQQrqwlW64glHUcK2N9BDsnjLSxeZ+UPECh9RxH4W +AcKiZW+cqaKMmhP2WBfR4IcR7NOL32ml11ds87hhV1CZWWFCJAcCidYZz6CZa8exzHojP9SB5RH0 +/v1KdHAisqhSjtJl/UIAHIQ48elOn8wrTdFap4Yb5aHglmMeNx+fAIhDluWVfxTO7H4dTPU+SFVR +MLAh+wwKZfqb94nMeQ== +-----END CERTIFICATE----- + +SECOM TLS ECC Root CA 2024 +========================== +-----BEGIN CERTIFICATE----- +MIICTDCCAdGgAwIBAgIJAIF6LO+PI3pEMAoGCCqGSM49BAMDMFoxCzAJBgNVBAYTAkpQMSYwJAYD +VQQKEx1TRUNPTSBUcnVzdCBTeXN0ZW1zIENvLiwgTHRkLjEjMCEGA1UEAxMaU0VDT00gVExTIEVD +QyBSb290IENBIDIwMjQwHhcNMjQwMTMxMDU1MjM0WhcNNDkwMTE0MDU1MjM0WjBaMQswCQYDVQQG +EwJKUDEmMCQGA1UEChMdU0VDT00gVHJ1c3QgU3lzdGVtcyBDby4sIEx0ZC4xIzAhBgNVBAMTGlNF +Q09NIFRMUyBFQ0MgUm9vdCBDQSAyMDI0MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7NzFMtu9dzQX +SNC12fabk0+GlC5finB3R7XaZonRUd20aFiWObtuNBCLUZSfk6QXAE55BjEXsXQ/NG8yUqicXjsu +9ksDK3JZBgCwLOVh6+nwJXTvso/dEj/GUYH5mBdoo2MwYTAdBgNVHQ4EFgQUO3YReyl04k4GTFaC +QNAhL3qzydUwHwYDVR0jBBgwFoAUO3YReyl04k4GTFaCQNAhL3qzydUwDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwMDaQAwZgIxAN3ib8fi1pMYtAPjMilB5e5/H+t5 +CL0xPL+cZ5oTTZuSCjpAn1v7F/VAr8bFxQXAowIxAKsBVO1ACFp7skwzPvdv1EUY5a897WGLT4lb ++bjxFAWyl8wDcZJdwGZ/pAHxt1AJ1g== +-----END CERTIFICATE----- + +Telia EC TLS Root CA v3 +======================= +-----BEGIN CERTIFICATE----- +MIICMjCCAbegAwIBAgIPAYvSIlRjTQSLbOVHH9K1MAoGCCqGSM49BAMDMEoxCzAJBgNVBAYTAlNF +MRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFCMSAwHgYDVQQDDBdUZWxpYSBFQyBUTFMgUm9vdCBD +QSB2MzAeFw0yMzExMTUwODU1MjZaFw00ODA1MjMxMTAwMDBaMEoxCzAJBgNVBAYTAlNFMRkwFwYD +VQQKDBBUZWxpYSBDb21wYW55IEFCMSAwHgYDVQQDDBdUZWxpYSBFQyBUTFMgUm9vdCBDQSB2MzB2 +MBAGByqGSM49AgEGBSuBBAAiA2IABMHIlhVDLbmFKUpW0iK4dpryT6emYOeS31JPwWnWPmkWRrAk +TbPX40sQfHI9mpR7Rbktu3ngg6W+BBSXSechtMCnBmWXj/EaVlmV5cY1jD2HoTfhBQ3AacpCNMLJ +K4NpZaNjMGEwHwYDVR0jBBgwFoAU1GToQ4g6cy/QGnGCNgtehd7H3kMwHQYDVR0OBBYEFNRk6EOI +OnMv0BpxgjYLXoXex95DMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 +BAMDA2kAMGYCMQCXAUdS/9bbJ8A1JYaGf/bWt/s7Ta0ot5Ulno8OjSNYRWQIlS4tVWldvTAVA7he +OFgCMQCvKr8+Z2Rn+OBr5UHzlgBObpad1LuwNTRcdNgUJxIWadcki+UBLEi1/AURKV5md2M= +-----END CERTIFICATE----- + +Telia RSA TLS Root CA v3 +======================== +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgIPAYvSUKtCVSxHWr2h3BrFMA0GCSqGSIb3DQEBDAUAMEsxCzAJBgNVBAYT +AlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFCMSEwHwYDVQQDDBhUZWxpYSBSU0EgVExTIFJv +b3QgQ0EgdjMwHhcNMjMxMTE1MDk0NzQyWhcNNDgwNTIzMTEwMDAwWjBLMQswCQYDVQQGEwJTRTEZ +MBcGA1UECgwQVGVsaWEgQ29tcGFueSBBQjEhMB8GA1UEAwwYVGVsaWEgUlNBIFRMUyBSb290IENB +IHYzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsV89KG19hCf4S1Fvk8D3TyDERhmc +vx8F7Kmb4WATx3ije1id3KHxRE0TKmcNCbAQ57bvHFEYa4hR2l20VjVadExqOW+2ld99MbEiO+jR +VOz+BbxLxJnmGwCqI+BfuTjjVReDxsxjQvjgBsClaO/sm5i70nlZcWGRtIkvWDK3NNkT5RtwXc/O +8NTFVpbUqT6cRjIj3olAblR+lRf4Ffy5o+Q9fabjYn9Z9S4itruElcEFf9Ljk7fwdTycT/rvJW9w +/B3G2a3r0f/zXNOVruIBcqE6pkSospACU2bG42fYKrbM/GWnp7u+p9Frz4jaNwpb4YHuEeS8Brat +NcP8X62jXIvvKHxlsMDJCnb4U8JzFOLsU6mohVY58BdZrvi0Gk9UOuqmgoG6dskHoksjZTlK61D/ +InzmEoA1yAYJFDVysjRxDUOu9cAwANbqmq77WIFL6BpnZgVqPtMfG6wN8BrTKdapvilVsYR59BFg +IsAVBMxrGh+W+QcvmJafUpASvlArKvVG2FI4i6PiLjSBT0+6F6EQLrYqefOQF/fBNEXb+njUQ0SU +VrAqtH4Y+OjCI/a4/JJQppxeemZcQ0SUShgiI5AM5xHO5iyaUrTjYH4zxUz9j+1FEbDH/xpstr1g +XBykspup+hRTaJcbA+UbpJqtWZndAPddJmt6YJQ+dU3pDu8CAwEAAaNjMGEwHwYDVR0jBBgwFoAU +sMep0t2yKFZzBJSMFFxIbzdSkqgwHQYDVR0OBBYEFLDHqdLdsihWcwSUjBRcSG83UpKoMA4GA1Ud +DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQBdYzFsNGDRk7bR +/AgRKq+5637YuOW+w6uhpoS0VnKMUpyHCwku86hEvqivakPtfmlm4bFwt++sb/8OXsWBqtfbXMaB +NDTZl8XRMJuLWOW2JrbKkRzgG0eBUcvsadG1rrhbmZqYvFXaAZO7o4TdOZzxhBB5GOAWWXB3Iera +NP4J63zyo9n8Gqw3sJBG44em5hoYjBffP+npibyslnslRi4L6xHsCYj/Pab+OlqbMCB6v+sTCLeE +IukRVzoR9aQ45pEK7Z1QBnSsbAKQtss0JKD9d/mX143H1xePjPhTXlv5JCkhrcj+SShz0P9+EHoW +e6m9lyUEOIVn0rp+yVJWNbmyDv3VkwFxHC1ApSQsgSimjGQ4wtr6cSmordYxkV+Ro8lOIIhRksXP +yDk27gW6IjUXCkZKpxFjkL3jiBSc8SkxnwCWtXg8xwNwdFVNBGLCCuJnsneYXjJNqzRqUcoGwzsv +F3Qi/ZnHUNvISdevlgIAXL4Wvrxaqvoa01wB+GCfs57RTGE4TvAGhKNKus8K3hRT1BSpigzMIRzS +xtAOrqPN6j//QSmW9f8Jcncri4j2ihSpVrFU0NdNkMhZeAKidTFPsxCVFuW4Aniz7jqiw5sWtjbQ +rlW035izIEU4sYwQoC1Nx0Svy+mMTRai50LqFQ+A1/Hq6xHHDNx7CI83d23Erw== +-----END CERTIFICATE----- diff --git a/assets/fonts/DejaVuSans-Bold.ttf b/assets/fonts/DejaVuSans-Bold.ttf new file mode 100644 index 0000000..6d65fa7 Binary files /dev/null and b/assets/fonts/DejaVuSans-Bold.ttf differ diff --git a/assets/fonts/DejaVuSans.ttf b/assets/fonts/DejaVuSans.ttf new file mode 100644 index 0000000..e5f7eec Binary files /dev/null and b/assets/fonts/DejaVuSans.ttf differ diff --git a/assets/fonts/LICENSE-DejaVu.txt b/assets/fonts/LICENSE-DejaVu.txt new file mode 100644 index 0000000..df52c17 --- /dev/null +++ b/assets/fonts/LICENSE-DejaVu.txt @@ -0,0 +1,187 @@ +Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. +Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) + + +Bitstream Vera Fonts Copyright +------------------------------ + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is +a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the fonts accompanying this license ("Fonts") and associated +documentation files (the "Font Software"), to reproduce and distribute the +Font Software, including without limitation the rights to use, copy, merge, +publish, distribute, and/or sell copies of the Font Software, and to permit +persons to whom the Font Software is furnished to do so, subject to the +following conditions: + +The above copyright and trademark notices and this permission notice shall +be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular +the designs of glyphs or characters in the Fonts may be modified and +additional glyphs or characters may be added to the Fonts, only if the fonts +are renamed to names not containing either the words "Bitstream" or the word +"Vera". + +This License becomes null and void to the extent applicable to Fonts or Font +Software that has been modified and is distributed under the "Bitstream +Vera" names. + +The Font Software may be sold as part of a larger software package but no +copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME +FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING +ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE +FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font Software +without prior written authorization from the Gnome Foundation or Bitstream +Inc., respectively. For further information, contact: fonts at gnome dot +org. + +Arev Fonts Copyright +------------------------------ + +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license ("Fonts") and +associated documentation files (the "Font Software"), to reproduce +and distribute the modifications to the Bitstream Vera Font Software, +including without limitation the rights to use, copy, merge, publish, +distribute, and/or sell copies of the Font Software, and to permit +persons to whom the Font Software is furnished to do so, subject to +the following conditions: + +The above copyright and trademark notices and this permission notice +shall be included in all copies of one or more of the Font Software +typefaces. + +The Font Software may be modified, altered, or added to, and in +particular the designs of glyphs or characters in the Fonts may be +modified and additional glyphs or characters may be added to the +Fonts, only if the fonts are renamed to names not containing either +the words "Tavmjong Bah" or the word "Arev". + +This License becomes null and void to the extent applicable to Fonts +or Font Software that has been modified and is distributed under the +"Tavmjong Bah Arev" names. + +The Font Software may be sold as part of a larger software package but +no copy of one or more of the Font Software typefaces may be sold by +itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL +TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the name of Tavmjong Bah shall not +be used in advertising or otherwise to promote the sale, use or other +dealings in this Font Software without prior written authorization +from Tavmjong Bah. For further information, contact: tavmjong @ free +. fr. + +TeX Gyre DJV Math +----------------- +Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. + +Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski +(on behalf of TeX users groups) are in public domain. + +Letters imported from Euler Fraktur from AMSfonts are (c) American +Mathematical Society (see below). +Bitstream Vera Fonts Copyright +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera +is a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the fonts accompanying this license (“Fonts”) and associated +documentation +files (the “Font Software”), to reproduce and distribute the Font Software, +including without limitation the rights to use, copy, merge, publish, +distribute, +and/or sell copies of the Font Software, and to permit persons to whom +the Font Software is furnished to do so, subject to the following +conditions: + +The above copyright and trademark notices and this permission notice +shall be +included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular +the designs of glyphs or characters in the Fonts may be modified and +additional +glyphs or characters may be added to the Fonts, only if the fonts are +renamed +to names not containing either the words “Bitstream” or the word “Vera”. + +This License becomes null and void to the extent applicable to Fonts or +Font Software +that has been modified and is distributed under the “Bitstream Vera” +names. + +The Font Software may be sold as part of a larger software package but +no copy +of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME +FOUNDATION +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, +SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN +ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR +INABILITY TO USE +THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. +Except as contained in this notice, the names of GNOME, the GNOME +Foundation, +and Bitstream Inc., shall not be used in advertising or otherwise to promote +the sale, use or other dealings in this Font Software without prior written +authorization from the GNOME Foundation or Bitstream Inc., respectively. +For further information, contact: fonts at gnome dot org. + +AMSFonts (v. 2.2) copyright + +The PostScript Type 1 implementation of the AMSFonts produced by and +previously distributed by Blue Sky Research and Y&Y, Inc. are now freely +available for general use. This has been accomplished through the +cooperation +of a consortium of scientific publishers with Blue Sky Research and Y&Y. +Members of this consortium include: + +Elsevier Science IBM Corporation Society for Industrial and Applied +Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS) + +In order to assure the authenticity of these fonts, copyright will be +held by +the American Mathematical Society. This is not meant to restrict in any way +the legitimate use of the fonts, such as (but not limited to) electronic +distribution of documents containing these fonts, inclusion of these fonts +into other public domain or commercial font collections or computer +applications, use of the outline data to create derivative fonts and/or +faces, etc. However, the AMS does require that the AMS copyright notice be +removed from any derivative versions of the fonts which have been altered in +any way. In addition, to ensure the fidelity of TeX documents using Computer +Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces, +has requested that any alterations which yield different font metrics be +given a different name. + +$Id$ diff --git a/assets/icon0.png b/assets/icon0.png new file mode 100644 index 0000000..a85b46b Binary files /dev/null and b/assets/icon0.png differ diff --git a/cmake/XcDeps.cmake b/cmake/XcDeps.cmake new file mode 100644 index 0000000..c5d5975 --- /dev/null +++ b/cmake/XcDeps.cmake @@ -0,0 +1,56 @@ +# Nubix — third-party dependencies. +# Copyright (C) 2026 Nubix contributors +# SPDX-License-Identifier: GPL-3.0-only +# +# pkg-config: sdl2, SDL2_ttf, libavcodec, libavutil, opus, libcurl, openssl +# (PS5: prospero-pkg-config, which always passes --static and points into the SDK sysroot) +# libdatachannel (static, + juice, srtp2, usrsctp) from ${XC_DEPS_DIR} (scripts/build-deps.sh) +# +# Provides imported targets: +# xc::sdl2 xc::sdl2_ttf xc::avcodec xc::avutil xc::opus xc::curl xc::openssl xc::datachannel + +find_package(PkgConfig REQUIRED) +find_package(Threads REQUIRED) + +# PS5: the toolchain file sets PKG_CONFIG_EXECUTABLE to prospero-pkg-config, which always +# passes --static and resolves inside the SDK sysroot. + +macro(xc_pkg name module) + pkg_check_modules(XC_PC_${name} REQUIRED IMPORTED_TARGET GLOBAL ${module}) + add_library(xc::${name} ALIAS PkgConfig::XC_PC_${name}) + message(STATUS " ${module} ${XC_PC_${name}_VERSION}") +endmacro() + +xc_pkg(sdl2 sdl2) +xc_pkg(sdl2_ttf SDL2_ttf) +xc_pkg(avcodec libavcodec) +xc_pkg(avutil libavutil) +xc_pkg(opus opus) +xc_pkg(curl libcurl) +xc_pkg(openssl openssl) + +# ---- libdatachannel ------------------------------------------------------- +set(XC_DEPS_DIR "${CMAKE_SOURCE_DIR}/deps/${XC_TARGET}" CACHE PATH "Prefix with libdatachannel (scripts/build-deps.sh)") + +if(NOT EXISTS "${XC_DEPS_DIR}/include/rtc/rtc.hpp") + message(FATAL_ERROR "libdatachannel not found in ${XC_DEPS_DIR}. Run: scripts/build-deps.sh ${XC_TARGET}") +endif() + +set(_xc_ldc_libs datachannel juice srtp2 usrsctp) +set(_xc_ldc_targets "") +foreach(lib IN LISTS _xc_ldc_libs) + set(_path "${XC_DEPS_DIR}/lib/lib${lib}.a") + if(NOT EXISTS "${_path}") + message(FATAL_ERROR "missing ${_path}. Run: scripts/build-deps.sh ${XC_TARGET}") + endif() + add_library(xc_ldc_${lib} STATIC IMPORTED GLOBAL) + set_target_properties(xc_ldc_${lib} PROPERTIES IMPORTED_LOCATION "${_path}") + list(APPEND _xc_ldc_targets xc_ldc_${lib}) +endforeach() + +add_library(xc_datachannel INTERFACE) +target_include_directories(xc_datachannel SYSTEM INTERFACE "${XC_DEPS_DIR}/include") +# static archives listed in dependency order, followed by OpenSSL and threads +target_link_libraries(xc_datachannel INTERFACE ${_xc_ldc_targets} xc::openssl Threads::Threads) +add_library(xc::datachannel ALIAS xc_datachannel) +message(STATUS " libdatachannel from ${XC_DEPS_DIR}") diff --git a/cmake/XcPlatform.cmake b/cmake/XcPlatform.cmake new file mode 100644 index 0000000..426f2ae --- /dev/null +++ b/cmake/XcPlatform.cmake @@ -0,0 +1,32 @@ +# Nubix — target detection. +# Copyright (C) 2026 Nubix contributors +# SPDX-License-Identifier: GPL-3.0-only +# Sets XC_PS5 (BOOL) and XC_TARGET ("ps5" | "host"). +# +# The pacbrew prospero toolchain file (prospero.cmake) sets PS5/PROSPERO = TRUE and +# CMAKE_SYSTEM_NAME = FreeBSD; the prospero-clang driver defines __PROSPERO__. + +include(CheckCXXSourceCompiles) + +set(_xc_ps5_default OFF) +if(PS5 OR PROSPERO) + set(_xc_ps5_default ON) +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND CMAKE_CROSSCOMPILING) + check_cxx_source_compiles(" + #ifndef __PROSPERO__ + #error not prospero + #endif + int main() { return 0; }" XC_HAVE_PROSPERO_MACRO) + if(XC_HAVE_PROSPERO_MACRO) + set(_xc_ps5_default ON) + endif() +endif() + +option(XC_PS5 "Build the PS5 payload (auto-detected from the prospero toolchain)" ${_xc_ps5_default}) + +if(XC_PS5) + set(XC_TARGET "ps5") +else() + set(XC_TARGET "host") +endif() +message(STATUS "nubix target: ${XC_TARGET}") diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..86329b4 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,10 @@ +# Nubix — PS5 build image: Ubuntu 24.04 + clang-18 + pacbrew ps5-payload-dev v0.40.2. +# Copyright (C) 2026 Nubix contributors +# SPDX-License-Identifier: GPL-3.0-only +FROM ubuntu:24.04 +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + clang-18 lld-18 llvm-18 llvm-18-dev make cmake pkg-config python3 python3-pyelftools git wget ca-certificates unzip nasm perl file xz-utils \ + && rm -rf /var/lib/apt/lists/* +RUN wget -qO /tmp/p.tgz https://github.com/ps5-payload-dev/pacbrew-repo/releases/download/v0.40.2/ps5-payload-dev.tar.gz && tar -xzf /tmp/p.tgz -C / && rm /tmp/p.tgz +ENV PS5_PAYLOAD_SDK=/opt/ps5-payload-sdk +ENV LLVM_CONFIG=/usr/bin/llvm-config-18 diff --git a/homebrew/homebrew.js b/homebrew/homebrew.js new file mode 100644 index 0000000..ddb6686 --- /dev/null +++ b/homebrew/homebrew.js @@ -0,0 +1,48 @@ +/* Nubix — websrv Homebrew Launcher entry. + +Copyright (C) 2026 Nubix contributors + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, version 3. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; see the file LICENSE. If not, see +. + +SPDX-License-Identifier: GPL-3.0-only + +Install layout (websrv): /data/homebrew/nubix/{eboot.elf, homebrew.js, +sce_sys/icon0.png, assets/}. websrv sets window.workingDir to that folder. */ + +async function main() { + const CWD = window.workingDir; + const ENV = {TMPDIR: '/user/temp'}; + + function launch(extraArgs) { + return { + path: CWD + '/eboot.elf', + args: extraArgs, + cwd: CWD, + env: ENV + }; + } + + return { + mainText: 'Nubix', + secondaryText: 'Unofficial client for Xbox Cloud Gaming & Remote Play', + onclick: async () => launch([]), + options: [{ + text: 'Start', + onclick: async () => launch([]) + }, { + text: 'Start (verbose log)', + onclick: async () => launch(['--verbose']) + }] + }; +} diff --git a/homebrew/sce_sys/icon0.png b/homebrew/sce_sys/icon0.png new file mode 100644 index 0000000..a85b46b Binary files /dev/null and b/homebrew/sce_sys/icon0.png differ diff --git a/patches/libdatachannel-xbox-pli.patch b/patches/libdatachannel-xbox-pli.patch new file mode 100644 index 0000000..e778f54 --- /dev/null +++ b/patches/libdatachannel-xbox-pli.patch @@ -0,0 +1,27 @@ +Nubix patch for libdatachannel v0.24.6: send RTCP PLI with sender SSRC 0. + +This patch is a Modification of libdatachannel +(https://github.com/paullouisageneau/libdatachannel), which is licensed under +the Mozilla Public License, v. 2.0. This patch is likewise licensed under the +Mozilla Public License, v. 2.0 (see licenses/MPL-2.0.txt, or obtain a copy at +https://mozilla.org/MPL/2.0/). The logic is taken from GreenOvercast +(https://github.com/Producdevity/GreenOvercast, MPL-2.0, by Producdevity). +SPDX-License-Identifier: MPL-2.0 + +diff --git a/src/rtcpreceivingsession.cpp b/src/rtcpreceivingsession.cpp +index 0584180..59a704b 100644 +--- a/src/rtcpreceivingsession.cpp ++++ b/src/rtcpreceivingsession.cpp +@@ -179,7 +179,11 @@ bool RtcpReceivingSession::requestKeyframe(const message_callback &send) { + void RtcpReceivingSession::pushPLI(const message_callback &send) { + auto message = make_message(RtcpPli::Size(), Message::Control); + auto *pli = reinterpret_cast(message->data()); +- pli->preparePacket(mSsrc); ++ // Xbox (xCloud/xHome) ignores PLIs whose sender SSRC equals the media SSRC; ++ // browsers send sender SSRC 0 here. (nubix patch, from GreenOvercast) ++ pli->header.header.prepareHeader(206, 1, 2); ++ pli->header.setPacketSenderSSRC(0); ++ pli->header.setMediaSourceSSRC(mSsrc); + send(message); + } + diff --git a/scripts/build-deps.sh b/scripts/build-deps.sh new file mode 100755 index 0000000..7ee82d2 --- /dev/null +++ b/scripts/build-deps.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +# Nubix — build third-party dependencies that are not part of the PS5 SDK. +# Copyright (C) 2026 Nubix contributors +# SPDX-License-Identifier: GPL-3.0-only +# +# Usage: scripts/build-deps.sh [--clean] +# +# Fetches libdatachannel v0.24.6 (with submodules), applies patches/libdatachannel-*.patch +# and installs a static build into deps// (include/, lib/). +# +# ps5 : runs inside the "ps5dev" docker image (built from docker/Dockerfile if missing), +# cross-compiled with the prospero toolchain against the SDK's OpenSSL. +# host : native build against Homebrew openssl@3 (macOS) or system OpenSSL (Linux). +# +# Environment: +# LDC_LOCAL_SRC optional path to an existing libdatachannel v0.24.6 checkout (with +# submodules) to copy instead of cloning from GitHub. +# PS5DEV_IMAGE docker image name (default: ps5dev) +# JOBS parallel build jobs +set -euo pipefail + +LDC_VERSION="v0.24.6" +LDC_REPO="https://github.com/paullouisageneau/libdatachannel.git" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +TARGET="${1:-}" +shift || true +CLEAN=0 +IN_DOCKER=0 +for a in "$@"; do + case "$a" in + --clean) CLEAN=1 ;; + --in-docker) IN_DOCKER=1 ;; + *) echo "unknown option: $a" >&2; exit 2 ;; + esac +done + +case "${TARGET}" in + ps5|host) ;; + *) echo "usage: $0 [--clean]" >&2; exit 2 ;; +esac + +ncpu() { + if [[ -n "${JOBS:-}" ]]; then echo "${JOBS}"; return; fi + getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4 +} + +SRC="${ROOT}/.cache/libdatachannel-${LDC_VERSION}" +BUILD="${ROOT}/.cache/build-libdatachannel-${TARGET}" +DEPS_PREFIX="${ROOT}/deps/${TARGET}" + +# ---------------------------------------------------------------- source ---- +fetch_source() { + if [[ -f "${SRC}/.xc-ready" ]]; then + return + fi + rm -rf "${SRC}" + mkdir -p "${ROOT}/.cache" + if [[ -n "${LDC_LOCAL_SRC:-}" && -f "${LDC_LOCAL_SRC}/CMakeLists.txt" ]]; then + echo ">> copying libdatachannel from ${LDC_LOCAL_SRC}" + mkdir -p "${SRC}" + # copy everything except previous build trees + (cd "${LDC_LOCAL_SRC}" && tar cf - --exclude='./build*' .) | (cd "${SRC}" && tar xf -) + # discard local modifications so the patch applies to pristine sources + if [[ -d "${SRC}/.git" ]]; then + (cd "${SRC}" && git checkout -q -- . && git submodule foreach -q --recursive git checkout -q -- . ) || true + fi + else + echo ">> cloning libdatachannel ${LDC_VERSION}" + git clone --depth 1 --branch "${LDC_VERSION}" --recurse-submodules --shallow-submodules \ + "${LDC_REPO}" "${SRC}" + fi + for p in "${ROOT}"/patches/libdatachannel-*.patch; do + [[ -f "$p" ]] || continue + echo ">> applying $(basename "$p")" + (cd "${SRC}" && git apply --whitespace=nowarn "$p") + done + touch "${SRC}/.xc-ready" +} + +# ------------------------------------------------------------------ build --- +common_args=( + -DCMAKE_BUILD_TYPE=Release + -DBUILD_SHARED_LIBS=OFF + -DNO_WEBSOCKET=ON -DNO_EXAMPLES=ON -DNO_TESTS=ON + -DUSE_GNUTLS=OFF -DUSE_MBEDTLS=OFF -DUSE_NICE=OFF + -Dsctp_werror=OFF -DWARNINGS_AS_ERRORS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_INSTALL_PREFIX="${DEPS_PREFIX}" + -DCMAKE_INSTALL_LIBDIR=lib +) + +build_host() { + local ossl="" + if command -v brew >/dev/null 2>&1; then + ossl="$(brew --prefix openssl@3 2>/dev/null || true)" + fi + local extra=() + [[ -n "${ossl}" ]] && extra+=(-DOPENSSL_ROOT_DIR="${ossl}") + cmake -S "${SRC}" -B "${BUILD}" "${common_args[@]}" ${extra[@]+"${extra[@]}"} + cmake --build "${BUILD}" -j"$(ncpu)" --target datachannel + cmake --install "${BUILD}" +} + +build_ps5_in_container() { + # shellcheck disable=SC1091 + source "${PS5_PAYLOAD_SDK}/toolchain/prospero.sh" + "${CMAKE}" -S "${SRC}" -B "${BUILD}" "${common_args[@]}" \ + -DOPENSSL_USE_STATIC_LIBS=ON -DOPENSSL_ROOT_DIR="${PS5_SYSROOT}/user/homebrew" \ + -DCMAKE_C_FLAGS="-DXC_NET_COMPAT_REDIRECT -include ${ROOT}/src/platform/net_compat.h" \ + -DCMAKE_CXX_FLAGS="-DXC_NET_COMPAT_REDIRECT -include ${ROOT}/src/platform/net_compat.h" + cmake --build "${BUILD}" -j"$(ncpu)" --target datachannel + # prospero.sh exports DESTDIR=; install into our own prefix instead. + DESTDIR= cmake --install "${BUILD}" +} + +docker_image_ensure() { + local img="${PS5DEV_IMAGE:-ps5dev}" + if ! docker image inspect "${img}" >/dev/null 2>&1; then + echo ">> building docker image ${img} from docker/Dockerfile" + docker build -t "${img}" "${ROOT}/docker" + fi +} + +if [[ ${CLEAN} -eq 1 && ${IN_DOCKER} -eq 0 ]]; then + rm -rf "${BUILD}" "${DEPS_PREFIX}" "${SRC}" +fi + +if [[ "${TARGET}" == "ps5" && -z "${PS5_PAYLOAD_SDK:-}" ]]; then + # Host side: fetch sources (needs git/network), then re-enter inside docker. + fetch_source + docker_image_ensure + exec docker run --rm -v "${ROOT}:/w" -w /w -e JOBS="$(ncpu)" "${PS5DEV_IMAGE:-ps5dev}" \ + bash /w/scripts/build-deps.sh ps5 --in-docker +fi + +fetch_source +if [[ "${TARGET}" == "ps5" ]]; then + # build directory may contain a cache from a host path; always reconfigure cleanly + [[ -f "${BUILD}/CMakeCache.txt" ]] && ! grep -q "CMAKE_HOME_DIRECTORY:INTERNAL=${SRC}$" "${BUILD}/CMakeCache.txt" && rm -rf "${BUILD}" + build_ps5_in_container +else + [[ -f "${BUILD}/CMakeCache.txt" ]] && ! grep -q "CMAKE_HOME_DIRECTORY:INTERNAL=${SRC}$" "${BUILD}/CMakeCache.txt" && rm -rf "${BUILD}" + build_host +fi + +echo ">> libdatachannel ${LDC_VERSION} installed to ${DEPS_PREFIX}" +ls -1 "${DEPS_PREFIX}/lib"/*.a diff --git a/scripts/build-host.sh b/scripts/build-host.sh new file mode 100755 index 0000000..16eac33 --- /dev/null +++ b/scripts/build-host.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Nubix — host (macOS/Linux) build: deps (if missing) + app + xc-cli + tests. +# Copyright (C) 2026 Nubix contributors +# SPDX-License-Identifier: GPL-3.0-only +# +# Usage: scripts/build-host.sh [Release|Debug] [--test] +# Output: build-host/nubix, build-host/xc-cli, build-host/xc-tests +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +BUILD_TYPE="Release" +RUN_TESTS=0 +for a in "$@"; do + case "$a" in + Release|Debug|RelWithDebInfo) BUILD_TYPE="$a" ;; + --test) RUN_TESTS=1 ;; + *) echo "usage: $0 [Release|Debug|RelWithDebInfo] [--test]" >&2; exit 2 ;; + esac +done + +if [[ ! -f "${ROOT}/deps/host/lib/libdatachannel.a" ]]; then + bash "${SCRIPT_DIR}/build-deps.sh" host +fi + +# Homebrew: keg-only openssl@3 must be visible to pkg-config +if command -v brew >/dev/null 2>&1; then + OSSL="$(brew --prefix openssl@3 2>/dev/null || true)" + [[ -n "${OSSL}" ]] && export PKG_CONFIG_PATH="${OSSL}/lib/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}" + # prefer Homebrew curl (OpenSSL backend, same as PS5) over the macOS system curl if installed + CURLP="$(brew --prefix curl 2>/dev/null || true)" + [[ -n "${CURLP}" && -d "${CURLP}/lib/pkgconfig" ]] && export PKG_CONFIG_PATH="${CURLP}/lib/pkgconfig:${PKG_CONFIG_PATH}" +fi + +JOBS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)" +cmake -S "${ROOT}" -B "${ROOT}/build-host" -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" +cmake --build "${ROOT}/build-host" -j"${JOBS}" + +echo ">> built ${ROOT}/build-host/nubix" +if [[ ${RUN_TESTS} -eq 1 ]]; then + (cd "${ROOT}/build-host" && ctest --output-on-failure) +fi diff --git a/scripts/build-ps5.sh b/scripts/build-ps5.sh new file mode 100755 index 0000000..71af5c6 --- /dev/null +++ b/scripts/build-ps5.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Nubix — PS5 payload build, run inside the "ps5dev" docker image (docker/Dockerfile: +# ubuntu 24.04 + clang-18 + pacbrew ps5-payload-dev v0.40.2 at $PS5_PAYLOAD_SDK). +# Copyright (C) 2026 Nubix contributors +# SPDX-License-Identifier: GPL-3.0-only +# +# Usage: scripts/build-ps5.sh [Release|Debug|RelWithDebInfo] +# Output: +# build-ps5/nubix.elf stripped payload (send to elfldr :9021) +# build-ps5/nubix-debug.elf unstripped, for symbolizing crashes +# dist/nubix/ websrv homebrew folder -> copy to /data/homebrew/nubix/ +# eboot.elf, homebrew.js, sce_sys/icon0.png, assets/, +# LICENSE, THIRD_PARTY_NOTICES.md, THIRD_PARTY_LICENSES.txt, SOURCE.txt (if present) +# build-ps5/nubix-v-websrv.zip the nubix/ folder, zipped for release +# +# The docker image is built from docker/Dockerfile on first use. Override with PS5DEV_IMAGE. +# XC_LINKCHECK=ON additionally builds build-ps5/xc-linkcheck.elf (full dependency link test). +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +BUILD_TYPE="${1:-Release}" +IMAGE="${PS5DEV_IMAGE:-ps5dev}" + +if [[ -z "${PS5_PAYLOAD_SDK:-}" ]]; then + # ---- host side: make sure image + deps exist, then re-run ourselves in docker ---- + if ! docker image inspect "${IMAGE}" >/dev/null 2>&1; then + echo ">> building docker image ${IMAGE} from docker/Dockerfile" + docker build -t "${IMAGE}" "${ROOT}/docker" + fi + if [[ ! -f "${ROOT}/deps/ps5/lib/libdatachannel.a" ]]; then + bash "${SCRIPT_DIR}/build-deps.sh" ps5 + fi + JOBS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)" + exec docker run --rm -v "${ROOT}:/w" -w /w -e JOBS="${JOBS}" -e XC_LINKCHECK="${XC_LINKCHECK:-OFF}" "${IMAGE}" \ + bash /w/scripts/build-ps5.sh "${BUILD_TYPE}" +fi + +# ---- inside the container ---- +# shellcheck disable=SC1091 +source "${PS5_PAYLOAD_SDK}/toolchain/prospero.sh" +JOBS="${JOBS:-$(nproc)}" + +if [[ ! -f "${ROOT}/deps/ps5/lib/libdatachannel.a" ]]; then + bash "${SCRIPT_DIR}/build-deps.sh" ps5 +fi + +# A build dir configured on another machine/path cannot be reused. +if [[ -f "${ROOT}/build-ps5/CMakeCache.txt" ]] && ! grep -q "CMAKE_HOME_DIRECTORY:INTERNAL=${ROOT}$" "${ROOT}/build-ps5/CMakeCache.txt"; then + rm -rf "${ROOT}/build-ps5" +fi + +"${CMAKE}" -S "${ROOT}" -B "${ROOT}/build-ps5" -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -DCMAKE_VERBOSE_MAKEFILE=OFF -DXC_BUILD_TESTS=OFF -DXC_BUILD_CLI=OFF \ + -DXC_BUILD_LINKCHECK="${XC_LINKCHECK:-OFF}" +cmake --build "${ROOT}/build-ps5" -j"${JOBS}" + +# ---- websrv homebrew layout ---- +DIST="${ROOT}/dist/nubix" +rm -rf "${DIST}" +mkdir -p "${DIST}/sce_sys" "${DIST}/assets" +cp "${ROOT}/build-ps5/nubix.elf" "${DIST}/eboot.elf" +cp "${ROOT}/homebrew/homebrew.js" "${DIST}/homebrew.js" +cp "${ROOT}/homebrew/sce_sys/icon0.png" "${DIST}/sce_sys/icon0.png" +cp -R "${ROOT}/assets/." "${DIST}/assets/" +rm -f "${DIST}/assets/icon0.png" +cp "${ROOT}/LICENSE" "${DIST}/LICENSE" +cp "${ROOT}/THIRD_PARTY_NOTICES.md" "${DIST}/THIRD_PARTY_NOTICES.md" +cp "${ROOT}/THIRD_PARTY_LICENSES.txt" "${DIST}/THIRD_PARTY_LICENSES.txt" +if [[ -f "${ROOT}/SOURCE.txt" ]]; then + cp "${ROOT}/SOURCE.txt" "${DIST}/SOURCE.txt" +fi + +# ---- release zip: build-ps5/nubix-v-websrv.zip containing nubix/ ---- +VERSION="$(sed -nE 's/^[[:space:]]*project\([^)]*VERSION[[:space:]]+([0-9][0-9A-Za-z.+-]*).*/\1/p' "${ROOT}/CMakeLists.txt" | head -n1)" +if [[ -z "${VERSION}" ]]; then + echo "!! could not read project(VERSION) from CMakeLists.txt" >&2 + exit 1 +fi +ZIP="${ROOT}/build-ps5/nubix-v${VERSION}-websrv.zip" +rm -f "${ZIP}" +(cd "$(dirname "${DIST}")" && python3 - "${ZIP}" "$(basename "${DIST}")" <<'PY' +import os, sys, zipfile +out, top = sys.argv[1], sys.argv[2] +with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z: + for d, dirs, files in os.walk(top): + dirs.sort() + z.write(d, d + "/") + for f in sorted(files): + z.write(os.path.join(d, f)) +PY +) + +echo ">> $(ls -l "${ROOT}/build-ps5/nubix.elf")" +echo ">> dist: ${DIST}" +echo ">> zip: ${ZIP}" diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..e2d254b --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Nubix — deploy to a jailbroken PS5. +# Copyright (C) 2026 Nubix contributors +# SPDX-License-Identifier: GPL-3.0-only +# +# Usage: +# PS5_HOST=192.168.1.50 scripts/deploy.sh run send build-ps5/nubix.elf to elfldr (port 9021) +# PS5_HOST=192.168.1.50 scripts/deploy.sh install upload dist/nubix/ to /data/homebrew/nubix/ +# over FTP (ftpsrv payload, port $PS5_FTP_PORT, default 2121) +# PS5_HOST=... scripts/deploy.sh log fetch /data/nubix/log.txt over FTP +# +# "run" starts the payload directly (stdout appears on elfldr's log / klog). The websrv +# Homebrew Launcher (install) is the supported way to start it with video/pad/audio access. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +CMD="${1:-run}" +HOST="${PS5_HOST:-}" +ELF_PORT="${PS5_ELF_PORT:-9021}" +FTP_PORT="${PS5_FTP_PORT:-2121}" + +if [[ -z "${HOST}" ]]; then + echo "error: set PS5_HOST to the console's IP address" >&2 + exit 2 +fi + +send_elf() { + local elf="$1" + [[ -f "${elf}" ]] || { echo "error: ${elf} not found (run scripts/build-ps5.sh)" >&2; exit 1; } + echo ">> sending $(basename "${elf}") ($(wc -c <"${elf}" | tr -d ' ') bytes) to ${HOST}:${ELF_PORT}" + if command -v nc >/dev/null 2>&1; then + nc -w 3 "${HOST}" "${ELF_PORT}" <"${elf}" + else + exec 3<>"/dev/tcp/${HOST}/${ELF_PORT}" + cat "${elf}" >&3 + exec 3>&- + fi + echo ">> sent" +} + +ftp_upload_dir() { + local src="$1" dst="$2" + command -v curl >/dev/null 2>&1 || { echo "error: curl required for FTP upload" >&2; exit 1; } + (cd "${src}" && find . -type f | sed 's|^\./||') | while read -r f; do + echo " ${dst}/${f}" + curl -sS --ftp-create-dirs -T "${src}/${f}" "ftp://${HOST}:${FTP_PORT}${dst}/${f}" + done +} + +case "${CMD}" in + run) + send_elf "${ROOT}/build-ps5/nubix.elf" + ;; + install) + [[ -d "${ROOT}/dist/nubix" ]] || { echo "error: dist/nubix missing (run scripts/build-ps5.sh)" >&2; exit 1; } + echo ">> uploading dist/nubix to ftp://${HOST}:${FTP_PORT}/data/homebrew/nubix" + ftp_upload_dir "${ROOT}/dist/nubix" "/data/homebrew/nubix" + echo ">> done; start it from the websrv Homebrew Launcher" + ;; + log) + curl -sS "ftp://${HOST}:${FTP_PORT}/data/nubix/log.txt" + ;; + *) + echo "usage: $0 " >&2 + exit 2 + ;; +esac diff --git a/scripts/make-source-bundle.sh b/scripts/make-source-bundle.sh new file mode 100755 index 0000000..93f7392 --- /dev/null +++ b/scripts/make-source-bundle.sh @@ -0,0 +1,305 @@ +#!/usr/bin/env bash +# Nubix — assemble the GPLv3 section 6 "Corresponding Source" archive for a binary release. +# +# Usage: +# scripts/make-source-bundle.sh [--nubix-ref | --nubix-dir ] [--offline] +# +# Output (all under dist/): +# nubix-v-corresponding-source.tar.xz the archive to upload next to the binary +# nubix-v-corresponding-source.MANIFEST copy of the MANIFEST inside the archive +# nubix-v-corresponding-source.tar.xz.sha256 checksum of the archive +# +# is taken from `project(nubix VERSION x.y.z ...)` in CMakeLists.txt. +# +# Nubix's own source is taken from (in this order of precedence): +# --nubix-dir every file below (e.g. a clean export of the release tree), +# except .git/, build*/, deps/, dist/, .cache/ and docs/ +# --nubix-ref `git archive ` (only committed content; use for tagged releases) +# default the CURRENT WORKING TREE: `git ls-files -co --exclude-standard`, i.e. +# tracked files (incl. uncommitted modifications) plus untracked files that +# are not gitignored. docs/ and files deleted from the working tree are left +# out; build trees, deps/, dist/ and .cache/ are gitignored. Make sure the +# tree is exactly what the binary was built from. +# +# Third-party sources are downloaded once into .cache/sources/ (tarballs are verified against +# pinned sha256 sums; git commits are fetched by full hash and exported with `git archive`), +# then copied unmodified into the archive under upstream/. Every included file is listed in +# MANIFEST and SHA256SUMS. Re-running the script reuses the cache and rebuilds the archive. +# +# --offline never touch the network; fail if something is missing from the cache. +# +# Environment: +# LDC_LOCAL_SRC existing libdatachannel checkout whose git objects may be used instead of +# fetching (default: .cache/libdatachannel-v0.24.6; it is only read, never +# modified — exports come from the pinned commits, not from its working tree). +# XZ_OPT read by xz itself (e.g. XZ_OPT=-9e for a slightly smaller outer archive). +# +# Requirements: bash, git, curl, tar (GNU tar or bsdtar), xz, sha256sum or shasum. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +NUBIX_REF="" +NUBIX_DIR="" +OFFLINE=0 +while [[ $# -gt 0 ]]; do + case "$1" in + --nubix-ref) NUBIX_REF="${2:?--nubix-ref needs a value}"; shift 2 ;; + --nubix-dir) NUBIX_DIR="${2:?--nubix-dir needs a value}"; shift 2 ;; + --offline) OFFLINE=1; shift ;; + -h|--help) sed -n '2,38p' "$0"; exit 0 ;; + *) echo "unknown argument: $1" >&2; exit 2 ;; + esac +done +if [[ -n "${NUBIX_REF}" && -n "${NUBIX_DIR}" ]]; then + echo "--nubix-ref and --nubix-dir are mutually exclusive" >&2; exit 2 +fi + +VERSION="$(sed -nE 's/^[[:space:]]*project\(nubix[[:space:]]+VERSION[[:space:]]+([0-9][0-9.]*).*/\1/p' "${ROOT}/CMakeLists.txt" | head -n1)" +[[ -n "${VERSION}" ]] || { echo "cannot read project(nubix VERSION ...) from CMakeLists.txt" >&2; exit 1; } + +NAME="nubix-v${VERSION}-corresponding-source" +CACHE="${ROOT}/.cache/sources" +GITCACHE="${CACHE}/git" +WORK="${ROOT}/.cache/source-bundle" +STAGE="${WORK}/${NAME}" +DIST="${ROOT}/dist" +OUT="${DIST}/${NAME}.tar.xz" +LDC_LOCAL_SRC="${LDC_LOCAL_SRC:-${ROOT}/.cache/libdatachannel-v0.24.6}" + +export COPYFILE_DISABLE=1 # macOS bsdtar: no AppleDouble ._* files +export LC_ALL=C + +log() { printf '>> %s\n' "$*" >&2; } +die() { printf 'error: %s\n' "$*" >&2; exit 1; } + +for t in git tar xz; do command -v "$t" >/dev/null 2>&1 || die "missing tool: $t"; done +[[ ${OFFLINE} -eq 1 ]] || command -v curl >/dev/null 2>&1 || die "missing tool: curl" + +sha256() { + if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}' + else shasum -a 256 "$1" | awk '{print $1}'; fi +} +fsize() { wc -c <"$1" | tr -d ' '; } + +if tar --version 2>/dev/null | grep -q 'GNU tar'; then + TAR_NORM=(--owner=0 --group=0 --numeric-owner --format=pax --pax-option=delete=atime,delete=ctime) +else + TAR_NORM=(--uid 0 --gid 0 --uname root --gname root --format=pax) +fi + +# ------------------------------------------------------------- components --- +# Release tarballs: id|version|file|sha256|url [mirror-url ...] +# sha256: from the pacbrew-repo v0.40.2 PKGBUILD (which pacbrew verified when building the +# SDK libraries) unless noted. FFmpeg's PKGBUILD has sha256sums=SKIP and ffmpeg.org publishes +# only PGP signatures, so that sum was computed on download (2026-10-07) and pinned here. +TARBALLS=' +ffmpeg|7.0.1|ffmpeg-7.0.1.tar.xz|bce9eeb0f17ef8982390b1f37711a61b4290dc8c2a0c1a37b5857e85bfb0e4ff|https://ffmpeg.org/releases/ffmpeg-7.0.1.tar.xz +libiconv|1.17|libiconv-1.17.tar.gz|8f74213b56238c85a50a5329f77e06198771e70dd9a739779f4c02f65d971313|https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.17.tar.gz https://ftpmirror.gnu.org/libiconv/libiconv-1.17.tar.gz +openssl|3.5.2|openssl-3.5.2.tar.gz|c53a47e5e441c930c3928cf7bf6fb00e5d129b630e0aa873b08258656e7345ec|https://github.com/openssl/openssl/releases/download/openssl-3.5.2/openssl-3.5.2.tar.gz +curl|8.18.0|curl-8.18.0.tar.xz|40df79166e74aa20149365e11ee4c798a46ad57c34e4f68fd13100e2c9a91946|https://curl.se/download/curl-8.18.0.tar.xz https://curl.haxx.se/download/curl-8.18.0.tar.xz +libpsl|0.21.5|libpsl-0.21.5.tar.gz|1dcc9ceae8b128f3c0b3f654decd0e1e891afc6ff81098f227ef260449dae208|https://github.com/rockdaboot/libpsl/releases/download/0.21.5/libpsl-0.21.5.tar.gz +SDL2_ttf|2.22.0|SDL2_ttf-2.22.0.tar.gz|d48cbd1ce475b9e178206bf3b72d56b66d84d44f64ac05803328396234d67723|https://libsdl.org/projects/SDL_ttf/release/SDL2_ttf-2.22.0.tar.gz https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.22.0/SDL2_ttf-2.22.0.tar.gz +freetype|2.13.2|freetype-2.13.2.tar.gz|1ac27e16c134a7f2ccea177faba19801131116fd682efc1f5737037c5db224b5|https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz https://downloads.sourceforge.net/project/freetype/freetype2/2.13.2/freetype-2.13.2.tar.gz +libpng|1.6.43|libpng-1.6.43.tar.xz|6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c|https://download.sourceforge.net/libpng/libpng-1.6.43.tar.xz https://downloads.sourceforge.net/project/libpng/libpng16/1.6.43/libpng-1.6.43.tar.xz +zlib|1.3.2|zlib-1.3.2.tar.gz|bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16|https://github.com/madler/zlib/releases/download/v1.3.2/zlib-1.3.2.tar.gz +zstd|1.5.6|zstd-1.5.6.tar.gz|8c29e06cf42aacc1eafc4077ae2ec6c6fcb96a626157e0593d5e82a34fd403c1|https://github.com/facebook/zstd/releases/download/v1.5.6/zstd-1.5.6.tar.gz +xz|5.4.6|xz-5.4.6.tar.xz|b92d4e3a438affcf13362a1305cd9d94ed47ddda22e456a42791e630a5644f5c|https://github.com/tukaani-project/xz/releases/download/v5.4.6/xz-5.4.6.tar.xz +bzip2|1.0.8|bzip2-1.0.8.tar.gz|ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269|https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz +openlibm|0.8.6|openlibm-0.8.6.tar.gz|347998968cfeb2f9b91de6a8e85d2ba92dec0915d53500a4bc483e056f85b94c|https://github.com/JuliaMath/openlibm/archive/refs/tags/v0.8.6.tar.gz +opus|1.5.2|opus-1.5.2.tar.gz|65c1d2f78b9f2fb20082c38cbe47c951ad5839345876e46941612ee87f9a7ce1|https://downloads.xiph.org/releases/opus/opus-1.5.2.tar.gz https://github.com/xiph/opus/releases/download/v1.5.2/opus-1.5.2.tar.gz +libsamplerate|0.2.2|libsamplerate-0.2.2.tar.xz|3258da280511d24b49d6b08615bbe824d0cacc9842b0e4caf11c52cf2b043893|https://github.com/libsndfile/libsamplerate/releases/download/0.2.2/libsamplerate-0.2.2.tar.xz +' + +# Git commits: id|version-label|repo-url|full-commit|archive-prefix +# Exported with `git archive --prefix=/ | xz -T1 -6`. +# The libdatachannel submodule archives use prefixes inside libdatachannel-v0.24.6/ so that +# extracting all six into one directory reproduces the recursive checkout. +GITSRCS=' +libdatachannel|v0.24.6|https://github.com/paullouisageneau/libdatachannel.git|6b1e2e620f1e37f0eafeee702eaea0043cb305fd|libdatachannel-v0.24.6 +libjuice|v1.7.4|https://github.com/paullouisageneau/libjuice.git|b89c792e3612faf2f12cf35bcc56857313a06be3|libdatachannel-v0.24.6/deps/libjuice +libsrtp|24b3bf8|https://github.com/cisco/libsrtp.git|24b3bf8f19b6f5ab4cd2bcceb4f4064efca86fd5|libdatachannel-v0.24.6/deps/libsrtp +usrsctp|fec583d|https://github.com/paullouisageneau/usrsctp.git|fec583d54493f879d2ae44a743423bf8a04371ab|libdatachannel-v0.24.6/deps/usrsctp +plog|94899e0|https://github.com/SergiusTheBest/plog.git|94899e0b926ac1b0f4750bfbd495167b4a6ae9ef|libdatachannel-v0.24.6/deps/plog +json|55f9368|https://github.com/nlohmann/json.git|55f93686c01528224f448c19128836e7df245f72|libdatachannel-v0.24.6/deps/json +x264|r3222-b35605a|https://code.videolan.org/videolan/x264.git|b35605ace3ddf7c1a5d67a2eb553f034aef41d55|x264-b35605a +SDL-ps5|2.30.12-a47182a20|https://github.com/ps5-payload-dev/SDL.git|a47182a20e741e4735d1fcd41e8656bf338b0c72|SDL-ps5-a47182a20 +ps5-payload-sdk|4eb7012|https://github.com/ps5-payload-dev/sdk.git|4eb701204fc3f8d31e84cf8ca272974e2be9c867|ps5-payload-sdk-4eb7012 +pacbrew-repo|v0.40.2|https://github.com/ps5-payload-dev/pacbrew-repo.git|1687e827675c66e1b040751c7f0811222bd3aea0|pacbrew-repo-v0.40.2 +' + +# ---------------------------------------------------------------- helpers --- +fetch_tarball() { # file sha256 urls... + local file="$1" want="$2"; shift 2 + local dst="${CACHE}/${file}" + if [[ -f "${dst}" ]]; then + [[ "$(sha256 "${dst}")" == "${want}" ]] && return 0 + log "cached ${file} has a wrong checksum, re-downloading" + rm -f "${dst}" + fi + [[ ${OFFLINE} -eq 0 ]] || die "${file} not in cache and --offline given" + local url got + for url in "$@"; do + log "downloading ${url}" + if curl -fsSL --retry 3 --connect-timeout 30 -o "${dst}.part" "${url}"; then + got="$(sha256 "${dst}.part")" + if [[ "${got}" == "${want}" ]]; then mv "${dst}.part" "${dst}"; return 0; fi + log "checksum mismatch for ${url}: got ${got}, want ${want}" + fi + rm -f "${dst}.part" + done + die "could not download ${file} with sha256 ${want}" +} + +have_commit() { git -C "$1" cat-file -e "$2^{commit}" 2>/dev/null; } + +# Find a git dir that already has : our bare cache, or the read-only libdatachannel checkout. +local_git_for() { # id commit -> prints dir or nothing + local id="$1" commit="$2" d + local cands=("${GITCACHE}/${id}.git") + if [[ -d "${LDC_LOCAL_SRC}" ]]; then + case "${id}" in + libdatachannel) cands+=("${LDC_LOCAL_SRC}") ;; + libjuice|libsrtp|usrsctp|plog|json) cands+=("${LDC_LOCAL_SRC}/deps/${id}") ;; + esac + fi + for d in "${cands[@]}"; do + [[ -e "${d}" ]] && have_commit "${d}" "${commit}" && { echo "${d}"; return 0; } + done + return 0 +} + +fetch_commit() { # id repo commit -> prints git dir containing commit + local id="$1" repo="$2" commit="$3" d + d="$(local_git_for "${id}" "${commit}")" + if [[ -n "${d}" ]]; then echo "${d}"; return 0; fi + [[ ${OFFLINE} -eq 0 ]] || die "commit ${commit} of ${id} not in cache and --offline given" + d="${GITCACHE}/${id}.git" + [[ -d "${d}" ]] || git init -q --bare "${d}" + log "fetching ${repo} ${commit}" + if ! git -C "${d}" fetch -q --depth 1 "${repo}" "${commit}" >&2; then + log "fetch by hash refused, fetching all branches and tags of ${repo}" + git -C "${d}" fetch -q "${repo}" '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*' >&2 + fi + have_commit "${d}" "${commit}" || die "commit ${commit} not found in ${repo}" + echo "${d}" +} + +export_commit() { # id label repo commit prefix -> prints archive file name (in CACHE) + local id="$1" label="$2" repo="$3" commit="$4" prefix="$5" + local file="${id}-${label}-${commit:0:12}.tar.xz" dst d + dst="${CACHE}/${file}" + if [[ ! -f "${dst}" ]]; then + d="$(fetch_commit "${id}" "${repo}" "${commit}")" + log "exporting ${id} ${commit} from ${d}" + git -C "${d}" archive --format=tar --prefix="${prefix}/" "${commit}" | xz -T1 -6 -c >"${dst}.part" + mv "${dst}.part" "${dst}" + fi + echo "${file}" +} + +# ------------------------------------------------------------ nubix source --- +nubix_source() { # -> writes ${STAGE}/nubix-v${VERSION}-src.tar.xz, prints description + local top="nubix-v${VERSION}" tmp="${WORK}/nubix-tree" + local out="${STAGE}/${top}-src.tar.xz" + rm -rf "${tmp}"; mkdir -p "${tmp}/${top}" + if [[ -n "${NUBIX_REF}" ]]; then + git -C "${ROOT}" archive --format=tar "${NUBIX_REF}" | tar -xf - -C "${tmp}/${top}" + rm -rf "${tmp}/${top}/docs" + echo "git archive $(git -C "${ROOT}" rev-parse "${NUBIX_REF}^{commit}") (docs/ excluded)" + elif [[ -n "${NUBIX_DIR}" ]]; then + local src; src="$(cd "${NUBIX_DIR}" && pwd)" + (cd "${src}" && find . -type f \ + ! -path './.git/*' ! -path './.git' ! -path './build*' ! -path './deps/*' \ + ! -path './dist/*' ! -path './.cache/*' ! -path './docs/*' ! -name '.DS_Store' -print0 \ + | tar -cf - --null -T -) | tar -xf - -C "${tmp}/${top}" + echo "directory ${src##*/}" + else + (cd "${ROOT}" && git ls-files -z -co --exclude-standard \ + | while IFS= read -r -d '' f; do + case "$f" in docs/*|dist/*|deps/*|.cache/*|build*/*) continue ;; esac + [[ -f "$f" || -L "$f" ]] && printf '%s\0' "$f" + done \ + | tar -cf - --null -T -) | tar -xf - -C "${tmp}/${top}" + local head dirty + head="$(git -C "${ROOT}" rev-parse HEAD 2>/dev/null || echo none)" + dirty="$(git -C "${ROOT}" status --porcelain 2>/dev/null | grep -vc '^?? docs/' || true)" + echo "working tree of ${ROOT##*/} at HEAD ${head} (${dirty} uncommitted/untracked paths; docs/ excluded)" + fi + (cd "${tmp}" && find "${top}" -print0 | LC_ALL=C sort -z \ + | tar -cf - "${TAR_NORM[@]}" --no-recursion --null -T -) | xz -T1 -6 -c >"${out}" + rm -rf "${tmp}" +} + +# ------------------------------------------------------------------ main ---- +mkdir -p "${CACHE}" "${GITCACHE}" "${DIST}" +rm -rf "${WORK}"; mkdir -p "${STAGE}/upstream" + +MANIFEST_ROWS=() # path|component|version|origin + +log "Nubix ${VERSION}: packaging own source" +NUBIX_DESC="$(nubix_source)" +MANIFEST_ROWS+=("nubix-v${VERSION}-src.tar.xz|nubix|${VERSION}|${NUBIX_DESC}") + +while IFS='|' read -r id ver file sum urls; do + [[ -n "${id}" ]] || continue + # shellcheck disable=SC2086 + fetch_tarball "${file}" "${sum}" ${urls} + cp "${CACHE}/${file}" "${STAGE}/upstream/${file}" + MANIFEST_ROWS+=("upstream/${file}|${id}|${ver}|${urls%% *}") +done <<<"${TARBALLS}" + +while IFS='|' read -r id label repo commit prefix; do + [[ -n "${id}" ]] || continue + file="$(export_commit "${id}" "${label}" "${repo}" "${commit}" "${prefix}")" + cp "${CACHE}/${file}" "${STAGE}/upstream/${file}" + MANIFEST_ROWS+=("upstream/${file}|${id}|${label}|${repo} @ ${commit}") +done <<<"${GITSRCS}" + +[[ -f "${ROOT}/SOURCE.txt" ]] && cp "${ROOT}/SOURCE.txt" "${STAGE}/SOURCE.txt" +[[ -f "${ROOT}/LICENSE" ]] && cp "${ROOT}/LICENSE" "${STAGE}/LICENSE" + +cat >"${STAGE}/README.txt" <"${STAGE}/MANIFEST" +(cd "${STAGE}" && find . -type f ! -name SHA256SUMS ! -name MANIFEST | sed 's|^\./||' | LC_ALL=C sort \ + | while IFS= read -r p; do printf '%s %s\n' "$(sha256 "$p")" "$p"; done) >"${STAGE}/SHA256SUMS" + +log "writing ${OUT}" +rm -f "${OUT}.part" +(cd "${WORK}" && find "${NAME}" -print0 | LC_ALL=C sort -z \ + | tar -cf - "${TAR_NORM[@]}" --no-recursion --null -T -) | xz -6 -T0 -c >"${OUT}.part" +mv "${OUT}.part" "${OUT}" +cp "${STAGE}/MANIFEST" "${DIST}/${NAME}.MANIFEST" +(cd "${DIST}" && printf '%s %s\n' "$(sha256 "${NAME}.tar.xz")" "${NAME}.tar.xz" >"${NAME}.tar.xz.sha256") +rm -rf "${WORK}" + +log "done: ${OUT} ($(fsize "${OUT}") bytes)" +cat "${DIST}/${NAME}.MANIFEST" diff --git a/src/core/auth.cpp b/src/core/auth.cpp new file mode 100644 index 0000000..c38f119 --- /dev/null +++ b/src/core/auth.cpp @@ -0,0 +1,646 @@ +// Nubix — Microsoft account + Xbox Live + gssv authentication. +// Flow and request shapes mirror green-nx src/core/auth.cpp (GPL-3.0) and xal-node src/msal.ts: +// MSAL v2 device code (consumers tenant, xbox.com web client id) -> XBL user token (RPS "d=") +// -> XSTS (http://gssv.xboxlive.com/ and http://xboxlive.com) -> /v2/login/user per offering, +// and the login.live.com transfer token (LPT) for xCloud /connect. +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "core/auth.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "core/http.hpp" +#include "core/http_internal.hpp" +#include "core/log.hpp" + +using nlohmann::json; + +namespace xc { +namespace { + +// Public client id of the xbox.com web app (Azure AD v2, consumers), used by green-nx, +// greenlight/xal-node and XStreaming. The same id is used for the login.live.com LPT request. +constexpr const char* kClientId = "1f907974-e22b-4810-a9de-d9647380c97e"; +constexpr const char* kScope = "xboxlive.signin openid profile offline_access"; +constexpr const char* kDeviceCodeUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"; +constexpr const char* kTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; +constexpr const char* kUserAuthUrl = "https://user.auth.xboxlive.com/user/authenticate"; +constexpr const char* kXstsUrl = "https://xsts.auth.xboxlive.com/xsts/authorize"; +constexpr const char* kLptUrl = "https://login.live.com/oauth20_token.srf"; +constexpr const char* kLptScope = "service::http://Passport.NET/purpose::PURPOSE_XBOX_CLOUD_CONSOLE_TRANSFER_TOKEN"; +constexpr const char* kProfileUrl = + "https://profile.xboxlive.com/users/me/profile/settings?settings=Gamertag"; +constexpr const char* kBrowserUa = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 " + "Safari/537.36"; + +const std::vector kFormHeaders = { + "Content-Type: application/x-www-form-urlencoded", + "Accept: application/json", +}; + +const std::vector kFormNoCacheHeaders = { + "Content-Type: application/x-www-form-urlencoded", + "Accept: application/json", + "Cache-Control: no-store, must-revalidate, no-cache", +}; + +// xal-node msal.ts doXstsAuthorization headers (also fine for user/authenticate). +const std::vector kXblHeaders = { + "Content-Type: application/json", + "Accept: */*", + "x-xbl-contract-version: 1", + "Cache-Control: no-cache", + "Origin: https://www.xbox.com", + "Referer: https://www.xbox.com/", + "ms-cv: 0", + std::string("User-Agent: ") + kBrowserUa, +}; + +int64_t now() { return static_cast(std::time(nullptr)); } + +json parseJson(const std::string& body) { + if (body.empty()) return json(); + return json::parse(body, nullptr, /*allow_exceptions=*/false); +} + +std::string jStr(const json& j, const char* key) { + if (!j.is_object()) return std::string(); + auto it = j.find(key); + return (it != j.end() && it->is_string()) ? it->get() : std::string(); +} + +int64_t jInt(const json& j, const char* key, int64_t def) { + if (!j.is_object()) return def; + auto it = j.find(key); + if (it == j.end()) return def; + if (it->is_number_integer()) return it->get(); + if (it->is_number()) return static_cast(it->get()); + if (it->is_string()) { + const std::string s = it->get(); + char* end = nullptr; + long long v = std::strtoll(s.c_str(), &end, 10); + if (end && end != s.c_str()) return v; + } + return def; +} + +std::string firstLine(std::string s, size_t maxLen = 200) { + const size_t nl = s.find_first_of("\r\n"); + if (nl != std::string::npos) s.resize(nl); + if (s.size() > maxLen) s = s.substr(0, maxLen) + "…"; + return s; +} + +// Human-readable reason for a failed HTTP call; never includes request bodies (tokens). +std::string describe(const HttpResponse& r, const std::string& label) { + if (!r.error.empty()) return label + ": network error: " + r.error; + const json j = parseJson(r.body); + std::string detail = jStr(j, "error_description"); + if (detail.empty()) detail = jStr(j, "message"); + if (detail.empty()) detail = jStr(j, "code"); + if (detail.empty()) detail = jStr(j, "error"); + if (detail.empty() && !r.body.empty() && !j.is_object()) detail = r.body; + std::string out = label + " failed (HTTP " + std::to_string(r.status) + ")"; + if (!detail.empty()) out += ": " + firstLine(detail); + return out; +} + +// ISO 8601 UTC ("2026-10-05T10:00:00.1234567Z") -> unix seconds; 0 if unparsable. +int64_t parseIso8601(const std::string& s) { + int y = 0, mo = 0, d = 0, h = 0, mi = 0, se = 0; + if (std::sscanf(s.c_str(), "%d-%d-%dT%d:%d:%d", &y, &mo, &d, &h, &mi, &se) != 6) return 0; + if (mo < 1 || mo > 12 || d < 1 || d > 31) return 0; + // days_from_civil (Howard Hinnant), avoids timegm() availability questions on the PS5 libc. + y -= mo <= 2 ? 1 : 0; + const int era = (y >= 0 ? y : y - 399) / 400; + const unsigned yoe = static_cast(y - era * 400); + const unsigned doy = static_cast((153 * (mo + (mo > 2 ? -3 : 9)) + 2) / 5 + d - 1); + const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + const int64_t days = static_cast(era) * 146097 + static_cast(doe) - 719468; + return days * 86400 + h * 3600 + mi * 60 + se; +} + +// XSTS 401 "XErr" codes (xbox-webapi / xal docs) -> actionable text. +std::string xstsErrorText(const HttpResponse& r, const std::string& label) { + const json j = parseJson(r.body); + const int64_t xerr = jInt(j, "XErr", 0); + switch (xerr) { + case 2148916227LL: return label + ": this account is banned from Xbox Live"; + case 2148916229LL: return label + ": account restricted by parental controls (online play not allowed)"; + case 2148916233LL: return label + ": this Microsoft account has no Xbox profile yet - sign in once at xbox.com"; + case 2148916234LL: return label + ": the account must accept the Xbox terms of use at xbox.com"; + case 2148916235LL: return label + ": Xbox Live is not available in this account's country"; + case 2148916236LL: + case 2148916237LL: return label + ": the account requires age verification at login.live.com"; + case 2148916238LL: return label + ": child account - an adult must add it to a Microsoft family"; + default: break; + } + std::string out = describe(r, label); + if (xerr) out += " (XErr " + std::to_string(xerr) + ")"; + return out; +} + +struct XboxToken { + std::string token, uhs, gamertag; + int64_t notAfter = 0; +}; + +bool parseXboxToken(const HttpResponse& r, XboxToken& out) { + const json j = parseJson(r.body); + out.token = jStr(j, "Token"); + if (out.token.empty()) return false; + out.notAfter = parseIso8601(jStr(j, "NotAfter")); + auto dc = j.find("DisplayClaims"); + if (dc != j.end() && dc->is_object()) { + auto xui = dc->find("xui"); + if (xui != dc->end() && xui->is_array() && !xui->empty()) { + out.uhs = jStr(xui->front(), "uhs"); + out.gamertag = jStr(xui->front(), "gtg"); + } + } + return true; +} + +// Outcome of an MSA token-endpoint call. +enum class MsaResult { Ok, Dead, Failed }; + +void clearXboxTokens(Tokens& t) { + t.xhomeGs.clear(); + t.xcloudGs.clear(); + t.xcloudF2pGs.clear(); + t.xhomeBase.clear(); + t.xcloudBase.clear(); + t.xcloudF2pBase.clear(); + t.xhomeRegions.clear(); + t.xcloudRegions.clear(); + t.xcloudF2pRegions.clear(); + t.gsExpiry = 0; + t.uhs.clear(); + t.xblToken.clear(); + t.xblExpiry = 0; + t.market.clear(); +} + +// Store an MSA token-endpoint response (device code success or refresh). Keeps the old +// refresh token if the response did not rotate it. +bool storeMsaTokens(Tokens& t, const json& j) { + const std::string access = jStr(j, "access_token"); + if (access.empty()) return false; + t.msaAccess = access; + const std::string rt = jStr(j, "refresh_token"); + if (!rt.empty()) t.msaRefresh = rt; + t.msaExpiry = now() + jInt(j, "expires_in", 3600); + return true; +} + +// t is the caller's working copy of the tokens; it is updated and the MSA fields are committed +// to cfg (and persisted). Callers hold Auth::mu_, the only writer of tokens. +MsaResult refreshMsa(Config& cfg, Tokens& t, std::string& err) { + if (t.msaRefresh.empty()) { + err = "not signed in"; + return MsaResult::Dead; + } + // xal-node msal.ts refreshUserToken / green-nx refresh_user_token. + const std::string body = std::string("client_id=") + kClientId + "&grant_type=refresh_token&refresh_token=" + + Http::urlEncode(t.msaRefresh) + "&scope=" + Http::urlEncode(kScope); + HttpResponse r = Http::request("POST", kTokenUrl, kFormNoCacheHeaders, body); + if (r.ok()) { + if (!storeMsaTokens(t, parseJson(r.body))) { + err = "token refresh: response without access_token"; + return MsaResult::Failed; + } + cfg.updateTokens([&t](Tokens& c) { + c.msaAccess = t.msaAccess; + c.msaRefresh = t.msaRefresh; + c.msaExpiry = t.msaExpiry; + }); + cfg.save(); + XC_LOGI("auth: MSA token refreshed (valid %lld s)", (long long)(t.msaExpiry - now())); + return MsaResult::Ok; + } + if (r.error.empty() && r.status >= 400 && r.status < 500) { + const json j = parseJson(r.body); + const std::string kind = jStr(j, "error"); + // invalid_grant: revoked/expired/rotated-away refresh token. interaction_required: + // consent/password change. Either way the stored login is unusable (msal.ts + // TokenRefreshError); forget it so the UI shows the device-code screen again. + if (kind == "invalid_grant" || kind == "interaction_required" || kind == "invalid_client" || + kind == "unauthorized_client") { + XC_LOGW("auth: refresh token rejected (%s); signing out", kind.c_str()); + const std::string gamertag = t.gamertag; + t = Tokens(); + cfg.setTokens(t); + cfg.save(); + err = "saved login expired; please sign in again" + (gamertag.empty() ? "" : " (" + gamertag + ")"); + return MsaResult::Dead; + } + } + err = describe(r, "token refresh"); + return MsaResult::Failed; +} + +struct OfferingLogin { + std::string gsToken, market; + std::vector regions; + int64_t duration = 0; +}; + +// POST https://{offering}.gssv-play-prod.xboxlive.com/v2/login/user (green-nx streaming_login). +bool gssvLogin(const std::string& xsts, const std::string& offering, OfferingLogin& out, std::string& err) { + const json body = {{"token", xsts}, {"offeringId", offering}}; + const std::vector headers = { + "Content-Type: application/json", + "Accept: application/json", + "Cache-Control: no-store, must-revalidate, no-cache", + "x-gssv-client: XboxComBrowser", + }; + HttpResponse r; + { + // 403 OfferingAccessDenied is a normal outcome (no Game Pass / no console). + httpdetail::QuietErrors quiet; + r = Http::request("POST", "https://" + offering + ".gssv-play-prod.xboxlive.com/v2/login/user", headers, + body.dump()); + } + if (!r.ok()) { + err = describe(r, offering); + return false; + } + const json j = parseJson(r.body); + out.gsToken = jStr(j, "gsToken"); + out.market = jStr(j, "market"); + out.duration = jInt(j, "durationInSeconds", 14400); + out.regions.clear(); + auto os = j.find("offeringSettings"); + if (j.is_object() && os != j.end() && os->is_object()) { + auto regs = os->find("regions"); + if (regs != os->end() && regs->is_array()) { + for (const auto& e : *regs) { + if (!e.is_object()) continue; + Region reg; + reg.name = jStr(e, "name"); + reg.baseUri = jStr(e, "baseUri"); + auto def = e.find("isDefault"); + reg.isDefault = def != e.end() && def->is_boolean() && def->get(); + while (!reg.baseUri.empty() && reg.baseUri.back() == '/') reg.baseUri.pop_back(); + if (!reg.baseUri.empty()) out.regions.push_back(std::move(reg)); + } + } + } + if (out.gsToken.empty()) { + err = offering + ": response without gsToken"; + return false; + } + if (out.regions.empty()) { + err = offering + ": no regions in response"; + return false; + } + return true; +} + +} // namespace + +Auth::Auth(Config& cfg) : cfg_(cfg) {} + +bool Auth::requestDeviceCode(DeviceCode& out, std::string& err) { + std::lock_guard lk(mu_); + const std::string body = std::string("client_id=") + kClientId + "&scope=" + Http::urlEncode(kScope); + HttpResponse r = Http::request("POST", kDeviceCodeUrl, kFormHeaders, body); + if (!r.ok()) { + err = describe(r, "device code request"); + return false; + } + const json j = parseJson(r.body); + out = DeviceCode(); + out.userCode = jStr(j, "user_code"); + out.deviceCode = jStr(j, "device_code"); + out.verificationUri = jStr(j, "verification_uri"); + out.message = jStr(j, "message"); + out.interval = static_cast(std::max(1, std::min(60, jInt(j, "interval", 5)))); + out.expiresIn = static_cast(std::max(30, std::min(3600, jInt(j, "expires_in", 900)))); + if (out.userCode.empty() || out.deviceCode.empty()) { + err = "device code request: malformed response"; + return false; + } + if (out.verificationUri.empty()) out.verificationUri = "https://www.microsoft.com/link"; + // The consumers endpoint has no verification_uri_complete; build the documented ?otc= form + // so a phone scanning the QR lands on the code screen pre-filled (green-nx). + out.qrUrl = jStr(j, "verification_uri_complete"); + if (out.qrUrl.empty()) { + std::string bare; + for (char c : out.userCode) + if (c != '-' && c != ' ') bare += c; + out.qrUrl = out.verificationUri + (out.verificationUri.find('?') == std::string::npos ? "?" : "&") + + "otc=" + Http::urlEncode(bare); + } + XC_LOGI("auth: device code %s at %s (expires in %d s, poll every %d s)", out.userCode.c_str(), + out.verificationUri.c_str(), out.expiresIn, out.interval); + return true; +} + +PollResult Auth::pollToken(const DeviceCode& dc, std::string& err) { + std::lock_guard lk(mu_); + const std::string body = std::string("grant_type=") + Http::urlEncode("urn:ietf:params:oauth:grant-type:device_code") + + "&client_id=" + kClientId + "&device_code=" + Http::urlEncode(dc.deviceCode); + HttpResponse r; + { + httpdetail::QuietErrors quiet; // 400 authorization_pending every interval is expected + r = Http::request("POST", kTokenUrl, kFormHeaders, body); + } + if (!r.error.empty() || r.status >= 500 || r.status == 429) { + // Transient (network blip, server hiccup): keep polling until the code expires. + err = describe(r, "device code poll"); + XC_LOGW("auth: %s (will retry)", err.c_str()); + return PollResult::Pending; + } + const json j = parseJson(r.body); + if (r.ok()) { + Tokens t = cfg_.tokens(); + if (!storeMsaTokens(t, j) || jStr(j, "refresh_token").empty()) { + err = "device code poll: response without access/refresh token"; + return PollResult::Error; + } + // A new login may be a different account: drop all derived tokens and the profile. + clearXboxTokens(t); + t.gamertag.clear(); + cfg_.setTokens(t); + cfg_.save(); + XC_LOGI("auth: device code login complete"); + err.clear(); + return PollResult::Success; + } + const std::string kind = jStr(j, "error"); + if (kind == "authorization_pending") return PollResult::Pending; + if (kind == "slow_down") { + // RFC 8628: the caller should add 5 s to its interval. + err = "slow_down"; + return PollResult::Pending; + } + if (kind == "expired_token" || kind == "code_expired" || kind == "bad_verification_code") { + err = "the sign-in code expired"; + return PollResult::Expired; + } + if (kind == "authorization_declined" || kind == "access_denied") { + err = "sign-in was declined"; + return PollResult::Denied; + } + err = describe(r, "device code poll"); + return PollResult::Error; +} + +bool Auth::refresh(std::string& err) { + std::lock_guard lk(mu_); + Tokens t = cfg_.tokens(); + return refreshMsa(cfg_, t, err) == MsaResult::Ok; +} + +bool Auth::authorizeStreaming(std::string& err) { + std::lock_guard lk(mu_); + // Work on a copy and publish it in one step on every exit (the UI thread reads the token + // lists while this runs). Auth::mu_ makes this the only writer meanwhile. + Tokens t = cfg_.tokens(); + const Settings settings = cfg_.settings(); + struct Commit { + Config& cfg; + const Tokens& t; + ~Commit() { cfg.setTokens(t); } + } commit{cfg_, t}; + if (t.msaRefresh.empty()) { + err = "not signed in"; + return false; + } + bool refreshed = false; + if (t.msaAccess.empty() || t.msaExpiry - now() < 120) { + if (refreshMsa(cfg_, t, err) != MsaResult::Ok) return false; + refreshed = true; + } + + // 1. XBL user token (XASU). "d=" prefix for MSAL v2 access tokens. + XboxToken user; + for (int attempt = 0; attempt < 2; ++attempt) { + const json body = { + {"Properties", + {{"AuthMethod", "RPS"}, {"RpsTicket", "d=" + t.msaAccess}, {"SiteName", "user.auth.xboxlive.com"}}}, + {"RelyingParty", "http://auth.xboxlive.com"}, + {"TokenType", "JWT"}, + }; + HttpResponse r = Http::request("POST", kUserAuthUrl, kXblHeaders, body.dump()); + if (r.ok() && parseXboxToken(r, user)) break; + // A rejected RPS ticket usually means the cached MSA access token went stale: refresh once. + if (!refreshed && r.error.empty() && (r.status == 400 || r.status == 401 || r.status == 403)) { + XC_LOGW("auth: XBL user token rejected (HTTP %ld); refreshing MSA token", r.status); + if (refreshMsa(cfg_, t, err) != MsaResult::Ok) return false; + refreshed = true; + continue; + } + err = r.ok() ? "Xbox Live user authentication: response without Token" : describe(r, "Xbox Live user authentication"); + return false; + } + + // 2. XSTS for gssv (trailing slash matters). + auto xsts = [&](const char* rp, XboxToken& out, std::string& e) { + const json body = { + {"Properties", {{"SandboxId", "RETAIL"}, {"UserTokens", {user.token}}}}, + {"RelyingParty", rp}, + {"TokenType", "JWT"}, + }; + HttpResponse r = Http::request("POST", kXstsUrl, kXblHeaders, body.dump()); + if (r.ok() && parseXboxToken(r, out)) return true; + e = r.ok() ? std::string("XSTS ") + rp + ": response without Token" : xstsErrorText(r, std::string("XSTS ") + rp); + return false; + }; + XboxToken gssvXsts; + if (!xsts("http://gssv.xboxlive.com/", gssvXsts, err)) return false; + + // 3. XSTS for http://xboxlive.com: web token for profile/smartglass/titlehub (optional). + XboxToken web; + std::string webErr; + if (xsts("http://xboxlive.com", web, webErr)) { + t.xblToken = web.token; + t.uhs = !web.uhs.empty() ? web.uhs : gssvXsts.uhs; + t.xblExpiry = web.notAfter; + } else { + XC_LOGW("auth: %s (profile/console features may be limited)", webErr.c_str()); + t.xblToken.clear(); + t.xblExpiry = 0; + t.uhs = gssvXsts.uhs; + } + + // 4. Gamertag via profile (optional; green-nx fetch_profile). + if (!t.xblToken.empty() && !t.uhs.empty()) { + HttpResponse r = Http::request( + "GET", kProfileUrl, + {"x-xbl-contract-version: 3", "Accept: application/json", "Accept-Language: " + settings.locale, + "Authorization: XBL3.0 x=" + t.uhs + ";" + t.xblToken}); + std::string gamertag; + if (r.ok()) { + const json j = parseJson(r.body); + auto users = j.is_object() ? j.find("profileUsers") : j.end(); + if (j.is_object() && users != j.end() && users->is_array()) { + for (const auto& u : *users) { + auto settings = u.is_object() ? u.find("settings") : u.end(); + if (!u.is_object() || settings == u.end() || !settings->is_array()) continue; + for (const auto& s : *settings) + if (jStr(s, "id") == "Gamertag") gamertag = jStr(s, "value"); + } + } + } + if (gamertag.empty()) gamertag = !web.gamertag.empty() ? web.gamertag : gssvXsts.gamertag; + if (!gamertag.empty()) t.gamertag = gamertag; + else XC_LOGW("auth: could not read gamertag"); + } + + // 5. gsToken per offering, each optional (green-nx fetch_streaming_credentials; xgpuweb is + // optional here because F2P-only and xHome-only accounts are valid users). + struct Offering { + const char* id; + std::string* gs; + std::string* base; + std::vector* regions; + bool cloud; + }; + const Offering offerings[] = { + {"xhome", &t.xhomeGs, &t.xhomeBase, &t.xhomeRegions, false}, + {"xgpuweb", &t.xcloudGs, &t.xcloudBase, &t.xcloudRegions, true}, + {"xgpuwebf2p", &t.xcloudF2pGs, &t.xcloudF2pBase, &t.xcloudF2pRegions, true}, + }; + int64_t minDuration = INT64_MAX; + int got = 0; + std::string reasons; + for (const auto& o : offerings) { + OfferingLogin login; + std::string e; + if (gssvLogin(gssvXsts.token, o.id, login, e)) { + *o.gs = login.gsToken; + *o.regions = login.regions; + *o.base = pickRegionBase(login.regions, o.cloud ? settings.region : std::string()); + if (!login.market.empty()) t.market = login.market; + minDuration = std::min(minDuration, login.duration); + ++got; + XC_LOGI("auth: %s ok, %zu regions, using %s", o.id, login.regions.size(), o.base->c_str()); + } else { + o.gs->clear(); + o.base->clear(); + o.regions->clear(); + XC_LOGI("auth: %s unavailable: %s", o.id, e.c_str()); + if (!reasons.empty()) reasons += "; "; + reasons += e; + } + } + if (got == 0) { + t.gsExpiry = 0; + cfg_.setTokens(t); + cfg_.save(); + err = "no streaming access (xhome, xgpuweb, xgpuwebf2p all refused): " + reasons; + return false; + } + t.gsExpiry = now() + minDuration; + cfg_.setTokens(t); + cfg_.save(); + XC_LOGI("auth: streaming authorized for %s (market %s, gs valid %lld s)", t.gamertag.c_str(), t.market.c_str(), + (long long)minDuration); + err.clear(); + return true; +} + +bool Auth::fetchLpt(std::string& outLpt, std::string& err) { + std::lock_guard lk(mu_); + outLpt.clear(); + // green-nx fetch_passport_token refreshes first, so the freshest refresh token is used (and the + // rotated one persisted). A transient refresh failure is not fatal: the stored one still works. + std::string rerr; + Tokens t = cfg_.tokens(); + if (refreshMsa(cfg_, t, rerr) == MsaResult::Dead) { + err = rerr; + return false; + } + if (t.msaRefresh.empty()) { + err = "not signed in"; + return false; + } + const std::string body = std::string("client_id=") + kClientId + "&scope=" + Http::urlEncode(kLptScope) + + "&grant_type=refresh_token&refresh_token=" + Http::urlEncode(t.msaRefresh); + HttpResponse r = Http::request("POST", kLptUrl, kFormHeaders, body); + if (!r.ok()) { + err = describe(r, "transfer token (LPT)"); + return false; + } + outLpt = jStr(parseJson(r.body), "access_token"); + if (outLpt.empty()) { + err = "transfer token (LPT): response without access_token"; + return false; + } + // The response also carries a login.live.com refresh token for this purpose scope; it is not a + // replacement for the MSAL v2 refresh token, so it is deliberately not stored (xal-node, green-nx). + XC_LOGI("auth: transfer token obtained"); + return true; +} + +bool Auth::isLoggedIn() const { + // No Auth::mu_ here: the UI asks this while a long refresh may hold it. + return !cfg_.tokens().msaRefresh.empty(); +} + +void Auth::logout() { + std::lock_guard lk(mu_); + cfg_.setTokens(Tokens()); + cfg_.save(); + XC_LOGI("auth: signed out"); +} + +void Auth::invalidateStreaming() { + // Deliberately no Auth::mu_: a session thread reports the rejection while another thread may + // be inside a long re-authorization (which then publishes a fresh expiry anyway). + cfg_.updateTokens([](Tokens& t) { t.gsExpiry = 0; }); + XC_LOGW("auth: streaming token rejected by the server; it will be re-authorized"); +} + +bool Auth::ensureFresh(std::string& err, int marginSec) { + std::lock_guard lk(mu_); + Tokens t = cfg_.tokens(); + if (t.msaRefresh.empty()) { + err = "not signed in"; + return false; + } + const int64_t n = now(); + const bool noGs = t.xhomeGs.empty() && t.xcloudGs.empty() && t.xcloudF2pGs.empty(); + // gsExpiry <= 0: never authorized, or invalidateStreaming() after a server rejection. + if (noGs || t.gsExpiry <= 0 || t.gsExpiry - n < marginSec) { + // authorizeStreaming refreshes the MSA token itself when needed. + if (t.msaExpiry - n < marginSec) { + if (refreshMsa(cfg_, t, err) != MsaResult::Ok) return false; + } + return authorizeStreaming(err); + } + if (t.msaAccess.empty() || t.msaExpiry - n < marginSec) { + if (refreshMsa(cfg_, t, err) != MsaResult::Ok) return false; + } + err.clear(); + return true; +} + +void Auth::applyRegion() { + std::lock_guard lk(mu_); + const std::string region = cfg_.settings().region; + std::string cloudBase; + cfg_.updateTokens([&](Tokens& t) { + t.xcloudBase = pickRegionBase(t.xcloudRegions, region); + t.xcloudF2pBase = pickRegionBase(t.xcloudF2pRegions, region); + t.xhomeBase = pickRegionBase(t.xhomeRegions, std::string()); + cloudBase = t.xcloudBase; + }); + cfg_.save(); + XC_LOGI("auth: region '%s' -> cloud %s", region.c_str(), cloudBase.c_str()); +} + +} // namespace xc diff --git a/src/core/auth.hpp b/src/core/auth.hpp new file mode 100644 index 0000000..b1a5f6e --- /dev/null +++ b/src/core/auth.hpp @@ -0,0 +1,79 @@ +// Nubix — Microsoft account + Xbox Live + gssv authentication. +// Flow: MSAL device code (client 1f907974-…) -> XBL user token -> XSTS (gssv relying party) +// -> /v2/login/user gsToken per offering (xhome, xgpuweb, xgpuwebf2p) + LPT for xCloud /connect. +// Flow as implemented by green-nx (src/core/auth.cpp) and xal-node (src/msal.ts). +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include +#include + +#include "core/config.hpp" + +namespace xc { + +struct DeviceCode { + std::string userCode; // short code the user types at verificationUri + std::string deviceCode; // opaque code used for polling + std::string verificationUri; // e.g. https://www.microsoft.com/link + std::string qrUrl; // verificationUri with ?otc= for the QR code + int interval = 5; // poll interval in seconds + int expiresIn = 900; // lifetime in seconds + std::string message; // server's human-readable instruction text (English) +}; + +enum class PollResult { Pending, Success, Expired, Denied, Error }; + +class Auth { +public: + // Tokens are read from and written to cfg (snapshots / locked updates); cfg.save() is + // called on changes. + explicit Auth(Config& cfg); + + // Start device-code login. Returns false and sets err on failure. + bool requestDeviceCode(DeviceCode& out, std::string& err); + + // Poll the token endpoint once; the caller sleeps dc.interval between calls. + // On Success the MSA tokens are stored in the config. + PollResult pollToken(const DeviceCode& dc, std::string& err); + + // Refresh the MSA access token with the stored refresh token (persists the new refresh token). + bool refresh(std::string& err); + + // XBL user -> XSTS(gssv) -> gsToken for xhome, xgpuweb, xgpuwebf2p (each optional), plus + // gamertag/uhs. Succeeds if at least one offering returned a token. + bool authorizeStreaming(std::string& err); + + // Fetch the passport long-lived token (LPT) needed by xCloud POST /connect. + bool fetchLpt(std::string& outLpt, std::string& err); + + // True if a refresh token is stored. Never blocks on a running refresh. + bool isLoggedIn() const; + + // A gssv/catalog request was rejected with an auth error (401/403) although the stored + // gsExpiry says the tokens are valid (revocation, entitlement change, wall clock jump): + // mark the streaming tokens expired so the next ensureFresh() re-authorizes. + void invalidateStreaming(); + + // Forget all tokens and persist. + void logout(); + + // Ensure MSA + gs tokens are valid for at least marginSec (refresh / re-authorize as needed). + bool ensureFresh(std::string& err, int marginSec = 300); + + // Re-select xcloudBase / xcloudF2pBase from the stored regions after Settings::region changed + // (no network), and persist. xhomeBase always uses the server default region. + void applyRegion(); + + // All public methods (except isLoggedIn / invalidateStreaming) are serialised by an internal + // mutex, so the UI thread and the streamer thread may share one Auth. Config is itself + // thread-safe (snapshots), so token readers never race these writers. + +private: + Config& cfg_; + mutable std::recursive_mutex mu_; +}; + +} // namespace xc diff --git a/src/core/catalog.cpp b/src/core/catalog.cpp new file mode 100644 index 0000000..1aa61ed --- /dev/null +++ b/src/core/catalog.cpp @@ -0,0 +1,625 @@ +// Nubix — cloud title library and xHome console list. +// Ported from green-nx src/core/catalog.cpp (GPL-3.0, (c) green-nx authors). +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "core/catalog.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "core/gssv.hpp" +#include "core/http.hpp" +#include "core/log.hpp" + +using nlohmann::json; + +namespace xc { + +namespace { + +// displaycatalog accepts comma-separated bigIds; the Details template runs to ~53 KB per +// product, so 20 already means a ~1 MB response to hold in memory (green-nx). +constexpr size_t kHydrateBatch = 20; +constexpr int kHydrateWorkers = 4; + +// One /v2/titles entry with the full green-nx access breakdown. +struct Entry { + Title t; + bool subscription = false; // offered under some Game Pass tier + bool inSubscription = false; // ... a tier this account actually holds + bool adSupported = false; // F2P program, no subscription needed + bool adGranted = false; // userPrograms holds F2P for this title + bool adPlayable = false; // the xgpuwebf2p offering will stream it +}; + +std::string fold(std::string id) { + for (char& c : id) c = static_cast(std::toupper(static_cast(c))); + return id; +} + +std::string lower(std::string s) { + for (char& c : s) c = static_cast(std::tolower(static_cast(c))); + return s; +} + +std::string str(const json& j, const char* key) { + if (!j.is_object()) return {}; + auto it = j.find(key); + return it != j.end() && it->is_string() ? it->get() : std::string(); +} + +std::string snippet(const std::string& body) { + std::string s = body.substr(0, 300); + for (char& c : s) + if (c == '\r' || c == '\n') c = ' '; + return s; +} + +std::string httpFailure(const char* label, const HttpResponse& r) { + if (!r.error.empty()) return std::string(label) + ": " + r.error; + return std::string(label) + ": HTTP " + std::to_string(r.status) + (r.body.empty() ? "" : ": " + snippet(r.body)); +} + +// GET {base}/v2/titles with the given offering token (green-nx fetch_catalog). +bool fetchOffering(const std::string& base, const std::string& token, const Settings& settings, + std::vector& out, std::string& err, bool& authRejected) { + out.clear(); + std::string url = base; + while (!url.empty() && url.back() == '/') url.pop_back(); + url += "/v2/titles"; + HttpResponse r = Http::request("GET", url, gssv::requestHeaders(token, settings.resolution), "", 30); + if (!r.ok()) { + authRejected = r.error.empty() && gssv::isAuthRejection(r.status, r.body); + err = httpFailure("titles", r); + return false; + } + json parsed = json::parse(r.body, nullptr, false); + if (parsed.is_discarded()) { + err = "titles: invalid JSON"; + return false; + } + const json* results = nullptr; + if (parsed.is_array()) results = &parsed; + else if (parsed.is_object() && parsed.contains("results") && parsed["results"].is_array()) + results = &parsed["results"]; + if (!results) { + err = "titles: unexpected response shape"; + return false; + } + + // Diagnostics: which program / subscription codenames this account and catalog use, so + // filter problems ("Game Pass shows nothing") can be read straight from the log. + std::map programCounts, subCounts, userProgramCounts; + int entitled = 0, entitledWithSubProgram = 0; + for (const json& item : *results) { + Entry e; + e.t.titleId = str(item, "titleId"); + if (e.t.titleId.empty()) continue; + const json details = item.is_object() ? item.value("details", json::object()) : json::object(); + if (!details.is_object()) continue; + e.t.productId = str(details, "productId"); + auto ent = details.find("hasEntitlement"); + e.t.hasEntitlement = ent != details.end() && ent->is_boolean() && ent->get(); + for (const char* key : {"maxSessionLengthInSeconds", "maxGameplayTimeInSeconds"}) { + auto it = details.find(key); + if (it != details.end() && it->is_number()) { + e.t.maxSessionSeconds = static_cast(it->get()); + break; + } + } + + // "programs" lists what the title is offered under, "userSubscriptions" what the account + // holds; their intersection is Game Pass access. Subscription programs are backend + // codenames that change over time, so anything not a known non-subscription program + // counts as one (green-nx). + const json programs = details.value("programs", json::array()); + const json subs = details.value("userSubscriptions", json::array()); + if (programs.is_array()) { + for (const json& program : programs) { + if (!program.is_string()) continue; + const std::string name = program.get(); + if (name == "F2P") e.adSupported = true; + else if (name != "BYOG" && name != "EARLYACCESS") e.subscription = true; + if (subs.is_array()) + for (const json& sub : subs) + if (program == sub) e.inSubscription = true; + } + } + // "userPrograms" = the program(s) through which THIS account may play the title. F2P means + // ad-supported; any other non-purchase program means the subscription covers it. This is + // the reliable signal: the account's own subscription code (e.g. XGPCORE for Game Pass + // Essential) need not appear in "programs" at all (observed on an Essential account: + // userSubscriptions {XGPCORE}, userPrograms {EUROPA, F2P}). + const json userPrograms = details.value("userPrograms", json::array()); + if (userPrograms.is_array()) + for (const json& p : userPrograms) { + if (!p.is_string()) continue; + const std::string name = p.get(); + if (name == "F2P") e.adGranted = true; + else if (name != "BYOG" && name != "EARLYACCESS") e.inSubscription = true; + } + + for (const json* arr : {&programs, &subs, &userPrograms}) { + if (!arr->is_array()) continue; + auto& counts = arr == &programs ? programCounts : arr == &subs ? subCounts : userProgramCounts; + for (const json& v : *arr) + if (v.is_string()) ++counts[v.get()]; + } + if (e.t.hasEntitlement) { + ++entitled; + if (e.subscription) ++entitledWithSubProgram; + } + out.push_back(std::move(e)); + } + auto fmt = [](const std::map& m) { + std::string r; + for (const auto& kv : m) r += (r.empty() ? "" : ", ") + kv.first + "=" + std::to_string(kv.second); + return r.empty() ? std::string("(none)") : r; + }; + XC_LOGI("catalog: %s: %zu titles, programs {%s}", base.c_str(), out.size(), fmt(programCounts).c_str()); + XC_LOGI("catalog: %s: userSubscriptions {%s}, userPrograms {%s}", base.c_str(), fmt(subCounts).c_str(), + fmt(userProgramCounts).c_str()); + XC_LOGI("catalog: %s: %d entitled (%d of them also in a subscription program)", base.c_str(), entitled, + entitledWithSubProgram); + std::sort(out.begin(), out.end(), [](const Entry& a, const Entry& b) { return a.t.titleId < b.t.titleId; }); + out.erase(std::unique(out.begin(), out.end(), + [](const Entry& a, const Entry& b) { return a.t.titleId == b.t.titleId; }), + out.end()); + return true; +} + +// Fold the ad-supported offering's catalog into the cloud one (green-nx merge_ad_supported). +void mergeAdSupported(std::vector& catalog, const std::vector& ads) { + std::unordered_map byTitle; + for (size_t i = 0; i < catalog.size(); ++i) byTitle[catalog[i].t.titleId] = i; + + std::vector extras; + for (const Entry& ad : ads) { + // Everything the offering returns is ad-supported by definition, but only what the + // account is entitled to there will actually start. + if (!ad.t.hasEntitlement && !ad.inSubscription && !ad.adGranted) continue; + auto found = byTitle.find(ad.t.titleId); + if (found == byTitle.end()) { + Entry e = ad; + e.adSupported = true; + e.adPlayable = true; + // The offering only answers for itself; ownership / plan coverage is the cloud + // catalog's business, and it does not list this title at all. + e.t.hasEntitlement = false; + e.inSubscription = false; + extras.push_back(std::move(e)); + continue; + } + Entry& e = catalog[found->second]; + e.adSupported = true; + e.adPlayable = true; + e.adGranted = ad.adGranted; + if (ad.t.maxSessionSeconds > 0) e.t.maxSessionSeconds = ad.t.maxSessionSeconds; + if (e.t.productId.empty()) e.t.productId = ad.t.productId; + } + catalog.insert(catalog.end(), std::make_move_iterator(extras.begin()), std::make_move_iterator(extras.end())); +} + +std::string marketFromLocale(const std::string& locale) { + size_t dash = locale.find_first_of("-_"); + if (dash == std::string::npos || dash + 1 >= locale.size()) return "US"; + return fold(locale.substr(dash + 1)); +} + +std::string absoluteImageUrl(const std::string& uri) { + if (uri.empty()) return uri; + if (uri.rfind("//", 0) == 0) return "https:" + uri; + if (uri.rfind("http://", 0) == 0) return "https://" + uri.substr(7); + return uri; +} + + +// ---- disk caches (title list, box art) --------------------------------------------------------- +// Bump to invalidate caches written by older builds (2: catalog diagnostics in the log, +// 3: Game Pass flag from userPrograms). +constexpr int kTitleCacheVersion = 3; +std::mutex g_cacheMu; +std::string g_cacheDir; + +std::string cacheDir() { + std::lock_guard lk(g_cacheMu); + return g_cacheDir; +} + +std::string fnv1aHex(const std::string& s) { + uint64_t h = 1469598103934665603ULL; + for (unsigned char c : s) { + h ^= c; + h *= 1099511628211ULL; + } + char buf[17]; + std::snprintf(buf, sizeof buf, "%016llx", static_cast(h)); + return buf; +} + +void mkdirs(const std::string& path) { + for (size_t i = 1; i <= path.size(); ++i) + if (i == path.size() || path[i] == '/') ::mkdir(path.substr(0, i).c_str(), 0755); +} + +bool readFile(const std::string& path, std::vector& out) { + FILE* f = std::fopen(path.c_str(), "rb"); + if (!f) return false; + uint8_t buf[16384]; + size_t n; + while ((n = std::fread(buf, 1, sizeof buf, f)) > 0) out.insert(out.end(), buf, buf + n); + const bool ok = !std::ferror(f); + std::fclose(f); + return ok; +} + +bool writeFileAtomic(const std::string& path, const std::string& data) { + const size_t slash = path.rfind('/'); + if (slash != std::string::npos) mkdirs(path.substr(0, slash)); + const std::string tmp = path + ".tmp" + std::to_string(reinterpret_cast(&data) & 0xffff); + FILE* f = std::fopen(tmp.c_str(), "wb"); + if (!f) return false; + const bool ok = std::fwrite(data.data(), 1, data.size(), f) == data.size(); + if (std::fclose(f) != 0 || !ok || std::rename(tmp.c_str(), path.c_str()) != 0) { + std::remove(tmp.c_str()); + return false; + } + return true; +} + +// Keep the box-art cache bounded (~600 posters ≈ 30 MB): wipe it when it grows past that. +void pruneImageCache(const std::string& dir) { + static std::atomic sinceCheck{1000}; + if (++sinceCheck < 50) return; + sinceCheck = 0; + DIR* d = ::opendir(dir.c_str()); + if (!d) return; + std::vector files; + while (dirent* e = ::readdir(d)) + if (e->d_name[0] != '.') files.push_back(dir + "/" + e->d_name); + ::closedir(d); + if (files.size() <= 600) return; + XC_LOGI("catalog: pruning box-art cache (%zu files)", files.size()); + for (const auto& f : files) std::remove(f.c_str()); +} + +std::string titleCachePath(const Tokens& tk, const Settings& st) { + const std::string dir = cacheDir(); + if (dir.empty()) return ""; + const std::string account = !tk.uhs.empty() ? tk.uhs : tk.gamertag; + if (account.empty()) return ""; + return dir + "/titles-" + + fnv1aHex(account + "|" + tk.xcloudBase + "|" + tk.xcloudF2pBase + "|" + st.locale) + ".json"; +} + +bool loadTitleCache(const std::string& path, long maxAgeSec, std::vector& out) { + std::vector<uint8_t> raw; + if (!readFile(path, raw) || raw.empty()) return false; + json j = json::parse(raw.begin(), raw.end(), nullptr, false); + if (j.is_discarded() || !j.is_object() || !j.contains("titles") || !j["titles"].is_array()) return false; + if (j.value("version", 0) != kTitleCacheVersion) return false; // older format: refetch + const int64_t savedAt = j.value("savedAt", int64_t(0)); + const int64_t now = static_cast<int64_t>(std::time(nullptr)); + if (maxAgeSec >= 0 && (savedAt > now || now - savedAt > maxAgeSec)) return false; + std::vector<Title> list; + for (const json& e : j["titles"]) { + if (!e.is_object()) continue; + Title t; + t.titleId = str(e, "titleId"); + if (t.titleId.empty()) continue; + t.productId = str(e, "productId"); + t.name = str(e, "name"); + t.publisher = str(e, "publisher"); + t.imageUrl = str(e, "imageUrl"); + auto flag = [&](const char* k) { return e.contains(k) && e[k].is_boolean() && e[k].get<bool>(); }; + t.hasEntitlement = flag("hasEntitlement"); + t.f2p = flag("f2p"); + t.gamePass = flag("gamePass"); + t.f2pOnly = flag("f2pOnly"); + if (e.contains("maxSessionSeconds") && e["maxSessionSeconds"].is_number_integer()) + t.maxSessionSeconds = e["maxSessionSeconds"].get<int>(); + list.push_back(std::move(t)); + } + if (list.empty()) return false; + out = std::move(list); + return true; +} + +void saveTitleCache(const std::string& path, const std::vector<Title>& titles) { + json arr = json::array(); + for (const Title& t : titles) + arr.push_back({{"titleId", t.titleId}, + {"productId", t.productId}, + {"name", t.name}, + {"publisher", t.publisher}, + {"imageUrl", t.imageUrl}, + {"hasEntitlement", t.hasEntitlement}, + {"f2p", t.f2p}, + {"gamePass", t.gamePass}, + {"f2pOnly", t.f2pOnly}, + {"maxSessionSeconds", t.maxSessionSeconds}}); + json j = {{"version", kTitleCacheVersion}, {"savedAt", static_cast<int64_t>(std::time(nullptr))}, {"titles", std::move(arr)}}; + if (!writeFileAtomic(path, j.dump())) XC_LOGW("catalog: could not write %s", path.c_str()); +} + +} // namespace + +Catalog::Catalog(Config& cfg) : cfg_(cfg) {} + +bool Catalog::fetchCloudTitles(std::vector<Title>& out, std::string& err, bool* authRejected, bool forceRefresh) { + out.clear(); + if (authRejected) *authRejected = false; + // Snapshots: the UI thread may change settings / Auth may publish new tokens meanwhile. + const Tokens tk = cfg_.tokens(); + const Settings st = cfg_.settings(); + const std::string cacheFile = titleCachePath(tk, st); + if (!forceRefresh && !cacheFile.empty() && loadTitleCache(cacheFile, kTitleCacheTtlSec, out)) { + XC_LOGI("catalog: %zu titles from cache", out.size()); + return true; + } + bool rejected = false; + if (fetchCloudTitlesNetwork(tk, st, out, err, rejected)) { + if (!cacheFile.empty() && !rejected) saveTitleCache(cacheFile, out); + if (authRejected && rejected) *authRejected = true; + return true; + } + if (authRejected) *authRejected = rejected; + if (!rejected && !cacheFile.empty() && loadTitleCache(cacheFile, -1, out)) { + XC_LOGW("catalog: network fetch failed (%s); showing the cached library", err.c_str()); + err.clear(); + return true; + } + return false; +} + +bool Catalog::fetchCloudTitlesNetwork(const Tokens& tk, const Settings& st, std::vector<Title>& out, + std::string& err, bool& authRejectedOut) { + out.clear(); + authRejectedOut = false; + bool rejectedLocal = false; + bool* authRejected = &rejectedLocal; + const bool haveCloud = !tk.xcloudGs.empty() && !tk.xcloudBase.empty(); + const bool haveF2p = !tk.xcloudF2pGs.empty() && !tk.xcloudF2pBase.empty(); + if (!haveCloud && !haveF2p) { + err = "no xCloud offering available for this account"; + return false; + } + + std::vector<Entry> cloud, ads; + std::string cloudErr, f2pErr; + bool cloudOk = false, f2pOk = false, cloudAuth = false, f2pAuth = false; + // Both offerings in parallel: each /v2/titles is ~2400 entries. + const std::atomic<bool>* abortFlag = Http::threadAbortFlag(); + std::thread f2pThread; + if (haveF2p) { + try { + f2pThread = std::thread([&] { + Http::AbortScope abort(abortFlag); + f2pOk = fetchOffering(tk.xcloudF2pBase, tk.xcloudF2pGs, st, ads, f2pErr, f2pAuth); + }); + } catch (const std::exception& e) { // thread limit: fetch it on this thread instead + XC_LOGW("catalog: cannot start a fetch thread (%s)", e.what()); + f2pOk = fetchOffering(tk.xcloudF2pBase, tk.xcloudF2pGs, st, ads, f2pErr, f2pAuth); + } + } + if (haveCloud) cloudOk = fetchOffering(tk.xcloudBase, tk.xcloudGs, st, cloud, cloudErr, cloudAuth); + if (f2pThread.joinable()) f2pThread.join(); + + if (haveCloud && !cloudOk) XC_LOGW("catalog: xgpuweb %s", cloudErr.c_str()); + if (haveF2p && !f2pOk) XC_LOGW("catalog: xgpuwebf2p %s", f2pErr.c_str()); + if (!cloudOk && !f2pOk) { + err = !cloudErr.empty() ? cloudErr : f2pErr; + authRejectedOut = cloudAuth || f2pAuth; + return false; + } + // One offering refused the token while the other answered: still a stale token. + if (authRejected && (cloudAuth || f2pAuth)) *authRejected = true; + XC_LOGI("catalog: %zu cloud + %zu f2p titles", cloud.size(), ads.size()); + if (f2pOk) mergeAdSupported(cloud, ads); + + // The library lists what this account can actually start (green-nx Game::playable()). + for (Entry& e : cloud) { + const bool playable = e.t.hasEntitlement || e.inSubscription || e.adPlayable; + if (!playable) continue; + e.t.gamePass = e.inSubscription; + e.t.f2p = e.adPlayable; + e.t.f2pOnly = e.adPlayable && !e.t.hasEntitlement && !e.inSubscription; + out.push_back(std::move(e.t)); + } + + hydrate(out); + for (Title& t : out) + if (t.name.empty()) t.name = t.titleId; + std::sort(out.begin(), out.end(), [](const Title& a, const Title& b) { + const std::string la = lower(a.name), lb = lower(b.name); + return la != lb ? la < lb : a.titleId < b.titleId; + }); + XC_LOGI("catalog: %zu playable titles", out.size()); + authRejectedOut = rejectedLocal; + return true; +} + +void Catalog::hydrate(std::vector<Title>& titles) { + // displaycatalog echoes ProductId upper-cased whatever case the bigId was sent in, while a + // few titles carry a lower-case productId; fold case on both sides (green-nx). + std::unordered_map<std::string, std::vector<Title*>> byProduct; + for (Title& t : titles) + if (!t.productId.empty() && t.name.empty()) byProduct[fold(t.productId)].push_back(&t); + if (byProduct.empty()) return; + + std::vector<std::string> ids; + ids.reserve(byProduct.size()); + for (const auto& kv : byProduct) ids.push_back(kv.first); + std::sort(ids.begin(), ids.end()); + + const Settings settings = cfg_.settings(); + const std::string locale = settings.locale.empty() ? std::string("en-US") : settings.locale; + const std::string market = marketFromLocale(locale); + const size_t batches = (ids.size() + kHydrateBatch - 1) / kHydrateBatch; + std::atomic<size_t> next{0}; + std::atomic<size_t> failed{0}; + std::mutex mu; + // Helper threads inherit the caller's cancellation (App shutdown). + const std::atomic<bool>* abortFlag = Http::threadAbortFlag(); + + auto worker = [&] { + Http::AbortScope abort(abortFlag); + for (size_t b = next++; b < batches; b = next++) { + std::string bigIds; + for (size_t i = b * kHydrateBatch; i < std::min((b + 1) * kHydrateBatch, ids.size()); ++i) { + if (!bigIds.empty()) bigIds += ","; + bigIds += ids[i]; + } + const std::string url = "https://displaycatalog.mp.microsoft.com/v7.0/products?bigIds=" + + bigIds + "&market=" + Http::urlEncode(market) + + "&languages=" + Http::urlEncode(locale) + "&fieldsTemplate=Details"; + HttpResponse r = Http::request("GET", url, {"Accept: application/json"}, "", 30); + if (!r.ok()) { // metadata is best-effort + XC_LOGW("%s", httpFailure("displaycatalog", r).c_str()); + ++failed; + continue; + } + json parsed = json::parse(r.body, nullptr, false); + if (parsed.is_discarded() || !parsed.is_object()) { + ++failed; + continue; + } + const json products = parsed.value("Products", json::array()); + if (!products.is_array()) continue; + for (const json& product : products) { + const std::string pid = fold(str(product, "ProductId")); + auto found = byProduct.find(pid); + if (found == byProduct.end()) continue; + const json localized = product.value("LocalizedProperties", json::array()); + if (!localized.is_array() || localized.empty()) continue; + const json& props = localized.front(); + const std::string name = str(props, "ProductTitle"); + std::string publisher = str(props, "PublisherName"); + if (publisher.empty()) publisher = str(props, "DeveloperName"); + + std::string poster, boxArt, tile; + const json images = props.value("Images", json::array()); + if (images.is_array()) { + for (const json& image : images) { + const std::string purpose = str(image, "ImagePurpose"); + if (purpose == "Poster" && poster.empty()) poster = str(image, "Uri"); + else if (purpose == "BoxArt" && boxArt.empty()) boxArt = str(image, "Uri"); + else if (purpose == "Tile" && tile.empty()) tile = str(image, "Uri"); + } + } + const std::string& uri = !poster.empty() ? poster : (!boxArt.empty() ? boxArt : tile); + + std::lock_guard<std::mutex> lock(mu); + for (Title* t : found->second) { + if (!name.empty()) t->name = name; + if (!publisher.empty()) t->publisher = publisher; + if (!uri.empty()) t->imageUrl = absoluteImageUrl(uri); + } + } + } + }; + + const int n = static_cast<int>(std::min<size_t>(kHydrateWorkers, batches)); + std::vector<std::thread> pool; + for (int i = 1; i < n; ++i) { + try { + pool.emplace_back(worker); + } catch (const std::exception& e) { // thread limit: the remaining workers share the load + XC_LOGW("catalog: hydrate thread %d not started (%s)", i, e.what()); + break; + } + } + worker(); + for (auto& th : pool) th.join(); + XC_LOGI("catalog: hydrated %zu products in %zu batches (%zu failed)", ids.size(), batches, failed.load()); +} + +bool Catalog::fetchConsoles(std::vector<Console>& out, std::string& err, bool* authRejected) { + out.clear(); + if (authRejected) *authRejected = false; + const Tokens tk = cfg_.tokens(); + if (tk.xhomeGs.empty() || tk.xhomeBase.empty()) { + err = "remote play (xHome) is not available for this account"; + return false; + } + std::string url = tk.xhomeBase; + while (!url.empty() && url.back() == '/') url.pop_back(); + url += "/v6/servers/home?mr=50"; + // xhome: always the android/720 fingerprint (green-nx kDeviceInfo for the console list). + HttpResponse r = Http::request("GET", url, gssv::requestHeaders(tk.xhomeGs, gssv::deviceTierFor(true, "")), "", 20); + if (!r.ok()) { + if (authRejected) *authRejected = r.error.empty() && gssv::isAuthRejection(r.status, r.body); + err = httpFailure("console list", r); + return false; + } + json parsed = json::parse(r.body, nullptr, false); + if (parsed.is_discarded()) { + err = "console list: invalid JSON"; + return false; + } + const json* results = nullptr; + if (parsed.is_array()) results = &parsed; + else if (parsed.is_object() && parsed.contains("results") && parsed["results"].is_array()) + results = &parsed["results"]; + if (!results) return true; // no consoles + for (const json& entry : *results) { + Console c; + c.serverId = str(entry, "serverId"); + if (c.serverId.empty()) continue; + c.name = str(entry, "deviceName"); + if (c.name.empty()) c.name = str(entry, "serverName"); + if (c.name.empty()) c.name = c.serverId; + c.powerState = str(entry, "powerState"); + c.consoleType = str(entry, "consoleType"); + out.push_back(std::move(c)); + } + XC_LOGI("catalog: %zu consoles", out.size()); + return true; +} + +bool Catalog::fetchImage(const std::string& url, std::vector<uint8_t>& bytes) { + bytes.clear(); + if (url.empty()) return false; + const std::string dir = cacheDir(); + const std::string cached = dir.empty() ? std::string() : dir + "/img/" + fnv1aHex(url) + ".bin"; + if (!cached.empty() && readFile(cached, bytes) && !bytes.empty()) return true; + bytes.clear(); + std::string u = absoluteImageUrl(url); + // store-images.s-microsoft.com scales server-side; ask for a grid-sized poster (green-nx h=300) + // instead of the multi-megabyte original. + if (u.find("store-images.s-microsoft.com") != std::string::npos && u.find('?') == std::string::npos) + u += "?h=300"; + HttpResponse r = Http::request("GET", u, {"Accept: image/png,image/jpeg,image/*;q=0.8"}, "", 20); + if (!r.ok() || r.body.empty()) { + XC_LOGD("%s", httpFailure("image", r).c_str()); + return false; + } + bytes.assign(r.body.begin(), r.body.end()); + if (!cached.empty()) { + pruneImageCache(dir + "/img"); + writeFileAtomic(cached, r.body); + } + return true; +} + +void Catalog::setCacheDir(const std::string& dir) { + std::lock_guard<std::mutex> lk(g_cacheMu); + g_cacheDir = dir; +} + +} // namespace xc diff --git a/src/core/catalog.hpp b/src/core/catalog.hpp new file mode 100644 index 0000000..bb8302b --- /dev/null +++ b/src/core/catalog.hpp @@ -0,0 +1,70 @@ +// Nubix — cloud title library and xHome console list. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstdint> +#include <string> +#include <vector> + +#include "core/config.hpp" + +namespace xc { + +struct Title { + std::string titleId; // gssv titleId (used for /play and /waittime) + std::string productId; // Store product id (displaycatalog) + std::string name, publisher; + std::string imageUrl; // box art (poster) URL + bool hasEntitlement = false; // owned / playable with current offering + bool f2p = false; // from the xgpuwebf2p offering + bool gamePass = false; // part of Game Pass + bool f2pOnly = false; // playable only via the xgpuwebf2p offering (not owned, not in plan): + // start the session with xcloudF2pGs/xcloudF2pBase + int maxSessionSeconds = 0; // session cap (ad-supported runs); 0 = no cap +}; + +struct Console { + std::string serverId; // xHome serverId (used for /play) + std::string name; // user-given console name + std::string powerState; // "On", "ConnectedStandby", ... + std::string consoleType; // "XboxSeriesX", "XboxOne", ... +}; + +class Catalog { +public: + explicit Catalog(Config& cfg); + + // GET {xcloudBase}/v2/titles (+ f2p offering), merged, names/art hydrated via displaycatalog + // (batched 20 product ids per request). Sorted by name. authRejected (optional) is set when + // the failure was the gsToken being refused (401/403): re-authorize and retry. + // The calling thread's Http abort flag also cancels the helper threads. + // Results are cached on disk (setCacheDir) for kTitleCacheTtlSec per account/offering: within + // that time the library is served from the cache unless forceRefresh. When the network fetch + // fails for a reason other than a rejected token, a stale cache is returned instead. + bool fetchCloudTitles(std::vector<Title>& out, std::string& err, bool* authRejected = nullptr, + bool forceRefresh = false); + + // GET {xhomeBase}/v6/servers/home — consoles registered for remote play (android/720 + // device fingerprint, as for every xhome call). authRejected as above. + bool fetchConsoles(std::vector<Console>& out, std::string& err, bool* authRejected = nullptr); + + // Download an image (box art). Appends size hints (?w=…) where the CDN supports it. Served + // from / stored in <cacheDir>/img when a cache dir is set (box art URLs are immutable). + static bool fetchImage(const std::string& url, std::vector<uint8_t>& bytes); + + // Directory for the title list and box-art caches (created on demand). Empty = no caching. + static void setCacheDir(const std::string& dir); + static constexpr long kTitleCacheTtlSec = 6 * 3600; + + // Fill name/publisher/imageUrl for titles that have a productId but no name yet + // (displaycatalog, batched 20, no auth). Best effort; titles keep titleId as name on failure. + void hydrate(std::vector<Title>& titles); + +private: + bool fetchCloudTitlesNetwork(const Tokens& tk, const Settings& st, std::vector<Title>& out, std::string& err, + bool& authRejected); + Config& cfg_; +}; + +} // namespace xc diff --git a/src/core/config.cpp b/src/core/config.cpp new file mode 100644 index 0000000..d34d1bd --- /dev/null +++ b/src/core/config.cpp @@ -0,0 +1,345 @@ +// Nubix — persisted settings and tokens (JSON file). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "core/config.hpp" + +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <cerrno> +#include <chrono> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <mutex> +#include <random> +#include <sstream> +#include <utility> + +#include <nlohmann/json.hpp> + +#include "core/log.hpp" + +#if !defined(__APPLE__) && !defined(__FreeBSD__) +#include <openssl/rand.h> +#endif + +using nlohmann::json; + +namespace xc { +namespace { + +constexpr int kConfigVersion = 1; + +// Serialises the file write of save() across threads (the UI thread, the App worker and the +// streamer may all persist). Serialising happens inside it, so the last writer always writes +// the newest state. +std::mutex g_saveMutex; + +// Type-checked readers: a hand-edited or older config with a wrong type must never throw. +void readStr(const json& j, const char* key, std::string& out) { + auto it = j.find(key); + if (it != j.end() && it->is_string()) out = it->get<std::string>(); +} + +void readInt(const json& j, const char* key, int& out) { + auto it = j.find(key); + if (it != j.end() && it->is_number_integer()) out = it->get<int>(); +} + +void readI64(const json& j, const char* key, int64_t& out) { + auto it = j.find(key); + if (it != j.end() && it->is_number_integer()) out = it->get<int64_t>(); +} + +void readBool(const json& j, const char* key, bool& out) { + auto it = j.find(key); + if (it != j.end() && it->is_boolean()) out = it->get<bool>(); +} + +json regionsToJson(const std::vector<Region>& regions) { + json arr = json::array(); + for (const auto& r : regions) arr.push_back({{"name", r.name}, {"baseUri", r.baseUri}, {"isDefault", r.isDefault}}); + return arr; +} + +void readRegions(const json& j, const char* key, std::vector<Region>& out) { + out.clear(); + auto it = j.find(key); + if (it == j.end() || !it->is_array()) return; + for (const auto& e : *it) { + if (!e.is_object()) continue; + Region r; + readStr(e, "name", r.name); + readStr(e, "baseUri", r.baseUri); + readBool(e, "isDefault", r.isDefault); + if (!r.baseUri.empty()) out.push_back(std::move(r)); + } +} + +void fillRandom(uint8_t* buf, size_t n) { +#if defined(__APPLE__) || defined(__FreeBSD__) + // macOS and the PS5 (FreeBSD-based libc) both provide arc4random_buf. + arc4random_buf(buf, n); +#else + if (RAND_bytes(buf, static_cast<int>(n)) == 1) return; + std::random_device rd; + std::mt19937_64 gen((static_cast<uint64_t>(rd()) << 32) ^ rd() ^ + static_cast<uint64_t>(std::chrono::high_resolution_clock::now().time_since_epoch().count())); + for (size_t i = 0; i < n; ++i) buf[i] = static_cast<uint8_t>(gen()); +#endif +} + +bool clampSettings(Settings& s) { + bool changed = false; + if (s.resolution != "720" && s.resolution != "1080" && s.resolution != "1080HQ") { + s.resolution = "1080"; + changed = true; + } + if (s.bitrateKbps < 5000) { s.bitrateKbps = 5000; changed = true; } + if (s.bitrateKbps > 20000) { s.bitrateKbps = 20000; changed = true; } + if (s.stickDeadzone < 0) { s.stickDeadzone = 0; changed = true; } + if (s.stickDeadzone > 50) { s.stickDeadzone = 50; changed = true; } + if (s.locale.empty()) { s.locale = "en-US"; changed = true; } + return changed; +} + +} // namespace + +Config::Config(std::string path) : path_(std::move(path)) {} + +bool Config::load() { + Settings settings; + Tokens tokens; + std::string installId; + + bool ok = true; + std::string text; + if (!path_.empty()) { + std::ifstream in(path_, std::ios::binary); + if (in) { + std::ostringstream ss; + ss << in.rdbuf(); + text = ss.str(); + } + } + + if (!text.empty()) { + json root = json::parse(text, nullptr, /*allow_exceptions=*/false); + if (root.is_discarded() || !root.is_object()) { + XC_LOGE("config: %s is not valid JSON; using defaults (kept as .bad)", path_.c_str()); + const std::string bad = path_ + ".bad"; + std::rename(path_.c_str(), bad.c_str()); + ok = false; + } else { + readStr(root, "installId", installId); + auto sIt = root.find("settings"); + if (sIt != root.end() && sIt->is_object()) { + const json& s = *sIt; + readStr(s, "resolution", settings.resolution); + readInt(s, "bitrateKbps", settings.bitrateKbps); + readStr(s, "region", settings.region); + readStr(s, "locale", settings.locale); + readBool(s, "f2pFallback", settings.f2pFallback); + readInt(s, "stickDeadzone", settings.stickDeadzone); + if (clampSettings(settings)) XC_LOGW("config: out-of-range settings were reset"); + } + auto tIt = root.find("tokens"); + if (tIt != root.end() && tIt->is_object()) { + const json& t = *tIt; + readStr(t, "msaRefresh", tokens.msaRefresh); + readStr(t, "msaAccess", tokens.msaAccess); + readI64(t, "msaExpiry", tokens.msaExpiry); + readStr(t, "xhomeGs", tokens.xhomeGs); + readStr(t, "xcloudGs", tokens.xcloudGs); + readStr(t, "xcloudF2pGs", tokens.xcloudF2pGs); + readStr(t, "xhomeBase", tokens.xhomeBase); + readStr(t, "xcloudBase", tokens.xcloudBase); + readStr(t, "xcloudF2pBase", tokens.xcloudF2pBase); + readI64(t, "gsExpiry", tokens.gsExpiry); + readStr(t, "gamertag", tokens.gamertag); + readStr(t, "uhs", tokens.uhs); + readStr(t, "xblToken", tokens.xblToken); + readI64(t, "xblExpiry", tokens.xblExpiry); + readRegions(t, "xhomeRegions", tokens.xhomeRegions); + readRegions(t, "xcloudRegions", tokens.xcloudRegions); + readRegions(t, "xcloudF2pRegions", tokens.xcloudF2pRegions); + readStr(t, "market", tokens.market); + } + } + } + + if (installId.size() != 36) installId = generateUuid(); + { + std::lock_guard<std::mutex> lk(mu_); + settings_ = std::move(settings); + tokens_ = std::move(tokens); + installId_ = std::move(installId); + } + ++revision_; + return ok; +} + +Settings Config::settings() const { + std::lock_guard<std::mutex> lk(mu_); + return settings_; +} + +Tokens Config::tokens() const { + std::lock_guard<std::mutex> lk(mu_); + return tokens_; +} + +std::string Config::gamertag() const { + std::lock_guard<std::mutex> lk(mu_); + return tokens_.gamertag; +} + +std::string Config::installId() const { + std::lock_guard<std::mutex> lk(mu_); + return installId_; +} + +void Config::setSettings(const Settings& s) { + { + std::lock_guard<std::mutex> lk(mu_); + settings_ = s; + } + ++revision_; +} + +void Config::updateSettings(const std::function<void(Settings&)>& fn) { + { + std::lock_guard<std::mutex> lk(mu_); + fn(settings_); + } + ++revision_; +} + +void Config::setTokens(const Tokens& t) { + { + std::lock_guard<std::mutex> lk(mu_); + tokens_ = t; + } + ++revision_; +} + +void Config::updateTokens(const std::function<void(Tokens&)>& fn) { + { + std::lock_guard<std::mutex> lk(mu_); + fn(tokens_); + } + ++revision_; +} + + + +bool Config::save() { + if (path_.empty()) { + XC_LOGE("config: save() without a path"); + return false; + } + + std::lock_guard<std::mutex> saveLock(g_saveMutex); + Settings settings; + Tokens tokens; + std::string installId; + { + std::lock_guard<std::mutex> lk(mu_); + settings = settings_; + tokens = tokens_; + installId = installId_; + } + json root; + root["version"] = kConfigVersion; + root["installId"] = installId; + root["settings"] = { + {"resolution", settings.resolution}, {"bitrateKbps", settings.bitrateKbps}, + {"region", settings.region}, {"locale", settings.locale}, + {"f2pFallback", settings.f2pFallback}, {"stickDeadzone", settings.stickDeadzone}, + }; + root["tokens"] = { + {"msaRefresh", tokens.msaRefresh}, + {"msaAccess", tokens.msaAccess}, + {"msaExpiry", tokens.msaExpiry}, + {"xhomeGs", tokens.xhomeGs}, + {"xcloudGs", tokens.xcloudGs}, + {"xcloudF2pGs", tokens.xcloudF2pGs}, + {"xhomeBase", tokens.xhomeBase}, + {"xcloudBase", tokens.xcloudBase}, + {"xcloudF2pBase", tokens.xcloudF2pBase}, + {"gsExpiry", tokens.gsExpiry}, + {"gamertag", tokens.gamertag}, + {"uhs", tokens.uhs}, + {"xblToken", tokens.xblToken}, + {"xblExpiry", tokens.xblExpiry}, + {"xhomeRegions", regionsToJson(tokens.xhomeRegions)}, + {"xcloudRegions", regionsToJson(tokens.xcloudRegions)}, + {"xcloudF2pRegions", regionsToJson(tokens.xcloudF2pRegions)}, + {"market", tokens.market}, + }; + // Replace invalid UTF-8 instead of throwing (values come from the network). + const std::string text = root.dump(2, ' ', false, json::error_handler_t::replace) + "\n"; + + const std::string tmp = path_ + ".tmp"; + // 0600: the file holds the MSA refresh token. + int fd = ::open(tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) { + XC_LOGE("config: cannot create %s: %s", tmp.c_str(), std::strerror(errno)); + return false; + } + size_t off = 0; + while (off < text.size()) { + ssize_t n = ::write(fd, text.data() + off, text.size() - off); + if (n < 0) { + if (errno == EINTR) continue; + XC_LOGE("config: write %s failed: %s", tmp.c_str(), std::strerror(errno)); + ::close(fd); + ::unlink(tmp.c_str()); + return false; + } + off += static_cast<size_t>(n); + } + ::fsync(fd); + if (::close(fd) != 0) { + XC_LOGE("config: close %s failed: %s", tmp.c_str(), std::strerror(errno)); + ::unlink(tmp.c_str()); + return false; + } + if (std::rename(tmp.c_str(), path_.c_str()) != 0) { + XC_LOGE("config: rename %s -> %s failed: %s", tmp.c_str(), path_.c_str(), std::strerror(errno)); + ::unlink(tmp.c_str()); + return false; + } + return true; +} + +std::string pickRegionBase(const std::vector<Region>& regions, const std::string& preferred) { + if (regions.empty()) return std::string(); + if (!preferred.empty()) + for (const auto& r : regions) + if (r.name == preferred) return r.baseUri; + for (const auto& r : regions) + if (r.isDefault) return r.baseUri; + return regions.front().baseUri; +} + +std::string generateUuid() { + uint8_t b[16]; + fillRandom(b, sizeof(b)); + b[6] = static_cast<uint8_t>((b[6] & 0x0F) | 0x40); // version 4 + b[8] = static_cast<uint8_t>((b[8] & 0x3F) | 0x80); // RFC 4122 variant + static const char* hex = "0123456789abcdef"; + std::string out; + out.reserve(36); + for (int i = 0; i < 16; ++i) { + if (i == 4 || i == 6 || i == 8 || i == 10) out += '-'; + out += hex[b[i] >> 4]; + out += hex[b[i] & 0x0F]; + } + return out; +} + +} // namespace xc diff --git a/src/core/config.hpp b/src/core/config.hpp new file mode 100644 index 0000000..e2708ef --- /dev/null +++ b/src/core/config.hpp @@ -0,0 +1,101 @@ +// Nubix — persisted settings and tokens (JSON file). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <atomic> +#include <cstdint> +#include <functional> +#include <mutex> +#include <string> +#include <vector> + +namespace xc { + +struct Settings { + std::string resolution = "1080"; // "720" | "1080" | "1080HQ" + int bitrateKbps = 12000; // requested max bitrate (REMB / clientdevicecapabilities), 5000..20000 + std::string region = ""; // gssv region name; empty = server default + std::string locale = "en-US"; // used for catalog + session start + bool f2pFallback = true; // retry cloud start with the F2P offering on OfferingDoesNotContainTitle + int stickDeadzone = 0; // extra radial deadzone in percent (0 = rely on server) +}; + +// One gssv region of an offering, from /v2/login/user offeringSettings.regions[]. +struct Region { + std::string name; // e.g. "WestEurope" + std::string baseUri; // e.g. https://weu.core.gssv-play-prod.xboxlive.com + bool isDefault = false; +}; + +struct Tokens { + std::string msaRefresh, msaAccess; // MSA (login.live.com) refresh + access token + int64_t msaExpiry = 0; // unix seconds when msaAccess expires + std::string xhomeGs, xcloudGs, xcloudF2pGs; // gssv gsToken per offering (empty = not entitled) + std::string xhomeBase, xcloudBase, xcloudF2pBase; // base URI per offering, e.g. https://weu.core.gssv-play-prod.xboxlive.com + int64_t gsExpiry = 0; // unix seconds when the gs tokens expire + std::string gamertag, uhs, xblToken; // profile + XBL user token (for XSTS / displaycatalog) + // xblToken is the XSTS token for RelyingParty http://xboxlive.com and uhs its user hash: + // use as "Authorization: XBL3.0 x=<uhs>;<xblToken>" (profile, smartglass, titlehub, ...). + int64_t xblExpiry = 0; // unix seconds when xblToken expires (XSTS NotAfter) + // Regions per offering (empty = offering not entitled). The *Base fields above hold the + // selected region's baseUri (Settings::region for cloud offerings, else isDefault). + std::vector<Region> xhomeRegions, xcloudRegions, xcloudF2pRegions; + std::string market; // gssv "market" (country), e.g. "DE" +}; + +// Thread-safe: settings and tokens are shared by the UI thread, the App worker (Auth, Catalog) +// and the Streamer worker, so they are only reachable through copies (snapshots) and locked +// updates. Never hold a reference into them across threads. +class Config { +public: + // path: JSON file location, normally platform::dataDir() + "/config.json". + explicit Config(std::string path = ""); + + // Load from path. Missing file = defaults (returns true); malformed JSON = defaults + false. + // Generates installId if absent. + bool load(); + + // Atomically write settings + tokens to path (write temp file, then rename). The state is + // serialised under the lock; the file I/O runs outside it (serialised among savers). + bool save(); + + // File location set at construction / via setPath() (set before threads start). + const std::string& path() const { return path_; } + void setPath(const std::string& p) { path_ = p; } + + // Snapshots (copies taken under the lock). + Settings settings() const; + Tokens tokens() const; + std::string gamertag() const; + // Random UUID v4, generated once and persisted. + std::string installId() const; + + // Locked writes. The callback runs with the lock held: it must not call back into Config. + // None of these persist; call save() afterwards. + void setSettings(const Settings& s); + void updateSettings(const std::function<void(Settings&)>& fn); + void setTokens(const Tokens& t); + void updateTokens(const std::function<void(Tokens&)>& fn); + + // Incremented on every change of settings or tokens (load included): lets per-frame UI code + // refresh its cached snapshot only when something changed. + uint64_t revision() const { return revision_.load(); } + +private: + std::string path_; + mutable std::mutex mu_; + Settings settings_; + Tokens tokens_; + std::string installId_; + std::atomic<uint64_t> revision_{0}; +}; + +// Pick a region baseUri: the region named `preferred` if present, else the isDefault one, +// else the first; "" if regions is empty. +std::string pickRegionBase(const std::vector<Region>& regions, const std::string& preferred); + +// Generate a random RFC 4122 v4 UUID string (lower-case, with dashes). +std::string generateUuid(); + +} // namespace xc diff --git a/src/core/gssv.cpp b/src/core/gssv.cpp new file mode 100644 index 0000000..87ac390 --- /dev/null +++ b/src/core/gssv.cpp @@ -0,0 +1,566 @@ +// Nubix — gssv session REST API. +// Ported from green-nx src/core/session.cpp (GPL-3.0, (c) green-nx authors). Protocol behaviour +// (/configuration, transferUri, error codes) cross-checked against XStreaming and green-vita. +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "core/gssv.hpp" + +#include <algorithm> +#include <cstdlib> +#include <ctime> +#include <functional> + +#include <nlohmann/json.hpp> + +#include "core/http.hpp" +#include "core/log.hpp" +#include "core/ratelimit.hpp" +#include "core/protocol.hpp" + +using nlohmann::json; + +namespace xc { + +namespace { + +constexpr long kRequestTimeoutSec = 20; +// keepalive can block server-side for a while (reports §4: up to 15 s). +constexpr long kKeepAliveTimeoutSec = 25; + +std::string trimSlashes(std::string s, bool leading, bool trailing) { + if (trailing) + while (!s.empty() && s.back() == '/') s.pop_back(); + if (leading) { + size_t i = 0; + while (i < s.size() && s[i] == '/') ++i; + s.erase(0, i); + } + return s; +} + +std::string snippet(const std::string& body, size_t max = 300) { + std::string out = body.substr(0, max); + for (char& c : out) + if (c == '\r' || c == '\n') c = ' '; + return out; +} + +// "HTTP 403: {...}" / "transport: <curl error>". +std::string describeFailure(const char* label, const HttpResponse& r) { + if (!r.error.empty()) return std::string(label) + ": " + r.error; + std::string s = std::string(label) + ": HTTP " + std::to_string(r.status); + if (!r.body.empty()) s += ": " + snippet(r.body); + return s; +} + +std::string jsonString(const json& j, const char* key) { + auto it = j.find(key); + if (it == j.end() || !it->is_string()) return {}; + return it->get<std::string>(); +} + +// Pull {code, message} out of a gssv error body. Shapes seen: {"code":..,"message":..}, +// {"errorCode":..,"errorMessage":..}, {"errorDetails":{"code":..,"message":..}}. green-vita +// simply substring-matches the body, so fall back to that for the codes we act on. +void extractError(const std::string& body, std::string& code, std::string& message) { + json j = json::parse(body, nullptr, false); + if (!j.is_discarded() && j.is_object()) { + const json* src = &j; + auto ed = j.find("errorDetails"); + if (ed != j.end() && ed->is_object()) src = &*ed; + code = jsonString(*src, "code"); + if (code.empty()) code = jsonString(*src, "errorCode"); + message = jsonString(*src, "message"); + if (message.empty()) message = jsonString(*src, "errorMessage"); + if (src != &j) { + if (code.empty()) code = jsonString(j, "code"); + if (message.empty()) message = jsonString(j, "message"); + } + } + if (code.empty() && body.find(kErrOfferingDoesNotContainTitle) != std::string::npos) + code = kErrOfferingDoesNotContainTitle; +} + +// Home consoles attach an errorDetails object with all-null fields to perfectly good +// responses (the answer rides right alongside it); only a non-null field is a real failure. +// (green-nx is_real_exchange_error) +bool isRealExchangeError(const json& value) { + if (!value.is_object()) return false; + auto it = value.find("errorDetails"); + if (it == value.end() || it->is_null()) return false; + if (!it->is_object()) return true; + for (const auto& item : it->items()) + if (!item.value().is_null()) return true; + return false; +} + +// Body is "pending" when the server answers 204 or with nothing at all. +bool isPending(const HttpResponse& r) { + if (r.status == 204) return true; + for (char c : r.body) + if (c != ' ' && c != '\r' && c != '\n' && c != '\t') return false; + return true; +} + +// Local UTC offset in minutes (east positive), as the browser's -getTimezoneOffset(). +int timezoneOffsetMinutes() { + std::time_t now = std::time(nullptr); + std::tm local{}, utc{}; +#if defined(_WIN32) + localtime_s(&local, &now); + gmtime_s(&utc, &now); +#else + localtime_r(&now, &local); + gmtime_r(&now, &utc); +#endif + int diff = (local.tm_hour - utc.tm_hour) * 60 + (local.tm_min - utc.tm_min); + int dayDiff = local.tm_yday - utc.tm_yday; + if (local.tm_year != utc.tm_year) dayDiff = local.tm_year > utc.tm_year ? 1 : -1; + diff += dayDiff * 24 * 60; + if (diff < -12 * 60 || diff > 14 * 60) diff = 0; + return diff; +} + +} // namespace + +// ---- headers --------------------------------------------------------------------------------- + +namespace gssv { + +const char* osNameForResolution(const std::string& resolution) { + if (resolution == "720") return "android"; + if (resolution == "1080HQ") return "tizen"; + return "windows"; +} + +std::string deviceInfoJson(const std::string& resolution) { + const bool p720 = resolution == "720"; + // green-nx: report the tier's real display size; a fabricated 4K size risks landing in an + // unknown-device profile server-side. + json info = { + {"appInfo", + {{"env", + {{"clientAppId", "www.xbox.com"}, + {"clientAppType", "browser"}, + {"clientAppVersion", "26.1.97"}, + {"clientSdkVersion", "10.3.7"}, + {"httpEnvironment", "prod"}, + {"sdkInstallId", ""}}}}}, + {"dev", + {{"hw", {{"make", "Microsoft"}, {"model", "unknown"}, {"sdktype", "web"}}}, + {"os", {{"name", osNameForResolution(resolution)}, {"ver", "22631.2715"}, {"platform", "desktop"}}}, + {"displayInfo", + {{"dimensions", {{"widthInPixels", p720 ? 1280 : 1920}, {"heightInPixels", p720 ? 720 : 1080}}}, + {"pixelDensity", {{"dpiX", 1}, {"dpiY", 1}}}}}, + {"browser", {{"browserName", "chrome"}, {"browserVersion", "140.0.3485.54"}}}}}, + }; + return info.dump(); +} + +std::string deviceTierFor(bool home, const std::string& resolution) { return home ? std::string("720") : resolution; } + +bool isAuthRejection(long status, const std::string& body) { + if (status == 401) return true; + if (status != 403) return false; + std::string code, message; + extractError(body, code, message); + return code.empty(); +} + +std::vector<std::string> requestHeaders(const std::string& gsToken, const std::string& resolution) { + return { + "Accept: application/json", + "Content-Type: application/json", + "X-Gssv-Client: XboxComBrowser", + "X-MS-Device-Info: " + deviceInfoJson(resolution), + "Authorization: Bearer " + gsToken, + }; +} + +} // namespace gssv + +// ---- GssvClient ------------------------------------------------------------------------------ + +GssvClient::GssvClient(const std::string& baseUri, const std::string& gsToken, const Settings& settings) + : base_(trimSlashes(baseUri, false, true)), token_(gsToken), settings_(settings) {} + +std::vector<std::string> GssvClient::headers() const { return gssv::requestHeaders(token_, settings_.resolution); } + +void GssvClient::noteResponse(const HttpResponse& r) { + if (!r.error.empty() || !gssv::isAuthRejection(r.status, r.body)) return; + if (!authRejected_.exchange(true)) XC_LOGW("gssv: request rejected as unauthorized (HTTP %ld)", r.status); +} + +std::string GssvClient::apiUrl(const std::string& path) const { + return base_ + "/" + trimSlashes(path, true, false); +} + +std::string GssvClient::sessionUrl(const SessionInfo& s, const std::string& suffix) const { + const std::string host = s.host.empty() ? base_ : trimSlashes(s.host, false, true); + std::string url = host + "/" + trimSlashes(s.sessionPath, true, true); + if (!suffix.empty()) url += "/" + suffix; + return url; +} + +bool GssvClient::cleanupActive(SessionKind kind) { + if (base_.empty() || token_.empty()) return false; + const char* platform = kind == SessionKind::Home ? "home" : "cloud"; + HttpResponse r = Http::request("GET", apiUrl(std::string("v5/sessions/") + platform + "/active"), headers(), "", + kRequestTimeoutSec); + noteResponse(r); + if (r.status == 404 || isPending(r)) { + XC_LOGD("gssv: no active %s sessions (HTTP %ld)", platform, r.status); + return r.error.empty(); + } + if (!r.ok()) { + XC_LOGW("%s", describeFailure("gssv active sessions", r).c_str()); + return false; + } + json parsed = json::parse(r.body, nullptr, false); + if (parsed.is_discarded()) { + XC_LOGW("gssv active sessions: invalid JSON"); + return false; + } + // Collect any "sessionPath"/"path" strings anywhere in the response (green-nx). + std::vector<std::string> paths; + std::function<void(const json&)> walk = [&](const json& node) { + if (node.is_array()) { + for (const json& item : node) walk(item); + } else if (node.is_object()) { + for (const auto& item : node.items()) { + if ((item.key() == "sessionPath" || item.key() == "path") && item.value().is_string()) + paths.push_back(item.value().get<std::string>()); + walk(item.value()); + } + } + }; + walk(parsed); + std::sort(paths.begin(), paths.end()); + paths.erase(std::unique(paths.begin(), paths.end()), paths.end()); + + bool allOk = true; + for (const std::string& p : paths) { + if (p.empty()) continue; + SessionInfo s; + s.sessionPath = p; + XC_LOGI("gssv: stopping stale session %s", p.c_str()); + if (!stop(s)) allOk = false; + } + return allOk; +} + +bool GssvClient::play(SessionKind kind, const std::string& titleOrServerId, SessionInfo& out, std::string& err) { + out = SessionInfo{}; + if (base_.empty() || token_.empty()) { + err = kind == SessionKind::Home ? "no xHome token (remote play not available for this account)" + : "no xCloud token for this offering"; + return false; + } + const bool home = kind == SessionKind::Home; + // Client-side cap on session starts (ratelimit.hpp): a retry loop or impatient re-starts must + // never turn into a burst of /play requests. + int waitSec = 0; + if (!SessionStartLimiter::global().allow(home, steadyNowMs(), waitSec)) { + out.state = "Failed"; + out.errorCode = "ClientRateLimited"; + err = "Too many session starts in a short time. Please wait " + std::to_string(waitSec) + + " s and try again."; + XC_LOGW("gssv: play blocked by the session start limiter (%d s)", waitSec); + return false; + } + // Field values mirror green-nx/green-vita exactly; useIceConnection must stay false (true makes + // the console agent reject the start with AgentCommandError). + json body = { + {"clientSessionId", ""}, + {"titleId", home ? "" : titleOrServerId}, + {"systemUpdateGroup", ""}, + {"settings", + {{"nanoVersion", "V3;WebrtcTransport.dll"}, + {"enableOptionalDataCollection", false}, + {"enableTextToSpeech", false}, + {"highContrast", 0}, + {"locale", settings_.locale.empty() ? std::string("en-US") : settings_.locale}, + {"useIceConnection", false}, + {"timezoneOffsetMinutes", timezoneOffsetMinutes()}, + {"sdkType", "web"}, + {"osName", gssv::osNameForResolution(settings_.resolution)}}}, + {"serverId", home ? titleOrServerId : ""}, + {"fallbackRegionNames", json::array()}, + }; + const std::string url = apiUrl(home ? "v5/sessions/home/play" : "v5/sessions/cloud/play"); + XC_LOGI("gssv: play %s %s (osName %s)", home ? "home" : "cloud", titleOrServerId.c_str(), + gssv::osNameForResolution(settings_.resolution)); + HttpResponse r = Http::request("POST", url, headers(), body.dump(), kRequestTimeoutSec); + noteResponse(r); + if (!r.ok()) { + extractError(r.body, out.errorCode, out.errorMessage); + out.state = "Failed"; + err = describeFailure("session start", r); + XC_LOGE("%s", err.c_str()); + return false; + } + json parsed = json::parse(r.body, nullptr, false); + if (parsed.is_discarded() || !parsed.is_object()) { + err = "session start: invalid JSON response"; + return false; + } + out.sessionPath = trimSlashes(jsonString(parsed, "sessionPath"), true, true); + if (out.sessionPath.empty()) { + err = "session start: response has no sessionPath: " + snippet(r.body); + return false; + } + out.sessionId = jsonString(parsed, "sessionId"); + if (out.sessionId.empty()) { + size_t slash = out.sessionPath.rfind('/'); + out.sessionId = slash == std::string::npos ? out.sessionPath : out.sessionPath.substr(slash + 1); + } + out.state = jsonString(parsed, "state"); + if (out.state.empty()) out.state = "New"; + XC_LOGI("gssv: session %s started (%s)", out.sessionId.c_str(), out.state.c_str()); + return true; +} + +bool GssvClient::pollState(SessionInfo& io, std::string& err) { + if (io.sessionPath.empty()) { + err = "no session"; + return false; + } + HttpResponse r = Http::request("GET", sessionUrl(io, "state"), headers(), "", kRequestTimeoutSec); + noteResponse(r); + if (!r.ok()) { + err = describeFailure("session state", r); + return false; + } + json parsed = json::parse(r.body, nullptr, false); + if (parsed.is_discarded() || !parsed.is_object()) { + err = "session state: invalid JSON response"; + return false; + } + const std::string transfer = jsonString(parsed, "transferUri"); + if (!transfer.empty() && trimSlashes(transfer, false, true) != (io.host.empty() ? base_ : io.host)) { + XC_LOGI("gssv: session transferred to %s", transfer.c_str()); + io.host = trimSlashes(transfer, false, true); + } + std::string state = jsonString(parsed, "state"); + if (state == "Error") state = "Failed"; + if (state != io.state) XC_LOGD("gssv: state %s -> %s", io.state.c_str(), state.c_str()); + io.state = state; + if (state == "Failed") { + extractError(r.body, io.errorCode, io.errorMessage); + if (io.errorCode.empty() && io.errorMessage.empty()) io.errorMessage = snippet(r.body); + XC_LOGE("gssv: session failed: %s %s", io.errorCode.c_str(), io.errorMessage.c_str()); + } + return true; +} + +bool GssvClient::connect(const SessionInfo& s, const std::string& lpt, std::string& err) { + if (lpt.empty()) { + err = "connect: empty user token"; + return false; + } + json body = {{"userToken", lpt}}; + HttpResponse r = Http::request("POST", sessionUrl(s, "connect"), headers(), body.dump(), kRequestTimeoutSec); + noteResponse(r); + if (!r.ok()) { + err = describeFailure("session connect", r); + return false; + } + XC_LOGI("gssv: connect accepted (HTTP %ld)", r.status); + return true; +} + +int GssvClient::keepAliveSeconds(const SessionInfo& s) { + constexpr int kDefault = 20; + HttpResponse r = Http::request("GET", sessionUrl(s, "configuration"), headers(), "", kRequestTimeoutSec); + noteResponse(r); + if (!r.ok()) { + XC_LOGW("%s", describeFailure("session configuration", r).c_str()); + return kDefault; + } + json parsed = json::parse(r.body, nullptr, false); + if (parsed.is_discarded() || !parsed.is_object()) return kDefault; + XC_LOGD("gssv: configuration %s", snippet(r.body, 600).c_str()); + auto it = parsed.find("keepAlivePulseInSeconds"); + if (it == parsed.end()) return kDefault; + double v = 0; + if (it->is_number()) v = it->get<double>(); + else if (it->is_string()) v = std::atof(it->get<std::string>().c_str()); + if (!(v > 0)) return kDefault; + // XStreaming: max(pulse, 5). + return std::max(5, static_cast<int>(v)); +} + +int GssvClient::waitTimeSeconds(const std::string& titleId) { + if (titleId.empty()) return -1; + HttpResponse r = Http::request("GET", apiUrl("v1/waittime/" + Http::urlEncode(titleId)), headers(), "", + kRequestTimeoutSec); + if (!r.ok()) { + XC_LOGD("%s", describeFailure("waittime", r).c_str()); + return -1; + } + json parsed = json::parse(r.body, nullptr, false); + if (parsed.is_discarded() || !parsed.is_object()) return -1; + for (const char* key : {"estimatedTotalWaitTimeInSeconds", "estimatedProvisioningTimeInSeconds"}) { + auto it = parsed.find(key); + if (it != parsed.end() && it->is_number()) return std::max(0, static_cast<int>(it->get<double>())); + } + return -1; +} + +bool GssvClient::sendSdpOffer(const SessionInfo& s, const std::string& sdp, std::string& err) { + HttpResponse r = + Http::request("POST", sessionUrl(s, "sdp"), headers(), proto::sdpPostBody(sdp), kRequestTimeoutSec); + noteResponse(r); + if (!r.ok()) { + err = describeFailure("sdp offer", r); + return false; + } + return true; +} + +bool GssvClient::pollSdpAnswer(const SessionInfo& s, std::string& answerSdp, bool& ready, std::string& err) { + answerSdp.clear(); + ready = false; + HttpResponse r = Http::request("GET", sessionUrl(s, "sdp"), headers(), "", kRequestTimeoutSec); + noteResponse(r); + if (!r.ok()) { + err = describeFailure("sdp poll", r); + return false; + } + if (isPending(r)) return true; + json parsed = json::parse(r.body, nullptr, false); + if (parsed.is_discarded() || !parsed.is_object()) { + err = "sdp poll: invalid JSON response"; + return false; + } + if (isRealExchangeError(parsed)) { + err = "sdp exchange: " + snippet(parsed.dump(), 400); + return false; + } + // exchangeResponse is a JSON *string* holding {"sdp":"v=0...","sdpType":"answer",...}. + json exchange; + auto it = parsed.find("exchangeResponse"); + if (it == parsed.end() || it->is_null()) return true; // not answered yet + if (it->is_string()) exchange = json::parse(it->get<std::string>(), nullptr, false); + else exchange = *it; + if (exchange.is_discarded() || !exchange.is_object()) { + err = "sdp exchange: unparsable exchangeResponse"; + return false; + } + const std::string status = jsonString(exchange, "status"); + auto sdpIt = exchange.find("sdp"); + if (sdpIt == exchange.end() || !sdpIt->is_string() || sdpIt->get<std::string>().empty()) { + err = "sdp exchange: answer has no sdp" + (status.empty() ? std::string() : " (status " + status + ")") + + ": " + snippet(exchange.dump(), 400); + return false; + } + // Keep the answer verbatim (CRLF intact) — re-serialising corrupted ice-ufrag/pwd (green-nx). + answerSdp = sdpIt->get<std::string>(); + ready = true; + return true; +} + +bool GssvClient::sendIce(const SessionInfo& s, const std::vector<std::string>& candidateLines, + const std::string& ufrag, std::string& err) { + HttpResponse r = Http::request("POST", sessionUrl(s, "ice"), headers(), proto::icePostBody(candidateLines, ufrag), + kRequestTimeoutSec); + noteResponse(r); + if (!r.ok()) { + err = describeFailure("ice send", r); + return false; + } + if (!isPending(r)) { + json parsed = json::parse(r.body, nullptr, false); + if (!parsed.is_discarded() && isRealExchangeError(parsed)) { + err = "ice send: " + snippet(parsed.dump(), 400); + return false; + } + } + return true; +} + +bool GssvClient::pollIce(const SessionInfo& s, std::vector<std::string>& remoteCandidates, bool& ready, + std::string& err) { + remoteCandidates.clear(); + ready = false; + HttpResponse r = Http::request("GET", sessionUrl(s, "ice"), headers(), "", kRequestTimeoutSec); + noteResponse(r); + if (!r.ok()) { + err = describeFailure("ice poll", r); + return false; + } + if (isPending(r)) return true; + json parsed = json::parse(r.body, nullptr, false); + if (parsed.is_discarded() || !parsed.is_object()) { + err = "ice poll: invalid JSON response"; + return false; + } + if (isRealExchangeError(parsed)) { + err = "ice exchange: " + snippet(parsed.dump(), 400); + return false; + } + auto it = parsed.find("exchangeResponse"); + if (it == parsed.end() || it->is_null()) return true; + json exchange = it->is_string() ? json::parse(it->get<std::string>(), nullptr, false) : *it; + if (exchange.is_discarded()) { + err = "ice exchange: unparsable exchangeResponse"; + return false; + } + const json* items = nullptr; + if (exchange.is_array()) items = &exchange; + else if (exchange.is_object() && exchange.contains("candidates") && exchange["candidates"].is_array()) + items = &exchange["candidates"]; + else if (exchange.is_object() && exchange.contains("candidate") && exchange["candidate"].is_array()) + items = &exchange["candidate"]; + ready = true; + if (!items) return true; + + for (const json& item : *items) { + json entry = item; + if (entry.is_string()) entry = json::parse(entry.get<std::string>(), nullptr, false); + if (entry.is_discarded() || !entry.is_object()) continue; + std::string c = jsonString(entry, "candidate"); + if (c.rfind("a=", 0) == 0) c.erase(0, 2); + if (c.find("end-of-candidates") != std::string::npos) continue; + if (c.rfind("candidate:", 0) != 0) continue; + while (!c.empty() && (c.back() == ' ' || c.back() == '\r' || c.back() == '\n')) c.pop_back(); + remoteCandidates.push_back(c); + } + XC_LOGD("gssv: %zu remote candidates", remoteCandidates.size()); + return true; +} + +bool GssvClient::keepAlive(const SessionInfo& s) { + if (s.sessionPath.empty()) return false; + HttpResponse r = Http::request("POST", sessionUrl(s, "keepalive"), headers(), "", kKeepAliveTimeoutSec); + noteResponse(r); + if (!r.ok()) { + XC_LOGW("%s", describeFailure("keepalive", r).c_str()); + return false; + } + if (!isPending(r)) { + json parsed = json::parse(r.body, nullptr, false); + if (!parsed.is_discarded() && parsed.is_object()) { + const std::string code = jsonString(parsed, "code"); + if (code == "SessionNotActive" || code == "SessionNotFound") { + XC_LOGW("gssv: keepalive: %s", code.c_str()); + return false; + } + } + } + return true; +} + +bool GssvClient::stop(const SessionInfo& s, long timeoutSec) { + if (s.sessionPath.empty()) return false; + HttpResponse r = Http::request("DELETE", sessionUrl(s, ""), headers(), "", timeoutSec > 0 ? timeoutSec : kRequestTimeoutSec); + if (r.ok() || r.status == 404 || r.status == 410) { + XC_LOGI("gssv: session %s stopped (HTTP %ld)", s.sessionPath.c_str(), r.status); + return true; + } + XC_LOGW("%s", describeFailure("session stop", r).c_str()); + return false; +} + +} // namespace xc diff --git a/src/core/gssv.hpp b/src/core/gssv.hpp new file mode 100644 index 0000000..7f0c36b --- /dev/null +++ b/src/core/gssv.hpp @@ -0,0 +1,112 @@ +// Nubix — gssv session REST API (xCloud "xgpuweb"/"xgpuwebf2p" and xHome "xhome"). +// Endpoints and bodies as used by green-nx, Greenlight and xbox-xcloud-player. +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <atomic> +#include <string> +#include <vector> + +#include "core/config.hpp" + +namespace xc { + +struct HttpResponse; + +enum class SessionKind { Cloud, Home }; + +struct SessionInfo { + std::string sessionPath; // e.g. "v5/sessions/cloud/<id>" (relative to base URI) + std::string sessionId; + std::string state; // WaitingForResources | ReadyToConnect | Provisioning | Provisioned | Failed + std::string errorCode, errorMessage; // set when state == Failed or the start request failed + // Host the session lives on. Empty = the client's base URI. Set when /state returns a + // transferUri (session migrated, XStreaming honours it); all later calls follow it. + std::string host; +}; + +// Well-known gssv error codes (SessionInfo::errorCode). +constexpr const char* kErrOfferingDoesNotContainTitle = "OfferingDoesNotContainTitle"; + +namespace gssv { +// Quality tier -> X-MS-Device-Info os.name and play settings.osName: +// "720" -> "android", "1080" -> "windows", "1080HQ" -> "tizen" (anything else -> "windows"). +const char* osNameForResolution(const std::string& resolution); +// X-MS-Device-Info JSON (green-nx 2026 fingerprint) for the given quality tier. +std::string deviceInfoJson(const std::string& resolution); +// Standard gssv request headers (Accept, Content-Type, X-Gssv-Client, X-MS-Device-Info, Bearer). +std::vector<std::string> requestHeaders(const std::string& gsToken, const std::string& resolution); +// Quality tier for gssv calls of a session kind. The xHome console agent only accepts the +// android (720) fingerprint - windows/tizen get AgentCommandError (green-nx engine.cpp, green-vita) +// - so every xhome request uses "720" whatever the user picked; cloud uses the setting. +std::string deviceTierFor(bool home, const std::string& resolution); +// True when an HTTP answer means the gsToken itself was refused: 401, or 403 whose body carries +// no gssv error code (a coded 403 such as OfferingAccessDenied is a real answer). +bool isAuthRejection(long status, const std::string& body); +} // namespace gssv + +// All methods are const-safe w.r.t. internal state and may be called from different threads +// concurrently (e.g. keepAlive() from a keepalive thread while the worker polls /ice). +class GssvClient { +public: + // baseUri: offering base (Tokens::xcloudBase etc.), gsToken: matching Bearer token. + GssvClient(const std::string& baseUri, const std::string& gsToken, const Settings& settings); + + // Stop any session still running for this account (GET /v5/sessions/{kind}/active + DELETE). + bool cleanupActive(SessionKind kind); + + // POST /v5/sessions/{cloud|home}/play. titleOrServerId = titleId (Cloud) or serverId (Home). + bool play(SessionKind kind, const std::string& titleOrServerId, SessionInfo& out, std::string& err); + + // GET {sessionPath}/state; updates io.state (+ error fields). + bool pollState(SessionInfo& io, std::string& err); + + // POST {sessionPath}/connect with the user LPT (xCloud, at ReadyToConnect). + bool connect(const SessionInfo& s, const std::string& lpt, std::string& err); + + // Seconds between keepalives (from /configuration keepAlivePulseInSeconds; default 20). + int keepAliveSeconds(const SessionInfo& s); + + // Queue wait time in seconds for a cloud title (GET /v1/waittime/{titleId}); -1 if unknown. + int waitTimeSeconds(const std::string& titleId); + + // POST {sessionPath}/sdp with proto::sdpPostBody(offer). + bool sendSdpOffer(const SessionInfo& s, const std::string& sdp, std::string& err); + + // GET {sessionPath}/sdp. ready=false while the server has not answered yet (HTTP 204). + bool pollSdpAnswer(const SessionInfo& s, std::string& answerSdp, bool& ready, std::string& err); + + // POST {sessionPath}/ice with proto::icePostBody(candidateLines, ufrag). + bool sendIce(const SessionInfo& s, const std::vector<std::string>& candidateLines, const std::string& ufrag, + std::string& err); + + // GET {sessionPath}/ice. remoteCandidates = "candidate:..." lines (Teredo not yet expanded). + bool pollIce(const SessionInfo& s, std::vector<std::string>& remoteCandidates, bool& ready, std::string& err); + + // POST {sessionPath}/keepalive. + bool keepAlive(const SessionInfo& s); + + // DELETE {sessionPath}. timeoutSec bounds the whole request (shutdown uses a short one). + bool stop(const SessionInfo& s, long timeoutSec = 20); + + // True once any request of this client was rejected as unauthorized (isAuthRejection): + // the gsToken is no longer accepted and must be re-authorized (Auth::invalidateStreaming). + bool authRejected() const { return authRejected_.load(); } + +private: + // Absolute URL for a session sub-resource ("" = the session itself, "state", "sdp", ...). + std::string sessionUrl(const SessionInfo& s, const std::string& suffix) const; + // Absolute URL on the base host for an API path ("v5/sessions/cloud/play"). + std::string apiUrl(const std::string& path) const; + std::vector<std::string> headers() const; + // Records an auth rejection (see authRejected()). + void noteResponse(const HttpResponse& r); + + std::string base_, token_; + Settings settings_; + std::atomic<bool> authRejected_{false}; +}; + +} // namespace xc diff --git a/src/core/http.cpp b/src/core/http.cpp new file mode 100644 index 0000000..fb07dc5 --- /dev/null +++ b/src/core/http.cpp @@ -0,0 +1,381 @@ +// Nubix — minimal blocking HTTPS client (libcurl + OpenSSL). +// Request setup follows green-nx src/core/http.cpp (GPL-3.0, error-buffer diagnostics, one TLS +// connection per transfer) adapted to a stateless, thread-safe API. +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "core/http.hpp" + +#include <curl/curl.h> +#include <sys/stat.h> + +#include <algorithm> +#include <cctype> +#include <chrono> +#include <ctime> +#include <mutex> +#include <string> +#include <thread> + +#include "core/http_internal.hpp" +#include "core/log.hpp" +#include "core/ratelimit.hpp" + +namespace xc { +namespace { + +std::mutex g_mutex; // guards g_caBundle, g_forwardedFor, g_inited +std::string g_caBundle; +std::string g_forwardedFor; +bool g_inited = false; + +thread_local bool t_quiet = false; +thread_local const std::atomic<bool>* t_abort = nullptr; + +// CURLOPT_XFERINFOFUNCTION: non-zero aborts the transfer (CURLE_ABORTED_BY_CALLBACK). +int xferInfoCb(void* clientp, curl_off_t, curl_off_t, curl_off_t, curl_off_t) { + const auto* flag = static_cast<const std::atomic<bool>*>(clientp); + return (flag && flag->load()) ? 1 : 0; +} + +constexpr size_t kMaxBody = 64u * 1024u * 1024u; // refuse absurd responses (memory on PS5) + +struct Transfer { + HttpResponse* resp; + bool overflow = false; +}; + +size_t writeCb(char* ptr, size_t size, size_t nmemb, void* userdata) { + auto* t = static_cast<Transfer*>(userdata); + const size_t n = size * nmemb; + if (t->resp->body.size() + n > kMaxBody) { + t->overflow = true; + return 0; // aborts with CURLE_WRITE_ERROR + } + t->resp->body.append(ptr, n); + return n; +} + +std::string trim(const std::string& s) { + size_t b = 0, e = s.size(); + while (b < e && (s[b] == ' ' || s[b] == '\t')) ++b; + while (e > b && (s[e - 1] == ' ' || s[e - 1] == '\t' || s[e - 1] == '\r' || s[e - 1] == '\n')) --e; + return s.substr(b, e - b); +} + +size_t headerCb(char* buf, size_t size, size_t nitems, void* userdata) { + auto* t = static_cast<Transfer*>(userdata); + const size_t n = size * nitems; + std::string line(buf, n); + // A new status line starts a new response (redirect / 100-continue): drop older headers. + if (line.compare(0, 5, "HTTP/") == 0) { + t->resp->headers.clear(); + return n; + } + const size_t colon = line.find(':'); + if (colon == std::string::npos) return n; + std::string key = trim(line.substr(0, colon)); + std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); }); + std::string value = trim(line.substr(colon + 1)); + auto it = t->resp->headers.find(key); + if (it == t->resp->headers.end()) t->resp->headers.emplace(std::move(key), std::move(value)); + else it->second += ", " + value; + return n; +} + +// URL without query string, for logs (queries may carry ids we do not need in log files). +std::string logUrl(const std::string& url) { + const size_t q = url.find('?'); + return q == std::string::npos ? url : url.substr(0, q) + "?…"; +} + +std::string snippet(const std::string& body) { + std::string s = body.substr(0, 300); + for (char& c : s) + if (c == '\n' || c == '\r' || c == '\t') c = ' '; + if (body.size() > 300) s += "…"; + return s; +} + +bool hasHeader(const std::vector<std::string>& headers, const char* name) { + const size_t len = std::char_traits<char>::length(name); + for (const auto& h : headers) { + if (h.size() <= len || h[len] != ':') continue; + bool eq = true; + for (size_t i = 0; i < len && eq; ++i) + eq = std::tolower(static_cast<unsigned char>(h[i])) == std::tolower(static_cast<unsigned char>(name[i])); + if (eq) return true; + } + return false; +} + +} // namespace + +namespace httpdetail { +QuietErrors::QuietErrors() : prev_(t_quiet) { t_quiet = true; } +QuietErrors::~QuietErrors() { t_quiet = prev_; } +} // namespace httpdetail + +void Http::globalInit(const std::string& caBundlePath) { + std::lock_guard<std::mutex> lk(g_mutex); + if (!g_inited) { + CURLcode rc = curl_global_init(CURL_GLOBAL_DEFAULT); + if (rc != CURLE_OK) XC_LOGE("http: curl_global_init failed: %s", curl_easy_strerror(rc)); + g_inited = true; + const curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); + if (v) XC_LOGI("http: libcurl %s (%s)", v->version, v->ssl_version ? v->ssl_version : "no TLS"); + } + g_caBundle.clear(); + if (!caBundlePath.empty()) { + struct stat st {}; + if (::stat(caBundlePath.c_str(), &st) == 0 && st.st_size > 0) { + g_caBundle = caBundlePath; + XC_LOGD("http: CA bundle %s (%lld bytes)", caBundlePath.c_str(), (long long)st.st_size); + } else { + XC_LOGW("http: CA bundle %s not found; using libcurl's built-in CA path", caBundlePath.c_str()); + } + } +} + +void Http::globalCleanup() { + std::lock_guard<std::mutex> lk(g_mutex); + if (g_inited) { + curl_global_cleanup(); + g_inited = false; + } +} + +const std::atomic<bool>* Http::setThreadAbortFlag(const std::atomic<bool>* flag) { + const std::atomic<bool>* prev = t_abort; + t_abort = flag; + return prev; +} + +const std::atomic<bool>* Http::threadAbortFlag() { return t_abort; } + +void Http::setForwardedFor(const std::string& ip) { + std::lock_guard<std::mutex> lk(g_mutex); + g_forwardedFor = ip; +} + +namespace { +HttpResponse performOnce(const std::string& method, const std::string& url, + const std::vector<std::string>& headers, const std::string& body, long timeoutSec) { + HttpResponse resp; + std::string caBundle, forwardedFor; + { + std::lock_guard<std::mutex> lk(g_mutex); + if (!g_inited) { + // Be forgiving if a caller forgot globalInit(); curl_global_init is not thread-safe + // on old libcurl, hence under the mutex. + curl_global_init(CURL_GLOBAL_DEFAULT); + g_inited = true; + } + caBundle = g_caBundle; + forwardedFor = g_forwardedFor; + } + + const std::atomic<bool>* abortFlag = t_abort; + if (abortFlag && abortFlag->load()) { + resp.error = "aborted"; + XC_LOGD("http: %s %s skipped: aborted", method.c_str(), logUrl(url).c_str()); + return resp; + } + + CURL* curl = curl_easy_init(); + if (!curl) { + resp.error = "curl_easy_init failed"; + XC_LOGE("http: %s %s: %s", method.c_str(), logUrl(url).c_str(), resp.error.c_str()); + return resp; + } + + // curl_easy_strerror() only names the error class; the TLS backend's actual reason goes to + // the error buffer (green-nx). + char errBuf[CURL_ERROR_SIZE] = {0}; + Transfer xfer{&resp}; + + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errBuf); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); // required for multi-threaded use + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &xfer); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerCb); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &xfer); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L); + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); // all built-in encodings (gzip, deflate, ...) + const long total = timeoutSec > 0 ? timeoutSec : 20L; + curl_easy_setopt(curl, CURLOPT_TIMEOUT, total); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, std::min(total, 10L)); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); +#if LIBCURL_VERSION_NUM >= 0x075500 // 7.85.0 + curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, "http,https"); + curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "https"); +#endif + // One TLS connection per transfer, closed afterwards (green-nx: consoles have few TLS sessions). + curl_easy_setopt(curl, CURLOPT_MAXCONNECTS, 1L); + curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1L); + if (!caBundle.empty()) curl_easy_setopt(curl, CURLOPT_CAINFO, caBundle.c_str()); + if (abortFlag) { + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferInfoCb); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, const_cast<std::atomic<bool>*>(abortFlag)); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + } + + if (method == "GET") { + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + } else if (method == "POST") { + // Always send a body (possibly empty, Content-Length: 0) — gssv keepalive posts nothing. + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(body.size())); + } else { + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method.c_str()); + if (!body.empty() || method == "PUT" || method == "PATCH") { + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(body.size())); + } + } + + curl_slist* list = nullptr; + if (!forwardedFor.empty()) list = curl_slist_append(list, ("X-Forwarded-For: " + forwardedFor).c_str()); + if (!hasHeader(headers, "User-Agent")) + list = curl_slist_append(list, "User-Agent: nubix/" XC_VERSION); + if (method == "POST" && !hasHeader(headers, "Expect")) + list = curl_slist_append(list, "Expect:"); // no 100-continue round trip + for (const auto& h : headers) list = curl_slist_append(list, h.c_str()); + if (list) curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + + const auto t0 = std::chrono::steady_clock::now(); + const CURLcode code = curl_easy_perform(curl); + const long ms = static_cast<long>( + std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - t0).count()); + + if (code == CURLE_ABORTED_BY_CALLBACK && abortFlag && abortFlag->load()) { + resp.error = "aborted"; + resp.status = 0; + XC_LOGD("http: %s %s aborted after %ld ms", method.c_str(), logUrl(url).c_str(), ms); + } else if (code != CURLE_OK) { + std::string msg = curl_easy_strerror(code); + if (xfer.overflow) msg += " [response too large]"; + else if (errBuf[0]) msg += std::string(" [") + errBuf + "]"; + if (code == CURLE_PEER_FAILED_VERIFICATION || code == CURLE_SSL_CONNECT_ERROR || + code == CURLE_SSL_CACERT_BADFILE) { + // Name the address actually reached: a DNS block presenting its own certificate reads + // as "via 127.0.0.1" instead of a guessing game (green-nx #58). + char* ip = nullptr; + curl_easy_getinfo(curl, CURLINFO_PRIMARY_IP, &ip); + if (ip && *ip) msg += std::string(" via ") + ip; + } + long osErrno = 0; + curl_easy_getinfo(curl, CURLINFO_OS_ERRNO, &osErrno); + if (osErrno) msg += " errno=" + std::to_string(osErrno); + resp.error = msg; + resp.status = 0; + XC_LOGW("http: %s %s failed after %ld ms: %s", method.c_str(), logUrl(url).c_str(), ms, msg.c_str()); + } else { + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &resp.status); + if (resp.status >= 200 && resp.status < 300) { + XC_LOGD("http: %s %s -> %ld (%zu B, %ld ms)", method.c_str(), logUrl(url).c_str(), resp.status, + resp.body.size(), ms); + } else if (t_quiet) { + XC_LOGD("http: %s %s -> %ld (%ld ms): %s", method.c_str(), logUrl(url).c_str(), resp.status, ms, + snippet(resp.body).c_str()); + } else { + XC_LOGW("http: %s %s -> HTTP %ld (%ld ms): %s", method.c_str(), logUrl(url).c_str(), resp.status, ms, + snippet(resp.body).c_str()); + } + } + + if (list) curl_slist_free_all(list); + curl_easy_cleanup(curl); + return resp; +} + +// Abortable sleep in 100 ms slices. False when the thread's abort flag was raised. +bool pacedSleep(int64_t ms, const std::atomic<bool>* abortFlag) { + const int64_t until = steadyNowMs() + ms; + for (;;) { + if (abortFlag && abortFlag->load()) return false; + const int64_t left = until - steadyNowMs(); + if (left <= 0) return true; + std::this_thread::sleep_for(std::chrono::milliseconds(std::min<int64_t>(left, 100))); + } +} +} // namespace + +// Pacing wrapper around performOnce (see ratelimit.hpp): +// * every request takes a token from its host's bucket (waits briefly when the bucket is empty); +// * a host answering 429 (or 503 + Retry-After) is put into cooldown; requests to it wait the +// cooldown out when it is short, or fail fast ("rate limited") when it is long, instead of +// hammering the server; +// * idempotent requests (GET/DELETE) are retried after a short Retry-After, at most twice. +// POSTs (session start, SDP, ICE, keepalive, token polls) are never repeated here. +HttpResponse Http::request(const std::string& method, const std::string& url, + const std::vector<std::string>& headers, const std::string& body, + long timeoutSec) { + constexpr int64_t kMaxCooldownWaitMs = 15000; // wait out shorter cooldowns, fail on longer ones + constexpr int kMaxRetries = 2; + const std::atomic<bool>* abortFlag = t_abort; + const std::string host = urlHost(url); + const bool idempotent = method == "GET" || method == "DELETE" || method == "HEAD"; + RateLimiter& limiter = RateLimiter::global(); + + for (int attempt = 0;; ++attempt) { + for (;;) { + bool cooldown = false; + const int64_t wait = limiter.reserve(host, steadyNowMs(), &cooldown); + if (wait <= 0) break; + if (cooldown && wait > kMaxCooldownWaitMs) { + HttpResponse r; + const int sec = static_cast<int>((wait + 999) / 1000); + r.status = 429; + r.headers["retry-after"] = std::to_string(sec); + r.error = "rate limited by the server, retry in " + std::to_string(sec) + " s"; + XC_LOGW("http: %s %s not sent: %s is cooling down for %d s", method.c_str(), logUrl(url).c_str(), + host.c_str(), sec); + return r; + } + if (cooldown) + XC_LOGI("http: waiting %lld ms for %s (rate limit cooldown)", static_cast<long long>(wait), host.c_str()); + if (!pacedSleep(wait, abortFlag)) { + HttpResponse r; + r.error = "aborted"; + return r; + } + } + + HttpResponse resp = performOnce(method, url, headers, body, timeoutSec); + int retryAfter = -1; + auto it = resp.headers.find("retry-after"); + if (it != resp.headers.end()) retryAfter = parseRetryAfter(it->second, static_cast<int64_t>(std::time(nullptr))); + limiter.onResponse(host, resp.status, retryAfter, steadyNowMs()); + + const bool limited = resp.status == 429 || (resp.status == 503 && retryAfter >= 0); + if (!limited) return resp; + const int64_t cool = limiter.cooldownRemaining(host, steadyNowMs()); + XC_LOGW("http: %s rate limited (HTTP %ld), backing off %lld ms", host.c_str(), resp.status, + static_cast<long long>(cool)); + if (!idempotent || attempt >= kMaxRetries || cool > kMaxCooldownWaitMs) return resp; + // The cooldown loop above waits before the retry. + } +} + +std::string Http::urlEncode(const std::string& s) { + static const char* hex = "0123456789ABCDEF"; + std::string out; + out.reserve(s.size() * 3); + for (unsigned char c : s) { + const bool alnum = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); + if (alnum || c == '-' || c == '_' || c == '.' || c == '~') { + out += static_cast<char>(c); + } else { + out += '%'; + out += hex[c >> 4]; + out += hex[c & 0x0F]; + } + } + return out; +} + +} // namespace xc diff --git a/src/core/http.hpp b/src/core/http.hpp new file mode 100644 index 0000000..8b0b26b --- /dev/null +++ b/src/core/http.hpp @@ -0,0 +1,68 @@ +// Nubix — minimal blocking HTTPS client (libcurl + OpenSSL). +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <atomic> +#include <map> +#include <string> +#include <vector> + +namespace xc { + +struct HttpResponse { + long status = 0; // HTTP status code (0 on transport error) + std::string body; // response body + std::map<std::string, std::string> headers; // response headers, keys lower-cased + std::string error; // transport error (curl), empty on success + // True when the transfer succeeded and the status is 2xx. + bool ok() const { return error.empty() && status >= 200 && status < 300; } +}; + +class Http { +public: + // Call once at startup (curl_global_init). caBundlePath is used as CURLOPT_CAINFO for + // every request (assets/cacert.pem); empty = curl's built-in default. + static void globalInit(const std::string& caBundlePath); + + // Call once at shutdown (curl_global_cleanup). + static void globalCleanup(); + + // Perform a blocking request. method: GET/POST/PUT/DELETE. headers: "Name: value" lines. + // Follows redirects, accepts gzip, thread-safe (one easy handle per call). + static HttpResponse request(const std::string& method, const std::string& url, + const std::vector<std::string>& headers, const std::string& body = "", + long timeoutSec = 20); + + // Optional "X-Forwarded-For: <ip>" added to every request (geo/region override used by + // greenlight/XStreaming for unsupported countries). Empty = off. Thread-safe. + static void setForwardedFor(const std::string& ip); + + // Per-thread cancellation (green-nx style): while a flag is installed on the calling thread, + // requests made on that thread fail fast with error "aborted" once *flag becomes true + // (checked before the transfer and from curl's progress callback, i.e. within ~1 s). + // nullptr removes it. Returns the previously installed flag. The flag must outlive its use. + static const std::atomic<bool>* setThreadAbortFlag(const std::atomic<bool>* flag); + // Flag currently installed on the calling thread (nullptr = none). Lets helper threads + // spawned for one job inherit its cancellation. + static const std::atomic<bool>* threadAbortFlag(); + + // RAII: install `flag` (may be nullptr to temporarily disable cancellation, e.g. for a + // mandatory cleanup DELETE) for the current scope, restoring the previous one afterwards. + class AbortScope { + public: + explicit AbortScope(const std::atomic<bool>* flag) : prev_(setThreadAbortFlag(flag)) {} + ~AbortScope() { setThreadAbortFlag(prev_); } + AbortScope(const AbortScope&) = delete; + AbortScope& operator=(const AbortScope&) = delete; + + private: + const std::atomic<bool>* prev_; + }; + + // Percent-encode a string for application/x-www-form-urlencoded bodies / query strings. + static std::string urlEncode(const std::string& s); +}; + +} // namespace xc diff --git a/src/core/http_internal.hpp b/src/core/http_internal.hpp new file mode 100644 index 0000000..2f45419 --- /dev/null +++ b/src/core/http_internal.hpp @@ -0,0 +1,23 @@ +// Nubix — internal helpers of the Http module (used only by core/http.cpp and core/auth.cpp). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +namespace xc { +namespace httpdetail { + +// While alive, non-2xx responses on the current thread are logged at Debug instead of Warn. +// Used for expected errors such as the 400 "authorization_pending" device-code poll replies. +class QuietErrors { +public: + QuietErrors(); + ~QuietErrors(); + QuietErrors(const QuietErrors&) = delete; + QuietErrors& operator=(const QuietErrors&) = delete; + +private: + bool prev_; +}; + +} // namespace httpdetail +} // namespace xc diff --git a/src/core/log.cpp b/src/core/log.cpp new file mode 100644 index 0000000..4bac50e --- /dev/null +++ b/src/core/log.cpp @@ -0,0 +1,108 @@ +// Nubix — logging. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "core/log.hpp" + +#include <chrono> +#include <cstdio> +#include <ctime> +#include <mutex> + +#include <sys/stat.h> + +namespace xc { +namespace { + +// log.txt lives on the console's /data partition: once it grows past this it is moved to +// log.prev.txt at the next start (one generation kept), so the file never grows unbounded. +constexpr long long kMaxLogBytes = 2LL * 1024 * 1024; + +std::string previousLogPath(const std::string& path) { + const size_t slash = path.find_last_of('/'); + const size_t dot = path.find_last_of('.'); + if (dot != std::string::npos && (slash == std::string::npos || dot > slash)) + return path.substr(0, dot) + ".prev" + path.substr(dot); + return path + ".prev"; +} + +std::mutex g_mutex; +FILE* g_file = nullptr; +LogLevel g_level = LogLevel::Info; +const auto g_start = std::chrono::steady_clock::now(); + +const char* tag(LogLevel l) { + switch (l) { + case LogLevel::Debug: return "D"; + case LogLevel::Info: return "I"; + case LogLevel::Warn: return "W"; + case LogLevel::Error: return "E"; + } + return "?"; +} + +} // namespace + +void logInit(const std::string& filePath, LogLevel minLevel) { + std::lock_guard<std::mutex> lk(g_mutex); + g_level = minLevel; + if (g_file) { + std::fclose(g_file); + g_file = nullptr; + } + if (!filePath.empty()) { + struct stat st{}; + if (::stat(filePath.c_str(), &st) == 0 && static_cast<long long>(st.st_size) > kMaxLogBytes) { + const std::string prev = previousLogPath(filePath); + std::remove(prev.c_str()); + if (std::rename(filePath.c_str(), prev.c_str()) != 0) std::remove(filePath.c_str()); + } + g_file = std::fopen(filePath.c_str(), "a"); + if (g_file) std::setvbuf(g_file, nullptr, _IOLBF, 0); + } +} + +void logSetLevel(LogLevel minLevel) { + std::lock_guard<std::mutex> lk(g_mutex); + g_level = minLevel; +} + +LogLevel logLevel() { + std::lock_guard<std::mutex> lk(g_mutex); + return g_level; +} + +void logv(LogLevel level, const char* fmt, va_list ap) { + char msg[2048]; + std::vsnprintf(msg, sizeof(msg), fmt, ap); + const double t = std::chrono::duration<double>(std::chrono::steady_clock::now() - g_start).count(); + + std::lock_guard<std::mutex> lk(g_mutex); + if (level < g_level) return; + std::fprintf(stdout, "[%9.3f] %s %s\n", t, tag(level), msg); + std::fflush(stdout); + if (g_file) { + std::time_t now = std::time(nullptr); + char ts[32]; + std::tm tmv{}; + localtime_r(&now, &tmv); + std::strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", &tmv); + std::fprintf(g_file, "%s [%9.3f] %s %s\n", ts, t, tag(level), msg); + } +} + +void log(LogLevel level, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + logv(level, fmt, ap); + va_end(ap); +} + +void logShutdown() { + std::lock_guard<std::mutex> lk(g_mutex); + if (g_file) { + std::fclose(g_file); + g_file = nullptr; + } +} + +} // namespace xc diff --git a/src/core/log.hpp b/src/core/log.hpp new file mode 100644 index 0000000..bb0ee15 --- /dev/null +++ b/src/core/log.hpp @@ -0,0 +1,41 @@ +// Nubix — logging. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstdarg> +#include <string> + +namespace xc { + +enum class LogLevel { Debug = 0, Info = 1, Warn = 2, Error = 3 }; + +// Initialise logging. Messages >= minLevel go to stdout; if filePath is non-empty they are +// also appended to that file (used on PS5: <dataDir>/log.txt). Safe to call more than once. +void logInit(const std::string& filePath, LogLevel minLevel = LogLevel::Info); + +// Change the minimum level at runtime (e.g. --verbose). +void logSetLevel(LogLevel minLevel); + +// Current minimum level. +LogLevel logLevel(); + +// printf-style log line, thread-safe; a timestamp, level tag and newline are added. +void log(LogLevel level, const char* fmt, ...) +#if defined(__GNUC__) || defined(__clang__) + __attribute__((format(printf, 2, 3))) +#endif + ; + +// va_list variant of log(). +void logv(LogLevel level, const char* fmt, va_list ap); + +// Flush and close the log file. +void logShutdown(); + +} // namespace xc + +#define XC_LOGD(...) ::xc::log(::xc::LogLevel::Debug, __VA_ARGS__) +#define XC_LOGI(...) ::xc::log(::xc::LogLevel::Info, __VA_ARGS__) +#define XC_LOGW(...) ::xc::log(::xc::LogLevel::Warn, __VA_ARGS__) +#define XC_LOGE(...) ::xc::log(::xc::LogLevel::Error, __VA_ARGS__) diff --git a/src/core/protocol.cpp b/src/core/protocol.cpp new file mode 100644 index 0000000..d172046 --- /dev/null +++ b/src/core/protocol.cpp @@ -0,0 +1,490 @@ +// Nubix — xCloud wire protocol helpers. +// +// Input packet layout, control/message JSON and the /sdp + /ice bodies are ported from +// green-nx (GPL-3.0): src/core/xcloud_protocol.cpp and +// src/core/session.cpp. The offer template combines GreenOvercast's buildCompatibleOffer +// (src/session/webrtc_session.zig, MPL-2.0; session header + data-channel m-line, proven with +// libdatachannel) with green-nx's media m-lines (deps/patches/libpeer-switch.patch, +// sdp_append_h264/sdp_append_opus + session.cpp sdp_scale_video_caps_1080). +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Portions derived from GreenOvercast (https://github.com/Producdevity/GreenOvercast), MPL-2.0, used here under GPL-3.0 (MPL-2.0 section 3.3). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "core/protocol.hpp" + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> + +#include <cstdio> +#include <cstring> +#include <mutex> +#include <random> +#include <sstream> + +#include <nlohmann/json.hpp> + +namespace xc { +namespace proto { + +using nlohmann::json; + +// Serialize without ever throwing: invalid UTF-8 (e.g. from a hostile server payload echoed +// back) is replaced with U+FFFD instead of raising json::type_error. +constexpr json::error_handler_t kReplace = json::error_handler_t::replace; + +namespace { + +// ---- little-endian writers (explicit byte order, independent of host endianness) ---------- + +void putU8(std::vector<uint8_t>& out, uint8_t v) { out.push_back(v); } + +void putU16(std::vector<uint8_t>& out, uint16_t v) { + out.push_back(uint8_t(v & 0xFF)); + out.push_back(uint8_t(v >> 8)); +} + +void putU32(std::vector<uint8_t>& out, uint32_t v) { + for (int i = 0; i < 4; ++i) out.push_back(uint8_t((v >> (8 * i)) & 0xFF)); +} + +void putU32BE(std::vector<uint8_t>& out, uint32_t v) { + for (int i = 3; i >= 0; --i) out.push_back(uint8_t((v >> (8 * i)) & 0xFF)); +} + +void putF64(std::vector<uint8_t>& out, double v) { + uint64_t bits; + static_assert(sizeof(bits) == sizeof(v), "double must be 64-bit"); + std::memcpy(&bits, &v, sizeof(bits)); + for (int i = 0; i < 8; ++i) out.push_back(uint8_t((bits >> (8 * i)) & 0xFF)); +} + +uint16_t getU16(const uint8_t* p) { return uint16_t(p[0] | (uint16_t(p[1]) << 8)); } + +uint32_t getU32(const uint8_t* p) { + return uint32_t(p[0]) | (uint32_t(p[1]) << 8) | (uint32_t(p[2]) << 16) | (uint32_t(p[3]) << 24); +} + +// ---- string helpers ----------------------------------------------------------------------- + +std::string trimmed(const std::string& s) { + size_t b = 0, e = s.size(); + while (b < e && (s[b] == ' ' || s[b] == '\t' || s[b] == '\r' || s[b] == '\n')) ++b; + while (e > b && (s[e - 1] == ' ' || s[e - 1] == '\t' || s[e - 1] == '\r' || s[e - 1] == '\n')) --e; + return s.substr(b, e - b); +} + +std::vector<std::string> splitWs(const std::string& s) { + std::istringstream is(s); + std::vector<std::string> out; + std::string tok; + while (is >> tok) out.push_back(tok); + return out; +} + +bool startsWith(const std::string& s, const char* prefix) { return s.rfind(prefix, 0) == 0; } + +bool iequals(const std::string& a, const char* b) { + size_t n = std::strlen(b); + if (a.size() != n) return false; + for (size_t i = 0; i < n; ++i) { + char x = a[i], y = b[i]; + if (x >= 'A' && x <= 'Z') x = char(x - 'A' + 'a'); + if (y >= 'A' && y <= 'Z') y = char(y - 'A' + 'a'); + if (x != y) return false; + } + return true; +} + +} // namespace + +// ============================================================================================ +// Input channel +// ============================================================================================ + +InputSerializer::InputSerializer() : start_(std::chrono::steady_clock::now()) {} + +void InputSerializer::reset() { + seq_ = 0; + start_ = std::chrono::steady_clock::now(); +} + +double InputSerializer::elapsedMs() const { + auto d = std::chrono::steady_clock::now() - start_; + double ms = std::chrono::duration<double, std::milli>(d).count(); + return ms < 0.0 ? 0.0 : ms; +} + +std::vector<uint8_t> InputSerializer::clientMetadata(uint8_t maxTouch) { + std::vector<uint8_t> out; + out.reserve(kClientMetadataPacketSize); + putU16(out, report::ClientMetadata); + putU32(out, ++seq_); + putF64(out, elapsedMs()); + putU8(out, maxTouch); + return out; +} + +std::vector<uint8_t> InputSerializer::gamepad(const GamepadState& st, uint8_t index) { + std::vector<uint8_t> out; + out.reserve(kGamepadPacketSize); + // 14-byte header + putU16(out, report::Gamepad); + putU32(out, ++seq_); + putF64(out, elapsedMs()); + // Gamepad section: u8 count, then one 23-byte frame (offsets relative to the frame start). + putU8(out, 1); + putU8(out, index); // +0 gamepadIndex + putU16(out, st.buttons); // +1 buttons + putU16(out, uint16_t(st.lx)); // +3 LeftThumbX + putU16(out, uint16_t(st.ly)); // +5 LeftThumbY (Xbox convention, up = +) + putU16(out, uint16_t(st.rx)); // +7 RightThumbX + putU16(out, uint16_t(st.ry)); // +9 RightThumbY + putU16(out, st.lt); // +11 LeftTrigger + putU16(out, st.rt); // +13 RightTrigger + putU32(out, 1); // +15 PhysicalPhysicality, little-endian 1 + putU32BE(out, 1); // +19 VirtualPhysicality, big-endian 1 (packet.ts setUint32(.., 1, false)) + return out; +} + +void InputSerializer::rollback() { + if (seq_ > 0) --seq_; +} + +// ============================================================================================ +// Control / message channel JSON +// ============================================================================================ + +std::string newUuid() { + static std::mutex m; + static std::mt19937_64 rng = [] { + std::vector<uint32_t> seed; + try { + std::random_device rd; // arc4random on FreeBSD/PS5 libc++, /dev/urandom elsewhere + for (int i = 0; i < 8; ++i) seed.push_back(rd()); + } catch (...) { + // no entropy source: fall back to clocks + addresses (uniqueness, not secrecy, matters) + } + const auto now = uint64_t(std::chrono::steady_clock::now().time_since_epoch().count()); + const auto wall = uint64_t(std::chrono::system_clock::now().time_since_epoch().count()); + const auto addr = uint64_t(reinterpret_cast<uintptr_t>(&seed)); + for (uint64_t v : {now, wall, addr}) { + seed.push_back(uint32_t(v)); + seed.push_back(uint32_t(v >> 32)); + } + std::seed_seq seq(seed.begin(), seed.end()); + return std::mt19937_64(seq); + }(); + uint8_t b[16]; + { + std::lock_guard<std::mutex> lock(m); + uint64_t hi = rng(), lo = rng(); + for (int i = 0; i < 8; ++i) { + b[i] = uint8_t(hi >> (8 * i)); + b[8 + i] = uint8_t(lo >> (8 * i)); + } + } + b[6] = uint8_t((b[6] & 0x0F) | 0x40); // version 4 + b[8] = uint8_t((b[8] & 0x3F) | 0x80); // RFC 4122 variant + char s[37]; + std::snprintf(s, sizeof(s), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", b[0], b[1], + b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); + return std::string(s); +} + +std::string messageHandshake() { + return json{{"type", "Handshake"}, {"version", "messageV1"}, {"id", newUuid()}, {"cv", "0"}}.dump(-1, ' ', false, kReplace); +} + +std::string controlAuthorization() { + return json{{"message", "authorizationRequest"}, {"accessKey", kControlAccessKey}}.dump(-1, ' ', false, kReplace); +} + +std::string controlGamepadChanged(int idx, bool added) { + return json{{"message", "gamepadChanged"}, {"gamepadIndex", idx}, {"wasAdded", added}}.dump(-1, ' ', false, kReplace); +} + +std::string controlResolution(const std::string& alias) { + return json{{"message", "userRequestedResolutionUpdate"}, {"resolutionAlias", alias}}.dump(-1, ' ', false, kReplace); +} + +std::string controlKeyframeRequest() { + return json{{"message", "videoKeyframeRequested"}, {"ifrRequested", true}}.dump(-1, ' ', false, kReplace); +} + +std::string messageEnvelope(const std::string& target, const std::string& contentJson) { + return json{{"type", "Message"}, {"content", contentJson}, {"id", newUuid()}, {"target", target}, {"cv", ""}} + .dump(-1, ' ', false, kReplace); +} + +std::vector<std::string> messageInitSequence(const Settings& s, const std::string& installId, int w, int h) { + const int fps = 60; + const int kbps = s.bitrateKbps > 0 ? s.bitrateKbps : 12000; + const std::string install = installId.empty() ? newUuid() : installId; + std::vector<std::string> out; + out.push_back(messageEnvelope("/streaming/systemUi/configuration", + json{{"version", {0, 2, 0}}, {"systemUis", json::array()}}.dump(-1, ' ', false, kReplace))); + out.push_back(messageEnvelope("/streaming/properties/clientappinstallidchanged", + json{{"clientAppInstallId", install}}.dump(-1, ' ', false, kReplace))); + out.push_back(messageEnvelope("/streaming/characteristics/orientationchanged", json{{"orientation", 0}}.dump(-1, ' ', false, kReplace))); + out.push_back(messageEnvelope("/streaming/characteristics/touchinputenabledchanged", + json{{"touchInputEnabled", false}}.dump(-1, ' ', false, kReplace))); + out.push_back(messageEnvelope("/streaming/characteristics/clientdevicecapabilities", + json{{"supportsCustomResolution", true}, + {"supportsHevc", false}, + {"supportsHdr", false}, + {"supportsFps", fps}, + {"maxWidth", w}, + {"maxHeight", h}, + {"maxBitrateKbps", kbps}, + {"video", + {{"width", w}, + {"height", h}, + {"maxWidth", w}, + {"maxHeight", h}, + {"maxBitrateKbps", kbps}}}} + .dump(-1, ' ', false, kReplace))); + out.push_back(messageEnvelope("/streaming/characteristics/dimensionschanged", + json{{"horizontal", w}, + {"vertical", h}, + {"preferredWidth", w}, + {"preferredHeight", h}, + {"safeAreaLeft", 0}, + {"safeAreaTop", 0}, + {"safeAreaRight", w}, + {"safeAreaBottom", h}, + {"supportsCustomResolution", true}} + .dump(-1, ' ', false, kReplace))); + return out; +} + +std::string transactionComplete(const std::string& id, const std::string& contentJson, const std::string& cv) { + return json{{"type", "TransactionComplete"}, {"id", id}, {"content", contentJson}, {"cv", cv}}.dump(-1, ' ', false, kReplace); +} + +std::string transactionComplete(const std::string& id, const std::string& contentJson) { + return transactionComplete(id, contentJson, ""); +} + +std::string messageUnhandled(const std::string& id, const std::string& target, const std::string& cv) { + return json{{"type", "Unhandled"}, {"id", id}, {"target", target}, {"cv", cv}}.dump(-1, ' ', false, kReplace); +} + +bool parseChannelMessage(const std::string& text, ChannelMessage& out) { + out = ChannelMessage{}; + json j = json::parse(text, nullptr, false); + if (j.is_discarded() || !j.is_object()) return false; + auto it = j.find("type"); + if (it == j.end() || !it->is_string()) return false; + out.type = it->get<std::string>(); + auto str = [&](const char* key) -> std::string { + auto f = j.find(key); + if (f == j.end() || f->is_null()) return std::string(); + if (f->is_string()) return f->get<std::string>(); + return f->dump(); + }; + out.id = str("id"); + out.target = str("target"); + out.content = str("content"); + out.cv = str("cv"); + return true; +} + +bool isHandshakeAck(const std::string& text) { + ChannelMessage m; + return parseChannelMessage(text, m) && m.type == "HandshakeAck"; +} + +// ============================================================================================ +// Server -> client input packets +// ============================================================================================ + +bool parseVibration(const uint8_t* d, size_t n, Vibration& out) { + if (!d || n < 2) return false; + const uint16_t type = getU16(d); + if (!(type & report::Vibration)) return false; + size_t off = 2; + if (type & report::ServerMetadata) off += 8; // sections follow in bit order + if (n < off + 11) return false; + const uint8_t* p = d + off; + // p[0] = rumbleType (0 = FourMotorRumble), ignored like every reference client. + out.pad = p[1]; + out.left = p[2]; + out.right = p[3]; + out.lt = p[4]; + out.rt = p[5]; + out.durationMs = getU16(p + 6); + out.delayMs = getU16(p + 8); + out.repeat = p[10]; + return true; +} + +bool parseServerMetadata(const uint8_t* d, size_t n, uint32_t& width, uint32_t& height) { + width = height = 0; + if (!d || n < 10) return false; + if (!(getU16(d) & report::ServerMetadata)) return false; + height = getU32(d + 2); // height first + width = getU32(d + 6); + return true; +} + +// ============================================================================================ +// Teredo +// ============================================================================================ + +bool teredoDecode(const std::string& ipv6, std::string& ipv4, uint16_t& port) { + ipv4.clear(); + port = 0; + std::string addr = trimmed(ipv6); + if (!addr.empty() && addr.front() == '[' && addr.back() == ']') addr = addr.substr(1, addr.size() - 2); + size_t pct = addr.find('%'); + if (pct != std::string::npos) addr.resize(pct); + if (addr.find(':') == std::string::npos) return false; + struct in6_addr a; + if (inet_pton(AF_INET6, addr.c_str(), &a) != 1) return false; + const uint8_t* b = reinterpret_cast<const uint8_t*>(&a); + // Teredo prefix 2001:0000::/32 + if (b[0] != 0x20 || b[1] != 0x01 || b[2] != 0x00 || b[3] != 0x00) return false; + port = uint16_t(((b[10] ^ 0xFF) << 8) | (b[11] ^ 0xFF)); + char buf[16]; + std::snprintf(buf, sizeof(buf), "%u.%u.%u.%u", unsigned(b[12] ^ 0xFF), unsigned(b[13] ^ 0xFF), + unsigned(b[14] ^ 0xFF), unsigned(b[15] ^ 0xFF)); + ipv4 = buf; + return true; +} + +std::vector<std::string> expandTeredoCandidates(const std::vector<std::string>& candidates) { + std::vector<std::string> out; + out.reserve(candidates.size() * 3); + int foundation = 20; // synthetic foundations, as in green-nx receive_ice_candidates + for (const std::string& line : candidates) { + std::string body = trimmed(line); + const bool hasA = startsWith(body, "a="); + if (hasA) body = body.substr(2); + if (!startsWith(body, "candidate:")) { + out.push_back(line); + continue; + } + // candidate:<foundation> <component> <transport> <priority> <address> <port> typ <type> ... + std::vector<std::string> f = splitWs(body); + std::string ipv4; + uint16_t port = 0; + if (f.size() >= 8 && iequals(f[2], "udp") && teredoDecode(f[4], ipv4, port)) { + const std::string prefix = hasA ? "a=candidate:" : "candidate:"; + for (uint16_t p : {port, uint16_t(9002)}) { + out.push_back(prefix + std::to_string(foundation++) + " " + f[1] + " UDP 1 " + ipv4 + " " + + std::to_string(p) + " typ host"); + } + } + out.push_back(line); + } + return out; +} + +// ============================================================================================ +// SDP offer + signalling bodies +// ============================================================================================ + +std::string buildOffer(const std::string& ufrag, const std::string& pwd, const std::string& fingerprint, bool home, + const std::string& resolution) { + const std::string u = trimmed(ufrag), p = trimmed(pwd); + std::string fp = trimmed(fingerprint); + if (startsWith(fp, "sha-256 ")) fp = trimmed(fp.substr(8)); + if (u.empty() || p.empty() || fp.empty()) return std::string(); + + // Video caps: green-nx ships the 720p template verbatim for the 720 tier and for xHome + // (where the console agent also wants H.264 level 3.2, 42e020); 1080 tiers declare + // 1920x1080@60 decode capability (8160 MBs, 489600 MB/s). + const bool is720 = startsWith(resolution, "720"); + const char* profile = home ? "42e020" : "42e01f"; + const char* caps = (home || is720) ? "max-fs=3600;max-mbps=108000" : "max-fs=8160;max-mbps=489600"; + + std::string s; + s.reserve(1600); + auto L = [&s](const std::string& line) { + s += line; + s += "\r\n"; + }; + L("v=0"); + L("o=- 4611731400430051 2 IN IP4 127.0.0.1"); + L("s=-"); + L("t=0 0"); + L("a=group:BUNDLE video audio 0"); + L("a=ice-ufrag:" + u); + L("a=ice-pwd:" + p); + L("a=fingerprint:sha-256 " + fp); + L("a=setup:actpass"); + // video (green-nx sdp_append_h264) + L("m=video 9 UDP/TLS/RTP/SAVPF 102"); + L("c=IN IP4 0.0.0.0"); + L("a=mid:video"); + L("a=rtcp-mux"); + L("a=rtcp-rsize"); + L("a=rtpmap:102 H264/90000"); + L(std::string("a=fmtp:102 level-asymmetry-allowed=0;packetization-mode=1;profile-level-id=") + profile + ";" + + caps); + L("a=rtcp-fb:102 goog-remb"); + L("a=rtcp-fb:102 ccm fir"); + L("a=rtcp-fb:102 nack"); + L("a=rtcp-fb:102 nack pli"); + L("a=recvonly"); + // audio (green-nx sdp_append_opus) + L("m=audio 9 UDP/TLS/RTP/SAVPF 111"); + L("c=IN IP4 0.0.0.0"); + L("a=mid:audio"); + L("a=rtcp-mux"); + L("a=rtcp-rsize"); + L("a=rtpmap:111 opus/48000/2"); + L("a=fmtp:111 minptime=10;useinbandfec=1;stereo=1"); + L("a=recvonly"); + // data channels (GreenOvercast; libdatachannel assigns the SCTP m-line mid "0") + L("m=application 9 UDP/DTLS/SCTP webrtc-datachannel"); + L("c=IN IP4 0.0.0.0"); + L("a=mid:0"); + L("a=sctp-port:5000"); + L("a=max-message-size:262144"); + return s; +} + +std::string sdpPostBody(const std::string& offer) { + json body = { + {"messageType", "offer"}, + {"sdp", offer}, + {"requestId", "1"}, + {"configuration", + {{"chatConfiguration", + {{"bytesPerSample", 2}, + {"expectedClipDurationMs", 20}, + {"format", {{"codec", "opus"}, {"container", "webm"}}}, + {"numChannels", 1}, + {"sampleFrequencyHz", 24000}}}, + {"chat", {{"minVersion", 1}, {"maxVersion", 1}}}, + {"control", {{"minVersion", 1}, {"maxVersion", 3}}}, + {"input", {{"minVersion", 1}, {"maxVersion", 9}}}, + {"message", {{"minVersion", 1}, {"maxVersion", 1}}}, + {"reliableinput", {{"minVersion", 9}, {"maxVersion", 9}}}, + {"unreliableinput", {{"minVersion", 9}, {"maxVersion", 9}}}}}, + }; + return body.dump(-1, ' ', false, kReplace); +} + +std::string icePostBody(const std::vector<std::string>& candidates, const std::string& ufrag) { + auto entry = [&ufrag](const std::string& cand) { + return json{{"candidate", cand}, {"sdpMid", "0"}, {"sdpMLineIndex", 0}, {"usernameFragment", ufrag}}.dump(-1, ' ', false, kReplace); + }; + json list = json::array(); + for (const std::string& raw : candidates) { + std::string c = trimmed(raw); + if (startsWith(c, "a=")) c = c.substr(2); + if (!startsWith(c, "candidate:")) continue; + std::vector<std::string> f = splitWs(c); + if (f.size() < 8) continue; + if (iequals(f[2], "tcp")) continue; // xCloud only does UDP (nano-rs filters TCP too) + list.push_back(entry(c)); + } + list.push_back(entry("a=end-of-candidates")); + return json{{"messageType", "iceCandidate"}, {"candidate", list}}.dump(-1, ' ', false, kReplace); +} + +} // namespace proto +} // namespace xc diff --git a/src/core/protocol.hpp b/src/core/protocol.hpp new file mode 100644 index 0000000..619f7e8 --- /dev/null +++ b/src/core/protocol.hpp @@ -0,0 +1,184 @@ +// Nubix — xCloud wire protocol helpers: input packets, control/message JSON, +// vibration parsing, Teredo decoding, SDP offer template, /sdp and /ice POST bodies. +// Layouts cross-checked against green-nx (xcloud_protocol.cpp) and xbox-xcloud-player +// (src/channel/input/packet.ts, src/channel/message.ts). +// Byte layouts and message shapes ported from green-nx (GPL-3.0, +// src/core/xcloud_protocol.cpp, src/core/session.cpp) and cross-checked against +// xbox-xcloud-player (src/channel/input/packet.ts, src/lib/teredo.ts). +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Portions derived from GreenOvercast (https://github.com/Producdevity/GreenOvercast), MPL-2.0, used here under GPL-3.0 (MPL-2.0 section 3.3). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <chrono> +#include <cstddef> +#include <cstdint> +#include <string> +#include <vector> + +#include "core/config.hpp" + +namespace xc { + +// Absolute pad state in Xbox conventions. ly/ry already Xbox convention (up = +). +struct GamepadState { + uint16_t buttons = 0; // btn:: bitmask + int16_t lx = 0, ly = 0, rx = 0, ry = 0; + uint16_t lt = 0, rt = 0; // 0..65535 + bool operator==(const GamepadState& o) const { + return buttons == o.buttons && lx == o.lx && ly == o.ly && rx == o.rx && ry == o.ry && lt == o.lt && + rt == o.rt; + } + bool operator!=(const GamepadState& o) const { return !(*this == o); } +}; + +namespace btn { +enum : uint16_t { + Nexus = 2, Menu = 4, View = 8, A = 16, B = 32, X = 64, Y = 128, + DUp = 256, DDown = 512, DLeft = 1024, DRight = 2048, + LB = 4096, RB = 8192, LS = 16384, RS = 32768 +}; +} // namespace btn + +// Server -> client rumble request (input channel, reportType bit 128). Motor values 0..100 %. +struct Vibration { + uint8_t pad = 0, left = 0, right = 0, lt = 0, rt = 0; + uint16_t durationMs = 0, delayMs = 0; + uint8_t repeat = 0; +}; + +namespace proto { + +// input channel reportType bits. +namespace report { +enum : uint16_t { + Metadata = 1, Gamepad = 2, Pointer = 4, ClientMetadata = 8, ServerMetadata = 16, + Mouse = 32, Keyboard = 64, Vibration = 128 +}; +} // namespace report + +// Sizes of the fixed input packets (header 14 bytes). +constexpr size_t kInputHeaderSize = 14; +constexpr size_t kClientMetadataPacketSize = 15; // header + u8 maxTouchpoints +constexpr size_t kGamepadFrameSize = 23; +constexpr size_t kGamepadPacketSize = 38; // header + u8 count + one 23-byte frame + +// Fixed accessKey of the control-channel authorizationRequest (same in every client). +constexpr const char* kControlAccessKey = "4BDB3609-C1F1-4195-9B37-FEFF45DA8B8E"; + +// Builds binary packets for the "input" data channel (protocol "1.0"). Header: u16 reportType, +// u32 sequence (+1 per packet, no gaps), f64 timestamp ms. All little-endian except +// VirtualPhysicality (big-endian 1, as xbox-xcloud-player writes it). +// +// Sequence numbering follows green-nx: pre-incremented, so the first packet (ClientMetadata) +// carries 1. Build each packet right before sending it and call rollback() if the send fails, +// otherwise the server sees a gap and stops applying input. Not thread-safe; guard externally. +class InputSerializer { +public: + InputSerializer(); + // First packet after the channel opens: reportType 8 + u8 maxTouchpoints (15 bytes). + std::vector<uint8_t> clientMetadata(uint8_t maxTouch = 1); + // One gamepad frame (38 bytes). + std::vector<uint8_t> gamepad(const GamepadState& st, uint8_t index = 0); + // Undo the last sequence increment (call when a send failed so no gap is created). + void rollback(); + // Sequence number the next packet will carry. + uint32_t nextSequence() const { return seq_ + 1; } + // Start over (new session): sequence back to 0, timestamp reference = now. + void reset(); + +private: + double elapsedMs() const; + + uint32_t seq_ = 0; // sequence of the last packet built + std::chrono::steady_clock::time_point start_; // monotonic reference for the f64 timestamp +}; + +// "message" channel: {"type":"Handshake","version":"messageV1","id":<uuid>,"cv":"0"} sent when +// the channel opens. +std::string messageHandshake(); +// "control" channel: authorizationRequest (accessKey 4BDB3609-C1F1-4195-9B37-FEFF45DA8B8E). +std::string controlAuthorization(); +// "control" channel: gamepadChanged {gamepadIndex, wasAdded}. +std::string controlGamepadChanged(int idx, bool added); +// "control" channel: {"message":"userRequestedResolutionUpdate","resolutionAlias":alias} +// (alias "720", "720HQ", "1080", "1080HQ", "1440", "Auto"; unknown values are sent as-is). +std::string controlResolution(const std::string& alias); +// "control" channel: {"message":"videoKeyframeRequested","ifrRequested":true}. +std::string controlKeyframeRequest(); +// "message" channel startup messages after HandshakeAck, in send order (green-nx +// startup_messages): systemUi/configuration, clientappinstallidchanged, orientationchanged, +// touchinputenabledchanged, clientdevicecapabilities (w/h, maxBitrateKbps = s.bitrateKbps, +// 60 fps), dimensionschanged (w/h). Each is a "Message" envelope with a fresh uuid. +// An empty installId is replaced by a random uuid. +std::vector<std::string> messageInitSequence(const Settings& s, const std::string& installId, int w, int h); +// "message" channel reply to a TransactionStart with the given id. contentJson is the reply +// object serialized as JSON (embedded as a JSON *string*, e.g. "{\"Result\":0}"); cv "". +std::string transactionComplete(const std::string& id, const std::string& contentJson); + +// ---- additional message-channel helpers (additive) -------------------------------------- + +// Random RFC 4122 v4 UUID, lower-case with dashes (independent of xc::generateUuid). +std::string newUuid(); +// One-way "Message" envelope: {"type":"Message","content":contentJson,"id":<uuid>,"target":target,"cv":""}. +std::string messageEnvelope(const std::string& target, const std::string& contentJson); +// Reply for a Message/TransactionStart we do not handle: {"type":"Unhandled","id","target","cv"}. +std::string messageUnhandled(const std::string& id, const std::string& target, const std::string& cv = ""); +// TransactionComplete with an explicit correlation vector. +std::string transactionComplete(const std::string& id, const std::string& contentJson, const std::string& cv); + +// Parsed "message"-channel frame. +struct ChannelMessage { + std::string type; // Handshake, HandshakeAck, Message, TransactionStart, TransactionComplete, ... + std::string id; + std::string target; // e.g. /streaming/sessionLifetimeManagement/serverInitiatedDisconnect + std::string content; // raw content (itself JSON text when the server sent a string) + std::string cv; +}; +// Parse a message-channel text frame. False if not a JSON object with a string "type". +bool parseChannelMessage(const std::string& text, ChannelMessage& out); +// True for {"type":"HandshakeAck",...}. +bool isHandshakeAck(const std::string& text); + +namespace target { +constexpr const char* kServerInitiatedDisconnect = "/streaming/sessionLifetimeManagement/serverInitiatedDisconnect"; +constexpr const char* kShowMessageDialog = "/streaming/systemUi/messages/ShowMessageDialog"; +constexpr const char* kShowVirtualKeyboard = "/streaming/systemUi/messages/ShowVirtualKeyboard"; +constexpr const char* kTitleInfo = "/streaming/properties/titleinfo"; +} // namespace target + +// Parse a server->client input packet carrying a Vibration section. False if not vibration. +bool parseVibration(const uint8_t* d, size_t n, Vibration& out); +// Parse a ServerMetadata section (reportType 16): stream video size. False if absent. +bool parseServerMetadata(const uint8_t* d, size_t n, uint32_t& width, uint32_t& height); + +// Teredo (2001:0::/32) address -> client IPv4 + port (bytes 10..15 XOR 0xFF). False if not Teredo. +bool teredoDecode(const std::string& ipv6, std::string& ipv4, uint16_t& port); +// Rewrite remote "a=candidate"/"candidate:" lines: for each Teredo candidate append an IPv4 +// candidate with the decoded address and one with port 9002 (xHome). Other lines unchanged. +// Synthetic lines ("candidate:<20+n> <component> UDP 1 <ipv4> <port> typ host", same "a=" +// prefix as the source line) come right before the original Teredo line, decoded port first. +std::vector<std::string> expandTeredoCandidates(const std::vector<std::string>& candidates); + +// Hand-written browser-compatible offer (GreenOvercast buildCompatibleOffer / xCloud template): +// BUNDLE video audio 0, H.264 profiles, Opus, data channel m-line mid 0, with the given ICE +// credentials and sha-256 fingerprint ("AB:CD:..."). home selects xHome tweaks. +// Session header + application m-line from GreenOvercast (proven with libdatachannel); video and +// audio m-lines are green-nx's known-good template: PT 102 H.264 with +// cloud 720: profile-level-id=42e01f;max-fs=3600;max-mbps=108000 +// cloud 1080/1080HQ: profile-level-id=42e01f;max-fs=8160;max-mbps=489600 +// home (any): profile-level-id=42e020;max-fs=3600;max-mbps=108000 +// and Opus PT 111 "minptime=10;useinbandfec=1;stereo=1". All lines CRLF. Returns "" if +// ufrag, pwd or fingerprint is empty (a leading "sha-256 " on the fingerprint is stripped). +std::string buildOffer(const std::string& ufrag, const std::string& pwd, const std::string& fingerprint, bool home, + const std::string& resolution); +// JSON body for POST /sdp: {"messageType":"offer","sdp":..., "requestId":"1","configuration":{...}}. +std::string sdpPostBody(const std::string& offer); +// JSON body for POST /ice (green-nx shape: stringified {candidate,sdpMid:"0",sdpMLineIndex:0, +// usernameFragment} entries + end-of-candidates). Candidate lines may carry an "a=" prefix +// and trailing CR/LF (both stripped); TCP candidates are skipped. +std::string icePostBody(const std::vector<std::string>& candidates, const std::string& ufrag); + +} // namespace proto +} // namespace xc diff --git a/src/core/ratelimit.cpp b/src/core/ratelimit.cpp new file mode 100644 index 0000000..1e25e6e --- /dev/null +++ b/src/core/ratelimit.cpp @@ -0,0 +1,199 @@ +// Nubix — client-side request pacing (see ratelimit.hpp). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "core/ratelimit.hpp" + +#include <algorithm> +#include <cctype> +#include <chrono> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> + +namespace xc { + +namespace { + +bool endsWith(const std::string& s, const char* suffix) { + const size_t n = std::strlen(suffix); + return s.size() >= n && s.compare(s.size() - n, n, suffix) == 0; +} + +// Days since 1970-01-01 for a proleptic Gregorian date (timegm is not portable to the PS5 libc). +int64_t daysFromCivil(int64_t y, unsigned m, unsigned d) { + y -= m <= 2; + const int64_t era = (y >= 0 ? y : y - 399) / 400; + const unsigned yoe = static_cast<unsigned>(y - era * 400); + const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; + const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + static_cast<int64_t>(doe) - 719468; +} + +// "Sun, 06 Nov 1994 08:49:37 GMT" (IMF-fixdate, the only form servers send today). +bool parseHttpDate(const std::string& v, int64_t& unixSec) { + static const char* kMonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + const size_t comma = v.find(','); + if (comma == std::string::npos) return false; + int day = 0, year = 0, hh = 0, mm = 0, ss = 0; + char mon[4] = {0}; + if (std::sscanf(v.c_str() + comma + 1, " %d %3s %d %d:%d:%d", &day, mon, &year, &hh, &mm, &ss) != 6) return false; + int month = -1; + for (int i = 0; i < 12; ++i) + if (std::strcmp(mon, kMonths[i]) == 0) month = i + 1; + if (month < 0 || day < 1 || day > 31 || hh > 23 || mm > 59 || ss > 60) return false; + unixSec = daysFromCivil(year, static_cast<unsigned>(month), static_cast<unsigned>(day)) * 86400 + hh * 3600 + + mm * 60 + ss; + return true; +} + +} // namespace + +int64_t steadyNowMs() { + return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()) + .count(); +} + +std::string urlHost(const std::string& url) { + size_t start = url.find("://"); + start = start == std::string::npos ? 0 : start + 3; + size_t end = url.find_first_of("/?#", start); + std::string host = url.substr(start, end == std::string::npos ? std::string::npos : end - start); + const size_t at = host.rfind('@'); + if (at != std::string::npos) host = host.substr(at + 1); + if (!host.empty() && host[0] != '[') { + const size_t colon = host.find(':'); + if (colon != std::string::npos) host = host.substr(0, colon); + } + std::transform(host.begin(), host.end(), host.begin(), + [](unsigned char c) { return static_cast<char>(std::tolower(c)); }); + return host; +} + +int parseRetryAfter(const std::string& value, int64_t nowUnixSec) { + size_t b = 0, e = value.size(); + while (b < e && std::isspace(static_cast<unsigned char>(value[b]))) ++b; + while (e > b && std::isspace(static_cast<unsigned char>(value[e - 1]))) --e; + if (b == e) return -1; + const std::string v = value.substr(b, e - b); + if (std::all_of(v.begin(), v.end(), [](unsigned char c) { return std::isdigit(c); })) { + if (v.size() > 6) return 3600; + return std::min(3600, std::atoi(v.c_str())); + } + int64_t at = 0; + if (!parseHttpDate(v, at)) return -1; + return static_cast<int>(std::clamp<int64_t>(at - nowUnixSec, 0, 3600)); +} + +// --------------------------------------------------------------------------------------------- +// RateLimiter + +RateLimiter::Policy RateLimiter::policyFor(const std::string& host) { + // Box art / store images and the public, unauthenticated store catalog (title names, batched + // 20 per request): many small requests on a cold library load, CDN-backed. Cached on disk. + if (endsWith(host, "store-images.s-microsoft.com") || endsWith(host, ".akamaized.net") || + host.find("images") != std::string::npos || host == "displaycatalog.mp.microsoft.com") + return {10.0, 30.0}; + // Microsoft APIs (gssv, auth, catalog). Session signalling peaks at ~3 req/s (state + sdp/ice + // polls); the catalog hydration bursts ~10 requests once. + return {5.0, 12.0}; +} + +int64_t RateLimiter::reserve(const std::string& host, int64_t nowMs, bool* inCooldown) { + if (inCooldown) *inCooldown = false; + std::lock_guard<std::mutex> lk(mu_); + HostState& h = hosts_[host]; + if (nowMs < h.cooldownUntilMs) { + if (inCooldown) *inCooldown = true; + return h.cooldownUntilMs - nowMs; + } + const Policy p = policyFor(host); + if (h.tokens < 0) { + h.tokens = p.burst; + h.lastRefillMs = nowMs; + } else if (nowMs > h.lastRefillMs) { + h.tokens = std::min(p.burst, h.tokens + (nowMs - h.lastRefillMs) * p.ratePerSec / 1000.0); + h.lastRefillMs = nowMs; + } + if (h.tokens >= 1.0) { + h.tokens -= 1.0; + return 0; + } + const double missing = 1.0 - h.tokens; + return std::max<int64_t>(1, static_cast<int64_t>(missing * 1000.0 / p.ratePerSec + 0.999)); +} + +void RateLimiter::onResponse(const std::string& host, long status, int retryAfterSec, int64_t nowMs) { + if (status <= 0) return; // transport error: says nothing about server-side limits + std::lock_guard<std::mutex> lk(mu_); + HostState& h = hosts_[host]; + const bool limited = status == 429 || (status == 503 && retryAfterSec >= 0); + if (!limited) { + h.consecutive429 = 0; + return; + } + ++h.consecutive429; + int64_t backoff; + if (retryAfterSec >= 0) { + backoff = static_cast<int64_t>(retryAfterSec) * 1000; + } else { + const int shift = std::min(h.consecutive429 - 1, 6); + backoff = std::min<int64_t>(kMaxBackoffMs, int64_t(2000) << shift); + } + h.cooldownUntilMs = std::max(h.cooldownUntilMs, nowMs + backoff); + h.tokens = 0; // restart slowly after the cooldown + h.lastRefillMs = h.cooldownUntilMs; +} + +int64_t RateLimiter::cooldownRemaining(const std::string& host, int64_t nowMs) { + std::lock_guard<std::mutex> lk(mu_); + auto it = hosts_.find(host); + if (it == hosts_.end() || nowMs >= it->second.cooldownUntilMs) return 0; + return it->second.cooldownUntilMs - nowMs; +} + +void RateLimiter::reset() { + std::lock_guard<std::mutex> lk(mu_); + hosts_.clear(); +} + +RateLimiter& RateLimiter::global() { + static RateLimiter instance; + return instance; +} + +// --------------------------------------------------------------------------------------------- +// SessionStartLimiter + +bool SessionStartLimiter::allow(bool home, int64_t nowMs, int& waitSec) { + std::lock_guard<std::mutex> lk(mu_); + std::deque<int64_t>& q = home ? home_ : cloud_; + const int maxStarts = home ? kMaxHome : kMaxCloud; + while (!q.empty() && nowMs - q.front() >= kWindowMs) q.pop_front(); + int64_t waitMs = 0; + if (static_cast<int>(q.size()) >= maxStarts) waitMs = q.front() + kWindowMs - nowMs; + if (last_ >= 0 && nowMs - last_ < kMinSpacingMs) waitMs = std::max(waitMs, last_ + kMinSpacingMs - nowMs); + if (waitMs > 0) { + waitSec = static_cast<int>(std::max<int64_t>(1, (waitMs + 999) / 1000)); + return false; + } + q.push_back(nowMs); + last_ = nowMs; + waitSec = 0; + return true; +} + +void SessionStartLimiter::reset() { + std::lock_guard<std::mutex> lk(mu_); + cloud_.clear(); + home_.clear(); + last_ = -1; +} + +SessionStartLimiter& SessionStartLimiter::global() { + static SessionStartLimiter instance; + return instance; +} + +} // namespace xc diff --git a/src/core/ratelimit.hpp b/src/core/ratelimit.hpp new file mode 100644 index 0000000..99fc4db --- /dev/null +++ b/src/core/ratelimit.hpp @@ -0,0 +1,89 @@ +// Nubix — client-side request pacing: per-host token buckets, 429/Retry-After cooldowns with +// exponential backoff, and a sliding-window cap on session starts. Keeps the client's traffic +// pattern at or below what the official browser client produces. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstdint> +#include <deque> +#include <map> +#include <mutex> +#include <string> + +namespace xc { + +class RateLimiter { +public: + struct Policy { + double ratePerSec; // sustained requests per second + double burst; // bucket size + }; + + // Policy for a host: image CDNs (box art) get a larger budget than API hosts. + static Policy policyFor(const std::string& host); + + // Try to start a request to `host` at time nowMs. Returns 0 when the request may go now (a + // token is consumed), otherwise the milliseconds to wait before asking again. When the host + // is in a 429 cooldown, *inCooldown is set and the remaining cooldown is returned. + int64_t reserve(const std::string& host, int64_t nowMs, bool* inCooldown = nullptr); + + // Feed a response back. HTTP 429 (and 503 carrying Retry-After) puts the host into cooldown + // for Retry-After seconds, or an exponential backoff (2, 4, 8 … 120 s) when the header is + // missing. Any other completed response resets the backoff. retryAfterSec < 0 = no header. + void onResponse(const std::string& host, long status, int retryAfterSec, int64_t nowMs); + + // Remaining cooldown for host (0 = none). + int64_t cooldownRemaining(const std::string& host, int64_t nowMs); + + void reset(); + + // Process-wide instance used by Http::request. + static RateLimiter& global(); + + static constexpr int64_t kMaxBackoffMs = 120000; + +private: + struct HostState { + double tokens = -1; // < 0 = not initialised (full bucket) + int64_t lastRefillMs = 0; + int64_t cooldownUntilMs = 0; + int consecutive429 = 0; + }; + std::mutex mu_; + std::map<std::string, HostState> hosts_; +}; + +// Caps how often game sessions are requested (POST …/play), independent of HTTP 429s: +// cloud at most 4 starts per 5 minutes, home (console wake-up retries) at most 8, and at least +// 2 s between any two starts. +class SessionStartLimiter { +public: + static constexpr int64_t kWindowMs = 5 * 60 * 1000; + static constexpr int kMaxCloud = 4; + static constexpr int kMaxHome = 8; + static constexpr int64_t kMinSpacingMs = 2000; + + // Records the start and returns true when allowed; otherwise sets waitSec (>= 1). + bool allow(bool home, int64_t nowMs, int& waitSec); + void reset(); + + static SessionStartLimiter& global(); + +private: + std::mutex mu_; + std::deque<int64_t> cloud_, home_; + int64_t last_ = -1; +}; + +// Parse a Retry-After header value: delta-seconds or an HTTP-date. Returns -1 when absent or +// unparseable; clamps to [0, 3600]. +int parseRetryAfter(const std::string& value, int64_t nowUnixSec); + +// Lower-cased host of an http(s) URL ("" when none). +std::string urlHost(const std::string& url); + +// Monotonic milliseconds (steady clock). +int64_t steadyNowMs(); + +} // namespace xc diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..68e40f5 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,55 @@ +// Nubix — native Xbox Cloud Gaming / Remote Play client for PS5 (and host builds). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <cstdio> +#include <cstring> +#include <string> + +#include "core/log.hpp" +#include "platform/platform.hpp" +#include "ui/app.hpp" + +#ifndef XC_VERSION +#define XC_VERSION "dev" +#endif + +int main(int argc, char** argv) { + bool verbose = false; + for (int i = 1; i < argc; ++i) { + if (std::strcmp(argv[i], "--verbose") == 0 || std::strcmp(argv[i], "-v") == 0) { + verbose = true; + } else if (std::strcmp(argv[i], "--version") == 0) { + std::printf("nubix %s\n", XC_VERSION); + return 0; + } else if (std::strcmp(argv[i], "--help") == 0 || std::strcmp(argv[i], "-h") == 0) { + std::printf("usage: %s [--verbose|-v] [--version] [--selftest]\n" + " --verbose debug logging (log.txt in the data directory)\n" + " --selftest init all subsystems, render a few Login frames, exit 0 on success\n" + " (headless: SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy)\n" + "Host environment: XC_DATA_DIR, XC_ASSETS_DIR, XC_FULLSCREEN=1\n" + "Keys: arrows/Enter/Esc navigate, Q/E switch tabs, Y filter/refresh, F1 stream menu,\n" + " F12 screenshot (saved to the data directory)\n", + argc > 0 ? argv[0] : "nubix"); + return 0; + } + } + + if (!xc::platform::init(argc, argv)) { + std::fprintf(stderr, "platform init failed\n"); + return 1; + } + xc::logInit(xc::platform::dataDir() + "/log.txt", verbose ? xc::LogLevel::Debug : xc::LogLevel::Info); + XC_LOGI("nubix %s starting (%s), data=%s assets=%s", XC_VERSION, xc::platform::isPs5() ? "PS5" : "host", + xc::platform::dataDir().c_str(), xc::platform::assetsDir().c_str()); + + int rc = 0; + { + xc::App app; + rc = app.run(argc, argv); + } + + XC_LOGI("exit code %d", rc); + xc::logShutdown(); + xc::platform::shutdown(); + return rc; +} diff --git a/src/platform/common.cpp b/src/platform/common.cpp new file mode 100644 index 0000000..41b6005 --- /dev/null +++ b/src/platform/common.cpp @@ -0,0 +1,62 @@ +// Nubix — platform functions shared by PS5 and host builds (POSIX). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "platform/platform.hpp" +#include "platform/net_compat.h" + +#include <arpa/inet.h> +#include <ifaddrs.h> +#include <net/if.h> +#include <netinet/in.h> +#include <sys/socket.h> + +#include <chrono> +#include <cstdint> +#include <cstring> +#include <ctime> + +namespace xc { +namespace platform { + +std::string assetPath(const std::string& rel) { return assetsDir() + "/" + rel; } + +// Usable unicast address: not INADDR_ANY (PS5 lists idle interfaces as UP with 0.0.0.0, which +// websrv/elfldr skip for the same reason), not loopback, not link-local 169.254/16 (no DHCP lease). +bool usableIpv4(uint32_t hostOrder) { + if ((hostOrder >> 24) == 0) return false; // 0.0.0.0/8 + if ((hostOrder >> 24) == 127) return false; // loopback + if ((hostOrder >> 16) == 0xA9FE) return false; // 169.254/16 + return true; +} + +std::string localIpv4() { + struct ifaddrs* list = nullptr; + if (xc_getifaddrs(&list) != 0) return std::string(); // with default-route fallback (PS5) + std::string result; + for (struct ifaddrs* it = list; it; it = it->ifa_next) { + if (!it->ifa_addr || it->ifa_addr->sa_family != AF_INET) continue; + if (!(it->ifa_flags & IFF_UP) || (it->ifa_flags & IFF_LOOPBACK)) continue; + if (it->ifa_name && std::strncmp(it->ifa_name, "lo", 2) == 0) continue; + auto* sin = reinterpret_cast<struct sockaddr_in*>(it->ifa_addr); + if (!usableIpv4(ntohl(sin->sin_addr.s_addr))) continue; + char buf[INET_ADDRSTRLEN] = {0}; + if (inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf))) { + result = buf; + break; + } + } + xc_freeifaddrs(list); + return result; +} + +bool networkAvailable() { return !localIpv4().empty(); } + +uint64_t monotonicMs() { + using namespace std::chrono; + return static_cast<uint64_t>(duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count()); +} + +int64_t unixTime() { return static_cast<int64_t>(std::time(nullptr)); } + +} // namespace platform +} // namespace xc diff --git a/src/platform/gamepad.cpp b/src/platform/gamepad.cpp new file mode 100644 index 0000000..caeb2c1 --- /dev/null +++ b/src/platform/gamepad.cpp @@ -0,0 +1,522 @@ +// Nubix — SDL GameController input mapping (DualSense / any SDL pad -> Xbox layout), +// UI navigation edges with auto-repeat, stream-menu chord, rumble, host keyboard fallback. +// +// PS5 notes (SDL-ps5 joystick driver, src/joystick/ps5/SDL_ps5joystick.c): +// - the PS and Create buttons are not reported (the system owns them), the touchpad click is +// SDL_CONTROLLER_BUTTON_TOUCHPAD -> the menu chord on PS5 is Options + touchpad; +// - L2/R2 are reported as full-range axes that only emit an event once they change, so an +// untouched trigger would read as half-pressed through the GameController mapping until +// its first motion event. We therefore report 0 until a motion event was seen. +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "platform/gamepad.hpp" + +#include <SDL.h> + +#include <algorithm> +#include <cmath> +#include <vector> + +#include "core/log.hpp" +#include "platform/platform.hpp" + +namespace xc { +namespace { + +constexpr uint32_t kRepeatDelayMs = 380; +constexpr uint32_t kRepeatIntervalMs = 110; +constexpr int kStickNavThreshold = 20000; // ~61 % deflection counts as a direction press +constexpr int kStickNavRelease = 12000; // hysteresis +constexpr uint32_t kNexusPulseMs = 120; // short PS press -> Nexus held this long +constexpr int kTriggerNavThreshold = 16000; +constexpr uint32_t kMaxRumbleMs = 4000; +// Options (Menu) / Create+touchpad (View) are held back this long after being pressed: if the +// chord partner follows within it, neither reaches the game (nobody presses both in one frame). +constexpr uint32_t kChordGraceMs = 140; +constexpr uint32_t kTapPulseMs = 80; // a tap shorter than the grace is replayed this long // cap for one (approximated) rumble envelope + +struct Pad { + SDL_GameController* gc = nullptr; + SDL_JoystickID id = -1; + bool triggerSeen[2] = {false, false}; + bool hasTriggerRumble = false; + std::string name; +}; + +int16_t clampAxis(int v) { return static_cast<int16_t>(std::max(-32767, std::min(32767, v))); } + +// Radial deadzone in percent with rescaling so the output still reaches full deflection. +void applyDeadzone(int16_t& x, int16_t& y, int percent) { + if (percent <= 0) return; + const double dz = std::min(95, percent) / 100.0; + const double fx = x / 32767.0, fy = y / 32767.0; + const double mag = std::sqrt(fx * fx + fy * fy); + if (mag < dz || mag <= 0.0) { + x = 0; + y = 0; + return; + } + const double scaled = std::min(1.0, (mag - dz) / (1.0 - dz)); + const double k = scaled / mag; + x = clampAxis(static_cast<int>(std::lround(fx * k * 32767.0))); + y = clampAxis(static_cast<int>(std::lround(fy * k * 32767.0))); +} + +uint16_t triggerToXbox(int16_t v) { + if (v <= 0) return 0; + return static_cast<uint16_t>(std::min<int>(65535, (static_cast<int>(v) * 65535) / 32767)); +} + +} // namespace + +struct Gamepad::Impl { + std::vector<Pad> pads; + SDL_JoystickID active = -1; + bool inited = false; + bool lastKeyboard = false; + + // nav auto-repeat (D-pad / stick) + NavKey heldDir = NavKey::None; + uint64_t heldSince = 0, lastRepeat = 0; + int stickDirX = 0, stickDirY = 0; // -1/0/1 per axis (left stick as D-pad) + bool ltNav = false, rtNav = false; + + // menu chord / PS short press + uint64_t guideDownAt = 0; // 0 = not held + bool guideConsumed = false; // chord fired while held: do not send Nexus on release + uint64_t comboSince = 0; // Options + Create/touchpad held since + bool comboConsumed = false; + uint64_t nexusPulseUntil = 0; + bool chordFired = false; // edge for menuChordTriggered() + + // Menu/View hold-back (state() is const but polled once per frame: mutable bookkeeping). + struct HoldGate { + uint64_t downAt = 0; // 0 = released + bool forwarded = false; // grace elapsed without the chord partner: sent to the game + bool consumed = false; // became part of the chord: never sent until released + }; + mutable HoldGate startGate, viewGate; + mutable bool chordLatch = false; // both were held together; cleared when both are up + mutable uint64_t menuPulseUntil = 0, viewPulseUntil = 0; + + void resetGates() { + startGate = HoldGate(); + viewGate = HoldGate(); + chordLatch = false; + menuPulseUntil = viewPulseUntil = 0; + } + + // Returns whether the gated button is forwarded to the game this frame. + bool gate(HoldGate& g, bool down, uint64_t& pulseUntil, uint64_t now) const { + if (!down) { + // Short tap that never got past the grace period and was not part of the chord: + // replay it as a brief press so a quick Options tap still pauses the game. + if (g.downAt != 0 && !g.forwarded && !g.consumed) pulseUntil = now + kTapPulseMs; + g = HoldGate(); + return now < pulseUntil; + } + if (g.downAt == 0) g.downAt = now; + if (chordLatch) g.consumed = true; + if (g.consumed) return false; + if (!g.forwarded && now - g.downAt >= kChordGraceMs) g.forwarded = true; + return g.forwarded; + } + + Pad* find(SDL_JoystickID id) { + for (auto& p : pads) + if (p.id == id) return &p; + return nullptr; + } + const Pad* activePad() const { + for (auto& p : pads) + if (p.id == active) return &p; + return pads.empty() ? nullptr : &pads.front(); + } + + void open(int deviceIndex) { + if (!SDL_IsGameController(deviceIndex)) { + XC_LOGI("gamepad: joystick %d (%s) has no controller mapping, ignored", deviceIndex, + SDL_JoystickNameForIndex(deviceIndex) ? SDL_JoystickNameForIndex(deviceIndex) : "?"); + return; + } + SDL_JoystickID id = SDL_JoystickGetDeviceInstanceID(deviceIndex); + if (find(id)) return; + SDL_GameController* gc = SDL_GameControllerOpen(deviceIndex); + if (!gc) { + XC_LOGW("gamepad: open %d failed: %s", deviceIndex, SDL_GetError()); + return; + } + Pad p; + p.gc = gc; + p.id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gc)); + const char* n = SDL_GameControllerName(gc); + p.name = n ? n : "Controller"; +#if SDL_VERSION_ATLEAST(2, 0, 18) + p.hasTriggerRumble = SDL_GameControllerHasRumbleTriggers(gc) == SDL_TRUE; +#endif + // Non-PS5 drivers report triggers correctly from the start. + if (!platform::isPs5()) p.triggerSeen[0] = p.triggerSeen[1] = true; +#if SDL_VERSION_ATLEAST(2, 0, 14) + if (SDL_GameControllerHasLED(gc)) SDL_GameControllerSetLED(gc, 36, 110, 240); // Nubix blue +#endif + XC_LOGI("gamepad: connected \"%s\" (instance %d, trigger rumble %s)", p.name.c_str(), (int)p.id, + p.hasTriggerRumble ? "yes" : "no"); + pads.push_back(p); + if (active < 0) active = p.id; + } + + void close(SDL_JoystickID id) { + for (auto it = pads.begin(); it != pads.end(); ++it) { + if (it->id != id) continue; + XC_LOGI("gamepad: disconnected \"%s\"", it->name.c_str()); + SDL_GameControllerClose(it->gc); + pads.erase(it); + break; + } + if (active == id) { + active = pads.empty() ? -1 : pads.front().id; + guideDownAt = 0; + comboSince = 0; + resetGates(); + heldDir = NavKey::None; + stickDirX = stickDirY = 0; + } + } + + bool button(SDL_GameControllerButton b) const { + const Pad* p = activePad(); + return p && SDL_GameControllerGetButton(p->gc, b) == 1; + } + + // Options + (Create or touchpad) currently held on the active pad. + bool comboHeld() const { + return button(SDL_CONTROLLER_BUTTON_START) && + (button(SDL_CONTROLLER_BUTTON_BACK) || button(SDL_CONTROLLER_BUTTON_TOUCHPAD)); + } + + void updateCombo(uint64_t now) { + if (comboHeld()) { + if (comboSince == 0) comboSince = now; + } else { + comboSince = 0; + comboConsumed = false; + } + } + + NavKey startHeld(NavKey k, uint64_t now) { + heldDir = k; + heldSince = now; + lastRepeat = now; + return k; + } + void releaseHeld(NavKey k) { + if (heldDir == k) heldDir = NavKey::None; + } +}; + +Gamepad::Gamepad() : d_(new Impl) {} +Gamepad::~Gamepad() { shutdown(); } + +bool Gamepad::init() { + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER) == 0) { + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) { + XC_LOGE("gamepad: SDL_INIT_GAMECONTROLLER failed: %s", SDL_GetError()); + return false; + } + } + SDL_GameControllerEventState(SDL_ENABLE); + d_->inited = true; + const int n = SDL_NumJoysticks(); + for (int i = 0; i < n; ++i) d_->open(i); + XC_LOGI("gamepad: %d joystick(s), %zu controller(s) open", n, d_->pads.size()); + return true; +} + +void Gamepad::shutdown() { + if (!d_) return; + for (auto& p : d_->pads) + if (p.gc) SDL_GameControllerClose(p.gc); + d_->pads.clear(); + d_->active = -1; + d_->inited = false; +} + +NavKey Gamepad::handleEvent(const SDL_Event& ev) { + const uint64_t now = platform::monotonicMs(); + Impl& d = *d_; + switch (ev.type) { + case SDL_CONTROLLERDEVICEADDED: + d.open(ev.cdevice.which); + return NavKey::None; + case SDL_CONTROLLERDEVICEREMOVED: + d.close(ev.cdevice.which); + return NavKey::None; + + case SDL_CONTROLLERBUTTONDOWN: { + if (!d.find(ev.cbutton.which)) return NavKey::None; + d.active = ev.cbutton.which; + d.lastKeyboard = false; + d.updateCombo(now); + switch (ev.cbutton.button) { + case SDL_CONTROLLER_BUTTON_A: return NavKey::Accept; + case SDL_CONTROLLER_BUTTON_B: return NavKey::Back; + case SDL_CONTROLLER_BUTTON_Y: return NavKey::Option; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return NavKey::TabLeft; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return NavKey::TabRight; + // START is deliberately not a NavKey: while streaming it is the game's Menu + // button; the stream overlay opens via menuChordTriggered() / keyboard F1. + case SDL_CONTROLLER_BUTTON_GUIDE: + d.guideDownAt = now; + d.guideConsumed = false; + return NavKey::None; + case SDL_CONTROLLER_BUTTON_DPAD_UP: return d.startHeld(NavKey::Up, now); + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return d.startHeld(NavKey::Down, now); + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return d.startHeld(NavKey::Left, now); + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return d.startHeld(NavKey::Right, now); + default: return NavKey::None; + } + } + case SDL_CONTROLLERBUTTONUP: { + if (!d.find(ev.cbutton.which)) return NavKey::None; + d.updateCombo(now); + switch (ev.cbutton.button) { + case SDL_CONTROLLER_BUTTON_GUIDE: + if (d.guideDownAt != 0 && !d.guideConsumed) d.nexusPulseUntil = now + kNexusPulseMs; + d.guideDownAt = 0; + d.guideConsumed = false; + break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: d.releaseHeld(NavKey::Up); break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: d.releaseHeld(NavKey::Down); break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: d.releaseHeld(NavKey::Left); break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: d.releaseHeld(NavKey::Right); break; + default: break; + } + return NavKey::None; + } + case SDL_CONTROLLERAXISMOTION: { + Pad* p = d.find(ev.caxis.which); + if (!p) return NavKey::None; + const int v = ev.caxis.value; + if (ev.caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT) p->triggerSeen[0] = true; + if (ev.caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) p->triggerSeen[1] = true; + if (ev.caxis.which != d.active && std::abs(v) > kStickNavThreshold && + ev.caxis.axis != SDL_CONTROLLER_AXIS_TRIGGERLEFT && ev.caxis.axis != SDL_CONTROLLER_AXIS_TRIGGERRIGHT) + d.active = ev.caxis.which; + if (ev.caxis.which != d.active) return NavKey::None; + if (ev.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX) { + int dir = v > kStickNavThreshold ? 1 : v < -kStickNavThreshold ? -1 : (std::abs(v) < kStickNavRelease ? 0 : d.stickDirX); + if (dir != d.stickDirX) { + NavKey old = d.stickDirX > 0 ? NavKey::Right : NavKey::Left; + if (d.stickDirX != 0) d.releaseHeld(old); + d.stickDirX = dir; + if (dir != 0) { + d.lastKeyboard = false; + return d.startHeld(dir > 0 ? NavKey::Right : NavKey::Left, now); + } + } + } else if (ev.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY) { + int dir = v > kStickNavThreshold ? 1 : v < -kStickNavThreshold ? -1 : (std::abs(v) < kStickNavRelease ? 0 : d.stickDirY); + if (dir != d.stickDirY) { + NavKey old = d.stickDirY > 0 ? NavKey::Down : NavKey::Up; + if (d.stickDirY != 0) d.releaseHeld(old); + d.stickDirY = dir; + if (dir != 0) { + d.lastKeyboard = false; + return d.startHeld(dir > 0 ? NavKey::Down : NavKey::Up, now); + } + } + } else if (ev.caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT) { + bool on = v > kTriggerNavThreshold; + if (on != d.ltNav) { + d.ltNav = on; + if (on) return NavKey::TabLeft; + } + } else if (ev.caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { + bool on = v > kTriggerNavThreshold; + if (on != d.rtNav) { + d.rtNav = on; + if (on) return NavKey::TabRight; + } + } + return NavKey::None; + } + + case SDL_KEYDOWN: { + d.lastKeyboard = true; + switch (ev.key.keysym.sym) { + case SDLK_UP: return NavKey::Up; // OS key repeat handles holding + case SDLK_DOWN: return NavKey::Down; + case SDLK_LEFT: return NavKey::Left; + case SDLK_RIGHT: return NavKey::Right; + default: break; + } + if (ev.key.repeat) return NavKey::None; + switch (ev.key.keysym.sym) { + case SDLK_RETURN: + case SDLK_KP_ENTER: + case SDLK_SPACE: return NavKey::Accept; + case SDLK_ESCAPE: + case SDLK_BACKSPACE: return NavKey::Back; + case SDLK_q: + case SDLK_PAGEUP: return NavKey::TabLeft; + case SDLK_e: + case SDLK_PAGEDOWN: return NavKey::TabRight; + case SDLK_TAB: + case SDLK_y: return NavKey::Option; + case SDLK_F1: return NavKey::Menu; + default: return NavKey::None; + } + } + default: + return NavKey::None; + } +} + +NavKey Gamepad::pollRepeat() { + Impl& d = *d_; + if (d.heldDir == NavKey::None) return NavKey::None; + const uint64_t now = platform::monotonicMs(); + if (now - d.heldSince < kRepeatDelayMs) return NavKey::None; + if (now - d.lastRepeat < kRepeatIntervalMs) return NavKey::None; + d.lastRepeat = now; + return d.heldDir; +} + +bool Gamepad::connected() const { return !d_->pads.empty(); } + +std::string Gamepad::name() const { + const Pad* p = d_->activePad(); + return p ? p->name : std::string(); +} + +bool Gamepad::lastInputKeyboard() const { return d_->lastKeyboard || d_->pads.empty(); } + +GamepadState Gamepad::state(int deadzonePercent) const { + const Impl& d = *d_; + GamepadState st; + const uint64_t now = platform::monotonicMs(); + if (const Pad* p = d.activePad()) { + SDL_GameController* gc = p->gc; + auto b = [gc](SDL_GameControllerButton x) { return SDL_GameControllerGetButton(gc, x) == 1; }; + uint16_t m = 0; + if (b(SDL_CONTROLLER_BUTTON_A)) m |= btn::A; + if (b(SDL_CONTROLLER_BUTTON_B)) m |= btn::B; + if (b(SDL_CONTROLLER_BUTTON_X)) m |= btn::X; + if (b(SDL_CONTROLLER_BUTTON_Y)) m |= btn::Y; + if (b(SDL_CONTROLLER_BUTTON_LEFTSHOULDER)) m |= btn::LB; + if (b(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)) m |= btn::RB; + if (b(SDL_CONTROLLER_BUTTON_LEFTSTICK)) m |= btn::LS; + if (b(SDL_CONTROLLER_BUTTON_RIGHTSTICK)) m |= btn::RS; + if (b(SDL_CONTROLLER_BUTTON_DPAD_UP)) m |= btn::DUp; + if (b(SDL_CONTROLLER_BUTTON_DPAD_DOWN)) m |= btn::DDown; + if (b(SDL_CONTROLLER_BUTTON_DPAD_LEFT)) m |= btn::DLeft; + if (b(SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) m |= btn::DRight; + const bool start = b(SDL_CONTROLLER_BUTTON_START); + const bool view = b(SDL_CONTROLLER_BUTTON_BACK) || b(SDL_CONTROLLER_BUTTON_TOUCHPAD); + // Options + Create/touchpad is the stream-menu chord. Each half is held back for + // kChordGraceMs so the game never sees the first button of the chord; once both were + // down together neither is forwarded until it is released (also after the menu fired). + if (start && view) d.chordLatch = true; + else if (!start && !view) d.chordLatch = false; + if (d.gate(d.startGate, start, d.menuPulseUntil, now)) m |= btn::Menu; + if (d.gate(d.viewGate, view, d.viewPulseUntil, now)) m |= btn::View; + st.buttons = m; + + st.lx = clampAxis(SDL_GameControllerGetAxis(gc, SDL_CONTROLLER_AXIS_LEFTX)); + st.ly = clampAxis(-static_cast<int>(SDL_GameControllerGetAxis(gc, SDL_CONTROLLER_AXIS_LEFTY))); + st.rx = clampAxis(SDL_GameControllerGetAxis(gc, SDL_CONTROLLER_AXIS_RIGHTX)); + st.ry = clampAxis(-static_cast<int>(SDL_GameControllerGetAxis(gc, SDL_CONTROLLER_AXIS_RIGHTY))); + if (p->triggerSeen[0]) st.lt = triggerToXbox(SDL_GameControllerGetAxis(gc, SDL_CONTROLLER_AXIS_TRIGGERLEFT)); + if (p->triggerSeen[1]) st.rt = triggerToXbox(SDL_GameControllerGetAxis(gc, SDL_CONTROLLER_AXIS_TRIGGERRIGHT)); + applyDeadzone(st.lx, st.ly, deadzonePercent); + applyDeadzone(st.rx, st.ry, deadzonePercent); + } + if (now < d.nexusPulseUntil) st.buttons |= btn::Nexus; + + // Keyboard fallback (host development; also works with a USB keyboard on PS5). + int numKeys = 0; + const Uint8* ks = SDL_GetKeyboardState(&numKeys); + if (ks && numKeys > SDL_SCANCODE_F1) { + auto k = [ks](SDL_Scancode sc) { return ks[sc] != 0; }; + if (k(SDL_SCANCODE_RETURN) || k(SDL_SCANCODE_SPACE)) st.buttons |= btn::A; + if (k(SDL_SCANCODE_BACKSPACE)) st.buttons |= btn::B; + if (k(SDL_SCANCODE_X)) st.buttons |= btn::X; + if (k(SDL_SCANCODE_Y)) st.buttons |= btn::Y; + if (k(SDL_SCANCODE_Q)) st.buttons |= btn::LB; + if (k(SDL_SCANCODE_E)) st.buttons |= btn::RB; + if (k(SDL_SCANCODE_F)) st.buttons |= btn::LS; + if (k(SDL_SCANCODE_H)) st.buttons |= btn::RS; + if (k(SDL_SCANCODE_V) || k(SDL_SCANCODE_TAB)) st.buttons |= btn::View; + if (k(SDL_SCANCODE_N)) st.buttons |= btn::Menu; + if (k(SDL_SCANCODE_G)) st.buttons |= btn::Nexus; + if (k(SDL_SCANCODE_UP)) st.buttons |= btn::DUp; + if (k(SDL_SCANCODE_DOWN)) st.buttons |= btn::DDown; + if (k(SDL_SCANCODE_LEFT)) st.buttons |= btn::DLeft; + if (k(SDL_SCANCODE_RIGHT)) st.buttons |= btn::DRight; + if (k(SDL_SCANCODE_Z)) st.lt = 65535; + if (k(SDL_SCANCODE_C)) st.rt = 65535; + auto axis = [&](SDL_Scancode neg, SDL_Scancode pos, int16_t& out) { + int v = (k(pos) ? 32767 : 0) - (k(neg) ? 32767 : 0); + if (v != 0) out = static_cast<int16_t>(v); + }; + axis(SDL_SCANCODE_A, SDL_SCANCODE_D, st.lx); + axis(SDL_SCANCODE_S, SDL_SCANCODE_W, st.ly); // Xbox convention: up = + + axis(SDL_SCANCODE_J, SDL_SCANCODE_L, st.rx); + axis(SDL_SCANCODE_K, SDL_SCANCODE_I, st.ry); + } + return st; +} + +bool Gamepad::menuChordTriggered(uint32_t holdMs) { + Impl& d = *d_; + const uint64_t now = platform::monotonicMs(); + // Re-check the chord against live button state (events may have been consumed elsewhere). + d.updateCombo(now); + if (d.guideDownAt != 0 && !d.guideConsumed && !d.button(SDL_CONTROLLER_BUTTON_GUIDE)) d.guideDownAt = 0; + if (d.guideDownAt != 0 && !d.guideConsumed && now - d.guideDownAt >= holdMs) { + d.guideConsumed = true; + return true; + } + if (d.comboSince != 0 && !d.comboConsumed && now - d.comboSince >= holdMs) { + d.comboConsumed = true; + return true; + } + return false; +} + +void Gamepad::rumble(const Vibration& v) { + const Pad* p = d_->activePad(); + if (!p) return; + auto scale = [](uint8_t pct) -> Uint16 { return static_cast<Uint16>(std::min<int>(100, pct) * 65535 / 100); }; + // The server describes a pulse train (duration on, delay off, repeated `repeat` more times). + // SDL cannot play the gaps, so approximate the whole envelope as one effect like green-nx + // (handle_input_report): duration + repeat * (duration + delay), capped at 4 s. + uint32_t total = v.durationMs; + if (v.durationMs && v.repeat) + total += static_cast<uint32_t>(v.repeat) * (static_cast<uint32_t>(v.durationMs) + v.delayMs); + const Uint32 dur = std::min<uint32_t>(total, kMaxRumbleMs); + if (dur == 0 || (v.left == 0 && v.right == 0 && v.lt == 0 && v.rt == 0)) { + SDL_GameControllerRumble(p->gc, 0, 0, 0); +#if SDL_VERSION_ATLEAST(2, 0, 14) + if (p->hasTriggerRumble) SDL_GameControllerRumbleTriggers(p->gc, 0, 0, 0); +#endif + return; + } + // Xbox: left = low-frequency (heavy) motor, right = high-frequency motor. + SDL_GameControllerRumble(p->gc, scale(v.left), scale(v.right), dur); +#if SDL_VERSION_ATLEAST(2, 0, 14) + if (p->hasTriggerRumble) SDL_GameControllerRumbleTriggers(p->gc, scale(v.lt), scale(v.rt), dur); +#endif +} + +void Gamepad::stopRumble() { + for (auto& p : d_->pads) { + SDL_GameControllerRumble(p.gc, 0, 0, 0); +#if SDL_VERSION_ATLEAST(2, 0, 14) + if (p.hasTriggerRumble) SDL_GameControllerRumbleTriggers(p.gc, 0, 0, 0); +#endif + } +} + +} // namespace xc diff --git a/src/platform/gamepad.hpp b/src/platform/gamepad.hpp new file mode 100644 index 0000000..07dac12 --- /dev/null +++ b/src/platform/gamepad.hpp @@ -0,0 +1,81 @@ +// Nubix — SDL GameController -> xc::GamepadState (DualSense -> Xbox mapping), rumble, +// hotplug, and UI navigation edges. +// Mapping: Cross=A, Circle=B, Square=X, Triangle=Y, L1=LB, R1=RB, L2=LT, R2=RT, L3=LS, R3=RS, +// Options=Menu, touchpad click=View, D-pad=D-pad. Host only (the PS5 pad driver does not report +// these buttons): Create=View, PS=Nexus (short press). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstdint> +#include <memory> +#include <string> + +#include "core/protocol.hpp" + +union SDL_Event; + +namespace xc { + +// Edge-triggered navigation keys for the UI (from pad or keyboard on host). +// Pad: A=Accept, B=Back, Y=Option, LB/LT=TabLeft, RB/RT=TabRight, D-pad/left stick=directions. +// Keyboard: arrows, Enter/Space=Accept, Esc/Backspace=Back, Q/E=tabs, Y/Tab=Option, F1=Menu. +// The pad's Options button is never a NavKey (it is the game's Menu button while streaming). +enum class NavKey { None, Up, Down, Left, Right, Accept, Back, TabLeft, TabRight, Option, Menu }; + +class Gamepad { +public: + Gamepad(); + ~Gamepad(); + + // Init SDL_INIT_GAMECONTROLLER subsystem state and open already connected pads. + bool init(); + + // Close all controllers. + void shutdown(); + + // Feed every SDL event (controller add/remove, buttons, axes; keyboard on host). + // Returns a navigation key for UI use (None if the event is not a navigation press). + NavKey handleEvent(const SDL_Event& ev); + + // True if at least one controller is connected. + bool connected() const; + + // Current mapped state of the active controller (deadzone percent applied radially). + // Menu (Options) and View (Create/touchpad) are held back ~140 ms after being pressed and + // dropped entirely while they form the stream-menu chord (a tap shorter than that is + // replayed as a short press). Call once per frame while streaming. + GamepadState state(int deadzonePercent = 0) const; + + // True once when the stream-menu chord was held long enough: Options + touchpad (PS5 and + // host), Options + Create or the PS button (host only), held for holdMs. While held, the PS + // button is NOT forwarded as Nexus. + bool menuChordTriggered(uint32_t holdMs = 1000); + + // Apply a server rumble command (SDL_GameControllerRumble + trigger rumble if supported). + void rumble(const Vibration& v); + + // Auto-repeat for held directions (D-pad, left stick, arrow keys are repeated by the OS): + // call once per frame; returns a direction NavKey when a repeat is due, else None. + NavKey pollRepeat(); + + // Name of the active controller ("" if none), for diagnostics / UI. + std::string name() const; + + // True if the last navigation input came from the keyboard (UI shows key names then). + bool lastInputKeyboard() const; + + // Stop all rumble immediately (e.g. when a stream ends or the overlay opens). + void stopRumble(); + + // Host keyboard -> pad mapping used while streaming (merged into state()): + // arrows = D-pad, WASD = left stick, IJKL = right stick, Enter/Space = A, Backspace = B, + // X = X, Y = Y, Q = LB, E = RB, Z = LT, C = RT, V/Tab = View, N = Menu, G = Nexus, + // F = LS, H = RS. F1 opens the stream menu (NavKey::Menu). + +private: + struct Impl; + std::unique_ptr<Impl> d_; +}; + +} // namespace xc diff --git a/src/platform/host.cpp b/src/platform/host.cpp new file mode 100644 index 0000000..454fa83 --- /dev/null +++ b/src/platform/host.cpp @@ -0,0 +1,127 @@ +// Nubix — host (macOS/Linux) platform implementation. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#ifndef XC_PS5 + +#include <sys/stat.h> + +#include <cstdio> +#include <unistd.h> + +#include <cerrno> +#include <climits> +#include <cstdlib> +#include <string> + +#if defined(__APPLE__) +#include <mach-o/dyld.h> +#endif + +#include "core/log.hpp" +#include "platform/platform.hpp" + +namespace xc { +namespace platform { +namespace { + +std::string g_exeDir = "."; + +bool mkdirs(const std::string& path) { + if (path.empty()) return false; + std::string cur; + for (size_t i = 0; i < path.size(); ++i) { + cur.push_back(path[i]); + if (path[i] == '/' || i + 1 == path.size()) { + if (cur == "/") continue; + if (mkdir(cur.c_str(), 0755) != 0 && errno != EEXIST) return false; + } + } + return true; +} + +bool isDir(const std::string& p) { + struct stat st{}; + return stat(p.c_str(), &st) == 0 && S_ISDIR(st.st_mode); +} + +std::string dirName(const std::string& p) { + auto pos = p.find_last_of('/'); + if (pos == std::string::npos) return "."; + if (pos == 0) return "/"; + return p.substr(0, pos); +} + +std::string resolveExePath(const char* argv0) { + char buf[PATH_MAX] = {0}; +#if defined(__APPLE__) + uint32_t size = sizeof(buf); + if (_NSGetExecutablePath(buf, &size) == 0) { + char real[PATH_MAX] = {0}; + if (realpath(buf, real)) return real; + return buf; + } +#else + ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1); + if (n > 0) { + buf[n] = 0; + return buf; + } +#endif + if (argv0) { + char real[PATH_MAX] = {0}; + if (realpath(argv0, real)) return real; + return argv0; + } + return "./nubix"; +} + +} // namespace + +bool init(int argc, char** argv) { + g_exeDir = dirName(resolveExePath(argc > 0 ? argv[0] : nullptr)); + // Keep the sign-in and settings of installs from before the rename (0.3.0). + const std::string dir = dataDir(); + const size_t slash = dir.rfind('/'); + if (slash != std::string::npos && dir.compare(slash + 1, std::string::npos, "nubix") == 0) { + const std::string legacy = dir.substr(0, slash + 1) + "ps5-xcloud"; + struct stat st {}; + if (::stat(dir.c_str(), &st) != 0 && ::stat(legacy.c_str(), &st) == 0) std::rename(legacy.c_str(), dir.c_str()); + } + return mkdirs(dir); +} + +void shutdown() {} + +std::string dataDir() { + if (const char* env = std::getenv("XC_DATA_DIR"); env && *env) return env; + const char* home = std::getenv("HOME"); + std::string base = home && *home ? home : "."; +#if defined(__APPLE__) + return base + "/Library/Application Support/nubix"; +#else + if (const char* xdg = std::getenv("XDG_CONFIG_HOME"); xdg && *xdg) return std::string(xdg) + "/nubix"; + return base + "/.config/nubix"; +#endif +} + +std::string assetsDir() { + if (const char* env = std::getenv("XC_ASSETS_DIR"); env && *env) return env; + std::string local = g_exeDir + "/assets"; + if (isDir(local)) return local; +#ifdef XC_SOURCE_ASSETS_DIR + return XC_SOURCE_ASSETS_DIR; +#else + return local; +#endif +} + +std::string exeDir() { return g_exeDir; } + +void notify(const std::string& msg) { XC_LOGI("[notify] %s", msg.c_str()); } + +void onFirstFrame() {} + +} // namespace platform +} // namespace xc + +#endif // !XC_PS5 diff --git a/src/platform/net_compat.c b/src/platform/net_compat.c new file mode 100644 index 0000000..36700f0 --- /dev/null +++ b/src/platform/net_compat.c @@ -0,0 +1,306 @@ +/* Nubix — resolver / interface-list fallbacks (see net_compat.h). + * Copyright (C) 2026 Nubix contributors + * SPDX-License-Identifier: GPL-3.0-only */ +#define XC_NET_COMPAT_IMPL 1 +#include "platform/net_compat.h" + +#include <arpa/inet.h> +#include <errno.h> +#include <net/if.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* ---- getaddrinfo ------------------------------------------------------------------------- */ + +/* Each result node is one allocation: addrinfo followed by its sockaddr_storage. */ +struct xc_ai_node { + struct addrinfo ai; + struct sockaddr_storage ss; +}; + +static struct addrinfo *xc_ai_new(const struct sockaddr *sa, socklen_t len, int socktype, int protocol) { + struct xc_ai_node *n = (struct xc_ai_node *)calloc(1, sizeof(*n)); + if (!n) return NULL; + if (len > (socklen_t)sizeof(n->ss)) len = (socklen_t)sizeof(n->ss); + memcpy(&n->ss, sa, (size_t)len); + n->ai.ai_family = sa->sa_family; + n->ai.ai_socktype = socktype; + n->ai.ai_protocol = protocol; + n->ai.ai_addrlen = len; + n->ai.ai_addr = (struct sockaddr *)&n->ss; + return &n->ai; +} + +void xc_freeaddrinfo(struct addrinfo *ai) { + while (ai) { + struct addrinfo *next = ai->ai_next; + free(ai->ai_canonname); + free(ai); /* ai is the first member of its xc_ai_node */ + ai = next; + } +} + +/* Numeric service (or NULL = 0). Returns -1 for a service name. */ +static int xc_parse_port(const char *service) { + if (!service || !*service) return 0; + char *end = NULL; + long v = strtol(service, &end, 10); + if (*end != '\0' || v < 0 || v > 65535) return -1; + return (int)v; +} + +static void xc_hint_types(const struct addrinfo *hints, int *socktype, int *protocol) { + *socktype = hints ? hints->ai_socktype : 0; + *protocol = hints ? hints->ai_protocol : 0; + if (*socktype == 0) *socktype = SOCK_DGRAM; + if (*protocol == 0) *protocol = *socktype == SOCK_STREAM ? IPPROTO_TCP : IPPROTO_UDP; +} + +/* Copy a system result into our own nodes so xc_freeaddrinfo can free every list uniformly. */ +static int xc_copy_list(const struct addrinfo *src, struct addrinfo **res) { + struct addrinfo *head = NULL, **tail = &head; + for (const struct addrinfo *a = src; a; a = a->ai_next) { + if (!a->ai_addr || (a->ai_family != AF_INET && a->ai_family != AF_INET6)) continue; + struct addrinfo *n = xc_ai_new(a->ai_addr, a->ai_addrlen, a->ai_socktype, a->ai_protocol); + if (!n) { + xc_freeaddrinfo(head); + return EAI_MEMORY; + } + n->ai_flags = a->ai_flags; + if (a->ai_canonname) n->ai_canonname = strdup(a->ai_canonname); + *tail = n; + tail = &n->ai_next; + } + if (!head) return EAI_NONAME; + *res = head; + return 0; +} + +int xc_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { + if (!res) return EAI_FAIL; + *res = NULL; + const int family = hints ? hints->ai_family : AF_UNSPEC; + const int flags = hints ? hints->ai_flags : 0; + const int port = xc_parse_port(service); + int socktype, protocol; + xc_hint_types(hints, &socktype, &protocol); + + if (port >= 0) { + /* node == NULL: wildcard (AI_PASSIVE) or loopback. IPv4 unless IPv6 was asked for: + * the PS5 network stack is used IPv4-only here (Teredo candidates are decoded to IPv4). */ + if (!node) { + if (family == AF_INET6) { + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons((uint16_t)port); + sin6.sin6_addr = (flags & AI_PASSIVE) ? in6addr_any : in6addr_loopback; +#ifdef SIN6_LEN + sin6.sin6_len = sizeof(sin6); +#endif + *res = xc_ai_new((const struct sockaddr *)&sin6, sizeof(sin6), socktype, protocol); + } else { + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons((uint16_t)port); + sin.sin_addr.s_addr = htonl((flags & AI_PASSIVE) ? INADDR_ANY : INADDR_LOOPBACK); +#if defined(__FreeBSD__) || defined(__APPLE__) + sin.sin_len = sizeof(sin); +#endif + *res = xc_ai_new((const struct sockaddr *)&sin, sizeof(sin), socktype, protocol); + } + return *res ? 0 : EAI_MEMORY; + } + + struct in_addr a4; + if ((family == AF_UNSPEC || family == AF_INET) && inet_pton(AF_INET, node, &a4) == 1) { + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons((uint16_t)port); + sin.sin_addr = a4; +#if defined(__FreeBSD__) || defined(__APPLE__) + sin.sin_len = sizeof(sin); +#endif + *res = xc_ai_new((const struct sockaddr *)&sin, sizeof(sin), socktype, protocol); + return *res ? 0 : EAI_MEMORY; + } + struct in6_addr a6; + const char *pct = strchr(node, '%'); /* scoped literal: let the system handle it */ + if (!pct && (family == AF_UNSPEC || family == AF_INET6) && inet_pton(AF_INET6, node, &a6) == 1) { + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons((uint16_t)port); + sin6.sin6_addr = a6; +#ifdef SIN6_LEN + sin6.sin6_len = sizeof(sin6); +#endif + *res = xc_ai_new((const struct sockaddr *)&sin6, sizeof(sin6), socktype, protocol); + return *res ? 0 : EAI_MEMORY; + } + if (flags & AI_NUMERICHOST) return EAI_NONAME; + } + + /* A real host name (or a service name): system resolver. Retry without AI_ADDRCONFIG, + * which minimal resolvers may not support. */ + struct addrinfo h; + memset(&h, 0, sizeof(h)); + if (hints) h = *hints; + h.ai_addr = NULL; + h.ai_canonname = NULL; + h.ai_next = NULL; + struct addrinfo *sys = NULL; + int rc = getaddrinfo(node, service, &h, &sys); +#ifdef AI_ADDRCONFIG + if (rc != 0 && (h.ai_flags & AI_ADDRCONFIG)) { + h.ai_flags &= ~AI_ADDRCONFIG; + rc = getaddrinfo(node, service, &h, &sys); + } +#endif + if (rc != 0) return rc; + rc = xc_copy_list(sys, res); + freeaddrinfo(sys); + return rc; +} + +/* ---- getnameinfo ------------------------------------------------------------------------- */ + +int xc_getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t hostlen, char *serv, + socklen_t servlen, int flags) { + if (!sa) return EAI_FAIL; + const int wantNumericHost = !host || hostlen == 0 || (flags & NI_NUMERICHOST); + const int wantNumericServ = !serv || servlen == 0 || (flags & NI_NUMERICSERV); + if (!wantNumericHost || !wantNumericServ || (sa->sa_family != AF_INET && sa->sa_family != AF_INET6)) + return getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); + unsigned port; + if (sa->sa_family == AF_INET) { + if (salen < (socklen_t)sizeof(struct sockaddr_in)) return EAI_FAIL; + const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; + if (host && hostlen && !inet_ntop(AF_INET, &sin->sin_addr, host, hostlen)) return EAI_OVERFLOW; + port = ntohs(sin->sin_port); + } else { + if (salen < (socklen_t)sizeof(struct sockaddr_in6)) return EAI_FAIL; + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa; + if (host && hostlen && !inet_ntop(AF_INET6, &sin6->sin6_addr, host, hostlen)) return EAI_OVERFLOW; + port = ntohs(sin6->sin6_port); + } + if (serv && servlen) { + int n = snprintf(serv, servlen, "%u", port); + if (n < 0 || (socklen_t)n >= servlen) return EAI_OVERFLOW; + } + return 0; +} + +/* ---- getifaddrs -------------------------------------------------------------------------- */ + +struct xc_ifa_node { + struct ifaddrs ifa; + struct sockaddr_storage addr, mask; + char name[IFNAMSIZ]; +}; + +static struct ifaddrs *xc_ifa_new(const char *name, unsigned flags, const struct sockaddr *addr, + const struct sockaddr *mask) { + struct xc_ifa_node *n = (struct xc_ifa_node *)calloc(1, sizeof(*n)); + if (!n) return NULL; + strncpy(n->name, name ? name : "eth0", sizeof(n->name) - 1); + n->ifa.ifa_name = n->name; + n->ifa.ifa_flags = flags; + if (addr) { + const size_t len = addr->sa_family == AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); + memcpy(&n->addr, addr, len); + n->ifa.ifa_addr = (struct sockaddr *)&n->addr; + } + if (mask) { + const size_t len = mask->sa_family == AF_INET6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); + memcpy(&n->mask, mask, len); + n->ifa.ifa_netmask = (struct sockaddr *)&n->mask; + } + return &n->ifa; +} + +void xc_freeifaddrs(struct ifaddrs *ifa) { + while (ifa) { + struct ifaddrs *next = ifa->ifa_next; + free(ifa); /* ifa is the first member of its xc_ifa_node */ + ifa = next; + } +} + +/* Address the kernel would use for the default route: connect() a UDP socket (sends nothing). */ +static int xc_default_route_ipv4(struct sockaddr_in *out) { + int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (s < 0) return -1; + struct sockaddr_in dst; + memset(&dst, 0, sizeof(dst)); + dst.sin_family = AF_INET; + dst.sin_port = htons(53); + dst.sin_addr.s_addr = htonl(0x08080808); /* 8.8.8.8 — never contacted */ +#if defined(__FreeBSD__) || defined(__APPLE__) + dst.sin_len = sizeof(dst); +#endif + int rc = -1; + if (connect(s, (const struct sockaddr *)&dst, sizeof(dst)) == 0) { + socklen_t len = sizeof(*out); + if (getsockname(s, (struct sockaddr *)out, &len) == 0 && out->sin_family == AF_INET && + out->sin_addr.s_addr != htonl(INADDR_ANY)) + rc = 0; + } + close(s); + return rc; +} + +static int xc_usable_ipv4(const struct sockaddr *sa, unsigned flags) { + if (!sa || sa->sa_family != AF_INET) return 0; + if (!(flags & IFF_UP) || (flags & IFF_LOOPBACK)) return 0; + const uint32_t a = ntohl(((const struct sockaddr_in *)sa)->sin_addr.s_addr); + return a != 0 && (a >> 24) != 127 && (a >> 16) != 0xA9FE; /* not 0/8-any, loopback, link-local */ +} + +int xc_getifaddrs(struct ifaddrs **ifap) { + if (!ifap) { + errno = EINVAL; + return -1; + } + *ifap = NULL; + struct ifaddrs *head = NULL, **tail = &head; + int haveIpv4 = 0; + + struct ifaddrs *sys = NULL; + if (getifaddrs(&sys) == 0) { + for (struct ifaddrs *i = sys; i; i = i->ifa_next) { + if (!i->ifa_addr || (i->ifa_addr->sa_family != AF_INET && i->ifa_addr->sa_family != AF_INET6)) continue; + struct ifaddrs *n = xc_ifa_new(i->ifa_name, i->ifa_flags, i->ifa_addr, i->ifa_netmask); + if (!n) break; + if (xc_usable_ipv4(i->ifa_addr, i->ifa_flags)) haveIpv4 = 1; + *tail = n; + tail = &n->ifa_next; + } + freeifaddrs(sys); + } + + if (!haveIpv4) { + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + if (xc_default_route_ipv4(&sin) == 0) { + struct ifaddrs *n = xc_ifa_new("eth0", IFF_UP | IFF_RUNNING, (const struct sockaddr *)&sin, NULL); + if (n) { + ((struct sockaddr_in *)n->ifa_addr)->sin_port = 0; + *tail = n; + tail = &n->ifa_next; + } + } + } + + if (!head) { + errno = ENETDOWN; + return -1; + } + *ifap = head; + return 0; +} diff --git a/src/platform/net_compat.h b/src/platform/net_compat.h new file mode 100644 index 0000000..f4d67f7 --- /dev/null +++ b/src/platform/net_compat.h @@ -0,0 +1,45 @@ +/* Nubix — resolver / interface-list fallbacks for the PS5 libc. + * + * The PS5 libc resolves real host names (curl works), but getaddrinfo() fails for the special + * cases libjuice relies on: node == NULL with AI_PASSIVE ("any" address to bind a UDP socket) and + * numeric hosts with AI_NUMERICHOST. xc_getaddrinfo() answers those itself (inet_pton) and only + * hands real names to the system resolver. xc_getifaddrs() falls back to the address of the + * default route when getifaddrs() fails or lists no usable IPv4 interface. + * + * The PS5 build of libdatachannel force-includes this header into its C sources (libjuice) with + * XC_NET_COMPAT_REDIRECT defined (scripts/build-deps.sh), so their calls land here. + * Copyright (C) 2026 Nubix contributors + * SPDX-License-Identifier: GPL-3.0-only */ +#ifndef XC_NET_COMPAT_H +#define XC_NET_COMPAT_H + +#include <sys/types.h> +#include <sys/socket.h> +#include <ifaddrs.h> +#include <netdb.h> + +#ifdef __cplusplus +extern "C" { +#endif + +int xc_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); +void xc_freeaddrinfo(struct addrinfo *ai); +int xc_getifaddrs(struct ifaddrs **ifap); +void xc_freeifaddrs(struct ifaddrs *ifa); +/* Numeric conversions (NI_NUMERICHOST/NI_NUMERICSERV) via inet_ntop; others go to the system. */ +int xc_getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t hostlen, char *serv, + socklen_t servlen, int flags); + +#ifdef __cplusplus +} +#endif + +#if defined(XC_NET_COMPAT_REDIRECT) && !defined(XC_NET_COMPAT_IMPL) +#define getaddrinfo xc_getaddrinfo +#define freeaddrinfo xc_freeaddrinfo +#define getifaddrs xc_getifaddrs +#define freeifaddrs xc_freeifaddrs +#define getnameinfo xc_getnameinfo +#endif + +#endif /* XC_NET_COMPAT_H */ diff --git a/src/platform/platform.hpp b/src/platform/platform.hpp new file mode 100644 index 0000000..edcf5c3 --- /dev/null +++ b/src/platform/platform.hpp @@ -0,0 +1,68 @@ +// Nubix — platform abstraction (PS5 payload vs. host build). +// Implemented in platform/ps5.cpp (XC_PS5) or platform/host.cpp. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstdint> +#include <string> + +namespace xc { +namespace platform { + +// Early process init: detect the install / executable dir (PS5: absolute argv[0], else the cwd +// when it holds assets/, else /data/homebrew/nubix) and create dataDir(). Returns false +// on fatal error (PS5: also shows a system notification, since stderr is not visible). +// System modules (libScePad, AudioOut, VideoOut, ...) are not loaded here: the payload ELF's +// DT_NEEDED entries make the loader map them before main(). +bool init(int argc, char** argv); + +// Undo init(). +void shutdown(); + +// Writable data dir, created on demand. PS5: /data/nubix; host: $XC_DATA_DIR or +// ~/.config/nubix (macOS: ~/Library/Application Support/nubix). +std::string dataDir(); + +// Read-only assets dir: <exe dir>/assets, fallback /data/homebrew/nubix/assets (PS5) +// or the source tree's assets/ (host, XC_SOURCE_ASSETS_DIR). +std::string assetsDir(); + +// assetsDir() + "/" + rel. +std::string assetPath(const std::string& rel); + +// Directory containing the running executable. +std::string exeDir(); + +// Show a system notification (PS5: sceKernelSendNotificationRequest), host: log only. +void notify(const std::string& msg); + +// True if an UP interface has a usable IPv4 address (see localIpv4()). +bool networkAvailable(); + +// First usable IPv4 address of an UP interface ("" if none): skips loopback, 0.0.0.0 (PS5 +// lists idle interfaces that way) and link-local 169.254/16. Calls getifaddrs(): cache the +// result in per-frame code. +std::string localIpv4(); + +// Called by the UI once the first frame has been presented. PS5: hides the system splash +// screen shown while a (fake) app starts; host: no-op. +void onFirstFrame(); + +// Milliseconds from a monotonic clock. +uint64_t monotonicMs(); + +// Seconds since the Unix epoch (wall clock). +int64_t unixTime(); + +// True when built for / running on PS5. +constexpr bool isPs5() { +#ifdef XC_PS5 + return true; +#else + return false; +#endif +} + +} // namespace platform +} // namespace xc diff --git a/src/platform/ps5.cpp b/src/platform/ps5.cpp new file mode 100644 index 0000000..2f6205f --- /dev/null +++ b/src/platform/ps5.cpp @@ -0,0 +1,119 @@ +// Nubix — PS5 payload platform implementation. +// Notification request layout from the ps5-payload-sdk samples (hello_world / notify_debug). +// Notification layout from the ps5-payload-sdk samples (John Toernblom, GPL-3.0-or-later) and ProsperoLight (GPL-3.0). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#ifdef XC_PS5 + +#include <sys/stat.h> +#include <unistd.h> + +#include <atomic> +#include <cerrno> +#include <cstdio> +#include <cstring> +#include <string> + +#include "core/log.hpp" +#include "platform/platform.hpp" + +// Notification request as used by the PS5 payload SDK samples / ProsperoLight. +typedef struct notify_request { + char useless1[45]; + char message[3075]; +} notify_request_t; + +extern "C" int sceKernelSendNotificationRequest(int, notify_request_t*, size_t, int); +extern "C" int sceSystemServiceHideSplashScreen(void); + +namespace xc { +namespace platform { +namespace { + +const char* kDataDir = "/data/nubix"; +const char* kLegacyDataDir = "/data/ps5-xcloud"; // name before 0.3.0: migrated on first start +const char* kInstallDir = "/data/homebrew/nubix"; +std::string g_exeDir = kInstallDir; +std::atomic<bool> g_splashHidden{false}; + +bool isDir(const std::string& p) { + struct stat st{}; + return stat(p.c_str(), &st) == 0 && S_ISDIR(st.st_mode); +} + +// mkdir -p +bool mkdirs(const std::string& path) { + if (path.empty()) return false; + std::string cur; + for (size_t i = 0; i < path.size(); ++i) { + cur.push_back(path[i]); + if (path[i] == '/' || i + 1 == path.size()) { + if (cur == "/") continue; + if (mkdir(cur.c_str(), 0777) != 0 && errno != EEXIST) return false; + } + } + return isDir(path); +} + +std::string dirName(const std::string& p) { + auto pos = p.find_last_of('/'); + if (pos == std::string::npos) return "."; + if (pos == 0) return "/"; + return p.substr(0, pos); +} + +} // namespace + +bool init(int argc, char** argv) { + // websrv launches eboot.elf with an absolute argv[0] and cwd = install dir; elfldr payloads + // have no meaningful argv[0], so fall back to the cwd (if it has assets/) or the install dir. + char cwd[1024] = {0}; + if (argc > 0 && argv && argv[0] && std::strchr(argv[0], '/') && isDir(dirName(argv[0]))) { + g_exeDir = dirName(argv[0]); + } else if (getcwd(cwd, sizeof(cwd)) && isDir(std::string(cwd) + "/assets")) { + g_exeDir = cwd; + } else { + g_exeDir = kInstallDir; + } + // Keep the sign-in and settings of installs from before the rename. + if (!isDir(kDataDir) && isDir(kLegacyDataDir)) std::rename(kLegacyDataDir, kDataDir); + if (!mkdirs(kDataDir)) { + const int e = errno; + std::fprintf(stderr, "cannot create %s: %s\n", kDataDir, std::strerror(e)); + // stderr goes to the launcher pipe only: tell the user why the app closes right away. + notify(std::string("nubix: cannot create ") + kDataDir + " (" + std::strerror(e) + ")"); + return false; + } + return true; +} + +void shutdown() {} + +std::string dataDir() { return kDataDir; } + +std::string assetsDir() { + std::string local = g_exeDir + "/assets"; + if (isDir(local)) return local; + return std::string(kInstallDir) + "/assets"; +} + +std::string exeDir() { return g_exeDir; } + +void notify(const std::string& msg) { + XC_LOGI("[notify] %s", msg.c_str()); + notify_request_t req; + std::memset(&req, 0, sizeof(req)); + std::snprintf(req.message, sizeof(req.message), "%s", msg.c_str()); + sceKernelSendNotificationRequest(0, &req, sizeof(req), 0); +} + +void onFirstFrame() { + if (g_splashHidden.exchange(true)) return; + int rc = sceSystemServiceHideSplashScreen(); + if (rc != 0) XC_LOGD("sceSystemServiceHideSplashScreen: 0x%08x", static_cast<unsigned>(rc)); +} + +} // namespace platform +} // namespace xc + +#endif // XC_PS5 diff --git a/src/platform/ps5_shims.c b/src/platform/ps5_shims.c new file mode 100644 index 0000000..f5bb847 --- /dev/null +++ b/src/platform/ps5_shims.c @@ -0,0 +1,17 @@ +/* Nubix — libc functions missing from the PS5 payload SDK. + * Copyright (C) 2026 Nubix contributors + * SPDX-License-Identifier: GPL-3.0-only */ +#ifdef XC_PS5 +#include <stddef.h> + +/* usrsctp (sctp_auth.c, sctp_input.c) needs the BSD constant-time compare. */ +int timingsafe_bcmp(const void *b1, const void *b2, size_t n) { + const unsigned char *p1 = (const unsigned char *)b1, *p2 = (const unsigned char *)b2; + int ret = 0; + for (; n > 0; n--) ret |= *p1++ ^ *p2++; + return (ret != 0); +} +#else +/* Host builds get timingsafe_bcmp from libc (or usrsctp's own fallback). */ +typedef int xc_ps5_shims_empty_translation_unit; +#endif diff --git a/src/stream/audio.cpp b/src/stream/audio.cpp new file mode 100644 index 0000000..5e680ec --- /dev/null +++ b/src/stream/audio.cpp @@ -0,0 +1,440 @@ +// Nubix — Opus audio playback. +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +// +// RTP (or pre-depacketised Opus) -> libopus 48 kHz stereo s16 -> ring buffer -> SDL audio callback. +// +// Jitter handling: playback starts once ~60 ms are buffered; on underrun the callback plays +// silence and re-primes; if the buffer grows past a high watermark the oldest audio is dropped +// back to the target. A small clock-skew servo (linear-interpolation stretch of at most 0.2 %) +// steers the smoothed depth towards the target so sender/receiver crystal drift never builds up. +// Ring, prebuffer, servo and resampler design are ported from green-nx +// (src/switch/stream/audio_player.cpp, GPL-3.0); the output side uses SDL instead of audout. +// +// Threads: pushRtp()/pushOpus()/reset() may be called from any thread (normally the +// libdatachannel track callback); the SDL audio thread only touches the ring under ringMu. +#include "stream/audio.hpp" + +#include "core/log.hpp" + +#include <SDL.h> +#include <opus.h> + +#include <algorithm> +#include <atomic> +#include <cstring> +#include <mutex> +#include <vector> + +namespace xc { + +namespace { + +constexpr int kSampleRate = 48000; +constexpr int kChannels = 2; +constexpr int kMaxFrameSamples = 5760; // 120 ms per channel +constexpr int kInt16PerMs = kSampleRate * kChannels / 1000; // 96 interleaved samples per ms +constexpr int kRingMs = 500; // ring capacity +constexpr int kPrebufferMs = 60; // start playback at this depth +constexpr int kResumeMs = 40; // re-prime depth after an underrun +constexpr int kHighWatermarkMs = 150; // above this, shed audio... +constexpr int kShedTargetMs = 60; // ...down to here +constexpr int kMaxConcealFrames = 5; // PLC at most this many lost frames +constexpr Uint16 kDeviceSamples = 512; // ~10.7 ms; PS5 needs multiples of 256 + +// Clock-skew servo (see green-nx): ~1 s EMA of ring depth, 30 ppm per ms of error, clamp 0.2 %. +constexpr float kServoTargetMs = 60.0f; +constexpr float kServoEmaAlpha = 0.02f; +constexpr float kServoGain = 30e-6f; +constexpr float kServoMaxAdj = 2000e-6f; + +} // namespace + +struct AudioPlayer::Impl { + // ---- decoder side (guarded by decMu) ---- + std::mutex decMu; + OpusDecoder* dec = nullptr; + bool haveSeq = false; + uint16_t expectedSeq = 0; + int lastFrameSamples = kSampleRate / 50; // 20 ms until the first packet tells us otherwise + std::vector<int16_t> pcm; + std::vector<int16_t> pcmOut; + float depthEma = 0.0f; + float servoAdj = 0.0f; + float resamplePos = 0.0f; + int16_t carry[2] = {0, 0}; + + // ---- output side (guarded by ringMu) ---- + std::mutex ringMu; + std::vector<int16_t> ring; + int ringSize = 0; + int readPos = 0, writePos = 0, count = 0; + bool primed = false; + int primeTarget = kPrebufferMs * kInt16PerMs; + + SDL_AudioDeviceID dev = 0; + bool weInitedSdlAudio = false; + std::atomic<bool> muted{false}; + + // ---- stats ---- + std::atomic<uint64_t> packets{0}, lost{0}, late{0}, errors{0}, underruns{0}, droppedSamples{0}; + std::atomic<int> bufferedSamples{0}; + + static void sdlCallback(void* user, Uint8* stream, int len) { + static_cast<Impl*>(user)->fill(reinterpret_cast<int16_t*>(stream), + len / static_cast<int>(sizeof(int16_t))); + } + + void resetDecoderStateLocked() { + if (dec) opus_decoder_ctl(dec, OPUS_RESET_STATE); + haveSeq = false; + expectedSeq = 0; + lastFrameSamples = kSampleRate / 50; + depthEma = 0.0f; + servoAdj = 0.0f; + resamplePos = 0.0f; + carry[0] = carry[1] = 0; + } + + void resetRing() { + std::lock_guard<std::mutex> lk(ringMu); + readPos = writePos = count = 0; + primed = false; + primeTarget = kPrebufferMs * kInt16PerMs; + bufferedSamples.store(0, std::memory_order_relaxed); + } + + // Decode one packet (data == nullptr -> PLC, fec -> in-band FEC of the *previous* frame) + // and queue the PCM. Called with decMu held. + void decodeAndQueue(const uint8_t* data, size_t n, int frameSamples, bool fec) { + if (!dec) return; + const int maxSamples = (data && !fec) ? kMaxFrameSamples : frameSamples; + const int got = opus_decode(dec, data, static_cast<opus_int32>(data ? n : 0), pcm.data(), + maxSamples, fec ? 1 : 0); + if (got <= 0) { + errors.fetch_add(1, std::memory_order_relaxed); + if (errors.load(std::memory_order_relaxed) <= 5) + XC_LOGW("audio: opus_decode failed: %s", opus_strerror(got)); + return; + } + if (data && !fec) lastFrameSamples = got; + queuePcm(got); + } + + // Servo + resample `samples` frames from pcm into the ring. Called with decMu held. + void queuePcm(int samples) { + int depthSamples; + { + std::lock_guard<std::mutex> lk(ringMu); + depthSamples = count; + } + const float depthMs = static_cast<float>(depthSamples) / kInt16PerMs; + depthEma += kServoEmaAlpha * (depthMs - depthEma); + servoAdj = std::clamp((kServoTargetMs - depthEma) * kServoGain, -kServoMaxAdj, kServoMaxAdj); + + const int outSamples = servoResample(pcm.data(), samples, pcmOut.data(), + static_cast<int>(pcmOut.size()) / kChannels); + pushRing(pcmOut.data(), outSamples * kChannels); + } + + // Linear-interpolation stretch, phase-continuous across frames (green-nx servo_resample). + // step < 1 stretches (more output), step > 1 shrinks. + int servoResample(const int16_t* in, int inSamples, int16_t* out, int maxOut) { + const float step = 1.0f - servoAdj; + float pos = resamplePos; + int outN = 0; + while (outN < maxOut) { + const int i = static_cast<int>(pos); + if (i >= inSamples) break; + const float frac = pos - static_cast<float>(i); + const int16_t l0 = (i == 0) ? carry[0] : in[(i - 1) * kChannels]; + const int16_t r0 = (i == 0) ? carry[1] : in[(i - 1) * kChannels + 1]; + const int16_t l1 = in[i * kChannels]; + const int16_t r1 = in[i * kChannels + 1]; + out[outN * kChannels] = static_cast<int16_t>(l0 + static_cast<float>(l1 - l0) * frac); + out[outN * kChannels + 1] = static_cast<int16_t>(r0 + static_cast<float>(r1 - r0) * frac); + ++outN; + pos += step; + } + // Keep the fractional phase relative to the next frame (pos is >= inSamples here unless + // the output buffer filled up, which cannot happen with the +1 % headroom). + resamplePos = std::max(0.0f, pos - static_cast<float>(inSamples)); + carry[0] = in[(inSamples - 1) * kChannels]; + carry[1] = in[(inSamples - 1) * kChannels + 1]; + return outN; + } + + void pushRing(const int16_t* data, int n) { + if (n <= 0) return; + const int high = kHighWatermarkMs * kInt16PerMs; + const int target = kShedTargetMs * kInt16PerMs; + std::lock_guard<std::mutex> lk(ringMu); + if (ringSize == 0) return; + if (n > ringSize) { + data += n - ringSize; + n = ringSize; + } + // Overflow: drop the oldest audio down to the target to bound latency. + if (count + n > high) { + int shed = std::min(count + n - target, count); + if (shed > 0) { + shed -= shed % kChannels; // keep L/R alignment + readPos = (readPos + shed) % ringSize; + count -= shed; + droppedSamples.fetch_add(static_cast<uint64_t>(shed), std::memory_order_relaxed); + } + } + if (count + n > ringSize) { + const int over = count + n - ringSize; + readPos = (readPos + over) % ringSize; + count -= over; + droppedSamples.fetch_add(static_cast<uint64_t>(over), std::memory_order_relaxed); + } + const int first = std::min(n, ringSize - writePos); + std::memcpy(&ring[writePos], data, static_cast<size_t>(first) * sizeof(int16_t)); + if (n > first) + std::memcpy(&ring[0], data + first, static_cast<size_t>(n - first) * sizeof(int16_t)); + writePos = (writePos + n) % ringSize; + count += n; + if (!primed && count >= primeTarget) primed = true; + bufferedSamples.store(count, std::memory_order_relaxed); + } + + // SDL audio thread. + void fill(int16_t* out, int need) { + int given = 0; + { + std::lock_guard<std::mutex> lk(ringMu); + if (primed) { + const int give = std::min(need, count); + const int first = std::min(give, ringSize - readPos); + std::memcpy(out, &ring[readPos], static_cast<size_t>(first) * sizeof(int16_t)); + if (give > first) + std::memcpy(out + first, &ring[0], static_cast<size_t>(give - first) * sizeof(int16_t)); + readPos = (readPos + give) % ringSize; + count -= give; + given = give; + if (given < need) { + // Ran dry: play silence and wait for a shallower cushion before resuming. + primed = false; + primeTarget = kResumeMs * kInt16PerMs; + underruns.fetch_add(1, std::memory_order_relaxed); + } + } + bufferedSamples.store(count, std::memory_order_relaxed); + } + if (given < need) + std::memset(out + given, 0, static_cast<size_t>(need - given) * sizeof(int16_t)); + if (muted.load(std::memory_order_relaxed)) + std::memset(out, 0, static_cast<size_t>(need) * sizeof(int16_t)); + } + + // Handle one Opus payload with `lostBefore` missing packets in front of it. + void handleOpus(const uint8_t* data, size_t n, int lostBefore) { + if (!dec) return; + int frameSamples = lastFrameSamples; + const int ns = opus_packet_get_nb_samples(data, static_cast<opus_int32>(n), kSampleRate); + if (ns > 0 && ns <= kMaxFrameSamples) frameSamples = ns; + + if (lostBefore > 0) { + lost.fetch_add(static_cast<uint64_t>(lostBefore), std::memory_order_relaxed); + const int conceal = std::min(lostBefore, kMaxConcealFrames); + // PLC for all but the last missing frame; the last one is recovered from this + // packet's in-band FEC (LBRR) when present (libopus falls back to PLC otherwise). + for (int i = 0; i < conceal - 1; ++i) decodeAndQueue(nullptr, 0, lastFrameSamples, false); + decodeAndQueue(data, n, frameSamples, true); + } + decodeAndQueue(data, n, frameSamples, false); + packets.fetch_add(1, std::memory_order_relaxed); + } +}; + +AudioPlayer::AudioPlayer() : d_(new Impl) {} +AudioPlayer::~AudioPlayer() { close(); } + +bool AudioPlayer::init() { + close(); + Impl& d = *d_; + + { + std::lock_guard<std::mutex> lk(d.decMu); + int err = OPUS_OK; + d.dec = opus_decoder_create(kSampleRate, kChannels, &err); + if (err != OPUS_OK || !d.dec) { + XC_LOGE("audio: opus_decoder_create failed: %s", opus_strerror(err)); + d.dec = nullptr; + return false; + } + d.pcm.assign(static_cast<size_t>(kMaxFrameSamples) * kChannels, 0); + // +1 % headroom: the servo stretches a frame by at most 0.2 %. + d.pcmOut.assign(static_cast<size_t>(kMaxFrameSamples + kMaxFrameSamples / 100 + 2) * kChannels, 0); + d.resetDecoderStateLocked(); + } + { + std::lock_guard<std::mutex> lk(d.ringMu); + d.ringSize = kRingMs * kInt16PerMs; + d.ring.assign(static_cast<size_t>(d.ringSize), 0); + } + d.resetRing(); + d.packets = 0; + d.lost = 0; + d.late = 0; + d.errors = 0; + d.underruns = 0; + d.droppedSamples = 0; + + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { + XC_LOGE("audio: SDL_InitSubSystem(AUDIO) failed: %s", SDL_GetError()); + close(); + return false; + } + d.weInitedSdlAudio = true; + } + + SDL_AudioSpec want; + SDL_zero(want); + want.freq = kSampleRate; + want.format = AUDIO_S16SYS; + want.channels = kChannels; + want.samples = kDeviceSamples; + want.callback = &Impl::sdlCallback; + want.userdata = &d; + SDL_AudioSpec have; + SDL_zero(have); + // Only the buffer size may change; SDL converts anything else to our s16/48k/stereo. + d.dev = SDL_OpenAudioDevice(nullptr, 0, &want, &have, SDL_AUDIO_ALLOW_SAMPLES_CHANGE); + if (d.dev == 0) { + XC_LOGE("audio: SDL_OpenAudioDevice failed: %s", SDL_GetError()); + close(); + return false; + } + XC_LOGI("audio: device open (%s, %d Hz, %d ch, %u samples/buffer), prebuffer %d ms", + SDL_GetCurrentAudioDriver() ? SDL_GetCurrentAudioDriver() : "?", have.freq, + have.channels, static_cast<unsigned>(have.samples), kPrebufferMs); + SDL_PauseAudioDevice(d.dev, 0); + return true; +} + +void AudioPlayer::pushRtp(const uint8_t* rtp, size_t n) { + Impl& d = *d_; + // RFC 3550 fixed header (12 bytes) + CSRC list + optional header extension + padding. + if (!rtp || n < 12 || (rtp[0] >> 6) != 2) { + d.errors.fetch_add(1, std::memory_order_relaxed); + return; + } + const bool padding = (rtp[0] & 0x20) != 0; + const bool extension = (rtp[0] & 0x10) != 0; + const size_t csrc = rtp[0] & 0x0f; + const uint8_t pt = rtp[1] & 0x7f; + if (pt >= 72 && pt <= 76) return; // RTCP muxed on the same port (SR/RR/...), not media + size_t off = 12 + 4 * csrc; + size_t end = n; + if (off > end) { + d.errors.fetch_add(1, std::memory_order_relaxed); + return; + } + if (extension) { + if (off + 4 > end) { + d.errors.fetch_add(1, std::memory_order_relaxed); + return; + } + const size_t extWords = (static_cast<size_t>(rtp[off + 2]) << 8) | rtp[off + 3]; + off += 4 + extWords * 4; + if (off > end) { + d.errors.fetch_add(1, std::memory_order_relaxed); + return; + } + } + if (padding) { + const size_t pad = rtp[n - 1]; + if (pad == 0 || pad > end - off) { + d.errors.fetch_add(1, std::memory_order_relaxed); + return; + } + end -= pad; + } + if (end == off) return; // no payload (keep-alive) + const uint16_t seq = static_cast<uint16_t>((rtp[2] << 8) | rtp[3]); + + std::lock_guard<std::mutex> lk(d.decMu); + if (!d.dec) return; + int lostBefore = 0; + if (d.haveSeq) { + const int16_t diff = static_cast<int16_t>(seq - d.expectedSeq); + if (diff < 0) { + // Duplicate or arrived after we already concealed it: too late to play. + d.late.fetch_add(1, std::memory_order_relaxed); + return; + } + lostBefore = diff; + } + d.haveSeq = true; + d.expectedSeq = static_cast<uint16_t>(seq + 1); + d.handleOpus(rtp + off, end - off, lostBefore); +} + +void AudioPlayer::pushOpus(const uint8_t* opus, size_t n, int lostBefore) { + if (!opus || n == 0) return; + std::lock_guard<std::mutex> lk(d_->decMu); + if (!d_->dec) return; + d_->handleOpus(opus, n, lostBefore < 0 ? 0 : lostBefore); +} + +void AudioPlayer::reset() { + { + std::lock_guard<std::mutex> lk(d_->decMu); + d_->resetDecoderStateLocked(); + } + d_->resetRing(); +} + +int AudioPlayer::bufferedMs() const { + return d_->bufferedSamples.load(std::memory_order_relaxed) / kInt16PerMs; +} + +void AudioPlayer::setMuted(bool muted) { d_->muted.store(muted, std::memory_order_relaxed); } + +AudioPlayer::Stats AudioPlayer::stats() const { + Stats s; + s.packets = d_->packets.load(std::memory_order_relaxed); + s.lost = d_->lost.load(std::memory_order_relaxed); + s.late = d_->late.load(std::memory_order_relaxed); + s.decodeErrors = d_->errors.load(std::memory_order_relaxed); + s.underruns = d_->underruns.load(std::memory_order_relaxed); + s.droppedMs = d_->droppedSamples.load(std::memory_order_relaxed) / kInt16PerMs; + s.bufferedMs = bufferedMs(); + return s; +} + +void AudioPlayer::close() { + if (!d_) return; + Impl& d = *d_; + // Close the device first: SDL joins its audio thread, so fill() is no longer running. + if (d.dev) { + SDL_CloseAudioDevice(d.dev); + d.dev = 0; + XC_LOGI("audio: closed (%llu packets, %llu lost, %llu late, %llu underruns, %llu ms dropped)", + static_cast<unsigned long long>(d.packets.load()), + static_cast<unsigned long long>(d.lost.load()), + static_cast<unsigned long long>(d.late.load()), + static_cast<unsigned long long>(d.underruns.load()), + static_cast<unsigned long long>(d.droppedSamples.load() / kInt16PerMs)); + } + if (d.weInitedSdlAudio) { + SDL_QuitSubSystem(SDL_INIT_AUDIO); + d.weInitedSdlAudio = false; + } + { + std::lock_guard<std::mutex> lk(d.decMu); + if (d.dec) { + opus_decoder_destroy(d.dec); + d.dec = nullptr; + } + d.resetDecoderStateLocked(); + } + d.resetRing(); +} + +} // namespace xc diff --git a/src/stream/audio.hpp b/src/stream/audio.hpp new file mode 100644 index 0000000..b0dcf1e --- /dev/null +++ b/src/stream/audio.hpp @@ -0,0 +1,61 @@ +// Nubix — Opus audio: RTP -> libopus (48 kHz stereo) -> ring buffer -> SDL audio device. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstddef> +#include <cstdint> +#include <memory> + +namespace xc { + +class AudioPlayer { +public: + AudioPlayer(); + ~AudioPlayer(); + AudioPlayer(const AudioPlayer&) = delete; + AudioPlayer& operator=(const AudioPlayer&) = delete; + + // Create the Opus decoder and open the SDL audio device (48 kHz, s16, stereo). + // SDL_INIT_AUDIO must already be initialised. + bool init(); + + // Push one raw audio RTP packet (header included); decodes and queues PCM. Thread-safe. + void pushRtp(const uint8_t* rtp, size_t n); + + // Buffered audio latency in milliseconds (stats overlay). + int bufferedMs() const; + + // Mute/unmute output without closing the device. + void setMuted(bool muted); + + // Close the audio device and decoder. Idempotent. + void close(); + + // ---- additive API (media module) ---- + + // Push one Opus payload that was already de-packetised (e.g. by RtpOpusReceiver). + // lostBefore = packets missing immediately before this one; they are concealed with + // in-band FEC (last one) and PLC. Thread-safe. Do not mix with pushRtp() in one session. + void pushOpus(const uint8_t* opus, size_t n, int lostBefore = 0); + + // Drop all buffered audio and decoder/sequence state (stream reconnect). Thread-safe. + void reset(); + + struct Stats { + uint64_t packets = 0; // Opus packets decoded + uint64_t lost = 0; // packets concealed (gap in sequence numbers) + uint64_t late = 0; // duplicate / out-of-order packets dropped + uint64_t decodeErrors = 0; // opus_decode failures / malformed RTP + uint64_t underruns = 0; // callback ran dry after playback had started + uint64_t droppedMs = 0; // audio discarded to bound latency (overflow) + int bufferedMs = 0; + }; + Stats stats() const; + +private: + struct Impl; + std::unique_ptr<Impl> d_; +}; + +} // namespace xc diff --git a/src/stream/rtp.cpp b/src/stream/rtp.cpp new file mode 100644 index 0000000..ecc427e --- /dev/null +++ b/src/stream/rtp.cpp @@ -0,0 +1,1056 @@ +// Nubix — RTP receive side. +// +// H.264 reassembly and loss policy ported/adapted from green-nx +// (src/switch/stream/video_jitter.cpp, GPL-3.0, (c) rmrf404 and contributors): +// * padding-only packets consume sequence numbers but are never "loss"; +// * a frame is emitted as soon as it is internally complete; +// * an incomplete frame is held only while a NACK retransmission can still arrive; +// * after unrecoverable loss only a real IDR (NAL type 5) restarts output — xCloud repeats +// SPS/PPS without IDR and resuming on those produces self-sustaining garbage. +// Statistics follow RFC 3550 Appendix A.1 / A.3 / A.8. +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "stream/rtp.hpp" + +#include <algorithm> +#include <chrono> +#include <climits> +#include <cmath> +#include <cstring> +#include <deque> +#include <map> + +#include "core/log.hpp" + +namespace xc { + +uint64_t rtpNowMs() { + using namespace std::chrono; + return static_cast<uint64_t>(duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count()); +} + +namespace { + +uint64_t nowUs() { + using namespace std::chrono; + return static_cast<uint64_t>(duration_cast<microseconds>(steady_clock::now().time_since_epoch()).count()); +} + +// Parsed view of one RTP packet (RFC 3550 §5.1). +struct RtpView { + uint8_t pt = 0; + bool marker = false; + uint16_t seq = 0; + uint32_t ts = 0; + uint32_t ssrc = 0; + const uint8_t* payload = nullptr; + size_t payloadLen = 0; + bool padded = false; // had the P bit set (libwebrtc bandwidth probing) +}; + +bool parseRtp(const uint8_t* p, size_t n, RtpView& v) { + if (!p || n < 12) return false; + if ((p[0] >> 6) != 2) return false; + const uint8_t pt = p[1] & 0x7F; + if (pt >= 64 && pt <= 95) return false; // RTCP (RFC 5761 §4) + const size_t cc = p[0] & 0x0F; + const bool ext = (p[0] & 0x10) != 0; + const bool pad = (p[0] & 0x20) != 0; + size_t hdr = 12 + 4 * cc; + if (n < hdr) return false; + if (ext) { + if (n < hdr + 4) return false; + const size_t words = (static_cast<size_t>(p[hdr + 2]) << 8) | p[hdr + 3]; + hdr += 4 + 4 * words; + if (n < hdr) return false; + } + size_t plen = n - hdr; + if (pad) { + const size_t padLen = p[n - 1]; + plen = padLen <= plen ? plen - padLen : 0; + } + v.pt = pt; + v.marker = (p[1] & 0x80) != 0; + v.seq = static_cast<uint16_t>((p[2] << 8) | p[3]); + v.ts = (static_cast<uint32_t>(p[4]) << 24) | (static_cast<uint32_t>(p[5]) << 16) | + (static_cast<uint32_t>(p[6]) << 8) | p[7]; + v.ssrc = (static_cast<uint32_t>(p[8]) << 24) | (static_cast<uint32_t>(p[9]) << 16) | + (static_cast<uint32_t>(p[10]) << 8) | p[11]; + v.payload = p + hdr; + v.payloadLen = plen; + v.padded = pad; + return true; +} + +// Sequence number unwrapping to a monotonic 64-bit value around the highest seen. +int64_t unwrapSeq(int64_t highestExt, uint16_t seq) { + const int16_t diff = static_cast<int16_t>(static_cast<uint16_t>(seq - static_cast<uint16_t>(highestExt))); + return highestExt + diff; +} + +// Accepts an SSRC change only after a few consecutive packets of the new SSRC, so a stray packet +// cannot flush the whole receiver. +struct SsrcLatch { + uint32_t ssrc = 0; + bool have = false; + uint32_t candidate = 0; + int candidateCount = 0; + + enum class Result { Same, First, Switched, Foreign }; + Result check(uint32_t s) { + if (!have) { + have = true; + ssrc = s; + candidateCount = 0; + return Result::First; + } + if (s == ssrc) { + candidateCount = 0; + return Result::Same; + } + if (s == candidate) { + if (++candidateCount >= 3) { + ssrc = s; + candidateCount = 0; + return Result::Switched; + } + } else { + candidate = s; + candidateCount = 1; + } + return Result::Foreign; + } +}; + +constexpr uint8_t kStartCode[4] = {0, 0, 0, 1}; +constexpr size_t kMaxAccessUnitBytes = 4u * 1024u * 1024u; +constexpr int64_t kMaxSeqGap = 1000; // larger forward jump = stream discontinuity +constexpr size_t kMaxBufferedPackets = 4096; // reorder buffer safety cap +constexpr size_t kMaxQueuedFrames = 16; // output queue cap (consumer stalled) +constexpr int kMaxNackRetries = 3; +constexpr size_t kMaxNacksPerCall = 256; +constexpr uint64_t kNackHistoryMs = 1000; // keep NACK send times for late RTT samples + +// H.264 helpers --------------------------------------------------------------------------------- + +uint8_t nalType(uint8_t b) { return b & 0x1F; } + +// First bit of a slice header after the NAL byte: first_mb_in_slice is ue(v); a leading '1' bit +// encodes 0, i.e. the slice starts a new picture. +bool sliceStartsPicture(const uint8_t* sliceAfterNalHeader, size_t n) { + return n > 0 && (sliceAfterNalHeader[0] & 0x80) != 0; +} + +// True if an RTP payload is the first packet of an access unit (as far as the payload tells). +bool payloadStartsAccessUnit(const uint8_t* p, size_t n) { + if (n == 0) return false; + const uint8_t t = nalType(p[0]); + if (t == 1 || t == 5) return sliceStartsPicture(p + 1, n - 1); + if (t >= 6 && t <= 9) return true; // SEI, SPS, PPS, AUD precede the slices of a picture + if (t == 24) { // STAP-A: look at the first aggregated NAL + if (n < 4) return false; + const size_t len = (static_cast<size_t>(p[1]) << 8) | p[2]; + if (len == 0 || 3 + len > n) return false; + return payloadStartsAccessUnit(p + 3, len); + } + if (t == 28) { // FU-A start fragment + if (n < 3 || !(p[1] & 0x80)) return false; + const uint8_t inner = nalType(p[1]); + if (inner == 1 || inner == 5) return sliceStartsPicture(p + 2, n - 2); + return inner >= 6 && inner <= 9; + } + return false; +} + +// True if the payload can begin a frame's packet run (single NAL, STAP-A or FU-A start). +bool isPartitionHead(const uint8_t* p, size_t n) { + if (n == 0) return false; + const uint8_t t = nalType(p[0]); + if (t == 28) return n > 1 && (p[1] & 0x80) != 0; + return t >= 1 && t <= 24; +} + +} // namespace + +// ================================================================================================= +// RtpStatsTracker +// ================================================================================================= + +RtpStatsTracker::RtpStatsTracker(const RtpStatsTracker& o) { *this = o; } + +RtpStatsTracker& RtpStatsTracker::operator=(const RtpStatsTracker& o) { + if (this == &o) return *this; + std::unique_lock<std::mutex> a(mu_, std::defer_lock), b(o.mu_, std::defer_lock); + std::lock(a, b); + st_ = o.st_; + init_ = o.init_; + maxSeq_ = o.maxSeq_; + cycles_ = o.cycles_; + baseSeq_ = o.baseSeq_; + received_ = o.received_; + expectedPrior_ = o.expectedPrior_; + receivedPrior_ = o.receivedPrior_; + badSeq_ = o.badSeq_; + transit_ = o.transit_; + jitter_ = o.jitter_; + haveTransit_ = o.haveTransit_; + lastSrLocalMs_ = o.lastSrLocalMs_; + haveSr_ = o.haveSr_; + return *this; +} + +void RtpStatsTracker::initSeq(uint16_t seq) { + baseSeq_ = seq; + maxSeq_ = seq; + badSeq_ = 0x10001; // RTP_SEQ_MOD + 1: never equal to a 16-bit seq + cycles_ = 0; + received_ = 0; + receivedPrior_ = 0; + expectedPrior_ = 0; +} + +void RtpStatsTracker::onPacket(uint32_t ssrc, uint16_t seq, uint32_t rtpTs, uint32_t clockRate, size_t bytes) { + onPacketAt(ssrc, seq, rtpTs, clockRate, bytes, nowUs()); +} + +void RtpStatsTracker::onPacketAt(uint32_t ssrc, uint16_t seq, uint32_t rtpTs, uint32_t clockRate, size_t bytes, + uint64_t arrivalUs) { + std::lock_guard<std::mutex> lk(mu_); + if (!init_ || ssrc != st_.ssrc) { + // First packet: keep a sender report that arrived before it (normal right after DTLS), + // so the first RRs already carry LSR/DLSR. A new SSRC on an initialised tracker starts + // from scratch: an SR of the previous source is meaningless now. + const bool keepSr = !init_ && haveSr_; + const uint32_t keptLsr = keepSr ? st_.lastSr : 0; + st_ = RtpReceiveStats{}; + st_.ssrc = ssrc; + st_.lastSr = keptLsr; + if (!keepSr) { + haveSr_ = false; + lastSrLocalMs_ = 0; + } + initSeq(seq); + init_ = true; + haveTransit_ = false; + jitter_ = 0; + } else { + // RFC 3550 A.1 update_seq (without the probation phase: xCloud is a trusted, single source). + constexpr uint16_t kMaxDropout = 3000; + constexpr uint16_t kMaxMisorder = 100; + const uint16_t udelta = static_cast<uint16_t>(seq - maxSeq_); + if (udelta < kMaxDropout) { + if (seq < maxSeq_) cycles_ += 0x10000; // wrapped + maxSeq_ = seq; + } else if (udelta <= 0x10000 - kMaxMisorder) { + // Very large jump: resync only if the next packet continues from here. + if (static_cast<uint32_t>(seq) == badSeq_) { + initSeq(seq); + } else { + badSeq_ = (static_cast<uint32_t>(seq) + 1) & 0xFFFF; + return; + } + } + // else: duplicate or reordered packet — counted below, does not move maxSeq_. + } + ++received_; + ++st_.packets; + st_.bytes += bytes; + + // RFC 3550 A.8 interarrival jitter, in RTP timestamp units. + if (clockRate > 0) { + const uint32_t arrival = static_cast<uint32_t>((arrivalUs * clockRate) / 1000000u); + const uint32_t transit = arrival - rtpTs; + if (haveTransit_) { + const int32_t d = static_cast<int32_t>(transit - static_cast<uint32_t>(transit_)); + const double ad = std::fabs(static_cast<double>(d)); + // Ignore absurd deltas (timestamp jumps on source restart). + if (ad < static_cast<double>(clockRate) * 10.0) jitter_ += (ad - jitter_) / 16.0; + } + transit_ = static_cast<double>(transit); + haveTransit_ = true; + } +} + +void RtpStatsTracker::onSenderReport(uint64_t ntpTimestamp) { onSenderReportAt(ntpTimestamp, rtpNowMs()); } + +void RtpStatsTracker::onSenderReportAt(uint64_t ntpTimestamp, uint64_t nowMs) { + std::lock_guard<std::mutex> lk(mu_); + st_.lastSr = static_cast<uint32_t>((ntpTimestamp >> 16) & 0xFFFFFFFFu); + lastSrLocalMs_ = nowMs; + haveSr_ = true; +} + +RtpReceiveStats RtpStatsTracker::snapshotForReport() { return snapshotForReportAt(rtpNowMs()); } + +RtpReceiveStats RtpStatsTracker::snapshotForReportAt(uint64_t nowMs) { + std::lock_guard<std::mutex> lk(mu_); + if (!init_) return st_; + const uint32_t extendedMax = cycles_ + maxSeq_; + const uint32_t expected = extendedMax - baseSeq_ + 1; + int64_t lost = static_cast<int64_t>(expected) - static_cast<int64_t>(received_); + lost = std::max<int64_t>(-0x800000, std::min<int64_t>(0x7FFFFF, lost)); // 24-bit signed + + const uint32_t expectedInterval = expected - expectedPrior_; + expectedPrior_ = expected; + const uint32_t receivedInterval = received_ - receivedPrior_; + receivedPrior_ = received_; + const int64_t lostInterval = static_cast<int64_t>(expectedInterval) - static_cast<int64_t>(receivedInterval); + uint8_t fraction = 0; + if (expectedInterval != 0 && lostInterval > 0) + fraction = static_cast<uint8_t>(std::min<int64_t>(255, (lostInterval << 8) / expectedInterval)); + + st_.fractionLost = fraction; + st_.cumulativeLost = static_cast<int32_t>(lost); + st_.extHighestSeq = extendedMax; + st_.jitter = static_cast<uint32_t>(jitter_); + if (haveSr_) { + // DLSR is in units of 1/65536 s (green-nx: getting this unit wrong pins xCloud's + // encoder at its starvation bitrate). + const uint64_t delta = nowMs >= lastSrLocalMs_ ? nowMs - lastSrLocalMs_ : 0; + st_.delaySinceLastSr = static_cast<uint32_t>(std::min<uint64_t>((delta << 16) / 1000u, 0xFFFFFFFFu)); + } else { + st_.lastSr = 0; + st_.delaySinceLastSr = 0; + } + return st_; +} + +bool RtpStatsTracker::hasData() const { + std::lock_guard<std::mutex> lk(mu_); + return init_; +} + +void RtpStatsTracker::reset() { + std::lock_guard<std::mutex> lk(mu_); + st_ = RtpReceiveStats{}; + init_ = false; + maxSeq_ = 0; + cycles_ = baseSeq_ = received_ = expectedPrior_ = receivedPrior_ = 0; + badSeq_ = 0x10001; + transit_ = jitter_ = 0; + haveTransit_ = false; + lastSrLocalMs_ = 0; + haveSr_ = false; +} + +// ================================================================================================= +// RtpH264Depacketizer +// ================================================================================================= + +struct RtpH264Depacketizer::Impl { + struct Packet { + uint32_t ts = 0; + bool marker = false; + bool padding = false; // no media payload (bandwidth probe); consumes a seq only + std::vector<uint8_t> payload; + }; + struct Missing { + uint64_t detectedMs = 0; + uint64_t lastNackMs = 0; + int nacks = 0; + }; + struct Frame { + std::vector<uint8_t> au; + uint32_t ts = 0; + bool key = false; + }; + + mutable std::mutex mu; + RtpStatsTracker stats; + uint8_t ptFilter = 0; + SsrcLatch latch; + + bool haveSeq = false; + int64_t highestExt = 0; // highest extended seq received + int64_t nextExt = 0; // first extended seq not yet consumed + std::map<int64_t, Packet> buf; + std::map<int64_t, Missing> missing; + std::map<int64_t, uint64_t> nackHistory; // given-up seqs that were NACKed -> last NACK time + int backJumps = 0; + + std::deque<Frame> out; + bool waitingKey = true; + bool keyPending = false; + uint64_t lastKeyRequestMs = 0; + bool keyClockStarted = false; + + bool haveLastTs = false; + uint32_t lastEmittedTs = 0; + bool haveDropTs = false; // timestamp of the last frame given up on: late packets of it are junk + uint32_t dropTs = 0; + uint32_t frameInterval = 0; // smoothed RTP ts delta between consecutive frames + + double srttMs = 0; // NACK round-trip estimate + uint32_t minWaitMs = 40, maxWaitMs = 250, keyRetryMs = 1000; + RtpVideoCounters c; + + uint64_t lossWaitMs() const { + double w = minWaitMs; + if (srttMs > 0) w = std::max(w, srttMs * 1.5 + 10.0); + return static_cast<uint64_t>(std::min<double>(w, maxWaitMs)); + } + uint64_t nackRetryMs() const { + return srttMs > 0 ? static_cast<uint64_t>(std::max(15.0, srttMs * 1.2)) : 30; + } + + void rttSample(uint64_t sampleMs) { + if (sampleMs > 2000) return; + const double s = static_cast<double>(sampleMs); + srttMs = srttMs <= 0 ? s : srttMs * 0.875 + s * 0.125; + } + + void requireKeyframe() { + if (!waitingKey) XC_LOGD("rtp: video loss, waiting for IDR"); + waitingKey = true; + keyPending = true; + } + + void clearStream() { + buf.clear(); + missing.clear(); + nackHistory.clear(); + haveSeq = false; + backJumps = 0; + haveLastTs = false; + haveDropTs = false; + } + + void resetAll() { + clearStream(); + out.clear(); + stats.reset(); + latch = SsrcLatch{}; + waitingKey = true; + keyPending = false; + keyClockStarted = false; + frameInterval = 0; + srttMs = 0; + c = RtpVideoCounters{}; + } + + void retireMissingBefore(int64_t ext) { + while (!missing.empty() && missing.begin()->first < ext) { + if (missing.begin()->second.nacks > 0) nackHistory[missing.begin()->first] = missing.begin()->second.lastNackMs; + missing.erase(missing.begin()); + } + while (!buf.empty() && buf.begin()->first < ext) buf.erase(buf.begin()); + } + + void pruneNackHistory(uint64_t nowMs) { + for (auto it = nackHistory.begin(); it != nackHistory.end();) { + if (nowMs - it->second > kNackHistoryMs || nackHistory.size() > 2048) it = nackHistory.erase(it); + else ++it; + } + } + + void push(const uint8_t* pkt, size_t n, uint64_t nowMs) { + RtpView v; + if (!parseRtp(pkt, n, v)) return; + if (ptFilter && v.pt != ptFilter) return; + + switch (latch.check(v.ssrc)) { + case SsrcLatch::Result::Foreign: + return; + case SsrcLatch::Result::Switched: + XC_LOGI("rtp: video SSRC changed to %08x, resyncing", v.ssrc); + clearStream(); + out.clear(); + stats.reset(); + requireKeyframe(); + break; + case SsrcLatch::Result::First: + XC_LOGD("rtp: video SSRC %08x", v.ssrc); + break; + case SsrcLatch::Result::Same: + break; + } + if (!keyClockStarted) { + keyClockStarted = true; + lastKeyRequestMs = nowMs; + } + ++c.packets; + stats.onPacketAt(v.ssrc, v.seq, v.ts, 90000, v.payloadLen, nowMs * 1000); + + int64_t ext; + if (!haveSeq) { + haveSeq = true; + ext = static_cast<int64_t>(v.seq) + (int64_t(1) << 20); + highestExt = ext; + nextExt = ext; + } else { + ext = unwrapSeq(highestExt, v.seq); + } + + if (ext < nextExt) { + // Late (its slot was already consumed or given up) or duplicate. + ++c.lateOrDuplicate; + auto h = nackHistory.find(ext); + if (h != nackHistory.end()) { + rttSample(nowMs - h->second); + nackHistory.erase(h); + } + if (ext < nextExt - kMaxSeqGap) { + // Sender restarted its sequence numbering backwards: resync after a few packets. + if (++backJumps >= 3) { + XC_LOGI("rtp: video sequence jumped backwards, resyncing"); + clearStream(); + requireKeyframe(); + push(pkt, n, nowMs); + } + } + return; + } + backJumps = 0; + if (buf.count(ext)) { + ++c.lateOrDuplicate; + return; + } + + if (ext > highestExt) { + const int64_t gap = ext - highestExt - 1; + if (gap > kMaxSeqGap) { + XC_LOGI("rtp: video sequence discontinuity (%lld packets), resyncing", static_cast<long long>(gap)); + clearStream(); + requireKeyframe(); + haveSeq = true; + highestExt = nextExt = ext; + } else { + for (int64_t s = highestExt + 1; s < ext; ++s) missing[s] = Missing{nowMs, 0, 0}; + highestExt = ext; + } + } else { + auto m = missing.find(ext); + if (m != missing.end()) { + if (m->second.nacks > 0) { + ++c.recovered; + rttSample(nowMs - m->second.lastNackMs); + } + missing.erase(m); + } + } + + Packet p; + p.ts = v.ts; + p.marker = v.marker; + p.padding = v.payloadLen == 0; + if (!p.padding) p.payload.assign(v.payload, v.payload + v.payloadLen); + buf.emplace(ext, std::move(p)); + + if (buf.size() > kMaxBufferedPackets) { + XC_LOGW("rtp: video reorder buffer overflow, dropping"); + retireMissingBefore(highestExt + 1); + nextExt = highestExt + 1; + ++c.framesDropped; + requireKeyframe(); + } + drain(nowMs); + pruneNackHistory(nowMs); + } + + // Build an access unit from buf[first..last]. False if the payload sequence is malformed. + bool assemble(int64_t first, int64_t last, Frame& f) { + f.au.clear(); + f.key = false; + bool fuOpen = false; + bool sawHead = false; + for (int64_t e = first; e <= last; ++e) { + auto it = buf.find(e); + if (it == buf.end()) return false; + const Packet& pk = it->second; + if (pk.padding) continue; + const uint8_t* p = pk.payload.data(); + const size_t n = pk.payload.size(); + if (!sawHead) { + if (!isPartitionHead(p, n)) return false; + sawHead = true; + f.ts = pk.ts; + } + const uint8_t t = nalType(p[0]); + if (t >= 1 && t <= 23) { + if (fuOpen) return false; // FU-A without end fragment + f.au.insert(f.au.end(), kStartCode, kStartCode + 4); + f.au.insert(f.au.end(), p, p + n); + if (t == 5) f.key = true; + } else if (t == 24) { // STAP-A + if (fuOpen) return false; + size_t i = 1; + while (i + 2 <= n) { + const size_t len = (static_cast<size_t>(p[i]) << 8) | p[i + 1]; + i += 2; + if (len == 0 || i + len > n) return false; + f.au.insert(f.au.end(), kStartCode, kStartCode + 4); + f.au.insert(f.au.end(), p + i, p + i + len); + if (nalType(p[i]) == 5) f.key = true; + i += len; + } + } else if (t == 28) { // FU-A + if (n < 2) return false; + const bool start = (p[1] & 0x80) != 0, end = (p[1] & 0x40) != 0; + if (start) { + if (fuOpen) return false; + fuOpen = true; + f.au.insert(f.au.end(), kStartCode, kStartCode + 4); + f.au.push_back(static_cast<uint8_t>((p[0] & 0xE0) | (p[1] & 0x1F))); + if (nalType(p[1]) == 5) f.key = true; + } else if (!fuOpen) { + return false; + } + f.au.insert(f.au.end(), p + 2, p + n); + if (end) fuOpen = false; + } else { + // STAP-B / MTAP / FU-B are not allowed in packetization-mode=1; skip reserved types. + continue; + } + if (f.au.size() > kMaxAccessUnitBytes) return false; + } + if (fuOpen) return false; + return sawHead && !f.au.empty(); + } + + void emit(Frame&& f) { + if (haveLastTs) { + const uint32_t d = f.ts - lastEmittedTs; + if (d > 0 && d < 90000) frameInterval = frameInterval ? (frameInterval * 7 + d) / 8 : d; + } + haveLastTs = true; + lastEmittedTs = f.ts; + if (waitingKey) { + if (!f.key) { + ++c.framesDropped; + return; + } + waitingKey = false; + XC_LOGD("rtp: IDR received, video output resumes"); + } + if (out.size() >= kMaxQueuedFrames) { + // Consumer stalled: everything queued is now useless as reference chain. + XC_LOGW("rtp: video frame queue overflow (%zu), dropping and waiting for IDR", out.size()); + c.framesDropped += out.size() + 1; + out.clear(); + requireKeyframe(); + return; + } + ++c.framesEmitted; + out.push_back(std::move(f)); + } + + // Consume packets [nextExt..last] and continue after them. + void consumeThrough(int64_t last) { + retireMissingBefore(last + 1); + nextExt = last + 1; + } + + // Give up on the loss blocking the head of the buffer. gapExt = first missing seq. + // brokenFrameStart = ext of the incomplete frame's first packet, or -1 if the gap is at the head. + void giveUp(int64_t gapExt, bool frameStartedBeforeGap) { + // First present packet after the gap. + auto p = buf.upper_bound(gapExt); + if (p == buf.end()) { + // Cannot happen (highestExt is always present), but stay safe. + consumeThrough(highestExt); + requireKeyframe(); + return; + } + // Skip leading padding to find the real next media packet. + auto media = p; + while (media != buf.end() && media->second.padding) ++media; + + if (frameStartedBeforeGap) { + // The frame at the head is broken: drop all its packets (same timestamp), plus anything + // up to the next frame start. + const uint32_t brokenTs = buf.begin()->second.ts; + haveDropTs = true; + dropTs = brokenTs; + int64_t last = gapExt; + for (auto it = buf.begin(); it != buf.end(); ++it) { + if (it->first > gapExt && !it->second.padding && it->second.ts != brokenTs) break; + last = it->first; + } + ++c.framesDropped; + consumeThrough(last); + requireKeyframe(); + return; + } + + // Gap at the head: lost packets were padding, a whole frame, or the start of the next frame. + if (media == buf.end()) { + // Only padding follows; nothing decodable lost so far. + consumeThrough(std::prev(buf.end())->first); + return; + } + const Packet& mp = media->second; + const bool clean = payloadStartsAccessUnit(mp.payload.data(), mp.payload.size()); + if (clean) { + // Was a whole frame lost? Compare the timestamp step with the usual frame interval. + bool frameLost = false; + if (haveLastTs && frameInterval > 0) { + const uint32_t step = mp.ts - lastEmittedTs; + frameLost = step > frameInterval + frameInterval / 2; + } + nextExt = media->first; + retireMissingBefore(nextExt); + if (frameLost) { + ++c.framesDropped; + requireKeyframe(); + } + return; + } + // The next frame lost its beginning: drop it entirely. + const uint32_t brokenTs = mp.ts; + haveDropTs = true; + dropTs = brokenTs; + int64_t last = media->first; + for (auto it = media; it != buf.end(); ++it) { + if (!it->second.padding && it->second.ts != brokenTs) break; + last = it->first; + } + ++c.framesDropped; + consumeThrough(last); + requireKeyframe(); + } + + void drain(uint64_t nowMs) { + for (int guard = 0; guard < 100000; ++guard) { + // Leading padding packets are consumed without further ado. + auto head = buf.find(nextExt); + while (head != buf.end() && head->second.padding) { + consumeThrough(nextExt); + head = buf.find(nextExt); + } + if (nextExt > highestExt) return; // all consumed + if (head == buf.end()) { + // Head missing. + auto m = missing.find(nextExt); + const uint64_t since = m != missing.end() ? m->second.detectedMs : nowMs; + if (nowMs - since >= lossWaitMs()) { + giveUp(nextExt, false); + continue; + } + return; + } + // Remaining packets of a frame that was already given up on. + if (haveDropTs && head->second.ts == dropTs) { + consumeThrough(nextExt); + continue; + } + // Scan the frame starting at nextExt. + const uint32_t ts = head->second.ts; + int64_t e = nextExt; + enum { Complete, Gap, Pending } state = Pending; + int64_t gapAt = 0; + for (;;) { + auto it = buf.find(e); + const Packet& pk = it->second; + if (!pk.padding && pk.marker) { + state = Complete; + break; + } + const int64_t nx = e + 1; + if (nx > highestExt) { + state = Pending; + break; + } + auto nit = buf.find(nx); + if (nit == buf.end()) { + state = Gap; + gapAt = nx; + break; + } + if (!nit->second.padding && nit->second.ts != ts) { + state = Complete; // timestamp changed without marker: frame ended at e + break; + } + e = nx; + } + if (state == Pending) return; + if (state == Gap) { + auto m = missing.find(gapAt); + const uint64_t since = m != missing.end() ? m->second.detectedMs : nowMs; + if (nowMs - since >= lossWaitMs()) { + giveUp(gapAt, true); + continue; + } + return; + } + // Complete frame [nextExt..e]; trailing padding with the same ts is consumed later. + Frame f; + const bool ok = assemble(nextExt, e, f); + consumeThrough(e); + if (!ok) { + ++c.framesDropped; + requireKeyframe(); + continue; + } + emit(std::move(f)); + } + } + + std::vector<uint16_t> takeNacks(uint64_t nowMs) { + std::vector<uint16_t> outSeqs; + const uint64_t retry = nackRetryMs(); + const uint64_t useful = std::max<uint64_t>(lossWaitMs(), 1); + for (auto& kv : missing) { + if (outSeqs.size() >= kMaxNacksPerCall) break; + Missing& m = kv.second; + if (nowMs - m.detectedMs >= useful + retry) continue; // too late to help + const bool due = m.nacks == 0 || (m.nacks < kMaxNackRetries && nowMs - m.lastNackMs >= retry); + if (!due) continue; + ++m.nacks; + m.lastNackMs = nowMs; + outSeqs.push_back(static_cast<uint16_t>(kv.first & 0xFFFF)); + } + c.nacksSent += outSeqs.size(); + return outSeqs; + } +}; + +RtpH264Depacketizer::RtpH264Depacketizer() : d_(new Impl) {} +RtpH264Depacketizer::~RtpH264Depacketizer() = default; + +void RtpH264Depacketizer::push(const uint8_t* pkt, size_t n) { pushAt(pkt, n, rtpNowMs()); } + +void RtpH264Depacketizer::pushAt(const uint8_t* pkt, size_t n, uint64_t nowMs) { + std::lock_guard<std::mutex> lk(d_->mu); + d_->push(pkt, n, nowMs); +} + +bool RtpH264Depacketizer::popFrame(std::vector<uint8_t>& annexB, uint32_t& rtpTs, bool& isKeyframe) { + std::lock_guard<std::mutex> lk(d_->mu); + if (d_->out.empty()) { + annexB.clear(); + rtpTs = 0; + isKeyframe = false; + return false; + } + Impl::Frame& f = d_->out.front(); + annexB.swap(f.au); + rtpTs = f.ts; + isKeyframe = f.key; + d_->out.pop_front(); + return true; +} + +std::vector<uint16_t> RtpH264Depacketizer::takeNackList() { return takeNackListAt(rtpNowMs()); } + +std::vector<uint16_t> RtpH264Depacketizer::takeNackListAt(uint64_t nowMs) { + std::lock_guard<std::mutex> lk(d_->mu); + // Time also advances without packets: give up on stale losses so needKeyframe() fires. + if (d_->haveSeq) d_->drain(nowMs); + return d_->takeNacks(nowMs); +} + +bool RtpH264Depacketizer::needKeyframe() { return needKeyframeAt(rtpNowMs()); } + +bool RtpH264Depacketizer::needKeyframeAt(uint64_t nowMs) { + std::lock_guard<std::mutex> lk(d_->mu); + if (d_->haveSeq) d_->drain(nowMs); + bool need = false; + if (d_->keyPending) { + need = true; + } else if (d_->waitingKey && d_->keyClockStarted && nowMs - d_->lastKeyRequestMs >= d_->keyRetryMs) { + need = true; // PLI lost or ignored: ask again + } + if (need) { + d_->keyPending = false; + d_->lastKeyRequestMs = nowMs; + ++d_->c.keyframeRequests; + } + return need; +} + +RtpStatsTracker& RtpH264Depacketizer::stats() { return d_->stats; } + +void RtpH264Depacketizer::reset() { + std::lock_guard<std::mutex> lk(d_->mu); + d_->resetAll(); +} + +void RtpH264Depacketizer::setLossWait(uint32_t minLossWaitMs, uint32_t maxLossWaitMs) { + std::lock_guard<std::mutex> lk(d_->mu); + d_->minWaitMs = minLossWaitMs; + d_->maxWaitMs = std::max(minLossWaitMs, maxLossWaitMs); +} + +void RtpH264Depacketizer::setKeyframeRetryMs(uint32_t ms) { + std::lock_guard<std::mutex> lk(d_->mu); + d_->keyRetryMs = std::max<uint32_t>(ms, 50); +} + +void RtpH264Depacketizer::setPayloadType(uint8_t pt) { + std::lock_guard<std::mutex> lk(d_->mu); + d_->ptFilter = pt & 0x7F; +} + +RtpVideoCounters RtpH264Depacketizer::counters() const { + std::lock_guard<std::mutex> lk(d_->mu); + RtpVideoCounters c = d_->c; + c.rttMs = static_cast<uint32_t>(d_->srttMs + 0.5); + c.waitingForKeyframe = d_->waitingKey; + return c; +} + +uint32_t RtpH264Depacketizer::ssrc() const { + std::lock_guard<std::mutex> lk(d_->mu); + return d_->latch.have ? d_->latch.ssrc : 0; +} + +// ================================================================================================= +// RtpOpusReceiver +// ================================================================================================= + +struct RtpOpusReceiver::Impl { + struct Packet { + uint32_t ts = 0; + uint64_t arrivalMs = 0; + std::vector<uint8_t> payload; + }; + + // Small on purpose: the player's ~60 ms prebuffer absorbs it, so a swapped pair is played in + // order without adding steady-state latency. + static constexpr size_t kReorderPackets = 2; // packets buffered past a gap before skipping it + static constexpr uint64_t kReorderMs = 20; // or this long since the first packet past the gap + static constexpr size_t kMaxBuffered = 256; + static constexpr int64_t kMaxGap = 500; + + mutable std::mutex mu; + RtpStatsTracker stats; + SsrcLatch latch; + bool haveSeq = false; + int64_t highestExt = 0, nextExt = 0; + std::map<int64_t, Packet> buf; + int pendingLost = 0; // lost count carried over an empty (DTX/padding) packet + int backJumps = 0; + + void clearStream() { + buf.clear(); + haveSeq = false; + pendingLost = 0; + backJumps = 0; + } + + void push(const uint8_t* pkt, size_t n, uint64_t nowMs) { + RtpView v; + if (!parseRtp(pkt, n, v)) return; + switch (latch.check(v.ssrc)) { + case SsrcLatch::Result::Foreign: + return; + case SsrcLatch::Result::Switched: + XC_LOGI("rtp: audio SSRC changed to %08x", v.ssrc); + clearStream(); + stats.reset(); + break; + default: + break; + } + stats.onPacketAt(v.ssrc, v.seq, v.ts, 48000, v.payloadLen, nowMs * 1000); + + int64_t ext; + if (!haveSeq) { + haveSeq = true; + ext = static_cast<int64_t>(v.seq) + (int64_t(1) << 20); + highestExt = nextExt = ext; + } else { + ext = unwrapSeq(highestExt, v.seq); + } + if (ext < nextExt) { + if (ext < nextExt - kMaxGap && ++backJumps >= 3) { + clearStream(); + push(pkt, n, nowMs); + } + return; // late or duplicate + } + backJumps = 0; + if (ext > highestExt) { + if (ext - highestExt > kMaxGap) { + // Discontinuity: deliver what we have as-is, restart after it. + buf.clear(); + pendingLost = 0; + nextExt = ext; + } + highestExt = ext; + } + if (buf.count(ext)) return; + Packet p; + p.ts = v.ts; + p.arrivalMs = nowMs; + p.payload.assign(v.payload, v.payload + v.payloadLen); + buf.emplace(ext, std::move(p)); + while (buf.size() > kMaxBuffered) { + // Consumer is not popping: drop the oldest. + nextExt = buf.begin()->first + 1; + buf.erase(buf.begin()); + pendingLost = 0; + } + } + + bool pop(std::vector<uint8_t>& opus, uint32_t& rtpTs, int& lostBefore, uint64_t nowMs) { + while (!buf.empty()) { + auto it = buf.begin(); + int lost = 0; + if (it->first != nextExt) { + const bool skip = buf.size() >= kReorderPackets || nowMs - it->second.arrivalMs >= kReorderMs; + if (!skip) return false; + lost = static_cast<int>(std::min<int64_t>(it->first - nextExt, INT_MAX / 2)); + } + nextExt = it->first + 1; + Packet p = std::move(it->second); + buf.erase(it); + const int total = lost + pendingLost; + if (p.payload.empty()) { + pendingLost = total; // DTX/padding: nothing to decode, keep loss count for the next + continue; + } + pendingLost = 0; + opus.swap(p.payload); + rtpTs = p.ts; + lostBefore = total; + return true; + } + return false; + } +}; + +RtpOpusReceiver::RtpOpusReceiver() : d_(new Impl) {} +RtpOpusReceiver::~RtpOpusReceiver() = default; + +void RtpOpusReceiver::push(const uint8_t* pkt, size_t n) { pushAt(pkt, n, rtpNowMs()); } + +void RtpOpusReceiver::pushAt(const uint8_t* pkt, size_t n, uint64_t nowMs) { + std::lock_guard<std::mutex> lk(d_->mu); + d_->push(pkt, n, nowMs); +} + +bool RtpOpusReceiver::pop(std::vector<uint8_t>& opus, uint32_t& rtpTs, int& lostBefore) { + return popAt(opus, rtpTs, lostBefore, rtpNowMs()); +} + +bool RtpOpusReceiver::popAt(std::vector<uint8_t>& opus, uint32_t& rtpTs, int& lostBefore, uint64_t nowMs) { + std::lock_guard<std::mutex> lk(d_->mu); + if (d_->pop(opus, rtpTs, lostBefore, nowMs)) return true; + opus.clear(); + rtpTs = 0; + lostBefore = 0; + return false; +} + +RtpStatsTracker& RtpOpusReceiver::stats() { return d_->stats; } + +void RtpOpusReceiver::reset() { + std::lock_guard<std::mutex> lk(d_->mu); + d_->clearStream(); + d_->stats.reset(); + d_->latch = SsrcLatch{}; +} + +size_t RtpOpusReceiver::buffered() const { + std::lock_guard<std::mutex> lk(d_->mu); + return d_->buf.size(); +} + +uint32_t RtpOpusReceiver::ssrc() const { + std::lock_guard<std::mutex> lk(d_->mu); + return d_->latch.have ? d_->latch.ssrc : 0; +} + +} // namespace xc diff --git a/src/stream/rtp.hpp b/src/stream/rtp.hpp new file mode 100644 index 0000000..39796f8 --- /dev/null +++ b/src/stream/rtp.hpp @@ -0,0 +1,168 @@ +// Nubix — RTP receive side: H.264 depacketizer (RFC 6184: single NAL, STAP-A, FU-A) with +// reorder buffer, loss tracking and NACK list; Opus RTP receiver; RFC 3550 receiver statistics. +// +// Loss handling follows the lessons documented in green-nx (src/switch/stream/video_jitter.cpp, +// GPL-3.0): complete frames are emitted immediately, incomplete ones are held only while a +// retransmission can still arrive, RTP padding packets are not loss, and after an unrecoverable +// loss nothing but a real IDR access unit is passed to the decoder. +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <mutex> +#include <vector> + +namespace xc { + +// Receiver-side statistics for one SSRC, as needed for an RTCP RR report block (RFC 3550 §6.4.1). +struct RtpReceiveStats { + uint32_t ssrc = 0; + uint8_t fractionLost = 0; // since previous report, fixed point /256 + int32_t cumulativeLost = 0; // 24-bit signed on the wire + uint32_t extHighestSeq = 0; // cycles << 16 | highest seq + uint32_t jitter = 0; // interarrival jitter in RTP timestamp units + uint32_t lastSr = 0; // middle 32 bits of the last SR NTP timestamp (LSR) + uint32_t delaySinceLastSr = 0; // DLSR in 1/65536 s + uint64_t packets = 0, bytes = 0; +}; + +// Monotonic milliseconds used by the RTP classes when no explicit time is passed. +uint64_t rtpNowMs(); + +// RFC 3550 Appendix A.1/A.8 sequence + jitter tracker, shared by video and audio receivers. +// Thread-safe (internally locked). +class RtpStatsTracker { +public: + RtpStatsTracker() = default; + // Copyable / assignable (state is copied under the source's lock; the mutex is not). + RtpStatsTracker(const RtpStatsTracker& o); + RtpStatsTracker& operator=(const RtpStatsTracker& o); + + // Account one received RTP packet (seq, rtp timestamp, clockRate Hz, payload bytes). + void onPacket(uint32_t ssrc, uint16_t seq, uint32_t rtpTs, uint32_t clockRate, size_t bytes); + // Same, with an explicit arrival time in microseconds (monotonic; for tests / replay). + void onPacketAt(uint32_t ssrc, uint16_t seq, uint32_t rtpTs, uint32_t clockRate, size_t bytes, + uint64_t arrivalUs); + // Record an incoming RTCP sender report (NTP timestamp) to fill LSR/DLSR. + void onSenderReport(uint64_t ntpTimestamp); + void onSenderReportAt(uint64_t ntpTimestamp, uint64_t nowMs); + // Snapshot for an RR block; resets the "since last report" interval counters. + RtpReceiveStats snapshotForReport(); + RtpReceiveStats snapshotForReportAt(uint64_t nowMs); + // True once at least one packet was accounted. + bool hasData() const; + // Forget everything (new SSRC / reconnect). + void reset(); + +private: + void initSeq(uint16_t seq); + + mutable std::mutex mu_; + RtpReceiveStats st_; + bool init_ = false; + uint16_t maxSeq_ = 0; + uint32_t cycles_ = 0, baseSeq_ = 0, received_ = 0, expectedPrior_ = 0, receivedPrior_ = 0; + uint32_t badSeq_ = 0x10001; + double transit_ = 0, jitter_ = 0; + bool haveTransit_ = false; + uint64_t lastSrLocalMs_ = 0; + bool haveSr_ = false; +}; + +// Counters of the H.264 depacketizer (monotonic since construction / reset()). +struct RtpVideoCounters { + uint64_t packets = 0; // RTP packets accepted (incl. padding) + uint64_t framesEmitted = 0; // complete access units handed out + uint64_t framesDropped = 0; // incomplete or non-decodable access units discarded + uint64_t nacksSent = 0; // sequence numbers returned by takeNackList (incl. retries) + uint64_t recovered = 0; // NACKed packets that arrived in time + uint64_t lateOrDuplicate = 0; // packets that arrived after their slot was consumed + uint64_t keyframeRequests = 0; // times needKeyframe() returned true + uint32_t rttMs = 0; // NACK round-trip estimate (0 = none yet) + bool waitingForKeyframe = true; +}; + +class RtpH264Depacketizer { +public: + RtpH264Depacketizer(); + ~RtpH264Depacketizer(); + + // Push one raw RTP packet (header included). Packets may arrive out of order; they are + // reordered in a small window. Complete access units are emitted per marker bit / timestamp change. + void push(const uint8_t* pkt, size_t n); + // Same with explicit monotonic time in ms (tests / replay). Do not mix clocks. + void pushAt(const uint8_t* pkt, size_t n, uint64_t nowMs); + + // Pop the next complete access unit as Annex-B (00 00 00 01 start codes). isKeyframe = contains IDR. + bool popFrame(std::vector<uint8_t>& annexB, uint32_t& rtpTs, bool& isKeyframe); + + // Sequence numbers detected missing since the last call (for RTCP NACK). Clears the list. + // Missing packets are re-requested a limited number of times while still useful. + std::vector<uint16_t> takeNackList(); + std::vector<uint16_t> takeNackListAt(uint64_t nowMs); + + // True once after unrecoverable loss (frame dropped); caller should send PLI / keyframe request. + // While still waiting for an IDR it becomes true again every keyframeRetryMs (default 1000). + bool needKeyframe(); + bool needKeyframeAt(uint64_t nowMs); + + // Receiver statistics (for RTCP RR). + RtpStatsTracker& stats(); + + // Drop all buffered state (e.g. after reconnect). + void reset(); + + // Tuning: minimum time an incomplete frame is held for retransmission (default 40 ms; the + // effective value grows with the measured NACK round trip, capped at maxLossWaitMs), + // maximum hold (default 250 ms) and keyframe re-request interval (default 1000 ms). + void setLossWait(uint32_t minLossWaitMs, uint32_t maxLossWaitMs); + void setKeyframeRetryMs(uint32_t ms); + // Payload type filter (0 = accept any, default 0). Packets with another PT are ignored. + void setPayloadType(uint8_t pt); + + // Counters snapshot. + RtpVideoCounters counters() const; + // Latched media SSRC (0 = none yet). + uint32_t ssrc() const; + +private: + struct Impl; + std::unique_ptr<Impl> d_; +}; + +// Extracts Opus payloads from RTP, with a short reorder window and loss detection. +class RtpOpusReceiver { +public: + RtpOpusReceiver(); + ~RtpOpusReceiver(); + + // Push one raw RTP packet (header included). + void push(const uint8_t* pkt, size_t n); + void pushAt(const uint8_t* pkt, size_t n, uint64_t nowMs); + + // Pop the next Opus packet in sequence order. lostBefore = number of packets missing + // immediately before this one (decoder should run PLC/FEC for them). + bool pop(std::vector<uint8_t>& opus, uint32_t& rtpTs, int& lostBefore); + bool popAt(std::vector<uint8_t>& opus, uint32_t& rtpTs, int& lostBefore, uint64_t nowMs); + + // Receiver statistics (for RTCP RR). + RtpStatsTracker& stats(); + + // Drop all buffered state. + void reset(); + + // Number of packets currently buffered (in order or ahead of a gap). + size_t buffered() const; + // Latched media SSRC (0 = none yet). + uint32_t ssrc() const; + +private: + struct Impl; + std::unique_ptr<Impl> d_; +}; + +} // namespace xc diff --git a/src/stream/streamer.cpp b/src/stream/streamer.cpp new file mode 100644 index 0000000..7d34c37 --- /dev/null +++ b/src/stream/streamer.cpp @@ -0,0 +1,1935 @@ +// Nubix — stream session orchestration. +// +// Session flow, watchdogs and recovery heuristics are ported from green-nx +// (src/switch/stream/engine.cpp, GPL-3.0, (c) the green-nx authors): the +// gssv retry ladder (home console wake-up / registration, dead media path), +// remote ICE gathering that waits for a real (priority > 1000) candidate, +// the off-thread keepalive with worker-stall watchdog, media/decode stall +// watchdogs, input-channel death detection with same-session re-signaling, +// input revive after media disruption, handshake / TransactionStart handling, +// serverInitiatedDisconnect and rumble. The libpeer socket pump of the +// original is replaced by libdatachannel's own threads (see WebRtc). +// +// Threads (all owned by Streamer::Impl): +// worker — signaling state machine + per-transport event loop/watchdogs +// decode — consumes depacketized access units, feeds VideoDecoder +// input — 125 Hz gamepad sender (contiguous sequence numbers) +// rtcp — NACK flush (20 ms), RR + REMB + stats (1 s) +// keepalive — gssv /keepalive (per session), worker-stall watchdog +// libdatachannel callbacks never block on anything the worker holds while +// calling into WebRtc: they only push events, feed the depacketizer and the +// audio player, or record rumble. +// +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "stream/streamer.hpp" + +#include <algorithm> +#include <atomic> +#include <chrono> +#include <condition_variable> +#include <cstdio> +#include <deque> +#include <functional> +#include <mutex> +#include <set> +#include <sstream> +#include <thread> +#include <vector> + +#include <nlohmann/json.hpp> + +extern "C" { +#include <libavutil/frame.h> +} + +#include "core/http.hpp" +#include "core/log.hpp" +#include "platform/platform.hpp" +#include "stream/audio.hpp" +#include "stream/rtp.hpp" +#include "stream/video.hpp" +#include "stream/webrtc.hpp" + +namespace xc { + +const char* streamStateName(StreamState s) { + switch (s) { + case StreamState::Idle: return "Idle"; + case StreamState::Starting: return "Starting"; + case StreamState::Queued: return "Queued"; + case StreamState::Provisioning: return "Provisioning"; + case StreamState::Connecting: return "Connecting"; + case StreamState::Streaming: return "Streaming"; + case StreamState::Ended: return "Ended"; + case StreamState::Failed: return "Failed"; + } + return "?"; +} + +namespace { + +using json = nlohmann::json; + +// ---- tunables (values from green-nx unless noted) --------------------------- +// Decode queue limits. Every queued AU is latency (the newest decoded frame is shown), so a +// backlog is flushed and resynced on an IDR instead of being worked off: hard cap ~100 ms at +// 60 fps, or a soft backlog of kSoftQueuedVideo AUs whose oldest has waited kSoftQueueMs. +constexpr size_t kMaxQueuedVideo = 6; +constexpr size_t kSoftQueuedVideo = 3; +constexpr uint64_t kSoftQueueMs = 60; +constexpr uint64_t kDecodeReportMs = 10000; // decoder speed check window +constexpr double kFrameIntervalMs = 1000.0 / 60.0; +constexpr long kQuitDeleteTimeoutSec = 5; // final DELETE when the user stops / app exits +constexpr long kDeleteTimeoutSec = 20; +constexpr int kInputPeriodMs = 8; // 125 Hz input cadence +constexpr int kInputHeartbeatMs = 50; // resend unchanged pad state (ARCHITECTURE.md §5) +constexpr int kInputBackoffMs = 100; // after a refused SCTP send +constexpr int kKeyframeMinIntervalMs = 1000; // PLI + control request rate limit +constexpr int kRtcpTickMs = 20; // NACK flush cadence +constexpr int kReportIntervalMs = 1000; // RR + REMB + stats +constexpr int kGatherTimeoutMs = 5000; // local ICE gathering +constexpr int kSdpAnswerTimeoutMs = 60000; // green-nx: 120 polls x 500 ms +constexpr int kRemoteIceTimeoutMs = 15000; // green-nx gather deadline (home consoles: Teredo) +constexpr int kRemoteIceCloudTimeoutMs = 4000; // cloud: the first answer already carries usable candidates +constexpr int kRemoteIcePollMs = 300; +constexpr int kRemoteIceQuietPolls = 4; // no new candidates for 4 polls -> complete +constexpr unsigned long kRealCandidatePriority = 1000; // placeholder front candidate has 100 +constexpr int kDeadMediaPathMs = 12000; // ICE up, channels never open +constexpr int kNegotiationTimeoutMs = 45000; // remote set -> handshake complete +constexpr int kFirstFrameTimeoutMs = 30000; // handshake -> first decoded frame +constexpr int kChannelsReadyGraceMs = 5000; // HandshakeAck -> control/input open +constexpr int kDisconnectGraceMs = 5000; // "disconnected" may recover (ICE consent) +constexpr int kMediaStallMs = 10000; // no RTP at all +constexpr int kDecodeResyncMs = 5000; // no decoded frame -> depacketizer reset + IDR +constexpr int kDecodeStallMs = 15000; // no decoded frame -> give up +constexpr int kWorkerStallMs = 10000; // worker event loop wedged +constexpr int kInputDeadSeconds = 3; // all input sends failing -> re-signal +constexpr int kMaxResignals = 3; // per 2-minute window +constexpr int kResignalWindowMs = 120000; +constexpr int kStatePollMs = 1000; +constexpr int kWaitTimeQueryMs = 15000; +constexpr int kQueueCapMs = 2 * 60 * 60 * 1000; // WaitingForResources: wait as long as the server queues us +constexpr int kStateCapMs = 210000; // any other gssv state that should move +constexpr int kResumeStateCapMs = 30000; // re-signal: give up on the old session quickly +constexpr int kMaxPollErrors = 5; + +uint64_t nowMs() { return platform::monotonicMs(); } + +bool contains(const std::string& hay, const char* needle) { return hay.find(needle) != std::string::npos; } + +// Type-checked string field of server JSON: a missing key or any non-string value (null, number, +// object) yields "" instead of the type_error json::value() throws. +std::string jstr(const json& j, const char* key) { + if (!j.is_object()) return std::string(); + auto it = j.find(key); + return it != j.end() && it->is_string() ? it->get<std::string>() : std::string(); +} + +// "candidate:<foundation> <component> <transport> <priority> <address> <port> typ ..." +// (optionally prefixed with "a="). Returns false if the line has no priority field. +bool candidatePriority(const std::string& line, unsigned long& prio) { + std::string c = line; + if (c.rfind("a=", 0) == 0) c.erase(0, 2); + std::istringstream ss(c); + std::string tok; + for (int field = 0; ss >> tok; ++field) { + if (field == 3) { + char* end = nullptr; + prio = std::strtoul(tok.c_str(), &end, 10); + return end && *end == '\0'; + } + } + return false; +} + +std::string trimCandidate(std::string c) { + while (!c.empty() && (c.back() == '\r' || c.back() == '\n' || c.back() == ' ')) c.pop_back(); + return c; +} + +std::string preview(const std::string& s, size_t n = 220) { + return s.size() <= n ? s : s.substr(0, n) + "..."; +} + +void resolutionSize(const std::string& alias, int& w, int& h) { + if (alias == "720" || alias == "720HQ") { + w = 1280; + h = 720; + } else { + w = 1920; + h = 1080; + } +} + +} // namespace + +// ============================================================================= + +struct Streamer::Impl { + Impl(Config& c, Auth& a) : cfg(c), auth(a) {} + + Config& cfg; + Auth& auth; + + // ---- session parameters (copied at start) ---- + SessionKind kind = SessionKind::Cloud; + std::string target; + bool f2pOnly = false; + Settings settings; + std::string installId; + + // ---- threads / lifecycle ---- + std::mutex lifeMu; // serializes doStart()/stopLocked() + std::thread workerThr, decodeThr, inputThr, rtcpThr; + // Lifecycle thread: runs start/stop work (thread joins, final DELETE) so neither the UI + // thread nor the App's shared worker ever blocks on a session teardown. + std::mutex lifeQMu; + std::condition_variable lifeQCv; + std::deque<std::function<void()>> lifeQ; + std::thread lifeThr; + bool lifeQuit = false; // lifeQMu + bool lifeClosing = false; // lifeQMu: shutdownLife() in progress + std::atomic<uint64_t> startGen{0}; // bumped by every start()/stopAsync()/stop() + std::atomic<bool> active{false}; // a start was requested and not stopped since + std::atomic<bool> quit{false}; + std::atomic<bool> workerDone{true}; + std::atomic<bool> running{false}; // threads exist and must be joined + + // ---- public state ---- + std::atomic<StreamState> state{StreamState::Idle}; + mutable std::mutex statusMu; + std::string status; + std::atomic<int> queueWaitSec{-1}; + + // ---- current transport ---- + std::mutex rtcMu; + std::shared_ptr<WebRtc> rtc; + std::atomic<uint32_t> rtcGen{0}; + std::atomic<bool> handshakeDone{false}; + std::atomic<bool> pcConnected{false}; + std::atomic<bool> gotFrame{false}; // this transport decoded a frame + std::atomic<bool> everStreamed{false}; // any transport of this session decoded a frame + std::atomic<bool> resuming{false}; // same-session re-signal in progress + std::atomic<bool> serverEnded{false}; + std::string serverEndReason; // statusMu + + // ---- worker events (from libdatachannel callbacks) ---- + struct Event { + enum Type { ChannelOpen, Text, PcState } type; + uint32_t gen; + std::string ch, text; + }; + std::mutex evMu; + std::condition_variable evCv; // events + quit wakeups for the worker + std::deque<Event> events; + + // ---- video ---- + std::mutex depackMu; + RtpH264Depacketizer depack; + bool waitKeyframe = true; // depackMu: drop AUs until an IDR after (re)start/overflow + struct AccessUnit { + std::vector<uint8_t> data; + uint32_t rtpTs; + uint32_t gen; + uint64_t queuedMs; + }; + std::mutex frameMu; + std::condition_variable frameCv; + std::deque<AccessUnit> frameQ; + std::atomic<bool> keyframeWanted{false}; + VideoDecoder decoder; + std::atomic<bool> decoderOk{false}; + + // ---- audio ---- + AudioPlayer audio; + std::atomic<bool> audioOk{false}; + // Small reorder window in front of the decoder: a packet one slot late is still played + // instead of being concealed and then dropped. Also the audio RR statistics. + RtpOpusReceiver audioRx; // internally locked + std::mutex audioDrainMu; // keeps pop -> pushOpus in order across threads + std::vector<uint8_t> audioScratch; // audioDrainMu + + // ---- counters ---- + std::atomic<uint64_t> videoBytes{0}; + std::atomic<uint64_t> lastMediaMs{0}; + std::atomic<uint64_t> lastDecodeMs{0}; + std::atomic<uint64_t> framesDecoded{0}; + std::atomic<uint64_t> decodeUsAvg{0}; + std::atomic<uint32_t> decodeErrors{0}; + std::atomic<uint64_t> lastKeyframeReqMs{0}; + std::atomic<uint32_t> keyframeReqs{0}; + std::atomic<uint64_t> workerTick{0}; + std::atomic<int> videoLossPct{0}; + std::atomic<uint32_t> queueFlushes{0}; + + // ---- input ---- + std::mutex inputMu; // serializer + send order (contiguous sequence numbers) + proto::InputSerializer serializer; + std::mutex padMu; + GamepadState pad; + std::atomic<bool> padDirty{true}; + std::atomic<uint32_t> inSentSec{0}, inFailSec{0}, inRxSec{0}; + std::atomic<uint64_t> inputRxLastMs{0}; + + // ---- rumble ---- + std::mutex vibMu; + Vibration vib; + bool vibPending = false; + bool vibLogged = false; + + // ---- stats ---- + mutable std::mutex statsMu; + StreamStats st; + std::atomic<int> frameW{0}, frameH{0}; + + // ===================== helpers ===================== + + // Session progress from the worker side. Ignored once quit is set: a session being torn down + // must not overwrite the state a newer start() already published. + void setStatus(const std::string& s) { + if (quit) return; + { + std::lock_guard<std::mutex> lk(statusMu); + if (status == s) return; + status = s; + } + XC_LOGI("stream: %s", s.c_str()); + } + void setState(StreamState s) { + if (quit) return; + publishState(s); + } + // Unconditional (start() / stopLocked()). + void publishState(StreamState s) { + StreamState prev = state.exchange(s); + if (prev != s) XC_LOGI("stream state: %s -> %s", streamStateName(prev), streamStateName(s)); + } + // A failure the user caused by cancelling is not a failure. + void fail(const std::string& msg) { + if (quit) return; + XC_LOGE("stream FAIL: %s", msg.c_str()); + { + std::lock_guard<std::mutex> lk(statusMu); + status = msg; + } + setState(StreamState::Failed); + } + void endSession(const std::string& msg) { + if (quit) return; + { + std::lock_guard<std::mutex> lk(statusMu); + status = msg; + } + setState(StreamState::Ended); + } + + // Interruptible sleep; false if quit was requested. + bool sleepMs(int ms) { + std::unique_lock<std::mutex> lk(evMu); + evCv.wait_for(lk, std::chrono::milliseconds(ms), [this] { return quit.load(); }); + return !quit; + } + + std::shared_ptr<WebRtc> currentRtc() { + std::lock_guard<std::mutex> lk(rtcMu); + return rtc; + } + + bool sendText(WebRtc& r, const char* ch, const std::string& text) { + bool ok = r.sendText(ch, text); + if (ok) + XC_LOGD("send [%s] %s", ch, preview(text).c_str()); + else + XC_LOGW("send [%s] FAILED: %s", ch, preview(text, 120).c_str()); + return ok; + } + + // PLI + control keyframe request, at most once per second unless forced. + void requestKeyframeInternal(bool force) { + if (!handshakeDone) return; + uint64_t now = nowMs(); + uint64_t last = lastKeyframeReqMs.load(); + if (force) { + lastKeyframeReqMs = now; + } else { + if (last && now >= last && now - last < kKeyframeMinIntervalMs) return; + // Several threads may ask at once: only the CAS winner sends. + if (!lastKeyframeReqMs.compare_exchange_strong(last, now)) return; + } + std::shared_ptr<WebRtc> r = currentRtc(); + if (!r) return; + keyframeReqs++; + r->requestKeyframe(); // RTCP PLI: what xCloud actually acts on + r->sendText("control", proto::controlKeyframeRequest()); + XC_LOGD("keyframe requested (%u so far)", keyframeReqs.load()); + } + + void pushEvent(Event ev) { + { + std::lock_guard<std::mutex> lk(evMu); + events.push_back(std::move(ev)); + } + evCv.notify_all(); + } + + // ===================== libdatachannel callbacks ===================== + + WebRtc::Callbacks makeCallbacks(uint32_t gen) { + WebRtc::Callbacks cb; + cb.onVideoRtp = [this, gen](const uint8_t* d, size_t n) { onVideoRtp(gen, d, n); }; + cb.onAudioRtp = [this, gen](const uint8_t* d, size_t n) { onAudioRtp(gen, d, n); }; + cb.onSenderReport = [this, gen](bool video, uint32_t ssrc, uint64_t ntp) { + if (gen != rtcGen.load()) return; + if (video) { + std::lock_guard<std::mutex> lk(depackMu); + depack.stats().onSenderReport(ntp); + } else { + audioRx.stats().onSenderReport(ntp); + } + (void)ssrc; + }; + cb.onText = [this, gen](const std::string& ch, const std::string& text) { + if (gen != rtcGen.load()) return; + pushEvent(Event{Event::Text, gen, ch, text}); + }; + cb.onBinary = [this, gen](const std::string& ch, const uint8_t* d, size_t n) { + if (gen != rtcGen.load()) return; + onBinary(ch, d, n); + }; + cb.onChannelOpen = [this, gen](const std::string& ch) { + if (gen != rtcGen.load()) return; + pushEvent(Event{Event::ChannelOpen, gen, ch, std::string()}); + }; + cb.onState = [this, gen](const std::string& s) { + if (gen != rtcGen.load()) return; + pcConnected = (s == "connected"); + pushEvent(Event{Event::PcState, gen, std::string(), s}); + }; + return cb; + } + + void onVideoRtp(uint32_t gen, const uint8_t* d, size_t n) { + if (gen != rtcGen.load()) return; + lastMediaMs = nowMs(); + videoBytes += n; + bool overflow = false, softOverflow = false; + bool queued = false; + bool gatedDrop = false; + size_t flushed = 0; + { + std::lock_guard<std::mutex> lk(depackMu); + depack.push(d, n); + std::vector<uint8_t> au; + uint32_t ts = 0; + bool key = false; + while (depack.popFrame(au, ts, key)) { + if (waitKeyframe) { + if (!key) { // never feed the decoder a P-frame without its IDR + gatedDrop = true; + continue; + } + waitKeyframe = false; + XC_LOGI("video: keyframe (%zu bytes), decoding resumes", au.size()); + } + const uint64_t now = nowMs(); + std::lock_guard<std::mutex> fl(frameMu); + const bool hard = frameQ.size() >= kMaxQueuedVideo; + const bool soft = !hard && frameQ.size() >= kSoftQueuedVideo && + now - frameQ.front().queuedMs > kSoftQueueMs; + if (hard || soft) { + // The decoder is behind: every queued AU is added latency. Dropping single + // AUs corrupts the reference chain, so clear and restart from an IDR. + flushed += frameQ.size(); + frameQ.clear(); + waitKeyframe = true; + overflow = overflow || hard; + softOverflow = softOverflow || soft; + if (!key) { + gatedDrop = true; + continue; + } + waitKeyframe = false; + } + frameQ.push_back(AccessUnit{std::move(au), ts, gen, now}); + au = std::vector<uint8_t>(); + queued = true; + } + } + if (queued) frameCv.notify_one(); + if (overflow || softOverflow) { + XC_LOGW("video: decoder %zu frames behind (%s), flushed, waiting for a keyframe", flushed, + overflow ? "queue full" : "backlog too old"); + queueFlushes++; + } + // Keep asking while the gate drops frames: a single lost PLI/IDR must not freeze the + // picture until the 5 s decode watchdog (requestKeyframeInternal throttles to 1/s). + if (overflow || softOverflow || gatedDrop) keyframeWanted = true; + } + + void onAudioRtp(uint32_t gen, const uint8_t* d, size_t n) { + if (gen != rtcGen.load()) return; + lastMediaMs = nowMs(); + audioRx.push(d, n); // statistics + reorder window + drainAudio(); + } + + // Move every in-order (or timed-out gap) Opus packet from the reorder window to the player. + // Called per audio packet and from the 20 ms RTCP tick (so a gap times out without traffic). + void drainAudio() { + std::lock_guard<std::mutex> lk(audioDrainMu); + uint32_t ts = 0; + int lostBefore = 0; + while (audioRx.pop(audioScratch, ts, lostBefore)) + if (audioOk) audio.pushOpus(audioScratch.data(), audioScratch.size(), lostBefore); + } + + // Server -> client input-channel reports (rumble, server metadata). Runs on a + // libdatachannel thread; only touches its own small locks. + void onBinary(const std::string& ch, const uint8_t* d, size_t n) { + if (ch != "input") { + XC_LOGD("recv [%s] %zu binary bytes", ch.c_str(), n); + return; + } + inRxSec++; + inputRxLastMs = nowMs(); + Vibration v; + if (proto::parseVibration(d, n, v)) { + std::lock_guard<std::mutex> lk(vibMu); + vib = v; + vibPending = true; + if (!vibLogged) { + vibLogged = true; + XC_LOGI("rumble: first server vibration report received"); + } + } + uint32_t w = 0, h = 0; + if (proto::parseServerMetadata(d, n, w, h)) { + XC_LOGI("server metadata: stream %ux%u", w, h); + if (w && h && !frameW) { + frameW = static_cast<int>(w); + frameH = static_cast<int>(h); + } + } + } + + // ===================== helper threads ===================== + + // Thread entry guard: an exception (bad_alloc, a JSON type_error, ...) must end the session + // with an error instead of std::terminate taking the whole app down mid-stream. + template <typename F> + void guarded(const char* what, F&& body) { + try { + body(); + } catch (const std::exception& e) { + XC_LOGE("stream: %s thread: unexpected exception: %s", what, e.what()); + fail(std::string("Internal error in the ") + what + " thread: " + e.what()); + } catch (...) { + XC_LOGE("stream: %s thread: unexpected exception", what); + fail(std::string("Internal error in the ") + what + " thread"); + } + } + + void decodeMain() { guarded("decode", [this] { decodeLoop(); }); } + void inputMain() { guarded("input", [this] { inputLoop(); }); } + void rtcpMain() { guarded("rtcp", [this] { rtcpLoop(); }); } + + void decodeLoop() { + uint64_t prevFrames = 0; + // Decoder speed check: software decode slower than the frame interval cannot keep up and + // ends in queue flushes; say so in the log (and suggest 720p) instead of failing silently. + uint64_t windowStart = nowMs(), windowUs = 0, windowAus = 0, slowWindows = 0; + while (!quit && !workerDone) { + AccessUnit au; + { + std::unique_lock<std::mutex> lk(frameMu); + frameCv.wait_for(lk, std::chrono::milliseconds(20), + [this] { return quit.load() || workerDone.load() || !frameQ.empty(); }); + if (quit || workerDone) break; + if (frameQ.empty()) continue; + au = std::move(frameQ.front()); + frameQ.pop_front(); + } + if (au.gen != rtcGen.load()) continue; // belongs to a closed transport + const auto t0 = std::chrono::steady_clock::now(); + bool ok = decoder.decode(au.data.data(), au.data.size(), static_cast<int64_t>(au.rtpTs)); + windowUs += static_cast<uint64_t>( + std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - t0).count()); + ++windowAus; + const uint64_t tnow = nowMs(); + if (tnow - windowStart >= kDecodeReportMs) { + const double avgMs = windowAus ? static_cast<double>(windowUs) / 1000.0 / windowAus : 0; + const uint32_t flushes = queueFlushes.exchange(0); + if (avgMs > kFrameIntervalMs * 0.9 || flushes > 0) { + ++slowWindows; + XC_LOGW("video: decode %.1f ms/frame vs %.1f ms frame interval, %u backlog flush(es) in %llu s%s", + avgMs, kFrameIntervalMs, flushes, static_cast<unsigned long long>((tnow - windowStart) / 1000), + settings.resolution != "720" ? " - the decoder is too slow for this resolution, pick 720p in Settings" + : ""); + } else if (slowWindows) { + XC_LOGI("video: decode %.1f ms/frame, keeping up again", avgMs); + slowWindows = 0; + } + windowStart = tnow; + windowUs = windowAus = 0; + } + uint64_t fd = decoder.framesDecoded(); + decodeUsAvg = static_cast<uint64_t>(decoder.avgDecodeMs() * 1000.0); + if (fd != prevFrames) { + prevFrames = fd; + framesDecoded = fd; + lastDecodeMs = nowMs(); + if (au.gen == rtcGen.load() && !gotFrame.exchange(true)) { + XC_LOGI("video: first frame decoded"); + everStreamed = true; + } + } + if (!ok) { + decodeErrors++; + requestKeyframeInternal(false); + } + } + } + + void inputLoop() { + using clock = std::chrono::steady_clock; + auto next = clock::now(); + GamepadState last; + uint64_t lastSend = 0, backoffUntil = 0; + uint32_t lastGen = 0; + while (!quit && !workerDone) { + next += std::chrono::milliseconds(kInputPeriodMs); + auto now = clock::now(); + if (next < now - std::chrono::milliseconds(100)) next = now; // resync after a stall + std::this_thread::sleep_until(next); + if (!handshakeDone || !pcConnected) continue; + std::shared_ptr<WebRtc> r = currentRtc(); + if (!r) continue; + uint32_t gen = rtcGen.load(); + if (gen != lastGen) { // new transport: full state once, no stale backoff + lastGen = gen; + backoffUntil = 0; + lastSend = 0; + } + uint64_t t = nowMs(); + if (backoffUntil && t < backoffUntil) continue; + if (!r->isChannelOpen("input")) continue; + GamepadState cur; + { + std::lock_guard<std::mutex> lk(padMu); + cur = pad; + } + bool dirty = padDirty.load(); + if (!dirty && cur == last && lastSend && t - lastSend < kInputHeartbeatMs) continue; + bool ok; + { + // Build only when the send is certain to be attempted and roll back a refused one: + // the server stops applying input after a sequence gap (green-nx #45). + std::lock_guard<std::mutex> lk(inputMu); + ok = r->sendBinary("input", serializer.gamepad(cur, 0)); + if (!ok) serializer.rollback(); + } + if (ok) { + inSentSec++; + last = cur; + lastSend = t; + backoffUntil = 0; + padDirty = false; + } else { + inFailSec++; + backoffUntil = t + kInputBackoffMs; + } + } + } + + void rtcpLoop() { + uint64_t lastReport = nowMs(); + uint64_t prevBytes = videoBytes.load(), prevFrames = framesDecoded.load(); + while (!quit && !workerDone) { + std::this_thread::sleep_for(std::chrono::milliseconds(kRtcpTickMs)); + std::shared_ptr<WebRtc> r = currentRtc(); + std::vector<uint16_t> nacks; + bool needKey = false; + { + std::lock_guard<std::mutex> lk(depackMu); + nacks = depack.takeNackList(); + needKey = depack.needKeyframe(); + } + if (keyframeWanted.exchange(false)) needKey = true; + if (r && !nacks.empty()) { + uint32_t ssrc = r->videoSsrc(); + if (ssrc) r->sendNack(ssrc, nacks); + } + if (needKey) requestKeyframeInternal(false); + drainAudio(); // lets a reorder-window gap time out even when no audio arrives + + uint64_t now = nowMs(); + if (now - lastReport < kReportIntervalMs) continue; + double dt = static_cast<double>(now - lastReport) / 1000.0; + lastReport = now; + + RtpReceiveStats v; + RtpVideoCounters vc; + { + std::lock_guard<std::mutex> lk(depackMu); + v = depack.stats().snapshotForReport(); + vc = depack.counters(); + } + RtpReceiveStats a = audioRx.stats().snapshotForReport(); + if (r) { + std::vector<RtpReceiveStats> blocks; + if (v.ssrc && v.packets) blocks.push_back(v); + if (a.ssrc && a.packets) blocks.push_back(a); + if (!blocks.empty()) r->sendReceiverReport(blocks); + if (r->videoSsrc()) r->sendRemb(static_cast<uint32_t>(settings.bitrateKbps) * 1000u); + } + int lossPct = static_cast<int>(v.fractionLost * 100 / 256); + videoLossPct = lossPct; + + uint64_t bytes = videoBytes.load(), frames = framesDecoded.load(); + StreamStats s; + s.width = frameW; + s.height = frameH; + s.fps = dt > 0 ? static_cast<double>(frames - prevFrames) / dt : 0; + s.bitrateKbps = dt > 0 ? static_cast<double>(bytes - prevBytes) * 8.0 / 1000.0 / dt : 0; + s.decodeMs = static_cast<double>(decodeUsAvg.load()) / 1000.0; + s.lossPercent = v.fractionLost * 100.0 / 256.0; + s.rttMs = vc.rttMs ? static_cast<int>(vc.rttMs) : -1; // NACK round-trip estimate + s.audioBufferMs = audioOk ? audio.bufferedMs() : 0; + prevBytes = bytes; + prevFrames = frames; + { + std::lock_guard<std::mutex> lk(statsMu); + st = s; + } + } + } + + // gssv keepalive runs off the worker: it is a blocking HTTPS call (green-nx: a + // stalled pump every 15 s otherwise). It also watches the worker heartbeat. + void keepaliveMain(std::string base, std::string token, Settings gs, SessionInfo s, std::atomic<bool>* stop) { + guarded("keepalive", [&] { keepaliveLoop(base, token, gs, s, stop); }); + } + + // gs: the session's gssv settings (xhome: android/720 fingerprint, see runSession). + void keepaliveLoop(const std::string& base, const std::string& token, const Settings& gs, const SessionInfo& s, + std::atomic<bool>* stop) { + Http::AbortScope abortOnStop(stop); // join in ~Keepalive must not wait for a 25 s request + GssvClient gssv(base, token, gs); + bool authReported = false; + int interval = gssv.keepAliveSeconds(s); + if (interval < 5) interval = 20; + XC_LOGI("keepalive every %d s", interval); + uint64_t next = nowMs() + static_cast<uint64_t>(interval) * 1000; + uint64_t prevRound = nowMs(); + bool stallReported = false; + int failures = 0; + while (!quit && !*stop) { + uint64_t now = nowMs(); + // Worker-stall watchdog. A >2 s gap in our own rounds means the whole + // process was suspended: skip that round instead of misreading it. + uint64_t tick = workerTick.load(); + if (now - prevRound <= 2000 && !stallReported && tick && now > tick && now - tick > kWorkerStallMs) { + stallReported = true; + XC_LOGE("stream worker stalled for %llu ms", static_cast<unsigned long long>(now - tick)); + fail("Stream engine stalled, please start the stream again"); + } + prevRound = now; + if (now < next) { + std::this_thread::sleep_for(std::chrono::milliseconds(std::min<uint64_t>(100, next - now))); + continue; + } + uint64_t started = nowMs(); + if (gssv.keepAlive(s)) { + failures = 0; + } else if (!quit && !*stop) { + XC_LOGW("keepalive failed (%d in a row)", ++failures); + if (gssv.authRejected() && !authReported) { + authReported = true; + auth.invalidateStreaming(); // the next session re-authorizes first + } + } + uint64_t took = nowMs() - started; + if (took >= 1000) XC_LOGD("keepalive took %llu ms", static_cast<unsigned long long>(took)); + next = nowMs() + static_cast<uint64_t>(interval) * 1000; + } + } + + struct Keepalive { + std::atomic<bool> stop{false}; + std::thread thr; + ~Keepalive() { + stop = true; + if (thr.joinable()) thr.join(); + } + }; + + // ===================== transport ===================== + + void resetTransportState() { + handshakeDone = false; + pcConnected = false; + gotFrame = false; + serverEnded = false; + { + std::lock_guard<std::mutex> lk(depackMu); + depack.reset(); + waitKeyframe = true; + } + { + std::lock_guard<std::mutex> lk(frameMu); + frameQ.clear(); + } + { + std::lock_guard<std::mutex> lk(audioDrainMu); + audioRx.reset(); + } + if (audioOk) audio.reset(); // new transport: fresh RTP sequence + Opus state + { + std::lock_guard<std::mutex> lk(evMu); + events.clear(); + } + lastMediaMs = 0; + lastDecodeMs = 0; + lastKeyframeReqMs = 0; + keyframeWanted = false; + inSentSec = 0; + inFailSec = 0; + inRxSec = 0; + inputRxLastMs = 0; + padDirty = true; + } + + // Detach and close the current PeerConnection. Callbacks of the old generation are + // ignored from here on; close() is called without holding any of our locks. + void closePeer() { + std::shared_ptr<WebRtc> old; + { + std::lock_guard<std::mutex> lk(rtcMu); + old = std::move(rtc); + rtc.reset(); + rtcGen++; + } + handshakeDone = false; + pcConnected = false; + if (old) { + old->close(); + XC_LOGI("peer connection closed"); + } + { + std::lock_guard<std::mutex> lk(frameMu); + frameQ.clear(); + } + } + + enum class PeerOutcome { Finished, RetryFresh, Resignal }; + + // Re-announce the pad in-band (green-nx revive_input): ClientMetadata (consumes a + // sequence number, rolled back on failure) + gamepadChanged false/true. + void reviveInput(WebRtc& r, const char* reason) { + { + std::lock_guard<std::mutex> lk(inputMu); + if (!r.sendBinary("input", serializer.clientMetadata())) serializer.rollback(); + } + sendText(r, "control", proto::controlGamepadChanged(0, false)); + sendText(r, "control", proto::controlGamepadChanged(0, true)); + padDirty = true; + uint64_t rx = inputRxLastMs.load(), now = nowMs(); + XC_LOGI("input revive (%s): pad re-announced, server input last seen %s", reason, + rx && now >= rx ? (std::to_string((now - rx) / 1000) + "s ago").c_str() : "never"); + } + + void afterHandshakeAck(WebRtc& r) { + sendText(r, "control", proto::controlAuthorization()); + sendText(r, "control", proto::controlGamepadChanged(0, true)); + // Pin the resolution; without it servers pick 1440p for desktop-class clients. + sendText(r, "control", proto::controlResolution(settings.resolution)); + int w, h; + resolutionSize(settings.resolution, w, h); + for (const std::string& m : proto::messageInitSequence(settings, installId, w, h)) sendText(r, "message", m); + { + std::lock_guard<std::mutex> lk(inputMu); + if (!r.sendBinary("input", serializer.clientMetadata())) { + serializer.rollback(); + XC_LOGW("input ClientMetadata send failed"); + } + } + handshakeDone = true; + padDirty = true; + // Ask for an IDR right away (PLI + control) instead of waiting for the periodic one. + requestKeyframeInternal(true); + XC_LOGI("handshake complete, capabilities sent"); + } + + // Returns true if the session must end (server disconnect). + bool handleMessageChannel(WebRtc& r, const std::string& text, bool& ackReceived) { + json j = json::parse(text, nullptr, false); + if (j.is_discarded() || !j.is_object()) { + // Not JSON: still honour a disconnect notice (green-nx matches on the substring). + if (contains(text, "serverInitiatedDisconnect")) { + setServerEnded(""); + return true; + } + return false; + } + const std::string type = jstr(j, "type"); + if (type == "HandshakeAck") { + ackReceived = true; + return false; + } + if (type != "TransactionStart" && type != "Message") { + if (type == "Error") XC_LOGW("message channel error: %s", preview(text).c_str()); + return false; + } + const std::string id = jstr(j, "id"); + const std::string tgt = jstr(j, "target"); + const std::string cv = jstr(j, "cv"); + const std::string content = jstr(j, "content"); + const bool transaction = type == "TransactionStart"; + + if (contains(tgt, "serverInitiatedDisconnect")) { + std::string reason; + json c = json::parse(content, nullptr, false); + if (!c.is_discarded() && c.is_object()) reason = jstr(c, "reason"); + if (transaction) sendText(r, "message", proto::transactionComplete(id, "{}")); + setServerEnded(reason); + return true; + } + if (contains(tgt, "/streaming/systemUi/messages/ShowMessageDialog")) { + json c = json::parse(content, nullptr, false); + if (!c.is_discarded() && c.is_object()) + XC_LOGI("server dialog: \"%s\" — %s", jstr(c, "TitleText").c_str(), jstr(c, "ContentText").c_str()); + // No dialog UI: accept with the first (default) button. + if (transaction) sendText(r, "message", proto::transactionComplete(id, "{\"Result\":0}")); + return false; + } + if (contains(tgt, "/streaming/properties/titleinfo")) { + XC_LOGI("title info: %s", preview(content).c_str()); + } else if (contains(tgt, "/streaming/touchcontrols/")) { + // touch layouts are irrelevant for a gamepad client + } else { + XC_LOGD("unhandled %s target %s", type.c_str(), tgt.c_str()); + } + if (transaction) { + if (contains(tgt, "/streaming/properties/") || contains(tgt, "/streaming/touchcontrols/")) { + sendText(r, "message", proto::transactionComplete(id, "{}")); + } else { + // XStreaming answers unknown transactions with "Unhandled" so the server does not wait. + json u = {{"type", "Unhandled"}, {"id", id}, {"target", tgt}, {"cv", cv}}; + sendText(r, "message", u.dump()); + } + } + return false; + } + + void setServerEnded(const std::string& reason) { + XC_LOGI("server ended the session%s%s%s", reason.empty() ? "" : " (", reason.c_str(), + reason.empty() ? "" : ")"); + { + std::lock_guard<std::mutex> lk(statusMu); + serverEndReason = reason; + } + serverEnded = true; + } + + PeerOutcome runPeer(GssvClient& gssv, const SessionInfo& s) { + const bool home = kind == SessionKind::Home; + if (!resuming) setState(StreamState::Connecting); + setStatus(resuming ? "Reconnecting..." : "Negotiating connection..."); + resetTransportState(); + + auto r = std::make_shared<WebRtc>(); + uint32_t gen; + { + std::lock_guard<std::mutex> lk(rtcMu); + gen = ++rtcGen; + } + struct Closer { + Impl* self; + ~Closer() { self->closePeer(); } + } closer{this}; + if (!r->init(makeCallbacks(gen))) { + fail("Failed to create the WebRTC connection"); + return PeerOutcome::Finished; + } + { + std::lock_guard<std::mutex> lk(rtcMu); + if (gen != rtcGen.load()) return PeerOutcome::Finished; + rtc = r; + } + + // ---- local description / candidates ---- + std::string ufrag, pwd, fingerprint, err; + std::vector<std::string> local; + if (!r->gatherLocal(ufrag, pwd, fingerprint, local, kGatherTimeoutMs, &quit)) { + if (quit) return PeerOutcome::Finished; + fail("Local network setup (ICE gathering) failed"); + return PeerOutcome::Finished; + } + if (quit) return PeerOutcome::Finished; + XC_LOGI("gathered %zu local candidates (ufrag %s)", local.size(), ufrag.c_str()); + for (const auto& c : local) XC_LOGD(" local cand: %s", c.c_str()); + std::string offer = proto::buildOffer(ufrag, pwd, fingerprint, home, settings.resolution); + XC_LOGD("offer sdp:\n%s", offer.c_str()); + + // ---- SDP exchange ---- + if (!gssv.sendSdpOffer(s, offer, err)) { + if (resuming) { + XC_LOGW("re-signal refused (sdp): %s", err.c_str()); + return PeerOutcome::RetryFresh; + } + fail("SDP offer rejected: " + err); + return PeerOutcome::Finished; + } + std::string answer; + { + uint64_t deadline = nowMs() + kSdpAnswerTimeoutMs; + bool ready = false; + int errors = 0; + while (!quit && !ready) { + if (!gssv.pollSdpAnswer(s, answer, ready, err)) { + XC_LOGW("sdp poll failed: %s", err.c_str()); + if (++errors >= kMaxPollErrors) break; + } + if (ready) break; + if (nowMs() > deadline) break; + if (!sleepMs(500)) return PeerOutcome::Finished; + } + if (quit) return PeerOutcome::Finished; + if (!ready || answer.empty()) { + if (resuming) return PeerOutcome::RetryFresh; + fail(err.empty() ? "Timed out waiting for the SDP answer" : "SDP exchange failed: " + err); + return PeerOutcome::Finished; + } + } + XC_LOGI("answer received (%zu bytes)", answer.size()); + XC_LOGD("answer sdp:\n%s", answer.c_str()); + + // ---- ICE exchange ---- + // Our candidates go out over /ice too (the official client posts them explicitly). + if (!local.empty() && !gssv.sendIce(s, local, ufrag, err)) XC_LOGW("local candidate post failed: %s", err.c_str()); + + // xCloud trickles its candidates via /ice. It first returns a placeholder front + // candidate (priority 100, 13.104.x) that never answers STUN; the real (Teredo) + // candidate can arrive seconds later, so keep polling until one with priority > 1000. + std::vector<std::string> remote; + { + std::set<std::string> seen; + uint64_t deadline = nowMs() + (kind == SessionKind::Home ? kRemoteIceTimeoutMs : kRemoteIceCloudTimeoutMs); + int quiet = 0; + bool hasReal = false; + while (!quit && nowMs() < deadline) { + std::vector<std::string> got; + bool ready = false; + size_t before = remote.size(); + if (gssv.pollIce(s, got, ready, err)) { + for (auto& c : got) { + std::string line = trimCandidate(c); + if (line.empty() || !seen.insert(line).second) continue; + unsigned long prio = 0; + if (candidatePriority(line, prio) && prio > kRealCandidatePriority) hasReal = true; + remote.push_back(line); + } + } else { + XC_LOGW("ice poll failed: %s", err.c_str()); + } + quiet = remote.size() == before ? quiet + 1 : 0; + if (hasReal && quiet >= kRemoteIceQuietPolls) break; + if (!sleepMs(kRemoteIcePollMs)) break; + } + if (quit) return PeerOutcome::Finished; + if (!hasReal && !remote.empty()) + XC_LOGW("no remote candidate with priority > %lu, trying what we have", kRealCandidatePriority); + } + XC_LOGI("collected %zu remote candidates", remote.size()); + for (const auto& c : remote) XC_LOGD(" remote cand: %s", c.c_str()); + if (remote.empty()) { + XC_LOGW("server sent no ICE candidates"); + return PeerOutcome::RetryFresh; + } + std::vector<std::string> expanded = proto::expandTeredoCandidates(remote); + if (expanded.size() != remote.size()) + for (const auto& c : expanded) XC_LOGD(" expanded cand: %s", c.c_str()); + if (!r->setRemote(answer, expanded)) { + fail("The server's SDP answer could not be applied"); + return PeerOutcome::Finished; + } + XC_LOGI("remote description set, checking connectivity"); + return eventLoop(*r, gen); + } + + PeerOutcome eventLoop(WebRtc& r, uint32_t gen) { + const uint64_t negotiationStart = nowMs(); + uint64_t connectedAt = 0, disconnectedAt = 0, handshakeAt = 0, ackAt = 0; + uint64_t lastSecond = nowMs(), lastLoop = nowMs(); + bool sentHandshake = false, ackPending = false, decodeResynced = false, idrWaitLogged = false; + int inputDeadSeconds = 0; + uint64_t reviveDue = 0, reviveFiredAt = 0, lastRevive = 0; + int autoRevives = 0; + // Audio counters are cumulative per AudioPlayer: prime them so the first second of a + // re-signaled transport is not read as a fresh loss burst (green-nx pre8 death loop). + AudioPlayer::Stats prevAudio = audioOk ? audio.stats() : AudioPlayer::Stats(); + std::string pcState = "new"; + workerTick = nowMs(); + + struct TickClear { + std::atomic<uint64_t>& t; + ~TickClear() { t = 0; } + } tickClear{workerTick}; + + while (!quit) { + // ---- events ---- + std::deque<Event> evs; + { + std::unique_lock<std::mutex> lk(evMu); + if (events.empty()) + evCv.wait_for(lk, std::chrono::milliseconds(20), [this] { return quit.load() || !events.empty(); }); + evs.swap(events); + } + if (quit) break; + for (Event& ev : evs) { + if (ev.gen != gen) continue; + switch (ev.type) { + case Event::PcState: + if (ev.text != pcState) { + XC_LOGI("peer state: %s", ev.text.c_str()); + pcState = ev.text; + } + if (pcState == "connected") { + if (!connectedAt) connectedAt = nowMs(); + disconnectedAt = 0; + } else if (pcState == "disconnected" && !disconnectedAt) { + disconnectedAt = nowMs(); + } + break; + case Event::ChannelOpen: + XC_LOGI("data channel open: %s", ev.ch.c_str()); + break; + case Event::Text: + if (ev.ch == "message") { + XC_LOGD("recv [message] %s", preview(ev.text).c_str()); + bool ack = false; + if (handleMessageChannel(r, ev.text, ack)) break; + if (ack && !handshakeDone && !ackPending) { + ackPending = true; + ackAt = nowMs(); + setStatus("Starting the stream..."); + } + } else if (ev.ch == "chat") { + // chat audio is not supported + } else { + XC_LOGD("recv [%s] %s", ev.ch.c_str(), preview(ev.text).c_str()); + } + break; + } + } + + // A helper thread (keepalive stall watchdog) may have declared the session dead. + if (state.load() == StreamState::Failed) return PeerOutcome::Finished; + + const uint64_t now = nowMs(); + // Elapsed time since t, 0 when t lies in the future. Timestamps set while handling this + // iteration (after `now` was read) are a few µs ahead of it: plain `now - t` on uint64 + // wraps around and fires every timeout at once. + auto since = [now](uint64_t t) -> uint64_t { return now > t ? now - t : 0; }; + // The process can be suspended (PS5 rest mode, debugger): every thread freezes while + // the clock runs on, so a gap is not a stall. Restart the watchdog windows. + if (now - lastLoop > 2000) { + if (lastMediaMs) lastMediaMs = now; + if (lastDecodeMs) lastDecodeMs = now; + } + lastLoop = now; + workerTick = now; + + // ---- session end / transport death ---- + if (serverEnded) { + std::string reason; + { + std::lock_guard<std::mutex> lk(statusMu); + reason = serverEndReason; + } + endSession(reason.empty() ? "The session was ended by the server" + : "The session was ended by the server (" + reason + ")"); + return PeerOutcome::Finished; + } + if (pcState == "failed" || pcState == "closed" || + (disconnectedAt && since(disconnectedAt) > kDisconnectGraceMs)) { + if (!everStreamed && !resuming) { + // The session itself (and its queue slot) is still ours: renegotiate WebRTC + // on it before giving it up for a fresh one, which may mean queueing again. + XC_LOGW("WebRTC connection %s before streaming started: re-signaling", pcState.c_str()); + return PeerOutcome::Resignal; + } + if (handshakeDone || resuming) { + XC_LOGW("WebRTC connection %s mid-stream: re-signaling", pcState.c_str()); + return PeerOutcome::Resignal; + } + fail("Connection to the server was lost"); + return PeerOutcome::Finished; + } + + // ---- data channels / handshake ---- + if (!sentHandshake && r.isChannelOpen("message")) { + if (sendText(r, "message", proto::messageHandshake())) { + sentHandshake = true; + setStatus(resuming ? "Reconnecting..." : "Handshaking..."); + } + } + if (ackPending) { + bool ready = r.isChannelOpen("control") && r.isChannelOpen("input"); + if (ready || since(ackAt) > kChannelsReadyGraceMs) { + if (!ready) XC_LOGW("control/input channel not open %d ms after HandshakeAck", kChannelsReadyGraceMs); + ackPending = false; + afterHandshakeAck(r); + handshakeAt = now; + if (!resuming) setStatus("Waiting for video..."); + } + } + // Dead media path: ICE/DTLS up but SCTP channels never come up. A fresh session + // re-rolls that server-side fault. + if (connectedAt && !sentHandshake && since(connectedAt) > kDeadMediaPathMs) { + XC_LOGW("connected but data channels never opened: dead media path"); + return PeerOutcome::RetryFresh; + } + if (!handshakeDone && since(negotiationStart) > kNegotiationTimeoutMs) { + if (resuming || !connectedAt) return PeerOutcome::RetryFresh; + fail("Connection timed out"); + return PeerOutcome::Finished; + } + + // ---- first frame / streaming ---- + if (gotFrame) { + if (state != StreamState::Streaming) setState(StreamState::Streaming); + if (resuming) { + resuming = false; + XC_LOGI("re-signal complete, stream resumed"); + } + setStatus("Streaming"); + } else if (handshakeDone) { + // xCloud may start mid-GOP or drop the first request: keep asking (throttled 1/s). + requestKeyframeInternal(false); + if (!idrWaitLogged && since(handshakeAt) > 3000) { + idrWaitLogged = true; + XC_LOGW("still waiting for the first video frame (%u keyframe requests)", keyframeReqs.load()); + } + if (since(handshakeAt) > kFirstFrameTimeoutMs) { + if (resuming) return PeerOutcome::RetryFresh; + if (!everStreamed) { + XC_LOGW("no video %d ms after the handshake: re-signaling the session", kFirstFrameTimeoutMs); + return PeerOutcome::Resignal; + } + fail("No video received from the server"); + return PeerOutcome::Finished; + } + } + + // ---- media watchdogs (after the first frame) ---- + if (gotFrame) { + uint64_t lm = lastMediaMs.load(); + if (lm && now > lm && now - lm > kMediaStallMs) { + XC_LOGW("no video or audio for %d ms", kMediaStallMs); + return PeerOutcome::Resignal; + } + uint64_t ld = lastDecodeMs.load(); + if (ld && now > ld) { + if (now - ld > kDecodeResyncMs && !decodeResynced) { + // Units going in, nothing coming out: wipe the assembler and gate on a clean IDR. + decodeResynced = true; + { + std::lock_guard<std::mutex> lk(depackMu); + depack.reset(); + waitKeyframe = true; + } + { + std::lock_guard<std::mutex> lk(frameMu); + frameQ.clear(); + } + requestKeyframeInternal(true); + XC_LOGW("video decode stalled %d ms: depacketizer reset, waiting for a clean IDR", + kDecodeResyncMs); + } else if (now - ld <= kDecodeResyncMs) { + decodeResynced = false; + } + if (now - ld > kDecodeStallMs) { + fail("Video stalled for 15 s, please start the stream again"); + return PeerOutcome::Finished; + } + } + } + + // ---- once per second: input health, revive, telemetry ---- + if (now - lastSecond >= 1000) { + lastSecond = now; + uint32_t sent = inSentSec.exchange(0), failed = inFailSec.exchange(0), rx = inRxSec.exchange(0); + if (handshakeDone && gotFrame) { + StreamStats ss; + { + std::lock_guard<std::mutex> lk(statsMu); + ss = st; + } + XC_LOGD("stats| %dx%d %.1ffps %.0fkbps dec=%.1fms loss=%.1f%% abuf=%dms in: sent=%u fail=%u rx=%u", + ss.width, ss.height, ss.fps, ss.bitrateKbps, ss.decodeMs, ss.lossPercent, + ss.audioBufferMs, sent, failed, rx); + // Dead data channel (green-nx #61): SCTP association gone, media still flowing. + if (sent == 0 && failed > 0) { + if (++inputDeadSeconds >= kInputDeadSeconds) { + XC_LOGW("input channel dead for %d s: re-signaling", kInputDeadSeconds); + return PeerOutcome::Resignal; + } + } else { + inputDeadSeconds = 0; + } + // A loss burst can wedge the server's input pipeline while our sends still + // succeed: after it settles, re-announce the pad (bounded churn). + // Any shape of media disruption counts: controllers also died on pure latency + // spikes where only the audio ring (drops/underruns) showed the hit. + AudioPlayer::Stats as = audioOk ? audio.stats() : AudioPlayer::Stats(); + auto delta = [](uint64_t cur, uint64_t prev) { return cur >= prev ? cur - prev : 0; }; + bool burst = videoLossPct.load() >= 10 || delta(as.lost, prevAudio.lost) >= 3 || + delta(as.droppedMs, prevAudio.droppedMs) >= 150 || + delta(as.underruns, prevAudio.underruns) >= 3; + prevAudio = as; + if (burst && !reviveDue && !reviveFiredAt && autoRevives < 3 && + (!lastRevive || now - lastRevive > 45000)) + reviveDue = now + 2000; + if (reviveDue && now >= reviveDue) { + reviveDue = 0; + lastRevive = now; + ++autoRevives; + reviveInput(r, "after media disruption"); + reviveFiredAt = now; + } + if (reviveFiredAt) { + uint64_t rxl = inputRxLastMs.load(); + if (rxl > reviveFiredAt) { + XC_LOGI("input revive: server input traffic is back"); + reviveFiredAt = 0; + } else if (now - reviveFiredAt > 8000) { + reviveFiredAt = 0; // quiet games never send any; not judged + } + } + } + } + } + return PeerOutcome::Finished; // stop requested + } + + // ===================== session (gssv) ===================== + + enum class AttemptResult { Done, RetryFresh, SessionFailed }; + + AttemptResult runAttempt(GssvClient& gssv, const std::string& base, const std::string& token, + const Settings& gssvSettings, SessionInfo& s, std::string& sessionError, int& resignals, + uint64_t& resignalWindow) { + bool connected = false; + std::string lastState; + uint64_t stateSince = nowMs(), nextWaitQuery = 0; + int pollErrors = 0; + std::unique_ptr<Keepalive> ka; + std::string err; + + while (!quit) { + if (state.load() == StreamState::Failed) return AttemptResult::Done; + if (!gssv.pollState(s, err)) { + XC_LOGW("session state poll failed: %s", err.c_str()); + if (++pollErrors >= kMaxPollErrors) { + if (resuming) return AttemptResult::RetryFresh; + fail("Lost contact with the streaming service: " + err); + return AttemptResult::Done; + } + if (!sleepMs(kStatePollMs)) break; + continue; + } + pollErrors = 0; + const uint64_t now = nowMs(); + if (s.state != lastState) { + XC_LOGI("session state: %s", s.state.c_str()); + lastState = s.state; + stateSince = now; + } + + if (s.state == "WaitingForResources") { + if (!resuming) setState(StreamState::Queued); + if (kind == SessionKind::Cloud && now >= nextWaitQuery) { + nextWaitQuery = now + kWaitTimeQueryMs; + queueWaitSec = gssv.waitTimeSeconds(target); + } + int w = queueWaitSec; + if (!resuming) { + // Waiting is the expected path here: the server moves us to ReadyToConnect + // when a slot frees up. Show the estimate plus how long we have waited. + const uint64_t waited = (now - stateSince) / 1000; + char el[16]; + std::snprintf(el, sizeof el, "%llu:%02llu", static_cast<unsigned long long>(waited / 60), + static_cast<unsigned long long>(waited % 60)); + setStatus((w > 0 ? "Waiting in queue, about " + std::to_string((w + 59) / 60) + " min left" + : std::string("Waiting in queue for a free server")) + + " (waiting " + el + ")"); + } + } else { + queueWaitSec = -1; + } + + if (s.state == "ReadyToConnect" && !connected) { + if (!resuming) setState(StreamState::Provisioning); + setStatus(resuming ? "Reconnecting..." : "Authenticating..."); + std::string lpt; + if (!auth.fetchLpt(lpt, err) || !gssv.connect(s, lpt, err)) { + if (resuming) return AttemptResult::RetryFresh; + fail("Session authentication failed: " + err); + return AttemptResult::Done; + } + connected = true; + } else if (s.state == "Provisioned") { + if (!ka) { + ka.reset(new Keepalive); + try { + ka->thr = std::thread(&Impl::keepaliveMain, this, base, token, gssvSettings, s, &ka->stop); + } catch (const std::exception& e) { + fail(std::string("Could not start the keepalive thread: ") + e.what()); + return AttemptResult::Done; + } + } + PeerOutcome o = runPeer(gssv, s); + if (o == PeerOutcome::Finished) return AttemptResult::Done; + if (o == PeerOutcome::RetryFresh) return AttemptResult::RetryFresh; + // Same-session re-signal (green-nx #61): the backend accepts a fresh SDP/ICE + // exchange into the same sessionPath. Budgeted over a moving window. + if (quit) break; + uint64_t t = nowMs(); + if (!resignalWindow || t - resignalWindow > static_cast<uint64_t>(kResignalWindowMs)) { + resignalWindow = t; + resignals = 0; + } + if (++resignals > kMaxResignals) { + fail("The connection keeps dropping, please start the stream again"); + return AttemptResult::Done; + } + XC_LOGI("re-signaling the same session (%d/%d in this window)", resignals, kMaxResignals); + resuming = true; + setStatus("Reconnecting..."); + connected = false; + stateSince = nowMs(); + lastState.clear(); + continue; // poll state again right away + } else if (s.state == "Failed") { + if (resuming) { + XC_LOGW("resumed session unrecoverable: %s %s", s.errorCode.c_str(), s.errorMessage.c_str()); + return AttemptResult::RetryFresh; + } + sessionError = s.errorCode; + if (!s.errorMessage.empty()) sessionError += (sessionError.empty() ? "" : ": ") + s.errorMessage; + if (sessionError.empty()) sessionError = "unknown error"; + return AttemptResult::SessionFailed; + } else if (s.state != "WaitingForResources") { + if (!resuming) { + setState(StreamState::Provisioning); + setStatus("Preparing your session..."); + } + } + + uint64_t cap = resuming ? kResumeStateCapMs + : (s.state == "WaitingForResources" ? kQueueCapMs : kStateCapMs); + if (now - stateSince > cap) { + if (resuming) return AttemptResult::RetryFresh; + sessionError.clear(); // timeout + return AttemptResult::SessionFailed; + } + if (!sleepMs(kStatePollMs)) break; + } + return AttemptResult::Done; + } + + struct Offering { + std::string name, base, token; + }; + + // Offerings to try for this session, in order, from a token snapshot. + std::vector<Offering> buildOffers(const Tokens& t) const { + std::vector<Offering> offers; + if (kind == SessionKind::Home) { + if (!t.xhomeGs.empty() && !t.xhomeBase.empty()) offers.push_back({"xhome", t.xhomeBase, t.xhomeGs}); + } else { + const bool haveF2p = !t.xcloudF2pGs.empty() && !t.xcloudF2pBase.empty(); + if (f2pOnly && haveF2p) offers.push_back({"xgpuwebf2p", t.xcloudF2pBase, t.xcloudF2pGs}); + if (!t.xcloudGs.empty() && !t.xcloudBase.empty()) offers.push_back({"xgpuweb", t.xcloudBase, t.xcloudGs}); + if (!f2pOnly && (settings.f2pFallback || offers.empty()) && haveF2p) + offers.push_back({"xgpuwebf2p", t.xcloudF2pBase, t.xcloudF2pGs}); + } + return offers; + } + + void runSession() { + const bool home = kind == SessionKind::Home; + setState(StreamState::Starting); + setStatus(home ? "Signing in to your Xbox..." : "Signing in to xCloud..."); + std::string err; + if (!auth.ensureFresh(err)) { + fail("Sign-in failed: " + err); + return; + } + if (quit) return; + + std::vector<Offering> offers = buildOffers(cfg.tokens()); + if (offers.empty()) { + fail(home ? "Remote play is not available for this account" + : "Cloud gaming is not available for this account"); + return; + } + // gssv requests of an xhome session always carry the android/720 device fingerprint: the + // console agent answers windows/tizen with AgentCommandError (green-nx engine.cpp builds + // the home session with QualityTier::P720). The in-band resolution / capabilities and the + // SDP offer still follow the user's setting. + Settings gssvSettings = settings; + gssvSettings.resolution = gssv::deviceTierFor(home, settings.resolution); + + size_t offerIdx = 0; + std::vector<bool> cleaned(offers.size(), false); + bool authRetried = false; + // Home: a play request against a sleeping console wakes it but fails with + // AgentCommandError while it boots its streaming service, and a freshly booted + // console sits in WaitingForServerToRegister for a while. Cloud gets one retry to + // re-roll a dead media path. + const int attempts = home ? 6 : 2; + bool registering = false; + int resignals = 0; + uint64_t resignalWindow = 0; + + for (int attempt = 0; attempt < attempts && !quit; ++attempt) { + const Offering off = offers[offerIdx]; + if (attempt > 0) { + std::string of = " (attempt " + std::to_string(attempt + 1) + " of " + std::to_string(attempts) + ")"; + setState(StreamState::Starting); + setStatus(registering ? "Your console is still registering..." + of + : home ? "Waking your console..." + of + : "Retrying the connection..."); + // Home consoles need ~5 s to boot streaming; cloud needs the dead session's + // teardown to release the account's slot. + if (!sleepMs(home ? 5000 : 3000)) break; + } + GssvClient gssv(off.base, off.token, gssvSettings); + if (!cleaned[offerIdx]) { + setStatus("Cleaning up old sessions..."); + gssv.cleanupActive(kind); + cleaned[offerIdx] = true; + if (quit) break; + } + setState(StreamState::Starting); + setStatus("Requesting a session..."); + XC_LOGI("requesting %s session via %s (attempt %d of %d, device tier %s)", home ? "home" : "cloud", + off.name.c_str(), attempt + 1, attempts, gssvSettings.resolution.c_str()); + SessionInfo s; + if (!gssv.play(kind, target, s, err)) { + if (quit) break; + if (gssv.authRejected()) { + // The stored gsToken looked valid but was refused (revoked, entitlement + // change, wall clock jumped): re-authorize once and start over. + auth.invalidateStreaming(); + if (authRetried) { + fail("Xbox refused the sign-in for streaming. Please try again or sign in again."); + return; + } + authRetried = true; + setStatus(home ? "Signing in to your Xbox again..." : "Signing in to xCloud again..."); + std::string aerr; + if (!auth.ensureFresh(aerr)) { + fail("Sign-in failed: " + aerr); + return; + } + if (quit) break; + offers = buildOffers(cfg.tokens()); + if (offers.empty()) { + fail(home ? "Remote play is not available for this account" + : "Cloud gaming is not available for this account"); + return; + } + offerIdx = 0; + cleaned.assign(offers.size(), false); + --attempt; // does not count as a failed attempt + continue; + } + std::string all = err + " " + s.errorCode + " " + s.errorMessage; + if (contains(all, "OfferingDoesNotContainTitle") && offerIdx + 1 < offers.size()) { + ++offerIdx; + XC_LOGI("title not in %s, retrying with %s", off.name.c_str(), offers[offerIdx].name.c_str()); + --attempt; // does not count as a failed attempt + continue; + } + fail("Could not start the session: " + err); + return; + } + XC_LOGI("session created: %s", s.sessionPath.c_str()); + + std::string sessionError; + resuming = false; + AttemptResult res = + runAttempt(gssv, off.base, off.token, gssvSettings, s, sessionError, resignals, resignalWindow); + resuming = false; + closePeer(); + XC_LOGI("stopping session %s", s.sessionPath.c_str()); + { + Http::AbortScope mustRun(nullptr); // the DELETE has to go out even when cancelled + // ... but bounded: a user waiting on stop (or app exit) must not sit out 20 s. + gssv.stop(s, quit ? kQuitDeleteTimeoutSec : kDeleteTimeoutSec); + } + if (gssv.authRejected()) auth.invalidateStreaming(); // re-authorize before the next one + if (quit || res == AttemptResult::Done) return; + + if (res == AttemptResult::RetryFresh) { + if (attempt == attempts - 1) { + fail(everStreamed ? "The connection to the server was lost" + : "The server's media connection never came up"); + return; + } + XC_LOGW("retrying with a fresh session"); + continue; + } + // SessionFailed + if (sessionError.empty()) { + fail("Timed out waiting for a session"); + return; + } + XC_LOGW("session attempt %d failed: %s", attempt + 1, sessionError.c_str()); + if (contains(sessionError, "OfferingDoesNotContainTitle") && offerIdx + 1 < offers.size()) { + ++offerIdx; + --attempt; + continue; + } + registering = contains(sessionError, "WaitingForServerToRegister"); + bool consoleNotReady = registering || contains(sessionError, "AgentCommandError"); + if (!consoleNotReady || attempt == attempts - 1) { + fail(consoleNotReady ? "Your console never finished registering for remote play. Turn Remote " + "Features off and on, or restart the console, then try again." + : "Session failed: " + sessionError); + return; + } + } + } + + void workerMain() { + // stop() sets quit: in-flight gssv/auth requests on this thread then abort within ~1 s + // instead of running into their timeouts. + Http::AbortScope abortOnQuit(&quit); + guarded("session", [this] { runSession(); }); + try { + closePeer(); + } catch (const std::exception& e) { + XC_LOGE("stream: closing the peer connection threw: %s", e.what()); + } + queueWaitSec = -1; + if (audioOk) audio.setMuted(true); + workerDone = true; + frameCv.notify_all(); + XC_LOGI("stream worker finished (%s)", streamStateName(state.load())); + } + + // Raise quit and wake every waiter. Setting it under evMu closes the lost-wakeup window of + // sleepMs()/eventLoop (predicate checked, then wait entered after the notify). + void signalQuit() { + { + std::lock_guard<std::mutex> lk(evMu); + quit = true; + } + evCv.notify_all(); + { + std::lock_guard<std::mutex> lk(frameMu); + } + frameCv.notify_all(); + } + + void joinThreads() { + for (std::thread* t : {&workerThr, &decodeThr, &inputThr, &rtcpThr}) + if (t->joinable()) t->join(); + } + + // Caller holds lifeMu. Reports Idle/"Stopped" afterwards only while startGen still equals + // publishGen (checked under statusMu, like start() bumps it): a newer start() owns the + // public state and must not be overwritten. kNoPublish: never report. + static constexpr uint64_t kNoPublish = ~uint64_t(0); + void stopLocked(uint64_t publishGen) { + if (running) { + XC_LOGI("stream stop requested"); + signalQuit(); + // The worker closes the PeerConnection and DELETEs the gssv session itself. + joinThreads(); + closePeer(); // no-op unless the worker bailed out early + if (audioOk) audio.close(); + audioOk = false; + if (decoderOk.exchange(false)) decoder.close(); + { + std::lock_guard<std::mutex> lk(frameMu); + frameQ.clear(); + } + running = false; + workerDone = true; + } + std::lock_guard<std::mutex> lk(statusMu); + StreamState s = state.load(); + if (publishGen == startGen.load() && s != StreamState::Failed && s != StreamState::Ended) { + publishState(StreamState::Idle); + status = "Stopped"; + } + } + + // ---- lifecycle thread ---- + + void lifeLoop() { + for (;;) { + std::function<void()> job; + { + std::unique_lock<std::mutex> lk(lifeQMu); + lifeQCv.wait(lk, [this] { return lifeQuit || !lifeQ.empty(); }); + if (lifeQ.empty()) return; // lifeQuit and nothing left + job = std::move(lifeQ.front()); + lifeQ.pop_front(); + } + try { + job(); + } catch (const std::exception& e) { + XC_LOGE("stream: lifecycle job failed: %s", e.what()); + fail(std::string("Internal error: ") + e.what()); + } + } + } + + // Queue a lifecycle job (starts the thread on first use). False if it cannot run. + bool postLife(std::function<void()> job) { + std::lock_guard<std::mutex> lk(lifeQMu); + if (lifeClosing) return false; // stop() is shutting the thread down + if (!lifeThr.joinable()) { + lifeQuit = false; + try { + lifeThr = std::thread(&Impl::lifeLoop, this); + } catch (const std::exception& e) { + XC_LOGE("stream: cannot start the lifecycle thread: %s", e.what()); + return false; + } + } + lifeQ.push_back(std::move(job)); + lifeQCv.notify_one(); + return true; + } + + // Drop queued jobs, let the running one finish, join the thread. + void shutdownLife() { + std::thread t; + { + std::lock_guard<std::mutex> lk(lifeQMu); + lifeQ.clear(); + lifeQuit = true; + lifeClosing = true; + t = std::move(lifeThr); + } + lifeQCv.notify_all(); + if (t.joinable()) t.join(); + std::lock_guard<std::mutex> lk(lifeQMu); + lifeClosing = false; + } + + struct StartParams { + SessionKind kind; + std::string target; + bool f2pOnly; + }; + + // Lifecycle thread: release the previous session, then bring up a new one (unless a newer + // start/stop superseded this request meanwhile). + void doStart(const StartParams& p, uint64_t gen) { + std::lock_guard<std::mutex> life(lifeMu); + if (running) stopLocked(kNoPublish); + if (gen != startGen.load()) return; // cancelled while the previous session wound down + + kind = p.kind; + target = p.target; + f2pOnly = p.kind == SessionKind::Cloud && p.f2pOnly; + settings = cfg.settings(); + installId = cfg.installId(); + quit = false; + queueWaitSec = -1; + serverEnded = false; + everStreamed = false; + resuming = false; + handshakeDone = false; + pcConnected = false; + gotFrame = false; + videoBytes = 0; + framesDecoded = 0; + decodeUsAvg = 0; + decodeErrors = 0; + keyframeReqs = 0; + queueFlushes = 0; + workerTick = 0; + videoLossPct = 0; + frameW = 0; + frameH = 0; + { + std::lock_guard<std::mutex> lk(inputMu); + serializer = proto::InputSerializer(); + } + { + std::lock_guard<std::mutex> lk(padMu); + pad = GamepadState(); + } + { + std::lock_guard<std::mutex> lk(vibMu); + vibPending = false; + vibLogged = false; + } + { + std::lock_guard<std::mutex> lk(statsMu); + st = StreamStats(); + } + { + std::lock_guard<std::mutex> lk(statusMu); + serverEndReason.clear(); + } + setState(StreamState::Starting); + setStatus("Starting..."); + + XC_LOGI("stream start: %s %s (res %s, %d kbps)", kind == SessionKind::Home ? "home" : "cloud", target.c_str(), + settings.resolution.c_str(), settings.bitrateKbps); + + if (!decoder.init(4)) { + fail("The video decoder could not be initialised"); + active = false; + return; + } + decoderOk = true; + audioOk = audio.init(); + if (audioOk) + audio.setMuted(false); + else + XC_LOGW("audio output unavailable, streaming without sound"); + + workerDone = false; + running = true; + try { + workerThr = std::thread(&Impl::workerMain, this); + decodeThr = std::thread(&Impl::decodeMain, this); + inputThr = std::thread(&Impl::inputMain, this); + rtcpThr = std::thread(&Impl::rtcpMain, this); + } catch (const std::exception& e) { + // Thread limit (std::system_error): unwind whatever started and fail cleanly. + XC_LOGE("stream: cannot start the session threads: %s", e.what()); + fail(std::string("Could not start the stream threads: ") + e.what()); + signalQuit(); + workerDone = true; + joinThreads(); + closePeer(); + if (audioOk) audio.close(); + audioOk = false; + if (decoderOk.exchange(false)) decoder.close(); + running = false; + active = false; + } + } +}; + +// ============================================================================= + +Streamer::Streamer(Config& cfg, Auth& auth) : d_(new Impl(cfg, auth)) {} +Streamer::~Streamer() { stop(); } + +bool Streamer::start(SessionKind kind, const std::string& titleOrServerId, bool f2pOnly) { + Impl& d = *d_; + // An active session: requested and not stopped, unless it already finished on its own + // (Ended/Failed) - that one is released first by doStart(). + if (d.active && !(d.running && d.workerDone)) { + XC_LOGW("stream start ignored: a session is already running"); + return false; + } + uint64_t gen; + { + // Publish Starting now: the UI must not read a previous session's Failed/Ended state + // while the lifecycle thread is still releasing it (see stopLocked's publishGen). + std::lock_guard<std::mutex> lk(d.statusMu); + gen = ++d.startGen; + d.status = "Preparing..."; + d.publishState(StreamState::Starting); + } + d.active = true; + Impl::StartParams p{kind, titleOrServerId, f2pOnly}; + if (!d.postLife([&d, p, gen] { d.doStart(p, gen); })) { + d.active = false; + { + std::lock_guard<std::mutex> lk(d.statusMu); + d.status = "Could not start the stream (out of threads)"; + } + d.publishState(StreamState::Failed); + return false; + } + return true; +} + +void Streamer::stopAsync() { + Impl& d = *d_; + const uint64_t gen = ++d.startGen; // cancels a queued start + d.active = false; + if (d.running) d.signalQuit(); // the worker starts aborting right away + d.postLife([&d, gen] { + std::lock_guard<std::mutex> life(d.lifeMu); + d.stopLocked(gen); + }); +} + +StreamState Streamer::state() const { return d_->state.load(); } + +std::string Streamer::statusText() const { + std::lock_guard<std::mutex> lk(d_->statusMu); + return d_->status; +} + +// gssv exposes no queue position, only an estimated wait (see queueWaitSeconds()). +int Streamer::queuePosition() const { return -1; } + +int Streamer::queueWaitSeconds() const { + return d_->state.load() == StreamState::Queued ? d_->queueWaitSec.load() : -1; +} + +void Streamer::setGamepad(const GamepadState& st) { + std::lock_guard<std::mutex> lk(d_->padMu); + d_->pad = st; +} + +bool Streamer::takeVibration(Vibration& out) { + std::lock_guard<std::mutex> lk(d_->vibMu); + if (!d_->vibPending) return false; + out = d_->vib; + d_->vibPending = false; + return true; +} + +AVFrame* Streamer::currentFrame() { + if (!d_->decoderOk) return nullptr; + AVFrame* f = d_->decoder.latest(); + if (f && f->width > 0 && f->height > 0) { + d_->frameW = f->width; + d_->frameH = f->height; + } + return f; +} + +StreamStats Streamer::stats() const { + std::lock_guard<std::mutex> lk(d_->statsMu); + StreamStats s = d_->st; + if (d_->frameW) { + s.width = d_->frameW; + s.height = d_->frameH; + } + return s; +} + +void Streamer::requestKeyframe() { d_->requestKeyframeInternal(false); } + +void Streamer::stop() { + Impl& d = *d_; + const uint64_t gen = ++d.startGen; + d.active = false; + if (d.running) d.signalQuit(); + d.shutdownLife(); // queued starts are dropped; a running job finishes first + std::lock_guard<std::mutex> life(d.lifeMu); + d.stopLocked(gen); +} + +} // namespace xc diff --git a/src/stream/streamer.hpp b/src/stream/streamer.hpp new file mode 100644 index 0000000..6bbce38 --- /dev/null +++ b/src/stream/streamer.hpp @@ -0,0 +1,91 @@ +// Nubix — orchestrates one streaming session: auth refresh, gssv start/queue/provision, +// SDP/ICE exchange, WebRTC, control/message/input channels, decode, audio, RTCP, keepalive. +// Runs on its own worker thread; the main thread polls state and pulls frames. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstdint> +#include <memory> +#include <string> + +#include "core/auth.hpp" +#include "core/config.hpp" +#include "core/gssv.hpp" +#include "core/protocol.hpp" + +struct AVFrame; + +namespace xc { + +enum class StreamState { Idle, Starting, Queued, Provisioning, Connecting, Streaming, Ended, Failed }; + +// Human readable name for a state (logs / UI). +const char* streamStateName(StreamState s); + +// Live statistics for the stream overlay. +struct StreamStats { + int width = 0, height = 0; + double fps = 0; // decoded frames per second + double bitrateKbps = 0; // received video bitrate + double decodeMs = 0; // average decode time + double lossPercent = 0; // video packet loss over the last second + int rttMs = -1; // unknown = -1 + int audioBufferMs = 0; +}; + +class Streamer { +public: + Streamer(Config& cfg, Auth& auth); + ~Streamer(); + Streamer(const Streamer&) = delete; + Streamer& operator=(const Streamer&) = delete; + + // Start a session. Non-blocking: the work (releasing a previous session that is still + // winding down, decoder/audio init, the worker threads) runs on the Streamer's own lifecycle + // thread; state() reads Starting right away. False if a session is already active (started + // and neither stopped nor finished on its own). + // f2pOnly (cloud): the title is only playable through the free-to-play offering + // (Title::f2pOnly) - start with xgpuwebf2p instead of xgpuweb, regardless of f2pFallback. + bool start(SessionKind kind, const std::string& titleOrServerId, bool f2pOnly = false); + + // Current state, status line and queue position (-1 when not queued). Thread-safe. + StreamState state() const; + std::string statusText() const; + int queuePosition() const; + + // Estimated queue wait in seconds while state() == Queued (gssv /v1/waittime, cloud only), + // -1 when unknown or not queued. Thread-safe. + int queueWaitSeconds() const; + + // Latest gamepad state; called each frame from the main thread. + void setGamepad(const GamepadState& st); + + // Pop a pending rumble command from the server. Thread-safe. + bool takeVibration(Vibration& out); + + // Newest decoded frame not yet returned (new reference, caller av_frame_free's it), or nullptr. + AVFrame* currentFrame(); + + // Snapshot of stream statistics. + StreamStats stats() const; + + // Send a keyframe request (PLI + control message), rate-limited. + void requestKeyframe(); + + // Request the session to stop without blocking: signals the worker (in-flight requests + // abort within ~1 s) and queues the teardown (DELETE on gssv, close PC, release + // decoder/audio) on the lifecycle thread. A later start() runs after it. Idempotent. + void stopAsync(); + + // Stop the session synchronously and shut the lifecycle thread down (app exit / destructor): + // cancels queued starts, joins every thread. The final gssv DELETE is bounded to ~5 s. + // Safe to call in any state; idempotent. start() works again afterwards. + void stop(); + +private: + struct Impl; + std::unique_ptr<Impl> d_; +}; + +} // namespace xc diff --git a/src/stream/video.cpp b/src/stream/video.cpp new file mode 100644 index 0000000..e0b8eb4 --- /dev/null +++ b/src/stream/video.cpp @@ -0,0 +1,347 @@ +// Nubix — H.264 decoder (FFmpeg). +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +// +// Software H.264 decode with libavcodec: slice threads, AV_CODEC_FLAG_LOW_DELAY + FLAG2_FAST, +// default error concealment (guess MVs + deblock). One access unit in, at most one frame out. +// Decoder setup and the corrupt-frame detection follow green-nx +// (src/switch/stream/video_decoder.cpp, GPL-3.0). +// +// Threads: decode()/flush() run on the stream worker thread; latest() is called by the render +// thread. Frame handoff is double-buffered: the decoder writes into its private `work` frame and +// then moves the reference into the shared `pending` slot (replacing an unconsumed older frame); +// latest() moves `pending` out into a fresh AVFrame for the caller. Only refcounted buffers move, +// no pixel data is copied. +#include "stream/video.hpp" + +#include "core/log.hpp" + +#include <atomic> +#include <climits> +#include <chrono> +#include <cstdio> +#include <cstring> +#include <mutex> +#include <string> + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavutil/error.h> +#include <libavutil/frame.h> +#include <libavutil/log.h> +} + +namespace xc { + +namespace { + +using Clock = std::chrono::steady_clock; + +// After this many consecutive access units that produced no picture (without an explicit +// error), assume the decoder lost its references and ask for a keyframe. +constexpr int kMaxSilentAus = 5; + +// FFmpeg log forwarding. The h264 decoder prints one line per damaged slice when packets are +// lost, so everything goes to Debug and is rate-limited. +std::atomic<int64_t> g_avLogWindowStart{0}; +std::atomic<int> g_avLogCount{0}; +constexpr int kAvLogMaxPerSecond = 20; + +void avLogCallback(void* avcl, int level, const char* fmt, va_list vl) { + if (level > AV_LOG_WARNING) return; + if (level > AV_LOG_ERROR && logLevel() > LogLevel::Debug) return; + + const int64_t nowS = + std::chrono::duration_cast<std::chrono::seconds>(Clock::now().time_since_epoch()).count(); + int64_t start = g_avLogWindowStart.load(std::memory_order_relaxed); + if (nowS != start && g_avLogWindowStart.compare_exchange_strong(start, nowS)) { + const int suppressed = g_avLogCount.exchange(0) - kAvLogMaxPerSecond; + if (suppressed > 0) XC_LOGD("ffmpeg: %d log lines suppressed", suppressed); + } + if (g_avLogCount.fetch_add(1, std::memory_order_relaxed) >= kAvLogMaxPerSecond) return; + + char line[512]; + int prefix = 1; + av_log_format_line2(avcl, level, fmt, vl, line, sizeof(line), &prefix); + size_t len = std::strlen(line); + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; + if (len == 0) return; + if (level <= AV_LOG_FATAL) + XC_LOGE("ffmpeg: %s", line); + else + XC_LOGD("ffmpeg: %s", line); +} + +void installAvLog() { + static std::once_flag once; + std::call_once(once, [] { + av_log_set_level(AV_LOG_WARNING); + av_log_set_callback(avLogCallback); + }); +} + +std::string avErr(int err) { + char buf[AV_ERROR_MAX_STRING_SIZE] = {0}; + av_strerror(err, buf, sizeof(buf)); + return buf; +} + +} // namespace + +struct VideoDecoder::Impl { + // ---- decoder (guarded by decodeMu) ---- + std::mutex decodeMu; + AVCodecContext* ctx = nullptr; + AVPacket* pkt = nullptr; + AVFrame* work = nullptr; + int silentAus = 0; + bool gotFrame = false; + Clock::time_point windowStart{}; + double windowSumMs = 0; + uint64_t windowCount = 0; + bool windowPublished = false; + + // ---- handoff (guarded by handoffMu) ---- + std::mutex handoffMu; + AVFrame* pending = nullptr; + bool hasPending = false; + + // ---- stats (lock-free reads) ---- + std::atomic<double> avgMs{0.0}; + std::atomic<uint64_t> frames{0}; + std::atomic<uint64_t> errors{0}; + std::atomic<uint64_t> replaced{0}; + std::atomic<bool> needKey{false}; + std::atomic<int> width{0}; + std::atomic<int> height{0}; + + void markError() { + errors.fetch_add(1, std::memory_order_relaxed); + needKey.store(true, std::memory_order_relaxed); + } + + // Move `work` into the handoff slot. Called with decodeMu held. + void publish() { + width.store(work->width, std::memory_order_relaxed); + height.store(work->height, std::memory_order_relaxed); + std::lock_guard<std::mutex> lk(handoffMu); + if (hasPending) { + replaced.fetch_add(1, std::memory_order_relaxed); + av_frame_unref(pending); + } + av_frame_move_ref(pending, work); + hasPending = true; + } + + void recordTiming(double ms) { + const auto now = Clock::now(); + if (windowCount == 0 && windowSumMs == 0) windowStart = now; + windowSumMs += ms; + ++windowCount; + if (now - windowStart >= std::chrono::seconds(1)) { + avgMs.store(windowSumMs / static_cast<double>(windowCount), std::memory_order_relaxed); + windowPublished = true; + windowSumMs = 0; + windowCount = 0; + } else if (!windowPublished) { + // First second of the stream: report the running average. + avgMs.store(windowSumMs / static_cast<double>(windowCount), std::memory_order_relaxed); + } + } + + void resetCounters() { + silentAus = 0; + gotFrame = false; + windowSumMs = 0; + windowCount = 0; + windowPublished = false; + avgMs.store(0.0); + frames.store(0); + errors.store(0); + replaced.store(0); + needKey.store(false); + width.store(0); + height.store(0); + } + + void dropPending() { + std::lock_guard<std::mutex> lk(handoffMu); + if (pending) av_frame_unref(pending); + hasPending = false; + } + + void freeAll() { + if (ctx) avcodec_free_context(&ctx); + if (pkt) av_packet_free(&pkt); + if (work) av_frame_free(&work); + std::lock_guard<std::mutex> lk(handoffMu); + if (pending) av_frame_free(&pending); + hasPending = false; + } +}; + +VideoDecoder::VideoDecoder() : d_(new Impl) {} +VideoDecoder::~VideoDecoder() { close(); } + +bool VideoDecoder::init(int threads) { + installAvLog(); + std::lock_guard<std::mutex> lk(d_->decodeMu); + d_->freeAll(); + d_->resetCounters(); + + const AVCodec* codec = avcodec_find_decoder(AV_CODEC_ID_H264); + if (!codec) { + XC_LOGE("video: FFmpeg has no H.264 decoder"); + return false; + } + d_->ctx = avcodec_alloc_context3(codec); + d_->pkt = av_packet_alloc(); + d_->work = av_frame_alloc(); + { + std::lock_guard<std::mutex> hl(d_->handoffMu); + d_->pending = av_frame_alloc(); + } + if (!d_->ctx || !d_->pkt || !d_->work || !d_->pending) { + XC_LOGE("video: out of memory allocating decoder"); + d_->freeAll(); + return false; + } + + AVCodecContext* c = d_->ctx; + c->flags |= AV_CODEC_FLAG_LOW_DELAY; + c->flags2 |= AV_CODEC_FLAG2_FAST; + // Slice threading adds no latency (frame threading would add threads-1 frames of delay). + c->thread_type = FF_THREAD_SLICE; + c->thread_count = threads > 0 ? threads : 1; + c->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK; + c->workaround_bugs = FF_BUG_AUTODETECT; + + const int rc = avcodec_open2(c, codec, nullptr); + if (rc < 0) { + XC_LOGE("video: avcodec_open2 failed: %s", avErr(rc).c_str()); + d_->freeAll(); + return false; + } + XC_LOGI("video: %s decoder ready (slice threads=%d, low delay)", codec->name, c->thread_count); + return true; +} + +bool VideoDecoder::decode(const uint8_t* au, size_t n, int64_t pts) { + std::lock_guard<std::mutex> lk(d_->decodeMu); + Impl& d = *d_; + if (!d.ctx) return false; + if (!au || n == 0) return true; + if (n > static_cast<size_t>(INT32_MAX - AV_INPUT_BUFFER_PADDING_SIZE)) { + d.markError(); + return false; + } + + const auto t0 = Clock::now(); + bool ok = true; + + av_packet_unref(d.pkt); + if (av_new_packet(d.pkt, static_cast<int>(n)) < 0) { // zero-fills the input padding + XC_LOGE("video: av_new_packet(%zu) failed", n); + return false; + } + std::memcpy(d.pkt->data, au, n); + d.pkt->pts = pts; + d.pkt->dts = pts; + + int produced = 0; + auto drain = [&]() { + for (;;) { + const int r = avcodec_receive_frame(d.ctx, d.work); + if (r == AVERROR(EAGAIN) || r == AVERROR_EOF) break; + if (r < 0) { + XC_LOGD("video: receive_frame: %s", avErr(r).c_str()); + d.markError(); + ok = false; + break; + } + if (d.work->decode_error_flags != 0 || (d.work->flags & AV_FRAME_FLAG_CORRUPT)) { + // Concealed frame: still shown (better than freezing), but ask for a keyframe. + d.markError(); + ok = false; + } + ++produced; + d.frames.fetch_add(1, std::memory_order_relaxed); + d.publish(); // leaves `work` empty + } + }; + + int r = avcodec_send_packet(d.ctx, d.pkt); + if (r == AVERROR(EAGAIN)) { + // Should not happen with low delay since we always drain, but be safe. + drain(); + r = avcodec_send_packet(d.ctx, d.pkt); + } + if (r < 0) { + XC_LOGD("video: send_packet (%zu bytes): %s", n, avErr(r).c_str()); + d.markError(); + ok = false; + } + drain(); + av_packet_unref(d.pkt); + + if (produced > 0) { + d.gotFrame = true; + d.silentAus = 0; + } else if (ok && ++d.silentAus >= kMaxSilentAus) { + // SPS/PPS-only AUs legitimately produce nothing, but a run of them means the decoder + // is discarding pictures (missing references / no IDR yet). + d.silentAus = 0; + d.needKey.store(true, std::memory_order_relaxed); + if (d.gotFrame) ok = false; + } + + const double ms = std::chrono::duration<double, std::milli>(Clock::now() - t0).count(); + d.recordTiming(ms); + return ok; +} + +AVFrame* VideoDecoder::latest() { + std::lock_guard<std::mutex> lk(d_->handoffMu); + if (!d_->hasPending || !d_->pending) return nullptr; + AVFrame* out = av_frame_alloc(); + if (!out) return nullptr; + av_frame_move_ref(out, d_->pending); + d_->hasPending = false; + return out; +} + +double VideoDecoder::avgDecodeMs() const { return d_->avgMs.load(std::memory_order_relaxed); } + +uint64_t VideoDecoder::framesDecoded() const { return d_->frames.load(std::memory_order_relaxed); } + +bool VideoDecoder::needKeyframe() { return d_->needKey.exchange(false, std::memory_order_relaxed); } + +uint64_t VideoDecoder::decodeErrors() const { return d_->errors.load(std::memory_order_relaxed); } + +int VideoDecoder::width() const { return d_->width.load(std::memory_order_relaxed); } + +int VideoDecoder::height() const { return d_->height.load(std::memory_order_relaxed); } + +void VideoDecoder::flush() { + std::lock_guard<std::mutex> lk(d_->decodeMu); + if (d_->ctx) avcodec_flush_buffers(d_->ctx); + if (d_->work) av_frame_unref(d_->work); + d_->silentAus = 0; + d_->gotFrame = false; + d_->dropPending(); +} + +void VideoDecoder::close() { + if (!d_) return; + std::lock_guard<std::mutex> lk(d_->decodeMu); + if (d_->ctx) { + XC_LOGI("video: closing decoder (%llu frames, %llu errors, %llu unconsumed)", + static_cast<unsigned long long>(d_->frames.load()), + static_cast<unsigned long long>(d_->errors.load()), + static_cast<unsigned long long>(d_->replaced.load())); + } + d_->freeAll(); +} + +} // namespace xc diff --git a/src/stream/video.hpp b/src/stream/video.hpp new file mode 100644 index 0000000..453ed5b --- /dev/null +++ b/src/stream/video.hpp @@ -0,0 +1,63 @@ +// Nubix — H.264 decoder (FFmpeg libavcodec, software, slice threads, low delay). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstddef> +#include <cstdint> +#include <memory> + +struct AVFrame; + +namespace xc { + +class VideoDecoder { +public: + VideoDecoder(); + ~VideoDecoder(); + VideoDecoder(const VideoDecoder&) = delete; + VideoDecoder& operator=(const VideoDecoder&) = delete; + + // Open the H.264 decoder (thread_type = FF_THREAD_SLICE, AV_CODEC_FLAG_LOW_DELAY, FLAG2_FAST). + bool init(int threads = 4); + + // Decode one Annex-B access unit. False on decode error (caller requests a keyframe). + // Called from the stream worker thread. + bool decode(const uint8_t* au, size_t n, int64_t pts); + + // Thread-safe frame handoff: returns a NEW reference to the most recent decoded frame that + // has not been returned before, or nullptr if none. Caller owns it (av_frame_free). + AVFrame* latest(); + + // Average decode time in milliseconds over the last second (stats overlay). + double avgDecodeMs() const; + + // Decoded frames since init (stats overlay). + uint64_t framesDecoded() const; + + // Free decoder and buffered frames. Idempotent. + void close(); + + // ---- additive API (media module) ---- + + // True if a decode error / corrupt frame happened since the last call (flag is cleared). + // The caller should send PLI + control keyframe request (rate-limited). + bool needKeyframe(); + + // Number of decode errors (send/receive failures + frames flagged corrupt) since init. + uint64_t decodeErrors() const; + + // Dimensions of the most recently decoded frame (0 before the first frame). + int width() const; + int height() const; + + // Drop decoder state and any pending frame (e.g. after a stream reconnect). Keeps the + // decoder open; the next frame must be a keyframe. + void flush(); + +private: + struct Impl; + std::unique_ptr<Impl> d_; +}; + +} // namespace xc diff --git a/src/stream/webrtc.cpp b/src/stream/webrtc.cpp new file mode 100644 index 0000000..b111d98 --- /dev/null +++ b/src/stream/webrtc.cpp @@ -0,0 +1,858 @@ +// Nubix — libdatachannel wrapper. +// +// PeerConnection layout follows GreenOvercast (src/session/webrtc_session.zig, MPL-2.0): recvonly +// H.264 track with mid "video", then Opus with mid "audio", then the data channels (whose m-line +// therefore gets mid "0"), all created before the offer; ICE gathering is awaited, the ICE +// credentials and DTLS fingerprint are taken from libdatachannel's own local description, and +// the server answer is applied verbatim with remote candidates on mid "0". +// +// RTCP feedback (PLI with sender SSRC 0, generic NACK, compound RR+SDES with LSR/DLSR, REMB) is +// built by hand following green-nx deps/patches/libpeer-switch.patch (GPL-3.0) and sent through +// the video track: a track without media handler passes RTCP straight to the SRTP transport +// regardless of its direction (libdatachannel impl/track.cpp Track::outgoing). +// +// Incoming media: a PeerConnection-wide media handler sees every decrypted packet first. It +// parses sender reports, latches the media SSRCs by payload type, and delivers RTP whose SSRC the +// answer did not announce (libdatachannel would drop it — home consoles omit a=ssrc for audio, +// see green-nx). Everything else is delivered through the tracks' onMessage callbacks. +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Portions derived from GreenOvercast (https://github.com/Producdevity/GreenOvercast), MPL-2.0, used here under GPL-3.0 (MPL-2.0 section 3.3). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "stream/webrtc.hpp" + +#include <rtc/rtc.hpp> + +#include <algorithm> +#include <atomic> +#include <chrono> +#include <condition_variable> +#include <cstring> +#include <map> +#include <mutex> +#include <random> +#include <set> +#include <sstream> + +#include "core/log.hpp" + +namespace xc { + +namespace { + +constexpr uint8_t kDefaultVideoPt = 102; +constexpr uint8_t kDefaultAudioPt = 111; +const char* const kStunServer = "stun:stun.l.google.com:19302"; + +// Channel label / sub-protocol pairs (as in green-nx; reliable + ordered). +struct ChannelSpec { + const char* label; + const char* protocol; +}; +constexpr ChannelSpec kChannels[] = { + {"chat", "chatV1"}, + {"control", "controlV1"}, + {"input", "1.0"}, + {"message", "messageV1"}, +}; + +uint16_t be16(const uint8_t* p) { return static_cast<uint16_t>((p[0] << 8) | p[1]); } +uint32_t be32(const uint8_t* p) { + return (static_cast<uint32_t>(p[0]) << 24) | (static_cast<uint32_t>(p[1]) << 16) | + (static_cast<uint32_t>(p[2]) << 8) | p[3]; +} +void put16(std::vector<uint8_t>& v, uint16_t x) { + v.push_back(static_cast<uint8_t>(x >> 8)); + v.push_back(static_cast<uint8_t>(x)); +} +void put32(std::vector<uint8_t>& v, uint32_t x) { + v.push_back(static_cast<uint8_t>(x >> 24)); + v.push_back(static_cast<uint8_t>(x >> 16)); + v.push_back(static_cast<uint8_t>(x >> 8)); + v.push_back(static_cast<uint8_t>(x)); +} +void set16(std::vector<uint8_t>& v, size_t at, uint16_t x) { + v[at] = static_cast<uint8_t>(x >> 8); + v[at + 1] = static_cast<uint8_t>(x); +} + +bool isRtcpPacket(const uint8_t* p, size_t n) { + if (n < 8) return false; + const uint8_t pt = p[1] & 0x7F; + return pt >= 64 && pt <= 95; // RFC 5761 §4 +} + +const char* stateName(rtc::PeerConnection::State s) { + switch (s) { + case rtc::PeerConnection::State::New: return "new"; + case rtc::PeerConnection::State::Connecting: return "connecting"; + case rtc::PeerConnection::State::Connected: return "connected"; + case rtc::PeerConnection::State::Disconnected: return "disconnected"; + case rtc::PeerConnection::State::Failed: return "failed"; + case rtc::PeerConnection::State::Closed: return "closed"; + } + return "unknown"; +} + +std::string trim(const std::string& s) { + size_t b = 0, e = s.size(); + while (b < e && (s[b] == ' ' || s[b] == '\t' || s[b] == '\r' || s[b] == '\n')) ++b; + while (e > b && (s[e - 1] == ' ' || s[e - 1] == '\t' || s[e - 1] == '\r' || s[e - 1] == '\n')) --e; + return s.substr(b, e - b); +} + +void initRtcLoggerOnce() { + static std::once_flag once; + std::call_once(once, [] { + const rtc::LogLevel lvl = logLevel() == LogLevel::Debug ? rtc::LogLevel::Info : rtc::LogLevel::Warning; + rtc::InitLogger(lvl, [](rtc::LogLevel level, std::string message) { + LogLevel l = LogLevel::Debug; + switch (level) { + case rtc::LogLevel::Fatal: + case rtc::LogLevel::Error: l = LogLevel::Error; break; + case rtc::LogLevel::Warning: l = LogLevel::Warn; break; + case rtc::LogLevel::Info: l = LogLevel::Info; break; + default: l = LogLevel::Debug; break; + } + log(l, "rtc: %s", message.c_str()); + }); + }); +} + +uint32_t randomSsrc() { + std::random_device rd; + uint32_t v = 0; + while (v == 0) v = (static_cast<uint32_t>(rd()) << 1) ^ static_cast<uint32_t>(rd()); + return v; +} + +// What the server answer says about the media sections. +struct AnswerInfo { + std::set<uint8_t> videoPts, audioPts; + std::set<uint32_t> videoSsrcs, audioSsrcs; +}; + +AnswerInfo parseAnswer(const std::string& sdp) { + AnswerInfo info; + std::istringstream in(sdp); + std::string line; + enum class Sec { None, Video, Audio, Other } sec = Sec::None; + std::string kind; + while (std::getline(in, line)) { + line = trim(line); + if (line.rfind("m=", 0) == 0) { + kind = line.substr(2, line.find(' ') == std::string::npos ? std::string::npos : line.find(' ') - 2); + sec = kind == "video" ? Sec::Video : kind == "audio" ? Sec::Audio : Sec::Other; + continue; + } + if (sec != Sec::Video && sec != Sec::Audio) continue; + if (line.rfind("a=mid:", 0) == 0) { + const std::string mid = line.substr(6); + if (mid == "video") sec = Sec::Video; + else if (mid == "audio") sec = Sec::Audio; + } else if (line.rfind("a=rtpmap:", 0) == 0) { + const size_t sp = line.find(' '); + if (sp == std::string::npos) continue; + const int pt = std::atoi(line.substr(9, sp - 9).c_str()); + std::string codec = line.substr(sp + 1); + std::transform(codec.begin(), codec.end(), codec.begin(), [](unsigned char c) { return std::tolower(c); }); + if (pt < 0 || pt > 127) continue; + if (sec == Sec::Video && codec.rfind("h264/", 0) == 0) info.videoPts.insert(static_cast<uint8_t>(pt)); + if (sec == Sec::Audio && codec.rfind("opus/", 0) == 0) info.audioPts.insert(static_cast<uint8_t>(pt)); + } else if (line.rfind("a=ssrc:", 0) == 0) { + const uint32_t ssrc = static_cast<uint32_t>(std::strtoul(line.c_str() + 7, nullptr, 10)); + if (ssrc == 0) continue; + (sec == Sec::Video ? info.videoSsrcs : info.audioSsrcs).insert(ssrc); + } + } + return info; +} + +// State shared with every libdatachannel callback. Callbacks hold a shared_ptr to it, never to +// WebRtc, and are gated by `mu` + `closed` so that none fires after close() returned. +struct Shared { + std::recursive_mutex mu; // held while a user callback runs; recursive so callbacks may call back in + bool closed = false; + WebRtc::Callbacks cb; + + std::atomic<uint32_t> videoSsrc{0}, audioSsrc{0}; + + std::mutex infoMu; // answer-derived demux info + std::set<uint8_t> videoPts{kDefaultVideoPt}, audioPts{kDefaultAudioPt}; + std::set<uint32_t> routable; // SSRCs libdatachannel routes to a track (announced by a=ssrc) + std::set<uint32_t> answerVideoSsrcs, answerAudioSsrcs; + + std::mutex gatherMu; + std::condition_variable gatherCv; + bool gatherDone = false; + std::vector<std::string> trickled; + + enum class Kind { Unknown, Video, Audio }; + Kind classifyPt(uint8_t pt) { + std::lock_guard<std::mutex> lk(infoMu); + if (videoPts.count(pt)) return Kind::Video; + if (audioPts.count(pt)) return Kind::Audio; + return Kind::Unknown; + } + bool isRoutable(uint32_t ssrc) { + std::lock_guard<std::mutex> lk(infoMu); + return routable.count(ssrc) != 0; + } + Kind classifySsrc(uint32_t ssrc) { + if (ssrc != 0 && ssrc == videoSsrc.load()) return Kind::Video; + if (ssrc != 0 && ssrc == audioSsrc.load()) return Kind::Audio; + std::lock_guard<std::mutex> lk(infoMu); + if (answerVideoSsrcs.count(ssrc)) return Kind::Video; + if (answerAudioSsrcs.count(ssrc)) return Kind::Audio; + return Kind::Unknown; + } + void latch(Kind k, uint32_t ssrc) { + std::atomic<uint32_t>& a = k == Kind::Video ? videoSsrc : audioSsrc; + const uint32_t prev = a.exchange(ssrc); + if (prev != ssrc) + XC_LOGI("webrtc: %s SSRC %08x%s", k == Kind::Video ? "video" : "audio", ssrc, prev ? " (changed)" : ""); + } + + void media(Kind k, const uint8_t* d, size_t n) { + std::lock_guard<std::recursive_mutex> lk(mu); + if (closed) return; + if (k == Kind::Video && cb.onVideoRtp) cb.onVideoRtp(d, n); + else if (k == Kind::Audio && cb.onAudioRtp) cb.onAudioRtp(d, n); + } + void senderReport(bool video, uint32_t ssrc, uint64_t ntp) { + std::lock_guard<std::recursive_mutex> lk(mu); + if (!closed && cb.onSenderReport) cb.onSenderReport(video, ssrc, ntp); + } + void text(const std::string& ch, const std::string& t) { + std::lock_guard<std::recursive_mutex> lk(mu); + if (!closed && cb.onText) cb.onText(ch, t); + } + void binary(const std::string& ch, const uint8_t* d, size_t n) { + std::lock_guard<std::recursive_mutex> lk(mu); + if (!closed && cb.onBinary) cb.onBinary(ch, d, n); + } + void open(const std::string& ch) { + std::lock_guard<std::recursive_mutex> lk(mu); + if (!closed && cb.onChannelOpen) cb.onChannelOpen(ch); + } + void state(const std::string& s) { + std::lock_guard<std::recursive_mutex> lk(mu); + if (!closed && cb.onState) cb.onState(s); + } + + // Walk a (compound) RTCP packet and report every sender report. + void parseRtcp(const uint8_t* p, size_t n) { + size_t off = 0; + while (off + 8 <= n) { + const uint8_t* h = p + off; + if ((h[0] >> 6) != 2) return; + const size_t len = (static_cast<size_t>(be16(h + 2)) + 1) * 4; + if (off + len > n) return; + if (h[1] == 200 && len >= 28) { // SR: sender SSRC, NTP (64), RTP ts, counts + const uint32_t ssrc = be32(h + 4); + const uint64_t ntp = (static_cast<uint64_t>(be32(h + 8)) << 32) | be32(h + 12); + const Kind k = classifySsrc(ssrc); + if (k != Kind::Unknown) senderReport(k == Kind::Video, ssrc, ntp); + } else if (h[1] == 203) { + XC_LOGI("webrtc: RTCP BYE received"); + } + off += len; + } + } +}; + +// PeerConnection-wide handler: runs on every decrypted SRTP/SRTCP packet before track dispatch. +class GlobalReceiver final : public rtc::MediaHandler { +public: + explicit GlobalReceiver(std::shared_ptr<Shared> s) : s_(std::move(s)) {} + + void incoming(rtc::message_vector& messages, const rtc::message_callback&) override { + rtc::message_vector keep; + keep.reserve(messages.size()); + for (auto& m : messages) { + if (!m) continue; + const auto* d = reinterpret_cast<const uint8_t*>(m->data()); + const size_t n = m->size(); + if (m->type == rtc::Message::Control) { + // RTCP is consumed here; the tracks only carry RTP to our callbacks. + s_->parseRtcp(d, n); + continue; + } + if (m->type != rtc::Message::Binary || n < 12 || (d[0] >> 6) != 2) { + keep.push_back(std::move(m)); + continue; + } + const uint8_t pt = d[1] & 0x7F; + const uint32_t ssrc = be32(d + 8); + const Shared::Kind k = s_->classifyPt(pt); + if (k == Shared::Kind::Unknown) { + keep.push_back(std::move(m)); + continue; + } + const uint32_t latched = k == Shared::Kind::Video ? s_->videoSsrc.load() : s_->audioSsrc.load(); + if (latched != ssrc) s_->latch(k, ssrc); + if (s_->isRoutable(ssrc)) { + keep.push_back(std::move(m)); // libdatachannel routes it to the track -> onMessage + } else { + s_->media(k, d, n); // unannounced SSRC: libdatachannel would drop it + } + } + messages.swap(keep); + } + +private: + std::shared_ptr<Shared> s_; +}; + +} // namespace + +struct WebRtc::Impl { + mutable std::mutex mu; // guards the pointers below + std::shared_ptr<Shared> sh; + std::shared_ptr<rtc::PeerConnection> pc; + std::shared_ptr<rtc::Track> video, audio; + std::map<std::string, std::shared_ptr<rtc::DataChannel>> channels; + std::shared_ptr<GlobalReceiver> rx; + uint32_t localSsrc = 0; + std::string cname; + bool offered = false; + + std::shared_ptr<rtc::DataChannel> channel(const std::string& label) const { + std::lock_guard<std::mutex> lk(mu); + auto it = channels.find(label); + return it == channels.end() ? nullptr : it->second; + } + + bool sendRtcp(const std::vector<uint8_t>& pkt) { + std::shared_ptr<rtc::Track> t; + { + std::lock_guard<std::mutex> lk(mu); + t = video ? video : audio; + } + if (!t || pkt.size() < 8) return false; + try { + if (!t->isOpen()) return false; + return t->send(reinterpret_cast<const std::byte*>(pkt.data()), pkt.size()); + } catch (const std::exception& e) { + XC_LOGD("webrtc: RTCP send failed: %s", e.what()); + return false; + } + } +}; + +WebRtc::WebRtc() : d_(new Impl) {} +WebRtc::~WebRtc() { close(); } + +bool WebRtc::init(const Callbacks& cb) { + close(); + initRtcLoggerOnce(); + + auto sh = std::make_shared<Shared>(); + sh->cb = cb; + const uint32_t localSsrc = randomSsrc(); + char cname[32]; + std::snprintf(cname, sizeof(cname), "xc%08x", localSsrc); + + std::shared_ptr<rtc::PeerConnection> pc; + std::shared_ptr<rtc::Track> video, audio; + std::map<std::string, std::shared_ptr<rtc::DataChannel>> channels; + auto rx = std::make_shared<GlobalReceiver>(sh); + try { + rtc::Configuration cfg; + cfg.iceServers.emplace_back(kStunServer); + cfg.disableAutoNegotiation = true; // one explicit setLocalDescription(Offer) in gatherLocal() + pc = std::make_shared<rtc::PeerConnection>(cfg); + pc->setMediaHandler(rx); + + std::weak_ptr<Shared> wsh = sh; + pc->onStateChange([wsh](rtc::PeerConnection::State st) { + XC_LOGI("webrtc: peer connection %s", stateName(st)); + if (auto s = wsh.lock()) s->state(stateName(st)); + }); + pc->onIceStateChange([](rtc::PeerConnection::IceState st) { + XC_LOGD("webrtc: ICE state %d", static_cast<int>(st)); + }); + pc->onGatheringStateChange([wsh](rtc::PeerConnection::GatheringState st) { + XC_LOGD("webrtc: gathering state %d", static_cast<int>(st)); + if (st != rtc::PeerConnection::GatheringState::Complete) return; + if (auto s = wsh.lock()) { + std::lock_guard<std::mutex> lk(s->gatherMu); + s->gatherDone = true; + s->gatherCv.notify_all(); + } + }); + pc->onLocalCandidate([wsh](rtc::Candidate c) { + if (auto s = wsh.lock()) { + std::lock_guard<std::mutex> lk(s->gatherMu); + s->trickled.push_back(c.candidate()); + } + }); + pc->onDataChannel([](std::shared_ptr<rtc::DataChannel> dc) { + XC_LOGW("webrtc: unexpected remote data channel '%s'", dc->label().c_str()); + }); + + // 1) video (mid "video"), 2) audio (mid "audio") — order matters, see header. + rtc::Description::Video vdesc("video", rtc::Description::Direction::RecvOnly); + vdesc.addH264Codec(kDefaultVideoPt, + std::string("level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f")); + video = pc->addTrack(vdesc); + rtc::Description::Audio adesc("audio", rtc::Description::Direction::RecvOnly); + adesc.addOpusCodec(kDefaultAudioPt, std::string("minptime=10;useinbandfec=1")); + audio = pc->addTrack(adesc); + + auto onTrackRtp = [wsh](Shared::Kind k) { + return [wsh, k](rtc::binary b) { + const auto* d = reinterpret_cast<const uint8_t*>(b.data()); + if (b.size() < 12 || isRtcpPacket(d, b.size())) return; + if (auto s = wsh.lock()) s->media(k, d, b.size()); + }; + }; + video->onMessage(onTrackRtp(Shared::Kind::Video), [](rtc::string) {}); + audio->onMessage(onTrackRtp(Shared::Kind::Audio), [](rtc::string) {}); + + // 3) data channels, in-band (DCEP), reliable + ordered, before the offer -> m-line mid "0". + for (const ChannelSpec& spec : kChannels) { + rtc::DataChannelInit init; + init.protocol = spec.protocol; + auto dc = pc->createDataChannel(spec.label, init); + const std::string label = spec.label; + dc->onOpen([wsh, label]() { + XC_LOGI("webrtc: channel '%s' open", label.c_str()); + if (auto s = wsh.lock()) s->open(label); + }); + dc->onClosed([label]() { XC_LOGI("webrtc: channel '%s' closed", label.c_str()); }); + dc->onError([label](std::string err) { XC_LOGW("webrtc: channel '%s' error: %s", label.c_str(), err.c_str()); }); + dc->onMessage( + [wsh, label](rtc::binary b) { + if (auto s = wsh.lock()) s->binary(label, reinterpret_cast<const uint8_t*>(b.data()), b.size()); + }, + [wsh, label](rtc::string t) { + if (auto s = wsh.lock()) s->text(label, t); + }); + channels[label] = dc; + } + } catch (const std::exception& e) { + XC_LOGE("webrtc: init failed: %s", e.what()); + if (pc) { + try { + pc->close(); + } catch (...) { + } + } + return false; + } + + std::lock_guard<std::mutex> lk(d_->mu); + d_->sh = sh; + d_->pc = pc; + d_->video = video; + d_->audio = audio; + d_->channels = std::move(channels); + d_->rx = rx; + d_->localSsrc = localSsrc; + d_->cname = cname; + d_->offered = false; + return true; +} + +bool WebRtc::gatherLocal(std::string& ufrag, std::string& pwd, std::string& fingerprint, + std::vector<std::string>& candidates, int timeoutMs, const std::atomic<bool>* abort) { + ufrag.clear(); + pwd.clear(); + fingerprint.clear(); + candidates.clear(); + + std::shared_ptr<rtc::PeerConnection> pc; + std::shared_ptr<Shared> sh; + bool alreadyOffered; + { + std::lock_guard<std::mutex> lk(d_->mu); + pc = d_->pc; + sh = d_->sh; + alreadyOffered = d_->offered; + d_->offered = true; + } + if (!pc || !sh) { + XC_LOGE("webrtc: gatherLocal before init"); + return false; + } + try { + if (!alreadyOffered) pc->setLocalDescription(rtc::Description::Type::Offer); + } catch (const std::exception& e) { + XC_LOGE("webrtc: setLocalDescription(offer) failed: %s", e.what()); + return false; + } + + bool complete = false; + { + // Wait in short slices so a cancelled session does not sit out the whole timeout. + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(std::max(timeoutMs, 0)); + std::unique_lock<std::mutex> lk(sh->gatherMu); + for (;;) { + if (abort && abort->load()) { + XC_LOGD("webrtc: ICE gathering aborted"); + return false; + } + const auto now = std::chrono::steady_clock::now(); + if (sh->gatherDone || now >= deadline) { + complete = sh->gatherDone; + break; + } + sh->gatherCv.wait_for(lk, std::min<std::chrono::steady_clock::duration>(deadline - now, + std::chrono::milliseconds(100))); + } + } + if (!complete) XC_LOGW("webrtc: ICE gathering not complete after %d ms, using what we have", timeoutMs); + + std::optional<rtc::Description> desc; + try { + desc = pc->localDescription(); + } catch (const std::exception& e) { + XC_LOGE("webrtc: localDescription failed: %s", e.what()); + return false; + } + if (!desc) { + XC_LOGE("webrtc: no local description"); + return false; + } + ufrag = desc->iceUfrag().value_or(""); + pwd = desc->icePwd().value_or(""); + if (auto fp = desc->fingerprint()) { + if (fp->algorithm == rtc::CertificateFingerprint::Algorithm::Sha256) fingerprint = fp->value; + else XC_LOGE("webrtc: local fingerprint is not sha-256"); + } + + std::set<std::string> seen; + auto add = [&](const std::string& c) { + std::string s = trim(c); + if (s.rfind("a=", 0) == 0) s = s.substr(2); + if (s.rfind("candidate:", 0) != 0) return; + if (seen.insert(s).second) candidates.push_back(s); + }; + for (const rtc::Candidate& c : desc->candidates()) add(c.candidate()); + { + std::lock_guard<std::mutex> lk(sh->gatherMu); + for (const auto& c : sh->trickled) add(c); + } + + XC_LOGI("webrtc: local ICE ufrag=%s, %zu candidate(s)%s", ufrag.c_str(), candidates.size(), + complete ? "" : " (gathering incomplete)"); + for (const auto& c : candidates) XC_LOGD("webrtc: local %s", c.c_str()); + if (ufrag.empty() || pwd.empty() || fingerprint.empty()) { + XC_LOGE("webrtc: local description lacks ICE credentials or sha-256 fingerprint"); + return false; + } + if (candidates.empty()) { + XC_LOGE("webrtc: no local ICE candidates gathered"); + return false; + } + return true; +} + +std::string WebRtc::localDescriptionSdp() const { + std::shared_ptr<rtc::PeerConnection> pc; + { + std::lock_guard<std::mutex> lk(d_->mu); + pc = d_->pc; + } + if (!pc) return std::string(); + try { + if (auto desc = pc->localDescription()) return std::string(*desc); + } catch (const std::exception& e) { + XC_LOGW("webrtc: localDescription failed: %s", e.what()); + } + return std::string(); +} + +bool WebRtc::setRemote(const std::string& answerSdp, const std::vector<std::string>& remoteCandidates) { + std::shared_ptr<rtc::PeerConnection> pc; + std::shared_ptr<Shared> sh; + { + std::lock_guard<std::mutex> lk(d_->mu); + pc = d_->pc; + sh = d_->sh; + } + if (!pc || !sh) { + XC_LOGE("webrtc: setRemote before init"); + return false; + } + + const AnswerInfo info = parseAnswer(answerSdp); + { + std::lock_guard<std::mutex> lk(sh->infoMu); + if (!info.videoPts.empty()) sh->videoPts = info.videoPts; + if (!info.audioPts.empty()) sh->audioPts = info.audioPts; + sh->answerVideoSsrcs = info.videoSsrcs; + sh->answerAudioSsrcs = info.audioSsrcs; + sh->routable.clear(); + sh->routable.insert(info.videoSsrcs.begin(), info.videoSsrcs.end()); + sh->routable.insert(info.audioSsrcs.begin(), info.audioSsrcs.end()); + } + if (!info.videoSsrcs.empty() && sh->videoSsrc.load() == 0) sh->videoSsrc = *info.videoSsrcs.begin(); + if (!info.audioSsrcs.empty() && sh->audioSsrc.load() == 0) sh->audioSsrc = *info.audioSsrcs.begin(); + XC_LOGI("webrtc: answer: video pt %d ssrcs %zu, audio pt %d ssrcs %zu", + info.videoPts.empty() ? -1 : *info.videoPts.begin(), info.videoSsrcs.size(), + info.audioPts.empty() ? -1 : *info.audioPts.begin(), info.audioSsrcs.size()); + + try { + pc->setRemoteDescription(rtc::Description(answerSdp, rtc::Description::Type::Answer)); + } catch (const std::exception& e) { + XC_LOGE("webrtc: setRemoteDescription failed: %s", e.what()); + return false; + } + + size_t added = 0, tried = 0; + for (const std::string& raw : remoteCandidates) { + std::string c = trim(raw); + if (c.rfind("a=", 0) == 0) c = c.substr(2); + if (c.empty() || c.rfind("candidate:", 0) != 0) continue; // also skips end-of-candidates + ++tried; + try { + pc->addRemoteCandidate(rtc::Candidate(c, "0")); + ++added; + XC_LOGD("webrtc: remote %s", c.c_str()); + } catch (const std::exception& e) { + XC_LOGW("webrtc: remote candidate rejected (%s): %s", e.what(), c.c_str()); + } + } + XC_LOGI("webrtc: added %zu/%zu remote candidate(s)", added, tried); + return tried == 0 || added > 0; +} + +bool WebRtc::sendText(const std::string& ch, const std::string& text) { + auto dc = d_->channel(ch); + if (!dc) return false; + try { + if (!dc->isOpen()) return false; + dc->send(std::string(text)); // string message -> WebRTC DOMString PPID + return true; + } catch (const std::exception& e) { + XC_LOGW("webrtc: send on '%s' failed: %s", ch.c_str(), e.what()); + return false; + } +} + +bool WebRtc::sendBinary(const std::string& ch, const std::vector<uint8_t>& data) { + auto dc = d_->channel(ch); + if (!dc) return false; + try { + if (!dc->isOpen()) return false; + dc->send(reinterpret_cast<const std::byte*>(data.data()), data.size()); + return true; + } catch (const std::exception& e) { + XC_LOGW("webrtc: send on '%s' failed: %s", ch.c_str(), e.what()); + return false; + } +} + +bool WebRtc::isChannelOpen(const std::string& ch) const { + auto dc = d_->channel(ch); + if (!dc) return false; + try { + return dc->isOpen(); + } catch (...) { + return false; + } +} + +void WebRtc::requestKeyframe() { + const uint32_t media = videoSsrc(); + if (!media) return; + // RFC 4585 §6.3.1 PLI: PSFB (206), FMT 1, length 2. Sender SSRC 0 like browsers — Xbox + // ignores PLIs whose sender SSRC equals the media SSRC (GreenOvercast). + std::vector<uint8_t> p; + p.reserve(12); + p.push_back(0x80 | 1); + p.push_back(206); + put16(p, 2); + put32(p, 0); + put32(p, media); + d_->sendRtcp(p); +} + +void WebRtc::sendNack(uint32_t ssrc, const std::vector<uint16_t>& seqs) { + if (!ssrc) ssrc = videoSsrc(); + if (!ssrc || seqs.empty()) return; + uint32_t sender; + { + std::lock_guard<std::mutex> lk(d_->mu); + sender = d_->localSsrc; + } + // Order wrap-aware relative to the first entry, de-duplicate. + std::vector<uint16_t> s(seqs); + const uint16_t ref = static_cast<uint16_t>(s.front() - 0x4000); + std::sort(s.begin(), s.end(), [ref](uint16_t a, uint16_t b) { + return static_cast<uint16_t>(a - ref) < static_cast<uint16_t>(b - ref); + }); + s.erase(std::unique(s.begin(), s.end()), s.end()); + + // RFC 4585 §6.2.1 generic NACK: RTPFB (205), FMT 1, FCI = PID + BLP (16 following packets). + constexpr size_t kMaxFciPerPacket = 64; + std::vector<std::pair<uint16_t, uint16_t>> fci; + for (size_t i = 0; i < s.size();) { + const uint16_t pid = s[i]; + uint16_t blp = 0; + size_t j = i + 1; + while (j < s.size()) { + const uint16_t d = static_cast<uint16_t>(s[j] - pid); + if (d == 0 || d > 16) break; + blp |= static_cast<uint16_t>(1u << (d - 1)); + ++j; + } + fci.emplace_back(pid, blp); + i = j; + } + for (size_t off = 0; off < fci.size(); off += kMaxFciPerPacket) { + const size_t cnt = std::min(kMaxFciPerPacket, fci.size() - off); + std::vector<uint8_t> p; + p.reserve(12 + 4 * cnt); + p.push_back(0x80 | 1); + p.push_back(205); + put16(p, static_cast<uint16_t>(2 + cnt)); + put32(p, sender); + put32(p, ssrc); + for (size_t k = 0; k < cnt; ++k) { + put16(p, fci[off + k].first); + put16(p, fci[off + k].second); + } + d_->sendRtcp(p); + } +} + +void WebRtc::sendReceiverReport(const std::vector<RtpReceiveStats>& blocks) { + uint32_t sender; + std::string cname; + { + std::lock_guard<std::mutex> lk(d_->mu); + sender = d_->localSsrc; + cname = d_->cname; + } + if (!sender) return; + std::vector<const RtpReceiveStats*> valid; + for (const auto& b : blocks) + if (b.ssrc && valid.size() < 31) valid.push_back(&b); + + // RFC 3550 §6.4.2 RR + §6.5 SDES (CNAME) in one compound packet (green-nx: senders ignore + // bare RRs). DLSR/LSR come from RtpStatsTracker. + std::vector<uint8_t> p; + p.reserve(8 + 24 * valid.size() + 32); + p.push_back(static_cast<uint8_t>(0x80 | valid.size())); + p.push_back(201); + put16(p, static_cast<uint16_t>(1 + 6 * valid.size())); + put32(p, sender); + for (const RtpReceiveStats* b : valid) { + put32(p, b->ssrc); + int32_t lost = std::max<int32_t>(-0x800000, std::min<int32_t>(0x7FFFFF, b->cumulativeLost)); + put32(p, (static_cast<uint32_t>(b->fractionLost) << 24) | (static_cast<uint32_t>(lost) & 0x00FFFFFFu)); + put32(p, b->extHighestSeq); + put32(p, b->jitter); + put32(p, b->lastSr); + put32(p, b->lastSr ? b->delaySinceLastSr : 0); + } + const size_t sdes = p.size(); + p.push_back(0x80 | 1); + p.push_back(202); + put16(p, 0); // patched below + put32(p, sender); + p.push_back(1); // CNAME + p.push_back(static_cast<uint8_t>(cname.size())); + p.insert(p.end(), cname.begin(), cname.end()); + p.push_back(0); // end of items + while ((p.size() - sdes) % 4) p.push_back(0); + set16(p, sdes + 2, static_cast<uint16_t>((p.size() - sdes) / 4 - 1)); + d_->sendRtcp(p); +} + +void WebRtc::sendRemb(uint32_t bps) { + const uint32_t v = videoSsrc(), a = audioSsrc(); + if (!v) return; + uint32_t sender; + { + std::lock_guard<std::mutex> lk(d_->mu); + sender = d_->localSsrc; + } + // draft-alvestrand-rmcat-remb: PSFB (206), FMT 15, media SSRC 0, "REMB", num SSRC, + // 6-bit exponent + 18-bit mantissa, SSRC list. + uint32_t mantissa = bps, exp = 0; + while (mantissa > 0x3FFFF) { + mantissa >>= 1; + ++exp; + } + const uint8_t num = a ? 2 : 1; + std::vector<uint8_t> p; + p.reserve(28); + p.push_back(0x80 | 15); + p.push_back(206); + put16(p, static_cast<uint16_t>(4 + num)); + put32(p, sender); + put32(p, 0); + p.push_back('R'); + p.push_back('E'); + p.push_back('M'); + p.push_back('B'); + p.push_back(num); + p.push_back(static_cast<uint8_t>((exp << 2) | ((mantissa >> 16) & 0x03))); + p.push_back(static_cast<uint8_t>(mantissa >> 8)); + p.push_back(static_cast<uint8_t>(mantissa)); + put32(p, v); + if (a) put32(p, a); + d_->sendRtcp(p); +} + +uint32_t WebRtc::videoSsrc() const { + std::lock_guard<std::mutex> lk(d_->mu); + return d_->sh ? d_->sh->videoSsrc.load() : 0; +} + +uint32_t WebRtc::audioSsrc() const { + std::lock_guard<std::mutex> lk(d_->mu); + return d_->sh ? d_->sh->audioSsrc.load() : 0; +} + +void WebRtc::close() { + if (!d_) return; + std::shared_ptr<Shared> sh; + std::shared_ptr<rtc::PeerConnection> pc; + std::shared_ptr<rtc::Track> video, audio; + std::map<std::string, std::shared_ptr<rtc::DataChannel>> channels; + { + std::lock_guard<std::mutex> lk(d_->mu); + sh = std::move(d_->sh); + pc = std::move(d_->pc); + video = std::move(d_->video); + audio = std::move(d_->audio); + channels.swap(d_->channels); + d_->rx.reset(); + d_->offered = false; + } + if (sh) { + // Wait for a running callback to finish, then block all further ones. Must not be called + // while holding a lock that a callback also takes. + { + std::lock_guard<std::recursive_mutex> lk(sh->mu); + sh->closed = true; + } + { + std::lock_guard<std::mutex> lk(sh->gatherMu); + sh->gatherDone = true; + } + sh->gatherCv.notify_all(); + } + try { + for (auto& kv : channels) { + kv.second->resetCallbacks(); + kv.second->close(); + } + if (video) video->resetCallbacks(); + if (audio) audio->resetCallbacks(); + if (pc) { + pc->setMediaHandler(nullptr); + pc->resetCallbacks(); + pc->close(); + } + } catch (const std::exception& e) { + XC_LOGW("webrtc: close: %s", e.what()); + } +} + +} // namespace xc diff --git a/src/stream/webrtc.hpp b/src/stream/webrtc.hpp new file mode 100644 index 0000000..d0fa276 --- /dev/null +++ b/src/stream/webrtc.hpp @@ -0,0 +1,89 @@ +// Nubix — libdatachannel wrapper: recvonly H.264 + Opus tracks (mids "video", "audio"), +// four reliable/ordered data channels (control/controlV1, input/1.0, message/messageV1, +// chat/chatV1, created before the offer so the data m-line gets mid "0"), raw RTP delivery +// and hand-built RTCP (PLI with sender SSRC 0, NACK, RR, REMB). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <atomic> +#include <cstddef> +#include <cstdint> +#include <functional> +#include <memory> +#include <string> +#include <vector> + +#include "stream/rtp.hpp" + +namespace xc { + +class WebRtc { +public: + struct Callbacks { + // Raw RTP packets (header included) of the video / audio track. Called on libdatachannel threads. + std::function<void(const uint8_t*, size_t)> onVideoRtp, onAudioRtp; + // Incoming RTCP sender report on a track: media SSRC + 64-bit NTP timestamp (for LSR). + std::function<void(bool video, uint32_t ssrc, uint64_t ntp)> onSenderReport; + // Text / binary message on data channel ch ("control", "input", "message", "chat"). + std::function<void(const std::string& ch, const std::string& text)> onText; + std::function<void(const std::string& ch, const uint8_t*, size_t)> onBinary; + // Data channel ch opened. + std::function<void(const std::string& ch)> onChannelOpen; + // PeerConnection state changes: "new", "connecting", "connected", "disconnected", "failed", "closed". + std::function<void(const std::string& state)> onState; + }; + + WebRtc(); + ~WebRtc(); + WebRtc(const WebRtc&) = delete; + WebRtc& operator=(const WebRtc&) = delete; + + // Create the PeerConnection, tracks and data channels. Must be called first. + bool init(const Callbacks& cb); + + // Create the local offer, wait for ICE gathering (timeoutMs) and return the ICE credentials, + // sha-256 fingerprint and gathered candidate lines ("candidate:..."). The real local + // description stays inside libdatachannel; the caller sends proto::buildOffer(...) instead. + // abort (optional): checked every 100 ms; once true the wait ends and false is returned. + bool gatherLocal(std::string& ufrag, std::string& pwd, std::string& fingerprint, + std::vector<std::string>& candidates, int timeoutMs, + const std::atomic<bool>* abort = nullptr); + + // Full SDP of libdatachannel's own local offer (empty before gatherLocal). Diagnostics / tests. + std::string localDescriptionSdp() const; + + // Apply the server answer SDP and the remote candidates (added with mid "0"). + bool setRemote(const std::string& answerSdp, const std::vector<std::string>& remoteCandidates); + + // Send on a data channel. False if the channel is not open. + bool sendText(const std::string& ch, const std::string& text); + bool sendBinary(const std::string& ch, const std::vector<uint8_t>& data); + + // True if data channel ch is open. + bool isChannelOpen(const std::string& ch) const; + + // Send RTCP PLI (sender SSRC 0, media SSRC = video SSRC). + void requestKeyframe(); + // Send RTCP generic NACK (RFC 4585) for the given sequence numbers. + void sendNack(uint32_t ssrc, const std::vector<uint16_t>& seqs); + // Send an RTCP compound RR (+ SDES) with one report block per entry. + void sendReceiverReport(const std::vector<RtpReceiveStats>& blocks); + // Send RTCP REMB with the given max bitrate for the video SSRC. + void sendRemb(uint32_t bps); + + // Media SSRCs latched from the first received RTP packet (0 if none yet). + uint32_t videoSsrc() const; + uint32_t audioSsrc() const; + + // Close channels and the PeerConnection. Idempotent; no callbacks fire afterwards. + // Waits for a callback that is currently running, so do not call it while holding a lock that + // your callbacks also take, and preferably not from inside a callback (post to another thread). + void close(); + +private: + struct Impl; + std::unique_ptr<Impl> d_; +}; + +} // namespace xc diff --git a/src/ui/app.cpp b/src/ui/app.cpp new file mode 100644 index 0000000..6a3835d --- /dev/null +++ b/src/ui/app.cpp @@ -0,0 +1,489 @@ +// Nubix — application object and main loop. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "ui/app.hpp" + + +#include <SDL.h> + +#include <algorithm> +#include <atomic> +#include <cstdio> +#include <cstdlib> +#include <condition_variable> +#include <deque> +#include <exception> +#include <mutex> +#include <thread> +#include <vector> + +#include "core/http.hpp" +#include "core/log.hpp" +#include "core/protocol.hpp" +#include "platform/platform.hpp" +#include "stream/audio.hpp" +#include "stream/video.hpp" +#include "stream/webrtc.hpp" +#include "ui/qr.hpp" +#include "ui/screens_internal.hpp" + +namespace xc { +namespace { + +constexpr uint64_t kToastMs = 3500; +constexpr uint64_t kToastFadeMs = 400; +constexpr size_t kMaxToasts = 3; + +struct Toast { + std::string msg; + uint64_t until = 0; +}; + +struct StackOp { + enum Kind { Push, Pop, Replace, Reset } kind; + std::unique_ptr<Screen> screen; +}; + +// --selftest: bring up every subsystem the stream path needs, tear it down again and report. +// Returns the number of failed checks (each is logged). Audio is optional (headless hosts and +// SDL_AUDIODRIVER=dummy may lack a device) and only warns. +int runSubsystemSelftest() { + int failed = 0; + auto check = [&](const char* what, bool ok) { + xc::log(ok ? LogLevel::Info : LogLevel::Error, "selftest: %-28s %s", what, ok ? "ok" : "FAILED"); + if (!ok) ++failed; + }; + + for (const char* a : {"cacert.pem", "fonts/DejaVuSans.ttf", "fonts/DejaVuSans-Bold.ttf"}) { + std::string path = platform::assetPath(a); + FILE* f = std::fopen(path.c_str(), "rb"); + check(("asset " + std::string(a)).c_str(), f != nullptr); + if (f) std::fclose(f); + } + + { + std::vector<bool> modules; + int size = 0; + check("qr encode", qr::encode("https://www.microsoft.com/link?otc=ABCD1234", modules, size) && size > 0 && + modules.size() == static_cast<size_t>(size) * static_cast<size_t>(size)); + } + { + proto::InputSerializer ser; + check("input serializer", ser.clientMetadata().size() == proto::kClientMetadataPacketSize && + ser.gamepad(GamepadState{}).size() == proto::kGamepadPacketSize); + } + { + VideoDecoder dec; + check("h264 decoder init", dec.init(4)); + dec.close(); + } + { + AudioPlayer ap; + const bool ok = ap.init(); + xc::log(ok ? LogLevel::Info : LogLevel::Warn, "selftest: %-28s %s", "opus + SDL audio init", + ok ? "ok" : "unavailable (non-fatal)"); + ap.close(); + } + { + WebRtc rtc; + WebRtc::Callbacks cb; + bool ok = rtc.init(cb); + std::string ufrag, pwd, fp; + std::vector<std::string> cands; + if (ok) ok = rtc.gatherLocal(ufrag, pwd, fp, cands, 3000) && !ufrag.empty() && !fp.empty(); + if (ok) ok = !proto::buildOffer(ufrag, pwd, fp, false, "1080").empty(); + check("webrtc init + gather + offer", ok); + rtc.close(); + } + return failed; +} + +} // namespace + +struct App::Impl { + Impl() : cfg(platform::dataDir() + "/config.json"), auth(cfg), catalog(cfg), streamer(cfg, auth) { + Catalog::setCacheDir(platform::dataDir() + "/cache"); // title list + box art + } + Config cfg; + Auth auth; + Catalog catalog; + Streamer streamer; + Ui ui; + Gamepad gamepad; + std::atomic<bool> quit{false}; + // Set when the app exits: HTTP requests of worker jobs (catalog, auth) abort within ~1 s. + std::atomic<bool> cancel{false}; + bool statsOverlay = false; + + std::vector<std::unique_ptr<Screen>> stack; + std::vector<StackOp> ops; + + // background worker (FIFO) + std::thread worker; + std::mutex workMx; + std::condition_variable workCv; + std::deque<std::function<void()>> work; + bool workerStop = false; + + // main-thread queue + std::mutex mainMx; + std::vector<std::function<void()>> mainQueue; + + std::mutex toastMx; + std::deque<Toast> toasts; + + bool screenshotRequested = false; + int screenshotCount = 0; + // Development automation (host): XC_SHOT_FRAMES="60,120" saves screenshots at those frame + // numbers, XC_EXIT_FRAME=N quits after frame N. + std::vector<uint64_t> shotFrames; + uint64_t exitFrame = 0; + + void takeScreenshot() { + char name[64]; + std::snprintf(name, sizeof(name), "/screenshot-%03d.bmp", screenshotCount++); + if (ui.saveScreenshot(platform::dataDir() + name)) { + std::lock_guard<std::mutex> lk(toastMx); + toasts.push_back({std::string("Screenshot saved: ") + (name + 1), platform::monotonicMs() + kToastMs}); + } + } + + void workerLoop() { + // Every job's requests (and the catalog helper threads, which inherit the flag) end + // quickly once the app is closing instead of running into 20-30 s timeouts. + Http::AbortScope abortOnExit(&cancel); + for (;;) { + std::function<void()> job; + { + std::unique_lock<std::mutex> lk(workMx); + workCv.wait(lk, [&] { return workerStop || !work.empty(); }); + if (workerStop) return; + job = std::move(work.front()); + work.pop_front(); + } + if (!job) continue; + try { + job(); + } catch (const std::exception& e) { + // A failed job must not take the app down (std::terminate); its UI simply does + // not get its result. + XC_LOGE("app: background job failed: %s", e.what()); + std::lock_guard<std::mutex> lk(toastMx); + toasts.push_back({"Something went wrong, please try again", platform::monotonicMs() + kToastMs}); + } catch (...) { + XC_LOGE("app: background job failed with an unknown exception"); + } + } + } + + void stopWorker() { + if (!worker.joinable()) return; + { + std::lock_guard<std::mutex> lk(workMx); + workerStop = true; + work.clear(); + } + cancel = true; + workCv.notify_all(); + worker.join(); // waits for the job in progress (bounded by HTTP timeouts) + } + + void drainMainQueue() { + std::vector<std::function<void()>> q; + { + std::lock_guard<std::mutex> lk(mainMx); + q.swap(mainQueue); + } + for (auto& fn : q) + if (fn) fn(); + } + + void applyOps(App& app) { + // Ops queued from onEnter/onLeave are processed in the same pass. + for (int guard = 0; guard < 32 && !ops.empty(); ++guard) { + std::vector<StackOp> batch; + batch.swap(ops); + for (auto& op : batch) { + switch (op.kind) { + case StackOp::Push: + if (!stack.empty()) stack.back()->onLeave(app); + stack.push_back(std::move(op.screen)); + stack.back()->onEnter(app); + break; + case StackOp::Pop: + if (stack.empty()) break; + stack.back()->onLeave(app); + stack.pop_back(); + if (!stack.empty()) stack.back()->onEnter(app); + break; + case StackOp::Replace: + if (!stack.empty()) { + stack.back()->onLeave(app); + stack.pop_back(); + } + stack.push_back(std::move(op.screen)); + stack.back()->onEnter(app); + break; + case StackOp::Reset: + if (!stack.empty()) stack.back()->onLeave(app); + stack.clear(); + stack.push_back(std::move(op.screen)); + stack.back()->onEnter(app); + break; + } + } + } + } + + void renderToasts(Ui& ui, uint64_t now) { + std::lock_guard<std::mutex> lk(toastMx); + while (!toasts.empty() && toasts.front().until <= now) toasts.pop_front(); + int y = Ui::kHeight - 150; + for (auto it = toasts.rbegin(); it != toasts.rend(); ++it) { + const uint64_t left = it->until - now; + const uint8_t a = left < kToastFadeMs ? static_cast<uint8_t>(255 * left / kToastFadeMs) : 255; + const int size = 26; + const int w = std::min(1500, ui.textWidth(it->msg, size) + 64); + const int h = 64; + const int x = (Ui::kWidth - w) / 2; + ui.rect(x, y - h, w, h, withAlpha(colors::PanelHi, static_cast<uint8_t>(a * 240 / 255)), true, h / 2); + ui.rect(x, y - h, 8, h, withAlpha(colors::AccentBright, a), true, 4); + ui.textEllipsized(it->msg, Ui::kWidth / 2, y - h + (h - ui.lineHeight(size)) / 2, w - 64, size, + withAlpha(colors::Text, a), Align::Center); + y -= h + 12; + } + } +}; + +App::App() : d_(new Impl) {} +App::~App() { + d_->stopWorker(); +} + +int App::run(int argc, char** argv) { + Impl& d = *d_; + // --selftest: init all subsystems, render the Login screen for kSelftestFrames frames, exit + // 0 when everything came up (used headless with SDL_VIDEODRIVER=dummy). + constexpr uint64_t kSelftestFrames = 30; + bool selftest = false; + for (int i = 1; i < argc; ++i) + if (argv[i] && std::string(argv[i]) == "--selftest") selftest = true; + + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); +#ifdef SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); +#endif +#ifdef SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); +#endif + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0) { + XC_LOGE("SDL_Init failed: %s", SDL_GetError()); + platform::notify("Nubix: SDL init failed"); + return 1; + } + if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) XC_LOGW("SDL audio init failed: %s (no sound)", SDL_GetError()); + // Stream video is BT.709 at HD sizes, BT.601 at SD: let SDL pick per resolution. + SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_AUTOMATIC); + + Http::globalInit(platform::assetPath("cacert.pem")); + if (!d.cfg.load()) XC_LOGW("config: %s is malformed, using defaults", d.cfg.path().c_str()); + + if (!d.ui.init(platform::assetPath("fonts/DejaVuSans.ttf"), platform::assetPath("fonts/DejaVuSans-Bold.ttf"), + "Nubix")) { + platform::notify("Nubix: cannot start UI (assets missing?)"); + d.ui.shutdown(); + Http::globalCleanup(); + SDL_Quit(); + return 1; + } + if (platform::isPs5()) SDL_ShowCursor(SDL_DISABLE); + { + SDL_version v; + SDL_GetVersion(&v); + XC_LOGI("SDL %d.%d.%d, video driver %s, network %s (%s)", v.major, v.minor, v.patch, + SDL_GetCurrentVideoDriver() ? SDL_GetCurrentVideoDriver() : "?", + platform::networkAvailable() ? "up" : "DOWN", platform::localIpv4().c_str()); + } + d.gamepad.init(); + + int selftestFailures = 0; + if (selftest) { + XC_LOGI("selftest: starting"); + selftestFailures = runSubsystemSelftest(); + d.exitFrame = kSelftestFrames; + } + + try { + d.worker = std::thread([this] { d_->workerLoop(); }); + } catch (const std::exception& e) { + XC_LOGE("app: cannot start the worker thread: %s", e.what()); + platform::notify("Nubix: cannot start (out of threads)"); + d.gamepad.shutdown(); + d.ui.shutdown(); + Http::globalCleanup(); + SDL_Quit(); + return 1; + } + + push((d.auth.isLoggedIn() && !selftest) ? makeHomeScreen() : makeLoginScreen()); + if (!platform::networkAvailable()) toast("No network connection detected"); + + if (const char* e = std::getenv("XC_SHOT_FRAMES")) { + for (const char* p = e; *p;) { + char* end = nullptr; + unsigned long long v = std::strtoull(p, &end, 10); + if (end == p) break; + d.shotFrames.push_back(v); + p = (*end == ',') ? end + 1 : end; + } + } + if (const char* e = std::getenv("XC_EXIT_FRAME"); e && !selftest) d.exitFrame = std::strtoull(e, nullptr, 10); + + SDL_RendererInfo rinfo{}; + SDL_GetRendererInfo(d.ui.renderer(), &rinfo); + const bool vsync = (rinfo.flags & SDL_RENDERER_PRESENTVSYNC) != 0 || platform::isPs5(); // PS5 flip waits + uint64_t last = platform::monotonicMs(); + bool firstFrame = true; + uint64_t lastFrame = 0; + + while (!d.quit.load()) { + const uint64_t frameStart = platform::monotonicMs(); + const uint32_t dt = static_cast<uint32_t>(std::min<uint64_t>(250, frameStart - last)); + last = frameStart; + + SDL_Event ev; + while (SDL_PollEvent(&ev)) { + if (ev.type == SDL_QUIT) { + d.quit = true; + break; + } + if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_F12 && !ev.key.repeat) d.screenshotRequested = true; + NavKey k = d.gamepad.handleEvent(ev); + if (k == NavKey::None || d.stack.empty()) continue; + Screen* top = d.stack.back().get(); + if (top->capturesGamepad() && k != NavKey::Menu) continue; + top->onNav(*this, k); + } + if (NavKey r = d.gamepad.pollRepeat(); r != NavKey::None && !d.stack.empty()) { + Screen* top = d.stack.back().get(); + if (!top->capturesGamepad()) top->onNav(*this, r); + } + + d.drainMainQueue(); + d.applyOps(*this); + if (d.stack.empty()) break; + d.stack.back()->update(*this, dt); + d.applyOps(*this); + if (d.stack.empty()) break; + + d.ui.beginFrame(); + size_t first = d.stack.size() - 1; + while (first > 0 && d.stack[first]->isOverlay()) --first; + for (size_t i = first; i < d.stack.size(); ++i) d.stack[i]->render(*this, d.ui); + const uint64_t frameNo = d.ui.frameCount(); + lastFrame = frameNo; + if (std::find(d.shotFrames.begin(), d.shotFrames.end(), frameNo) != d.shotFrames.end()) + d.screenshotRequested = true; + if (d.screenshotRequested) { + d.screenshotRequested = false; + d.takeScreenshot(); // before toasts so the "saved" toast is not in the picture + } + d.renderToasts(d.ui, platform::monotonicMs()); + d.ui.endFrame(); + if (d.exitFrame && frameNo >= d.exitFrame) d.quit = true; + if (firstFrame) { + platform::onFirstFrame(); + firstFrame = false; + } + + // Present normally blocks on vsync; when it does not (hidden/occluded window, no vsync + // support, high-refresh displays) cap the loop at ~60 fps to keep CPU use sane. + const uint64_t spent = platform::monotonicMs() - frameStart; + if (spent < (vsync ? 15u : 16u)) SDL_Delay(static_cast<Uint32>((vsync ? 15u : 16u) - spent)); + } + + XC_LOGI("shutting down"); + d.cancel = true; // running worker job: abort its requests now + if (!d.stack.empty()) d.stack.back()->onLeave(*this); + d.ops.clear(); + d.gamepad.stopRumble(); + // Stopping a stream (final DELETE, bounded to ~5 s) and joining the worker can take a moment: + // show that the app is closing instead of a frozen last frame. + d.ui.beginFrame(); + ui::background(d.ui); + d.ui.spinner(Ui::kWidth / 2, Ui::kHeight / 2 - 40, 48, platform::monotonicMs()); + d.ui.text("Closing\xE2\x80\xA6", Ui::kWidth / 2, Ui::kHeight / 2 + 40, 32, colors::Text, Align::Center); + d.ui.endFrame(); + d.streamer.stop(); + d.stopWorker(); + d.stack.clear(); + { + std::lock_guard<std::mutex> lk(d.mainMx); + d.mainQueue.clear(); + } + d.gamepad.shutdown(); + d.ui.shutdown(); + Http::globalCleanup(); + SDL_Quit(); + if (selftest) { + const bool framesOk = lastFrame >= kSelftestFrames; + if (!framesOk) ++selftestFailures; + XC_LOGI("selftest: rendered %llu frames; %s", static_cast<unsigned long long>(lastFrame), + selftestFailures ? "FAILED" : "PASSED"); + return selftestFailures ? 1 : 0; + } + return 0; +} + +void App::push(std::unique_ptr<Screen> s) { + if (s) d_->ops.push_back({StackOp::Push, std::move(s)}); +} + +void App::pop() { d_->ops.push_back({StackOp::Pop, nullptr}); } + +void App::replace(std::unique_ptr<Screen> s) { + if (s) d_->ops.push_back({StackOp::Replace, std::move(s)}); +} + +void App::resetTo(std::unique_ptr<Screen> s) { + if (s) d_->ops.push_back({StackOp::Reset, std::move(s)}); +} + +void App::quit() { + d_->quit = true; + d_->cancel = true; +} + +void App::runAsync(std::function<void()> work) { + if (!work) return; + { + std::lock_guard<std::mutex> lk(d_->workMx); + if (d_->workerStop) return; + d_->work.push_back(std::move(work)); + } + d_->workCv.notify_one(); +} + +void App::postToMain(std::function<void()> fn) { + if (!fn) return; + std::lock_guard<std::mutex> lk(d_->mainMx); + d_->mainQueue.push_back(std::move(fn)); +} + +void App::toast(const std::string& msg) { + XC_LOGI("toast: %s", msg.c_str()); + std::lock_guard<std::mutex> lk(d_->toastMx); + d_->toasts.push_back({msg, platform::monotonicMs() + kToastMs}); + while (d_->toasts.size() > kMaxToasts) d_->toasts.pop_front(); +} + +Config& App::config() { return d_->cfg; } +Auth& App::auth() { return d_->auth; } +Catalog& App::catalog() { return d_->catalog; } +Streamer& App::streamer() { return d_->streamer; } +Ui& App::ui() { return d_->ui; } +Gamepad& App::gamepad() { return d_->gamepad; } +bool App::statsOverlay() const { return d_->statsOverlay; } +void App::setStatsOverlay(bool on) { d_->statsOverlay = on; } + +} // namespace xc diff --git a/src/ui/app.hpp b/src/ui/app.hpp new file mode 100644 index 0000000..38dceaa --- /dev/null +++ b/src/ui/app.hpp @@ -0,0 +1,66 @@ +// Nubix — application object: owns config/auth/catalog/streamer/ui/gamepad, the screen +// stack and the SDL event loop; runs blocking work on a background worker. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <functional> +#include <memory> +#include <string> + +#include "core/auth.hpp" +#include "core/catalog.hpp" +#include "core/config.hpp" +#include "platform/gamepad.hpp" +#include "stream/streamer.hpp" +#include "ui/screens.hpp" +#include "ui/ui.hpp" + +namespace xc { + +class App { +public: + App(); + ~App(); + App(const App&) = delete; + App& operator=(const App&) = delete; + + // Full lifecycle: load config, init SDL/UI/pad/HTTP, push Login or Home, run the loop until + // quit(), shut down. Returns the process exit code. + int run(int argc, char** argv); + + // Screen stack (applied between frames, so safe to call from screen callbacks). + void push(std::unique_ptr<Screen> s); + void pop(); + void replace(std::unique_ptr<Screen> s); // pop + push + // Clear the whole stack and push s (e.g. after logout). + void resetTo(std::unique_ptr<Screen> s); + + // Request the main loop to exit. + void quit(); + + // Run work on the background worker thread (HTTP etc.), FIFO. + void runAsync(std::function<void()> work); + // Queue a function to run on the main thread before the next frame (thread-safe). + void postToMain(std::function<void()> fn); + + // Show a transient toast message at the bottom of the screen. + void toast(const std::string& msg); + + Config& config(); + Auth& auth(); + Catalog& catalog(); + Streamer& streamer(); + Ui& ui(); + Gamepad& gamepad(); + + // Stream statistics overlay toggle (kept for the app's lifetime). + bool statsOverlay() const; + void setStatsOverlay(bool on); + +private: + struct Impl; + std::unique_ptr<Impl> d_; +}; + +} // namespace xc diff --git a/src/ui/image.cpp b/src/ui/image.cpp new file mode 100644 index 0000000..ca38c68 --- /dev/null +++ b/src/ui/image.cpp @@ -0,0 +1,265 @@ +// Nubix — still-image decoding via libavcodec + resampling. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "ui/image.hpp" + +#include <algorithm> +#include <cmath> +#include <cstring> + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavutil/frame.h> +#include <libavutil/pixdesc.h> +} + +#include "core/log.hpp" + +namespace xc { +namespace image { +namespace { + +AVCodecID sniff(const uint8_t* d, size_t n) { + if (n >= 8 && d[0] == 0x89 && d[1] == 'P' && d[2] == 'N' && d[3] == 'G') return AV_CODEC_ID_PNG; + if (n >= 3 && d[0] == 0xFF && d[1] == 0xD8 && d[2] == 0xFF) return AV_CODEC_ID_MJPEG; + if (n >= 12 && std::memcmp(d, "RIFF", 4) == 0 && std::memcmp(d + 8, "WEBP", 4) == 0) return AV_CODEC_ID_WEBP; + if (n >= 6 && (std::memcmp(d, "GIF87a", 6) == 0 || std::memcmp(d, "GIF89a", 6) == 0)) return AV_CODEC_ID_GIF; + if (n >= 2 && d[0] == 'B' && d[1] == 'M') return AV_CODEC_ID_BMP; + return AV_CODEC_ID_NONE; +} + +inline uint8_t clamp8(int v) { return static_cast<uint8_t>(v < 0 ? 0 : (v > 255 ? 255 : v)); } + +// BT.601 YCbCr -> RGB (still images are JPEG/WebP: BT.601), limited or full range. +inline void yuvToRgb(int y, int u, int v, bool full, uint8_t* o) { + int c = full ? y : (y - 16); + int dd = u - 128, e = v - 128; + if (full) { + o[0] = clamp8((256 * c + 359 * e + 128) >> 8); + o[1] = clamp8((256 * c - 88 * dd - 183 * e + 128) >> 8); + o[2] = clamp8((256 * c + 454 * dd + 128) >> 8); + } else { + o[0] = clamp8((298 * c + 409 * e + 128) >> 8); + o[1] = clamp8((298 * c - 100 * dd - 208 * e + 128) >> 8); + o[2] = clamp8((298 * c + 516 * dd + 128) >> 8); + } +} + +bool frameToRgba(const AVFrame* f, Rgba& out) { + const int w = f->width, h = f->height; + if (w <= 0 || h <= 0 || w > 8192 || h > 8192) return false; + const AVPixelFormat fmt = static_cast<AVPixelFormat>(f->format); + out.width = w; + out.height = h; + out.pixels.assign(static_cast<size_t>(w) * h * 4, 255); + uint8_t* dst = out.pixels.data(); + + auto rows = [&](auto fn) { + for (int y = 0; y < h; ++y) { + const uint8_t* s = f->data[0] + static_cast<ptrdiff_t>(y) * f->linesize[0]; + uint8_t* o = dst + static_cast<size_t>(y) * w * 4; + for (int x = 0; x < w; ++x, o += 4) fn(s, x, o); + } + }; + + switch (fmt) { + case AV_PIX_FMT_RGBA: + rows([](const uint8_t* s, int x, uint8_t* o) { std::memcpy(o, s + x * 4, 4); }); + return true; + case AV_PIX_FMT_BGRA: + rows([](const uint8_t* s, int x, uint8_t* o) { o[0] = s[x * 4 + 2]; o[1] = s[x * 4 + 1]; o[2] = s[x * 4]; o[3] = s[x * 4 + 3]; }); + return true; + case AV_PIX_FMT_ARGB: + rows([](const uint8_t* s, int x, uint8_t* o) { o[0] = s[x * 4 + 1]; o[1] = s[x * 4 + 2]; o[2] = s[x * 4 + 3]; o[3] = s[x * 4]; }); + return true; + case AV_PIX_FMT_ABGR: + rows([](const uint8_t* s, int x, uint8_t* o) { o[0] = s[x * 4 + 3]; o[1] = s[x * 4 + 2]; o[2] = s[x * 4 + 1]; o[3] = s[x * 4]; }); + return true; + case AV_PIX_FMT_RGB0: + rows([](const uint8_t* s, int x, uint8_t* o) { std::memcpy(o, s + x * 4, 3); }); + return true; + case AV_PIX_FMT_BGR0: + rows([](const uint8_t* s, int x, uint8_t* o) { o[0] = s[x * 4 + 2]; o[1] = s[x * 4 + 1]; o[2] = s[x * 4]; }); + return true; + case AV_PIX_FMT_RGB24: + rows([](const uint8_t* s, int x, uint8_t* o) { std::memcpy(o, s + x * 3, 3); }); + return true; + case AV_PIX_FMT_BGR24: + rows([](const uint8_t* s, int x, uint8_t* o) { o[0] = s[x * 3 + 2]; o[1] = s[x * 3 + 1]; o[2] = s[x * 3]; }); + return true; + case AV_PIX_FMT_GRAY8: + rows([](const uint8_t* s, int x, uint8_t* o) { o[0] = o[1] = o[2] = s[x]; }); + return true; + case AV_PIX_FMT_YA8: + rows([](const uint8_t* s, int x, uint8_t* o) { o[0] = o[1] = o[2] = s[x * 2]; o[3] = s[x * 2 + 1]; }); + return true; + case AV_PIX_FMT_GRAY16BE: + rows([](const uint8_t* s, int x, uint8_t* o) { o[0] = o[1] = o[2] = s[x * 2]; }); + return true; + case AV_PIX_FMT_YA16BE: + rows([](const uint8_t* s, int x, uint8_t* o) { o[0] = o[1] = o[2] = s[x * 4]; o[3] = s[x * 4 + 2]; }); + return true; + case AV_PIX_FMT_RGB48BE: + rows([](const uint8_t* s, int x, uint8_t* o) { o[0] = s[x * 6]; o[1] = s[x * 6 + 2]; o[2] = s[x * 6 + 4]; }); + return true; + case AV_PIX_FMT_RGBA64BE: + rows([](const uint8_t* s, int x, uint8_t* o) { o[0] = s[x * 8]; o[1] = s[x * 8 + 2]; o[2] = s[x * 8 + 4]; o[3] = s[x * 8 + 6]; }); + return true; + case AV_PIX_FMT_PAL8: { + const uint32_t* pal = reinterpret_cast<const uint32_t*>(f->data[1]); // native-endian ARGB + if (!pal) return false; + rows([pal](const uint8_t* s, int x, uint8_t* o) { + uint32_t c = pal[s[x]]; + o[0] = (c >> 16) & 0xFF; o[1] = (c >> 8) & 0xFF; o[2] = c & 0xFF; o[3] = (c >> 24) & 0xFF; + }); + return true; + } + default: break; + } + + // Generic 8-bit planar YUV(A) (yuv420p, yuvj422p, yuv444p, yuva420p, ...). + const AVPixFmtDescriptor* desc = av_pix_fmt_desc_get(fmt); + if (desc && !(desc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_PAL | AV_PIX_FMT_FLAG_BITSTREAM)) && + (desc->flags & AV_PIX_FMT_FLAG_PLANAR) && desc->nb_components >= 3 && desc->comp[0].depth == 8 && + desc->comp[1].depth == 8 && desc->comp[2].depth == 8) { + const bool full = f->color_range == AVCOL_RANGE_JPEG || fmt == AV_PIX_FMT_YUVJ420P || + fmt == AV_PIX_FMT_YUVJ422P || fmt == AV_PIX_FMT_YUVJ444P || fmt == AV_PIX_FMT_YUVJ440P || + fmt == AV_PIX_FMT_YUVJ411P; + const int sx = desc->log2_chroma_w, sy = desc->log2_chroma_h; + const bool alpha = (desc->flags & AV_PIX_FMT_FLAG_ALPHA) && desc->nb_components == 4; + const int pY = desc->comp[0].plane, pU = desc->comp[1].plane, pV = desc->comp[2].plane; + const int pA = alpha ? desc->comp[3].plane : 0; + for (int y = 0; y < h; ++y) { + const uint8_t* ry = f->data[pY] + static_cast<ptrdiff_t>(y) * f->linesize[pY]; + const uint8_t* ru = f->data[pU] + static_cast<ptrdiff_t>(y >> sy) * f->linesize[pU]; + const uint8_t* rv = f->data[pV] + static_cast<ptrdiff_t>(y >> sy) * f->linesize[pV]; + const uint8_t* ra = alpha ? f->data[pA] + static_cast<ptrdiff_t>(y) * f->linesize[pA] : nullptr; + uint8_t* o = dst + static_cast<size_t>(y) * w * 4; + for (int x = 0; x < w; ++x, o += 4) { + yuvToRgb(ry[x], ru[x >> sx], rv[x >> sx], full, o); + if (ra) o[3] = ra[x]; + } + } + return true; + } + XC_LOGW("image: unsupported pixel format %s", av_get_pix_fmt_name(fmt) ? av_get_pix_fmt_name(fmt) : "?"); + return false; +} + +} // namespace + +bool decode(const uint8_t* data, size_t size, Rgba& out) { + out = Rgba(); + if (!data || size < 8 || size > (64u << 20)) return false; + const AVCodecID id = sniff(data, size); + if (id == AV_CODEC_ID_NONE) { + XC_LOGD("image: unknown format (%zu bytes)", size); + return false; + } + const AVCodec* codec = avcodec_find_decoder(id); + if (!codec) { + XC_LOGW("image: no decoder for %s", avcodec_get_name(id)); + return false; + } + AVCodecContext* ctx = avcodec_alloc_context3(codec); + AVPacket* pkt = av_packet_alloc(); + AVFrame* frame = av_frame_alloc(); + bool ok = false; + if (ctx && pkt && frame) { + ctx->thread_count = 1; + if (avcodec_open2(ctx, codec, nullptr) == 0 && av_new_packet(pkt, static_cast<int>(size)) == 0) { + std::memcpy(pkt->data, data, size); + int rc = avcodec_send_packet(ctx, pkt); + if (rc >= 0) { + rc = avcodec_receive_frame(ctx, frame); + if (rc == AVERROR(EAGAIN)) { + avcodec_send_packet(ctx, nullptr); // flush (some decoders emit on drain) + rc = avcodec_receive_frame(ctx, frame); + } + if (rc == 0) ok = frameToRgba(frame, out); + } + } + } + av_frame_free(&frame); + av_packet_free(&pkt); + avcodec_free_context(&ctx); + if (!ok) out = Rgba(); + return ok; +} + +void resize(const Rgba& src, int w, int h, bool cover, Rgba& out) { + out = Rgba(); + if (src.empty() || w <= 0 || h <= 0) return; + // Source window (cover = centred crop with the target aspect). + double cx0 = 0, cy0 = 0, cw = src.width, ch = src.height; + if (cover) { + const double ta = static_cast<double>(w) / h, sa = static_cast<double>(src.width) / src.height; + if (sa > ta) { + cw = src.height * ta; + cx0 = (src.width - cw) / 2; + } else { + ch = src.width / ta; + cy0 = (src.height - ch) / 2; + } + } + out.width = w; + out.height = h; + out.pixels.resize(static_cast<size_t>(w) * h * 4); + const double fx = cw / w, fy = ch / h; + const uint8_t* sp = src.pixels.data(); + const int sw = src.width, sh = src.height; + + for (int y = 0; y < h; ++y) { + uint8_t* o = out.pixels.data() + static_cast<size_t>(y) * w * 4; + for (int x = 0; x < w; ++x, o += 4) { + if (fx > 1.0 || fy > 1.0) { + // Box filter over the source footprint (alpha-weighted to avoid dark fringes). + int x0 = static_cast<int>(cx0 + x * fx), x1 = static_cast<int>(cx0 + (x + 1) * fx); + int y0 = static_cast<int>(cy0 + y * fy), y1 = static_cast<int>(cy0 + (y + 1) * fy); + x0 = std::clamp(x0, 0, sw - 1); + y0 = std::clamp(y0, 0, sh - 1); + x1 = std::clamp(std::max(x1, x0 + 1), 1, sw); + y1 = std::clamp(std::max(y1, y0 + 1), 1, sh); + uint64_t r = 0, g = 0, b = 0, a = 0, n = 0; + for (int yy = y0; yy < y1; ++yy) { + const uint8_t* p = sp + (static_cast<size_t>(yy) * sw + x0) * 4; + for (int xx = x0; xx < x1; ++xx, p += 4) { + r += p[0] * p[3]; + g += p[1] * p[3]; + b += p[2] * p[3]; + a += p[3]; + ++n; + } + } + if (a > 0) { + o[0] = static_cast<uint8_t>(r / a); + o[1] = static_cast<uint8_t>(g / a); + o[2] = static_cast<uint8_t>(b / a); + } else { + o[0] = o[1] = o[2] = 0; + } + o[3] = static_cast<uint8_t>(n ? a / n : 0); + } else { + // Bilinear. + double sxf = cx0 + (x + 0.5) * fx - 0.5, syf = cy0 + (y + 0.5) * fy - 0.5; + sxf = std::clamp(sxf, 0.0, sw - 1.0); + syf = std::clamp(syf, 0.0, sh - 1.0); + const int ix = static_cast<int>(sxf), iy = static_cast<int>(syf); + const int ix1 = std::min(ix + 1, sw - 1), iy1 = std::min(iy + 1, sh - 1); + const double ax = sxf - ix, ay = syf - iy; + const uint8_t* p00 = sp + (static_cast<size_t>(iy) * sw + ix) * 4; + const uint8_t* p10 = sp + (static_cast<size_t>(iy) * sw + ix1) * 4; + const uint8_t* p01 = sp + (static_cast<size_t>(iy1) * sw + ix) * 4; + const uint8_t* p11 = sp + (static_cast<size_t>(iy1) * sw + ix1) * 4; + for (int c = 0; c < 4; ++c) { + const double top = p00[c] + (p10[c] - p00[c]) * ax; + const double bot = p01[c] + (p11[c] - p01[c]) * ax; + o[c] = clamp8(static_cast<int>(std::lround(top + (bot - top) * ay))); + } + } + } + } +} + +} // namespace image +} // namespace xc diff --git a/src/ui/image.hpp b/src/ui/image.hpp new file mode 100644 index 0000000..0aeebd0 --- /dev/null +++ b/src/ui/image.hpp @@ -0,0 +1,29 @@ +// Nubix — still-image decoding (PNG/JPEG/WebP/GIF/BMP via libavcodec) into RGBA8 pixels, +// plus a CPU resampler, so box art can be decoded + scaled off the main thread. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstddef> +#include <cstdint> +#include <vector> + +namespace xc { +namespace image { + +// Tightly packed RGBA8 (byte order R,G,B,A), width * height * 4 bytes. +struct Rgba { + int width = 0, height = 0; + std::vector<uint8_t> pixels; + bool empty() const { return width <= 0 || height <= 0; } +}; + +// Decode an encoded image (format sniffed from magic bytes). Thread-safe. +bool decode(const uint8_t* data, size_t size, Rgba& out); + +// Scale src to exactly w x h. cover = true crops to the target aspect ratio first (centred, +// like CSS object-fit: cover); false stretches. Box filter when shrinking, bilinear otherwise. +void resize(const Rgba& src, int w, int h, bool cover, Rgba& out); + +} // namespace image +} // namespace xc diff --git a/src/ui/qr.cpp b/src/ui/qr.cpp new file mode 100644 index 0000000..258829b --- /dev/null +++ b/src/ui/qr.cpp @@ -0,0 +1,31 @@ +// Nubix — QR code encoding. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "ui/qr.hpp" + +#include <exception> + +#include "ui/qrcodegen.hpp" + +namespace xc { +namespace qr { + +bool encode(const std::string& text, std::vector<bool>& modules, int& size) { + modules.clear(); + size = 0; + try { + const qrcodegen::QrCode code = qrcodegen::QrCode::encodeText(text.c_str(), qrcodegen::QrCode::Ecc::MEDIUM); + size = code.getSize(); + modules.resize(static_cast<size_t>(size) * static_cast<size_t>(size)); + for (int y = 0; y < size; ++y) + for (int x = 0; x < size; ++x) modules[static_cast<size_t>(y) * size + x] = code.getModule(x, y); + return true; + } catch (const std::exception&) { + modules.clear(); + size = 0; + return false; + } +} + +} // namespace qr +} // namespace xc diff --git a/src/ui/qr.hpp b/src/ui/qr.hpp new file mode 100644 index 0000000..c1d1692 --- /dev/null +++ b/src/ui/qr.hpp @@ -0,0 +1,17 @@ +// Nubix — QR code encoding (thin wrapper over Nayuki qrcodegen, MIT; see qrcodegen.hpp). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <string> +#include <vector> + +namespace xc { +namespace qr { + +// Encode text (byte mode, ECC level M with boost). On success size = modules per side and +// modules[y * size + x] == true for dark modules. Returns false if the text is too long. +bool encode(const std::string& text, std::vector<bool>& modules, int& size); + +} // namespace qr +} // namespace xc diff --git a/src/ui/qrcodegen.cpp b/src/ui/qrcodegen.cpp new file mode 100644 index 0000000..4977f76 --- /dev/null +++ b/src/ui/qrcodegen.cpp @@ -0,0 +1,831 @@ +// SPDX-License-Identifier: MIT +/* + * QR Code generator library (C++) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#include <algorithm> +#include <cassert> +#include <climits> +#include <cstddef> +#include <cstdlib> +#include <cstring> +#include <sstream> +#include <utility> +#include "qrcodegen.hpp" + +using std::int8_t; +using std::uint8_t; +using std::size_t; +using std::vector; + + +namespace qrcodegen { + +/*---- Class QrSegment ----*/ + +QrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) : + modeBits(mode) { + numBitsCharCount[0] = cc0; + numBitsCharCount[1] = cc1; + numBitsCharCount[2] = cc2; +} + + +int QrSegment::Mode::getModeBits() const { + return modeBits; +} + + +int QrSegment::Mode::numCharCountBits(int ver) const { + return numBitsCharCount[(ver + 7) / 17]; +} + + +const QrSegment::Mode QrSegment::Mode::NUMERIC (0x1, 10, 12, 14); +const QrSegment::Mode QrSegment::Mode::ALPHANUMERIC(0x2, 9, 11, 13); +const QrSegment::Mode QrSegment::Mode::BYTE (0x4, 8, 16, 16); +const QrSegment::Mode QrSegment::Mode::KANJI (0x8, 8, 10, 12); +const QrSegment::Mode QrSegment::Mode::ECI (0x7, 0, 0, 0); + + +QrSegment QrSegment::makeBytes(const vector<uint8_t> &data) { + if (data.size() > static_cast<unsigned int>(INT_MAX)) + throw std::length_error("Data too long"); + BitBuffer bb; + for (uint8_t b : data) + bb.appendBits(b, 8); + return QrSegment(Mode::BYTE, static_cast<int>(data.size()), std::move(bb)); +} + + +QrSegment QrSegment::makeNumeric(const char *digits) { + BitBuffer bb; + int accumData = 0; + int accumCount = 0; + int charCount = 0; + for (; *digits != '\0'; digits++, charCount++) { + char c = *digits; + if (c < '0' || c > '9') + throw std::domain_error("String contains non-numeric characters"); + accumData = accumData * 10 + (c - '0'); + accumCount++; + if (accumCount == 3) { + bb.appendBits(static_cast<uint32_t>(accumData), 10); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 or 2 digits remaining + bb.appendBits(static_cast<uint32_t>(accumData), accumCount * 3 + 1); + return QrSegment(Mode::NUMERIC, charCount, std::move(bb)); +} + + +QrSegment QrSegment::makeAlphanumeric(const char *text) { + BitBuffer bb; + int accumData = 0; + int accumCount = 0; + int charCount = 0; + for (; *text != '\0'; text++, charCount++) { + const char *temp = std::strchr(ALPHANUMERIC_CHARSET, *text); + if (temp == nullptr) + throw std::domain_error("String contains unencodable characters in alphanumeric mode"); + accumData = accumData * 45 + static_cast<int>(temp - ALPHANUMERIC_CHARSET); + accumCount++; + if (accumCount == 2) { + bb.appendBits(static_cast<uint32_t>(accumData), 11); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 character remaining + bb.appendBits(static_cast<uint32_t>(accumData), 6); + return QrSegment(Mode::ALPHANUMERIC, charCount, std::move(bb)); +} + + +vector<QrSegment> QrSegment::makeSegments(const char *text) { + // Select the most efficient segment encoding automatically + vector<QrSegment> result; + if (*text == '\0'); // Leave result empty + else if (isNumeric(text)) + result.push_back(makeNumeric(text)); + else if (isAlphanumeric(text)) + result.push_back(makeAlphanumeric(text)); + else { + vector<uint8_t> bytes; + for (; *text != '\0'; text++) + bytes.push_back(static_cast<uint8_t>(*text)); + result.push_back(makeBytes(bytes)); + } + return result; +} + + +QrSegment QrSegment::makeEci(long assignVal) { + BitBuffer bb; + if (assignVal < 0) + throw std::domain_error("ECI assignment value out of range"); + else if (assignVal < (1 << 7)) + bb.appendBits(static_cast<uint32_t>(assignVal), 8); + else if (assignVal < (1 << 14)) { + bb.appendBits(2, 2); + bb.appendBits(static_cast<uint32_t>(assignVal), 14); + } else if (assignVal < 1000000L) { + bb.appendBits(6, 3); + bb.appendBits(static_cast<uint32_t>(assignVal), 21); + } else + throw std::domain_error("ECI assignment value out of range"); + return QrSegment(Mode::ECI, 0, std::move(bb)); +} + + +QrSegment::QrSegment(const Mode &md, int numCh, const std::vector<bool> &dt) : + mode(&md), + numChars(numCh), + data(dt) { + if (numCh < 0) + throw std::domain_error("Invalid value"); +} + + +QrSegment::QrSegment(const Mode &md, int numCh, std::vector<bool> &&dt) : + mode(&md), + numChars(numCh), + data(std::move(dt)) { + if (numCh < 0) + throw std::domain_error("Invalid value"); +} + + +int QrSegment::getTotalBits(const vector<QrSegment> &segs, int version) { + int result = 0; + for (const QrSegment &seg : segs) { + int ccbits = seg.mode->numCharCountBits(version); + if (seg.numChars >= (1L << ccbits)) + return -1; // The segment's length doesn't fit the field's bit width + if (4 + ccbits > INT_MAX - result) + return -1; // The sum will overflow an int type + result += 4 + ccbits; + if (seg.data.size() > static_cast<unsigned int>(INT_MAX - result)) + return -1; // The sum will overflow an int type + result += static_cast<int>(seg.data.size()); + } + return result; +} + + +bool QrSegment::isNumeric(const char *text) { + for (; *text != '\0'; text++) { + char c = *text; + if (c < '0' || c > '9') + return false; + } + return true; +} + + +bool QrSegment::isAlphanumeric(const char *text) { + for (; *text != '\0'; text++) { + if (std::strchr(ALPHANUMERIC_CHARSET, *text) == nullptr) + return false; + } + return true; +} + + +const QrSegment::Mode &QrSegment::getMode() const { + return *mode; +} + + +int QrSegment::getNumChars() const { + return numChars; +} + + +const std::vector<bool> &QrSegment::getData() const { + return data; +} + + +const char *QrSegment::ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + + + +/*---- Class QrCode ----*/ + +int QrCode::getFormatBits(Ecc ecl) { + switch (ecl) { + case Ecc::LOW : return 1; + case Ecc::MEDIUM : return 0; + case Ecc::QUARTILE: return 3; + case Ecc::HIGH : return 2; + default: throw std::logic_error("Unreachable"); + } +} + + +QrCode QrCode::encodeText(const char *text, Ecc ecl) { + vector<QrSegment> segs = QrSegment::makeSegments(text); + return encodeSegments(segs, ecl); +} + + +QrCode QrCode::encodeBinary(const vector<uint8_t> &data, Ecc ecl) { + vector<QrSegment> segs{QrSegment::makeBytes(data)}; + return encodeSegments(segs, ecl); +} + + +QrCode QrCode::encodeSegments(const vector<QrSegment> &segs, Ecc ecl, + int minVersion, int maxVersion, int mask, bool boostEcl) { + if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) + throw std::invalid_argument("Invalid value"); + + // Find the minimal version number to use + int version, dataUsedBits; + for (version = minVersion; ; version++) { + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available + dataUsedBits = QrSegment::getTotalBits(segs, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + if (version >= maxVersion) { // All versions in the range could not fit the given data + std::ostringstream sb; + if (dataUsedBits == -1) + sb << "Segment too long"; + else { + sb << "Data length = " << dataUsedBits << " bits, "; + sb << "Max capacity = " << dataCapacityBits << " bits"; + } + throw data_too_long(sb.str()); + } + } + assert(dataUsedBits != -1); + + // Increase the error correction level while the data still fits in the current version number + for (Ecc newEcl : {Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high + if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) + ecl = newEcl; + } + + // Concatenate all segments to create the data bit string + BitBuffer bb; + for (const QrSegment &seg : segs) { + bb.appendBits(static_cast<uint32_t>(seg.getMode().getModeBits()), 4); + bb.appendBits(static_cast<uint32_t>(seg.getNumChars()), seg.getMode().numCharCountBits(version)); + bb.insert(bb.end(), seg.getData().begin(), seg.getData().end()); + } + assert(bb.size() == static_cast<unsigned int>(dataUsedBits)); + + // Add terminator and pad up to a byte if applicable + size_t dataCapacityBits = static_cast<size_t>(getNumDataCodewords(version, ecl)) * 8; + assert(bb.size() <= dataCapacityBits); + bb.appendBits(0, std::min(4, static_cast<int>(dataCapacityBits - bb.size()))); + bb.appendBits(0, (8 - static_cast<int>(bb.size() % 8)) % 8); + assert(bb.size() % 8 == 0); + + // Pad with alternating bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bb.size() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + bb.appendBits(padByte, 8); + + // Pack bits into bytes in big endian + vector<uint8_t> dataCodewords(bb.size() / 8); + for (size_t i = 0; i < bb.size(); i++) + dataCodewords.at(i >> 3) |= (bb.at(i) ? 1 : 0) << (7 - (i & 7)); + + // Create the QR Code object + return QrCode(version, ecl, dataCodewords, mask); +} + + +QrCode::QrCode(int ver, Ecc ecl, const vector<uint8_t> &dataCodewords, int msk) : + // Initialize fields and check arguments + version(ver), + errorCorrectionLevel(ecl) { + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw std::domain_error("Version value out of range"); + if (msk < -1 || msk > 7) + throw std::domain_error("Mask value out of range"); + size = ver * 4 + 17; + size_t sz = static_cast<size_t>(size); + modules = vector<vector<bool> >(sz, vector<bool>(sz)); // Initially all light + isFunction = vector<vector<bool> >(sz, vector<bool>(sz)); + + // Compute ECC, draw modules + drawFunctionPatterns(); + const vector<uint8_t> allCodewords = addEccAndInterleave(dataCodewords); + drawCodewords(allCodewords); + + // Do masking + if (msk == -1) { // Automatically choose best mask + long minPenalty = LONG_MAX; + for (int i = 0; i < 8; i++) { + applyMask(i); + drawFormatBits(i); + long penalty = getPenaltyScore(); + if (penalty < minPenalty) { + msk = i; + minPenalty = penalty; + } + applyMask(i); // Undoes the mask due to XOR + } + } + assert(0 <= msk && msk <= 7); + mask = msk; + applyMask(msk); // Apply the final choice of mask + drawFormatBits(msk); // Overwrite old format bits + + isFunction.clear(); + isFunction.shrink_to_fit(); +} + + +int QrCode::getVersion() const { + return version; +} + + +int QrCode::getSize() const { + return size; +} + + +QrCode::Ecc QrCode::getErrorCorrectionLevel() const { + return errorCorrectionLevel; +} + + +int QrCode::getMask() const { + return mask; +} + + +bool QrCode::getModule(int x, int y) const { + return 0 <= x && x < size && 0 <= y && y < size && module(x, y); +} + + +void QrCode::drawFunctionPatterns() { + // Draw horizontal and vertical timing patterns + for (int i = 0; i < size; i++) { + setFunctionModule(6, i, i % 2 == 0); + setFunctionModule(i, 6, i % 2 == 0); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + drawFinderPattern(3, 3); + drawFinderPattern(size - 4, 3); + drawFinderPattern(3, size - 4); + + // Draw numerous alignment patterns + const vector<int> alignPatPos = getAlignmentPatternPositions(); + size_t numAlign = alignPatPos.size(); + for (size_t i = 0; i < numAlign; i++) { + for (size_t j = 0; j < numAlign; j++) { + // Don't draw on the three finder corners + if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) + drawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j)); + } + } + + // Draw configuration data + drawFormatBits(0); // Dummy mask value; overwritten later in the constructor + drawVersion(); +} + + +void QrCode::drawFormatBits(int msk) { + // Calculate error correction code and pack bits + int data = getFormatBits(errorCorrectionLevel) << 3 | msk; // errCorrLvl is uint2, msk is uint3 + int rem = data; + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + int bits = (data << 10 | rem) ^ 0x5412; // uint15 + assert(bits >> 15 == 0); + + // Draw first copy + for (int i = 0; i <= 5; i++) + setFunctionModule(8, i, getBit(bits, i)); + setFunctionModule(8, 7, getBit(bits, 6)); + setFunctionModule(8, 8, getBit(bits, 7)); + setFunctionModule(7, 8, getBit(bits, 8)); + for (int i = 9; i < 15; i++) + setFunctionModule(14 - i, 8, getBit(bits, i)); + + // Draw second copy + for (int i = 0; i < 8; i++) + setFunctionModule(size - 1 - i, 8, getBit(bits, i)); + for (int i = 8; i < 15; i++) + setFunctionModule(8, size - 15 + i, getBit(bits, i)); + setFunctionModule(8, size - 8, true); // Always dark +} + + +void QrCode::drawVersion() { + if (version < 7) + return; + + // Calculate error correction code and pack bits + int rem = version; // version is uint6, in the range [7, 40] + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + long bits = static_cast<long>(version) << 12 | rem; // uint18 + assert(bits >> 18 == 0); + + // Draw two copies + for (int i = 0; i < 18; i++) { + bool bit = getBit(bits, i); + int a = size - 11 + i % 3; + int b = i / 3; + setFunctionModule(a, b, bit); + setFunctionModule(b, a, bit); + } +} + + +void QrCode::drawFinderPattern(int x, int y) { + for (int dy = -4; dy <= 4; dy++) { + for (int dx = -4; dx <= 4; dx++) { + int dist = std::max(std::abs(dx), std::abs(dy)); // Chebyshev/infinity norm + int xx = x + dx, yy = y + dy; + if (0 <= xx && xx < size && 0 <= yy && yy < size) + setFunctionModule(xx, yy, dist != 2 && dist != 4); + } + } +} + + +void QrCode::drawAlignmentPattern(int x, int y) { + for (int dy = -2; dy <= 2; dy++) { + for (int dx = -2; dx <= 2; dx++) + setFunctionModule(x + dx, y + dy, std::max(std::abs(dx), std::abs(dy)) != 1); + } +} + + +void QrCode::setFunctionModule(int x, int y, bool isDark) { + size_t ux = static_cast<size_t>(x); + size_t uy = static_cast<size_t>(y); + modules .at(uy).at(ux) = isDark; + isFunction.at(uy).at(ux) = true; +} + + +bool QrCode::module(int x, int y) const { + return modules.at(static_cast<size_t>(y)).at(static_cast<size_t>(x)); +} + + +vector<uint8_t> QrCode::addEccAndInterleave(const vector<uint8_t> &data) const { + if (data.size() != static_cast<unsigned int>(getNumDataCodewords(version, errorCorrectionLevel))) + throw std::invalid_argument("Invalid argument"); + + // Calculate parameter numbers + int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[static_cast<int>(errorCorrectionLevel)][version]; + int blockEccLen = ECC_CODEWORDS_PER_BLOCK [static_cast<int>(errorCorrectionLevel)][version]; + int rawCodewords = getNumRawDataModules(version) / 8; + int numShortBlocks = numBlocks - rawCodewords % numBlocks; + int shortBlockLen = rawCodewords / numBlocks; + + // Split data into blocks and append ECC to each block + vector<vector<uint8_t> > blocks; + const vector<uint8_t> rsDiv = reedSolomonComputeDivisor(blockEccLen); + for (int i = 0, k = 0; i < numBlocks; i++) { + vector<uint8_t> dat(data.cbegin() + k, data.cbegin() + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1))); + k += static_cast<int>(dat.size()); + const vector<uint8_t> ecc = reedSolomonComputeRemainder(dat, rsDiv); + if (i < numShortBlocks) + dat.push_back(0); + dat.insert(dat.end(), ecc.cbegin(), ecc.cend()); + blocks.push_back(std::move(dat)); + } + + // Interleave (not concatenate) the bytes from every block into a single sequence + vector<uint8_t> result; + for (size_t i = 0; i < blocks.at(0).size(); i++) { + for (size_t j = 0; j < blocks.size(); j++) { + // Skip the padding byte in short blocks + if (i != static_cast<unsigned int>(shortBlockLen - blockEccLen) || j >= static_cast<unsigned int>(numShortBlocks)) + result.push_back(blocks.at(j).at(i)); + } + } + assert(result.size() == static_cast<unsigned int>(rawCodewords)); + return result; +} + + +void QrCode::drawCodewords(const vector<uint8_t> &data) { + if (data.size() != static_cast<unsigned int>(getNumRawDataModules(version) / 8)) + throw std::invalid_argument("Invalid argument"); + + size_t i = 0; // Bit index into the data + // Do the funny zigzag scan + for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair + if (right == 6) + right = 5; + for (int vert = 0; vert < size; vert++) { // Vertical counter + for (int j = 0; j < 2; j++) { + size_t x = static_cast<size_t>(right - j); // Actual x coordinate + bool upward = ((right + 1) & 2) == 0; + size_t y = static_cast<size_t>(upward ? size - 1 - vert : vert); // Actual y coordinate + if (!isFunction.at(y).at(x) && i < data.size() * 8) { + modules.at(y).at(x) = getBit(data.at(i >> 3), 7 - static_cast<int>(i & 7)); + i++; + } + // If this QR Code has any remainder bits (0 to 7), they were assigned as + // 0/false/light by the constructor and are left unchanged by this method + } + } + } + assert(i == data.size() * 8); +} + + +void QrCode::applyMask(int msk) { + if (msk < 0 || msk > 7) + throw std::domain_error("Mask value out of range"); + size_t sz = static_cast<size_t>(size); + for (size_t y = 0; y < sz; y++) { + for (size_t x = 0; x < sz; x++) { + bool invert; + switch (msk) { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + default: throw std::logic_error("Unreachable"); + } + modules.at(y).at(x) = modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x)); + } + } +} + + +long QrCode::getPenaltyScore() const { + long result = 0; + + // Adjacent modules in row having same color, and finder-like patterns + for (int y = 0; y < size; y++) { + bool runColor = false; + int runX = 0; + std::array<int,7> runHistory = {}; + for (int x = 0; x < size; x++) { + if (module(x, y) == runColor) { + runX++; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + result++; + } else { + finderPenaltyAddHistory(runX, runHistory); + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; + runColor = module(x, y); + runX = 1; + } + } + result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3; + } + // Adjacent modules in column having same color, and finder-like patterns + for (int x = 0; x < size; x++) { + bool runColor = false; + int runY = 0; + std::array<int,7> runHistory = {}; + for (int y = 0; y < size; y++) { + if (module(x, y) == runColor) { + runY++; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + result++; + } else { + finderPenaltyAddHistory(runY, runHistory); + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; + runColor = module(x, y); + runY = 1; + } + } + result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3; + } + + // 2*2 blocks of modules having same color + for (int y = 0; y < size - 1; y++) { + for (int x = 0; x < size - 1; x++) { + bool color = module(x, y); + if ( color == module(x + 1, y) && + color == module(x, y + 1) && + color == module(x + 1, y + 1)) + result += PENALTY_N2; + } + } + + // Balance of dark and light modules + int dark = 0; + for (const vector<bool> &row : modules) { + for (bool color : row) { + if (color) + dark++; + } + } + int total = size * size; // Note that size is odd, so dark/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)% + int k = static_cast<int>((std::abs(dark * 20L - total * 10L) + total - 1) / total) - 1; + assert(0 <= k && k <= 9); + result += k * PENALTY_N4; + assert(0 <= result && result <= 2568888L); // Non-tight upper bound based on default values of PENALTY_N1, ..., N4 + return result; +} + + +vector<int> QrCode::getAlignmentPatternPositions() const { + if (version == 1) + return vector<int>(); + else { + int numAlign = version / 7 + 2; + int step = (version == 32) ? 26 : + (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2; + vector<int> result; + for (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step) + result.insert(result.begin(), pos); + result.insert(result.begin(), 6); + return result; + } +} + + +int QrCode::getNumRawDataModules(int ver) { + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw std::domain_error("Version number out of range"); + int result = (16 * ver + 128) * ver + 64; + if (ver >= 2) { + int numAlign = ver / 7 + 2; + result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + result -= 36; + } + assert(208 <= result && result <= 29648); + return result; +} + + +int QrCode::getNumDataCodewords(int ver, Ecc ecl) { + return getNumRawDataModules(ver) / 8 + - ECC_CODEWORDS_PER_BLOCK [static_cast<int>(ecl)][ver] + * NUM_ERROR_CORRECTION_BLOCKS[static_cast<int>(ecl)][ver]; +} + + +vector<uint8_t> QrCode::reedSolomonComputeDivisor(int degree) { + if (degree < 1 || degree > 255) + throw std::domain_error("Degree out of range"); + // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. + // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. + vector<uint8_t> result(static_cast<size_t>(degree)); + result.at(result.size() - 1) = 1; // Start off with the monomial x^0 + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // and drop the highest monomial term which is always 1x^degree. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint8_t root = 1; + for (int i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (size_t j = 0; j < result.size(); j++) { + result.at(j) = reedSolomonMultiply(result.at(j), root); + if (j + 1 < result.size()) + result.at(j) ^= result.at(j + 1); + } + root = reedSolomonMultiply(root, 0x02); + } + return result; +} + + +vector<uint8_t> QrCode::reedSolomonComputeRemainder(const vector<uint8_t> &data, const vector<uint8_t> &divisor) { + vector<uint8_t> result(divisor.size()); + for (uint8_t b : data) { // Polynomial division + uint8_t factor = b ^ result.at(0); + result.erase(result.begin()); + result.push_back(0); + for (size_t i = 0; i < result.size(); i++) + result.at(i) ^= reedSolomonMultiply(divisor.at(i), factor); + } + return result; +} + + +uint8_t QrCode::reedSolomonMultiply(uint8_t x, uint8_t y) { + // Russian peasant multiplication + int z = 0; + for (int i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + assert(z >> 8 == 0); + return static_cast<uint8_t>(z); +} + + +int QrCode::finderPenaltyCountPatterns(const std::array<int,7> &runHistory) const { + int n = runHistory.at(1); + assert(n <= size * 3); + bool core = n > 0 && runHistory.at(2) == n && runHistory.at(3) == n * 3 && runHistory.at(4) == n && runHistory.at(5) == n; + return (core && runHistory.at(0) >= n * 4 && runHistory.at(6) >= n ? 1 : 0) + + (core && runHistory.at(6) >= n * 4 && runHistory.at(0) >= n ? 1 : 0); +} + + +int QrCode::finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array<int,7> &runHistory) const { + if (currentRunColor) { // Terminate dark run + finderPenaltyAddHistory(currentRunLength, runHistory); + currentRunLength = 0; + } + currentRunLength += size; // Add light border to final run + finderPenaltyAddHistory(currentRunLength, runHistory); + return finderPenaltyCountPatterns(runHistory); +} + + +void QrCode::finderPenaltyAddHistory(int currentRunLength, std::array<int,7> &runHistory) const { + if (runHistory.at(0) == 0) + currentRunLength += size; // Add light border to initial run + std::copy_backward(runHistory.cbegin(), runHistory.cend() - 1, runHistory.end()); + runHistory.at(0) = currentRunLength; +} + + +bool QrCode::getBit(long x, int i) { + return ((x >> i) & 1) != 0; +} + + +/*---- Tables of constants ----*/ + +const int QrCode::PENALTY_N1 = 3; +const int QrCode::PENALTY_N2 = 3; +const int QrCode::PENALTY_N3 = 40; +const int QrCode::PENALTY_N4 = 10; + + +const int8_t QrCode::ECC_CODEWORDS_PER_BLOCK[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low + {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium + {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile + {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High +}; + +const int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low + {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium + {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile + {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High +}; + + +data_too_long::data_too_long(const std::string &msg) : + std::length_error(msg) {} + + + +/*---- Class BitBuffer ----*/ + +BitBuffer::BitBuffer() + : std::vector<bool>() {} + + +void BitBuffer::appendBits(std::uint32_t val, int len) { + if (len < 0 || len > 31 || val >> len != 0) + throw std::domain_error("Value out of range"); + for (int i = len - 1; i >= 0; i--) // Append bit by bit + this->push_back(((val >> i) & 1) != 0); +} + +} diff --git a/src/ui/qrcodegen.hpp b/src/ui/qrcodegen.hpp new file mode 100644 index 0000000..e4e0c78 --- /dev/null +++ b/src/ui/qrcodegen.hpp @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: MIT +/* + * QR Code generator library (C++) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#pragma once + +#include <array> +#include <cstdint> +#include <stdexcept> +#include <string> +#include <vector> + + +namespace qrcodegen { + +/* + * A segment of character/binary/control data in a QR Code symbol. + * Instances of this class are immutable. + * The mid-level way to create a segment is to take the payload data + * and call a static factory function such as QrSegment::makeNumeric(). + * The low-level way to create a segment is to custom-make the bit buffer + * and call the QrSegment() constructor with appropriate values. + * This segment class imposes no length restrictions, but QR Codes have restrictions. + * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + * Any segment longer than this is meaningless for the purpose of generating QR Codes. + */ +class QrSegment final { + + /*---- Public helper enumeration ----*/ + + /* + * Describes how a segment's data bits are interpreted. Immutable. + */ + public: class Mode final { + + /*-- Constants --*/ + + public: static const Mode NUMERIC; + public: static const Mode ALPHANUMERIC; + public: static const Mode BYTE; + public: static const Mode KANJI; + public: static const Mode ECI; + + + /*-- Fields --*/ + + // The mode indicator bits, which is a uint4 value (range 0 to 15). + private: int modeBits; + + // Number of character count bits for three different version ranges. + private: int numBitsCharCount[3]; + + + /*-- Constructor --*/ + + private: Mode(int mode, int cc0, int cc1, int cc2); + + + /*-- Methods --*/ + + /* + * (Package-private) Returns the mode indicator bits, which is an unsigned 4-bit value (range 0 to 15). + */ + public: int getModeBits() const; + + /* + * (Package-private) Returns the bit width of the character count field for a segment in + * this mode in a QR Code at the given version number. The result is in the range [0, 16]. + */ + public: int numCharCountBits(int ver) const; + + }; + + + + /*---- Static factory functions (mid level) ----*/ + + /* + * Returns a segment representing the given binary data encoded in + * byte mode. All input byte vectors are acceptable. Any text string + * can be converted to UTF-8 bytes and encoded as a byte mode segment. + */ + public: static QrSegment makeBytes(const std::vector<std::uint8_t> &data); + + + /* + * Returns a segment representing the given string of decimal digits encoded in numeric mode. + */ + public: static QrSegment makeNumeric(const char *digits); + + + /* + * Returns a segment representing the given text string encoded in alphanumeric mode. + * The characters allowed are: 0 to 9, A to Z (uppercase only), space, + * dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ + public: static QrSegment makeAlphanumeric(const char *text); + + + /* + * Returns a list of zero or more segments to represent the given text string. The result + * may use various segment modes and switch modes to optimize the length of the bit stream. + */ + public: static std::vector<QrSegment> makeSegments(const char *text); + + + /* + * Returns a segment representing an Extended Channel Interpretation + * (ECI) designator with the given assignment value. + */ + public: static QrSegment makeEci(long assignVal); + + + /*---- Public static helper functions ----*/ + + /* + * Tests whether the given string can be encoded as a segment in numeric mode. + * A string is encodable iff each character is in the range 0 to 9. + */ + public: static bool isNumeric(const char *text); + + + /* + * Tests whether the given string can be encoded as a segment in alphanumeric mode. + * A string is encodable iff each character is in the following set: 0 to 9, A to Z + * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ + public: static bool isAlphanumeric(const char *text); + + + + /*---- Instance fields ----*/ + + /* The mode indicator of this segment. Accessed through getMode(). */ + private: const Mode *mode; + + /* The length of this segment's unencoded data. Measured in characters for + * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + * Always zero or positive. Not the same as the data's bit length. + * Accessed through getNumChars(). */ + private: int numChars; + + /* The data bits of this segment. Accessed through getData(). */ + private: std::vector<bool> data; + + + /*---- Constructors (low level) ----*/ + + /* + * Creates a new QR Code segment with the given attributes and data. + * The character count (numCh) must agree with the mode and the bit buffer length, + * but the constraint isn't checked. The given bit buffer is copied and stored. + */ + public: QrSegment(const Mode &md, int numCh, const std::vector<bool> &dt); + + + /* + * Creates a new QR Code segment with the given parameters and data. + * The character count (numCh) must agree with the mode and the bit buffer length, + * but the constraint isn't checked. The given bit buffer is moved and stored. + */ + public: QrSegment(const Mode &md, int numCh, std::vector<bool> &&dt); + + + /*---- Methods ----*/ + + /* + * Returns the mode field of this segment. + */ + public: const Mode &getMode() const; + + + /* + * Returns the character count field of this segment. + */ + public: int getNumChars() const; + + + /* + * Returns the data bits of this segment. + */ + public: const std::vector<bool> &getData() const; + + + // (Package-private) Calculates the number of bits needed to encode the given segments at + // the given version. Returns a non-negative number if successful. Otherwise returns -1 if a + // segment has too many characters to fit its length field, or the total bits exceeds INT_MAX. + public: static int getTotalBits(const std::vector<QrSegment> &segs, int version); + + + /*---- Private constant ----*/ + + /* The set of all legal characters in alphanumeric mode, where + * each character value maps to the index in the string. */ + private: static const char *ALPHANUMERIC_CHARSET; + +}; + + + +/* + * A QR Code symbol, which is a type of two-dimension barcode. + * Invented by Denso Wave and described in the ISO/IEC 18004 standard. + * Instances of this class represent an immutable square grid of dark and light cells. + * The class provides static factory functions to create a QR Code from text or binary data. + * The class covers the QR Code Model 2 specification, supporting all versions (sizes) + * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. + * + * Ways to create a QR Code object: + * - High level: Take the payload data and call QrCode::encodeText() or QrCode::encodeBinary(). + * - Mid level: Custom-make the list of segments and call QrCode::encodeSegments(). + * - Low level: Custom-make the array of data codeword bytes (including + * segment headers and final padding, excluding error correction codewords), + * supply the appropriate version number, and call the QrCode() constructor. + * (Note that all ways require supplying the desired error correction level.) + */ +class QrCode final { + + /*---- Public helper enumeration ----*/ + + /* + * The error correction level in a QR Code symbol. + */ + public: enum class Ecc { + LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords + MEDIUM , // The QR Code can tolerate about 15% erroneous codewords + QUARTILE, // The QR Code can tolerate about 25% erroneous codewords + HIGH , // The QR Code can tolerate about 30% erroneous codewords + }; + + + // Returns a value in the range 0 to 3 (unsigned 2-bit integer). + private: static int getFormatBits(Ecc ecl); + + + + /*---- Static factory functions (high level) ----*/ + + /* + * Returns a QR Code representing the given Unicode text string at the given error correction level. + * As a conservative upper bound, this function is guaranteed to succeed for strings that have 2953 or fewer + * UTF-8 code units (not Unicode code points) if the low error correction level is used. The smallest possible + * QR Code version is automatically chosen for the output. The ECC level of the result may be higher than + * the ecl argument if it can be done without increasing the version. + */ + public: static QrCode encodeText(const char *text, Ecc ecl); + + + /* + * Returns a QR Code representing the given binary data at the given error correction level. + * This function always encodes using the binary segment mode, not any text mode. The maximum number of + * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. + * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. + */ + public: static QrCode encodeBinary(const std::vector<std::uint8_t> &data, Ecc ecl); + + + /*---- Static factory functions (mid level) ----*/ + + /* + * Returns a QR Code representing the given segments with the given encoding parameters. + * The smallest possible QR Code version within the given range is automatically + * chosen for the output. Iff boostEcl is true, then the ECC level of the result + * may be higher than the ecl argument if it can be done without increasing the + * version. The mask number is either between 0 to 7 (inclusive) to force that + * mask, or -1 to automatically choose an appropriate mask (which may be slow). + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a mid-level API; the high-level API is encodeText() and encodeBinary(). + */ + public: static QrCode encodeSegments(const std::vector<QrSegment> &segs, Ecc ecl, + int minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true); // All optional parameters + + + + /*---- Instance fields ----*/ + + // Immutable scalar parameters: + + /* The version number of this QR Code, which is between 1 and 40 (inclusive). + * This determines the size of this barcode. */ + private: int version; + + /* The width and height of this QR Code, measured in modules, between + * 21 and 177 (inclusive). This is equal to version * 4 + 17. */ + private: int size; + + /* The error correction level used in this QR Code. */ + private: Ecc errorCorrectionLevel; + + /* The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). + * Even if a QR Code is created with automatic masking requested (mask = -1), + * the resulting object still has a mask value between 0 and 7. */ + private: int mask; + + // Private grids of modules/pixels, with dimensions of size*size: + + // The modules of this QR Code (false = light, true = dark). + // Immutable after constructor finishes. Accessed through getModule(). + private: std::vector<std::vector<bool> > modules; + + // Indicates function modules that are not subjected to masking. Discarded when constructor finishes. + private: std::vector<std::vector<bool> > isFunction; + + + + /*---- Constructor (low level) ----*/ + + /* + * Creates a new QR Code with the given version number, + * error correction level, data codeword bytes, and mask number. + * This is a low-level API that most users should not use directly. + * A mid-level API is the encodeSegments() function. + */ + public: QrCode(int ver, Ecc ecl, const std::vector<std::uint8_t> &dataCodewords, int msk); + + + + /*---- Public instance methods ----*/ + + /* + * Returns this QR Code's version, in the range [1, 40]. + */ + public: int getVersion() const; + + + /* + * Returns this QR Code's size, in the range [21, 177]. + */ + public: int getSize() const; + + + /* + * Returns this QR Code's error correction level. + */ + public: Ecc getErrorCorrectionLevel() const; + + + /* + * Returns this QR Code's mask, in the range [0, 7]. + */ + public: int getMask() const; + + + /* + * Returns the color of the module (pixel) at the given coordinates, which is false + * for light or true for dark. The top left corner has the coordinates (x=0, y=0). + * If the given coordinates are out of bounds, then false (light) is returned. + */ + public: bool getModule(int x, int y) const; + + + + /*---- Private helper methods for constructor: Drawing function modules ----*/ + + // Reads this object's version field, and draws and marks all function modules. + private: void drawFunctionPatterns(); + + + // Draws two copies of the format bits (with its own error correction code) + // based on the given mask and this object's error correction level field. + private: void drawFormatBits(int msk); + + + // Draws two copies of the version bits (with its own error correction code), + // based on this object's version field, iff 7 <= version <= 40. + private: void drawVersion(); + + + // Draws a 9*9 finder pattern including the border separator, + // with the center module at (x, y). Modules can be out of bounds. + private: void drawFinderPattern(int x, int y); + + + // Draws a 5*5 alignment pattern, with the center module + // at (x, y). All modules must be in bounds. + private: void drawAlignmentPattern(int x, int y); + + + // Sets the color of a module and marks it as a function module. + // Only used by the constructor. Coordinates must be in bounds. + private: void setFunctionModule(int x, int y, bool isDark); + + + // Returns the color of the module at the given coordinates, which must be in range. + private: bool module(int x, int y) const; + + + /*---- Private helper methods for constructor: Codewords and masking ----*/ + + // Returns a new byte string representing the given data with the appropriate error correction + // codewords appended to it, based on this object's version and error correction level. + private: std::vector<std::uint8_t> addEccAndInterleave(const std::vector<std::uint8_t> &data) const; + + + // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire + // data area of this QR Code. Function modules need to be marked off before this is called. + private: void drawCodewords(const std::vector<std::uint8_t> &data); + + + // XORs the codeword modules in this QR Code with the given mask pattern. + // The function modules must be marked and the codeword bits must be drawn + // before masking. Due to the arithmetic of XOR, calling applyMask() with + // the same mask value a second time will undo the mask. A final well-formed + // QR Code needs exactly one (not zero, two, etc.) mask applied. + private: void applyMask(int msk); + + + // Calculates and returns the penalty score based on state of this QR Code's current modules. + // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. + private: long getPenaltyScore() const; + + + + /*---- Private helper functions ----*/ + + // Returns an ascending list of positions of alignment patterns for this version number. + // Each position is in the range [0,177), and are used on both the x and y axes. + // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. + private: std::vector<int> getAlignmentPatternPositions() const; + + + // Returns the number of data bits that can be stored in a QR Code of the given version number, after + // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. + // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. + private: static int getNumRawDataModules(int ver); + + + // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any + // QR Code of the given version number and error correction level, with remainder bits discarded. + // This stateless pure function could be implemented as a (40*4)-cell lookup table. + private: static int getNumDataCodewords(int ver, Ecc ecl); + + + // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be + // implemented as a lookup table over all possible parameter values, instead of as an algorithm. + private: static std::vector<std::uint8_t> reedSolomonComputeDivisor(int degree); + + + // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. + private: static std::vector<std::uint8_t> reedSolomonComputeRemainder(const std::vector<std::uint8_t> &data, const std::vector<std::uint8_t> &divisor); + + + // Returns the product of the two given field elements modulo GF(2^8/0x11D). + // All inputs are valid. This could be implemented as a 256*256 lookup table. + private: static std::uint8_t reedSolomonMultiply(std::uint8_t x, std::uint8_t y); + + + // Can only be called immediately after a light run is added, and + // returns either 0, 1, or 2. A helper function for getPenaltyScore(). + private: int finderPenaltyCountPatterns(const std::array<int,7> &runHistory) const; + + + // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). + private: int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array<int,7> &runHistory) const; + + + // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). + private: void finderPenaltyAddHistory(int currentRunLength, std::array<int,7> &runHistory) const; + + + // Returns true iff the i'th bit of x is set to 1. + private: static bool getBit(long x, int i); + + + /*---- Constants and tables ----*/ + + // The minimum version number supported in the QR Code Model 2 standard. + public: static constexpr int MIN_VERSION = 1; + + // The maximum version number supported in the QR Code Model 2 standard. + public: static constexpr int MAX_VERSION = 40; + + + // For use in getPenaltyScore(), when evaluating which mask is best. + private: static const int PENALTY_N1; + private: static const int PENALTY_N2; + private: static const int PENALTY_N3; + private: static const int PENALTY_N4; + + + private: static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41]; + private: static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41]; + +}; + + + +/*---- Public exception class ----*/ + +/* + * Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include: + * - Decrease the error correction level if it was greater than Ecc::LOW. + * - If the encodeSegments() function was called with a maxVersion argument, then increase + * it if it was less than QrCode::MAX_VERSION. (This advice does not apply to the other + * factory functions because they search all versions up to QrCode::MAX_VERSION.) + * - Split the text data into better or optimal segments in order to reduce the number of bits required. + * - Change the text or binary data to be shorter. + * - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric). + * - Propagate the error upward to the caller/user. + */ +class data_too_long : public std::length_error { + + public: explicit data_too_long(const std::string &msg); + +}; + + + +/* + * An appendable sequence of bits (0s and 1s). Mainly used by QrSegment. + */ +class BitBuffer final : public std::vector<bool> { + + /*---- Constructor ----*/ + + // Creates an empty bit buffer (length 0). + public: BitBuffer(); + + + + /*---- Method ----*/ + + // Appends the given number of low-order bits of the given value + // to this buffer. Requires 0 <= len <= 31 and val < 2^len. + public: void appendBits(std::uint32_t val, int len); + +}; + +} diff --git a/src/ui/screen_home.cpp b/src/ui/screen_home.cpp new file mode 100644 index 0000000..5693f53 --- /dev/null +++ b/src/ui/screen_home.cpp @@ -0,0 +1,609 @@ +// Nubix — Home screen: tabs Cloud library (box-art grid) / My consoles (xHome) / Settings. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <algorithm> +#include <cmath> +#include <ctime> +#include <functional> +#include <memory> +#include <string> +#include <vector> + +#include "core/log.hpp" +#include "platform/platform.hpp" +#include "ui/app.hpp" +#include "ui/screens.hpp" +#include "ui/screens_internal.hpp" +#include "ui/ui.hpp" + +namespace xc { +namespace { + +// grid geometry (logical px) +constexpr int kGridLeft = 96; +constexpr int kGridTop = 300; +constexpr int kCols = 8; +constexpr int kGap = 30; +constexpr int kTileW = (Ui::kWidth - 2 * kGridLeft - (kCols - 1) * kGap) / kCols; // 189 +constexpr int kImageH = kTileW * 4 / 3; // poster 3:4 (252) +constexpr int kTileH = kImageH + 58; +constexpr int kRowPitch = kTileH + 30; // 340 +constexpr int kViewH = 2 * kRowPitch; // two full rows +constexpr int kGridBottom = kGridTop + kViewH; + +enum Tab { TabLibrary, TabConsoles, TabSettings, TabCount }; +enum Filter { FilterAll, FilterGamePass, FilterOwned, FilterFree, FilterCount }; + +const char* tabName(int t) { + switch (t) { + case TabLibrary: return "Cloud library"; + case TabConsoles: return "My consoles"; + default: return "Settings"; + } +} + +const char* filterName(int f) { + switch (f) { + case FilterGamePass: return "Game Pass"; + case FilterOwned: return "Owned"; + case FilterFree: return "Free to play"; + default: return "All games"; + } +} + +std::string normalizeUrl(const std::string& u) { + if (u.rfind("//", 0) == 0) return "https:" + u; + return u; +} + +struct HomeState { + // session / tokens + enum Phase { Idle, Authorizing, Ready, AuthFailed } phase = Idle; + std::string authError; + bool hasCloud = false, hasHome = false; + + // library + bool titlesLoading = false, titlesLoaded = false; + std::string titlesError; + std::vector<Title> titles; + uint64_t titlesVersion = 0; // bumped whenever `titles` is replaced + + // consoles + bool consolesLoading = false, consolesLoaded = false; + std::string consolesError; + std::vector<Console> consoles; +}; + +class HomeScreen : public Screen { +public: + void onEnter(App& app) override { + if (st_->phase == HomeState::Idle || st_->phase == HomeState::AuthFailed) authorize(app); + } + + void onNav(App& app, NavKey key) override { + if (key == NavKey::TabLeft || key == NavKey::TabRight) { + tab_ = (tab_ + (key == NavKey::TabRight ? 1 : TabCount - 1)) % TabCount; + if (tab_ == TabSettings) settings_.resetFocus(); + return; + } + if (st_->phase == HomeState::AuthFailed) { + if (key == NavKey::Accept) authorize(app); + else if (key == NavKey::Option) app.resetTo(makeLoginScreen()); + else if (key == NavKey::Back) app.push(ui::makeExitConfirm()); + return; + } + switch (tab_) { + case TabLibrary: navLibrary(app, key); break; + case TabConsoles: navConsoles(app, key); break; + case TabSettings: + if (!settings_.onNav(app, key) && key == NavKey::Back) app.push(ui::makeExitConfirm()); + break; + } + } + + void update(App& app, uint32_t dtMs) override { + (void)app; + // smooth grid scroll + const auto& list = visible(); + const int rows = static_cast<int>((list.size() + kCols - 1) / kCols); + const int row = focusTitle_ / kCols; + // minimal scrolling: keep the focused row fully visible, rows snap to the grid + const float rowTop = static_cast<float>(row * kRowPitch); + if (rowTop < targetScroll_) targetScroll_ = rowTop; + if (rowTop + kRowPitch > targetScroll_ + kViewH) targetScroll_ = rowTop + kRowPitch - kViewH; + const float maxScroll = static_cast<float>(std::max(0, rows * kRowPitch - kViewH)); + targetScroll_ = std::max(0.0f, std::min(maxScroll, targetScroll_)); + const float k = std::min(1.0f, dtMs * 0.014f); + scroll_ += (targetScroll_ - scroll_) * k; + if (std::fabs(targetScroll_ - scroll_) < 0.5f) scroll_ = targetScroll_; + } + + void render(App& app, Ui& ui) override { + ui::background(ui); + renderHeader(app, ui); + const uint64_t now = platform::monotonicMs(); + + if (st_->phase == HomeState::Authorizing || st_->phase == HomeState::Idle) { + ui.spinner(Ui::kWidth / 2, 500, 52, now); + ui.text("Connecting to Xbox\xE2\x80\xA6", Ui::kWidth / 2, 590, 32, colors::Text, Align::Center); + ui::hintBar(app, ui, {{PadIcon::Circle, "Exit"}}); + return; + } + if (st_->phase == HomeState::AuthFailed) { + ui::errorBadge(ui, Ui::kWidth / 2, 380, 48); + ui.text("Couldn't connect to Xbox", Ui::kWidth / 2, 460, 42, colors::Text, Align::Center, true); + ui.textWrapped(st_->authError, (Ui::kWidth - 1100) / 2, 532, 1100, 28, colors::TextDim); + ui::hintBar(app, ui, + {{PadIcon::Cross, "Retry"}, {PadIcon::Triangle, "Sign in again"}, {PadIcon::Circle, "Exit"}}); + return; + } + + switch (tab_) { + case TabLibrary: renderLibrary(app, ui, now); break; + case TabConsoles: renderConsoles(app, ui, now); break; + case TabSettings: + settings_.render(app, ui, 200); + ui::hintBar(app, ui, {{PadIcon::L1, ""}, {PadIcon::R1, "Switch tab"}, {PadIcon::Cross, "Change"}, + {PadIcon::Circle, "Exit"}}); + break; + } + } + +private: + // ---- data loading ----------------------------------------------------------------------- + + void authorize(App& app) { + st_->phase = HomeState::Authorizing; + std::weak_ptr<HomeState> w = st_; + App* a = &app; + app.runAsync([a, w] { + std::string err; + const bool ok = a->auth().ensureFresh(err); + const bool loggedIn = a->auth().isLoggedIn(); + const Tokens t = a->config().tokens(); + const bool hasCloud = !t.xcloudGs.empty() || !t.xcloudF2pGs.empty(); + const bool hasHome = !t.xhomeGs.empty(); + a->postToMain([a, w, ok, loggedIn, hasCloud, hasHome, err] { + auto s = w.lock(); + if (!s) return; + if (!ok && !loggedIn) { + a->toast("Your sign-in expired. Please sign in again."); + a->resetTo(makeLoginScreen()); + return; + } + if (!ok) { + s->phase = HomeState::AuthFailed; + s->authError = err.empty() ? std::string("Token refresh failed.") : err; + if (!platform::networkAvailable()) s->authError += "\nNo network connection detected."; + return; + } + s->phase = HomeState::Ready; + s->hasCloud = hasCloud; + s->hasHome = hasHome; + }); + if (ok) { + loadTitles(a, w); + loadConsoles(a, w); + } + }); + } + + // Worker thread: the server refused a gsToken the stored expiry still called valid. Mark it + // stale, re-authorize and run `retry` once. False (err set) if re-authorizing failed. + static bool reauthorizeAndRetry(App* a, std::string& err, const std::function<bool()>& retry) { + a->auth().invalidateStreaming(); + std::string aerr; + if (!a->auth().ensureFresh(aerr)) { + err = "Sign-in failed: " + aerr; + return false; + } + err.clear(); + return retry(); + } + + // Main thread: the stored login died on a worker (refresh token rejected). + static void goToLogin(App* a) { + a->toast("Your sign-in expired. Please sign in again."); + a->resetTo(makeLoginScreen()); + } + + // Runs on the worker (called from a worker job) or schedules itself. + static void loadTitles(App* a, std::weak_ptr<HomeState> w, bool force = false) { + a->postToMain([w] { + if (auto s = w.lock()) { + s->titlesLoading = true; + s->titlesError.clear(); + } + }); + a->runAsync([a, w, force] { + std::vector<Title> list; + std::string err; + bool ok = true; + auto fetch = [&](bool* authRejected) { + const Tokens t = a->config().tokens(); + if (t.xcloudGs.empty() && t.xcloudF2pGs.empty()) return true; + return a->catalog().fetchCloudTitles(list, err, authRejected, force || authRejected == nullptr); + }; + bool authRejected = false; + ok = fetch(&authRejected); + if (authRejected) ok = reauthorizeAndRetry(a, err, [&] { return fetch(nullptr); }); + const bool signedOut = !a->auth().isLoggedIn(); + a->postToMain([a, w, ok, signedOut, list = std::move(list), err]() mutable { + auto s = w.lock(); + if (!s) return; + if (signedOut) { + goToLogin(a); + return; + } + s->titlesLoading = false; + s->titlesLoaded = true; + if (ok) { + s->titles = std::move(list); + ++s->titlesVersion; + } else { + s->titlesError = err.empty() ? std::string("Could not load the cloud library.") : err; + } + }); + }); + } + + static void loadConsoles(App* a, std::weak_ptr<HomeState> w) { + a->postToMain([w] { + if (auto s = w.lock()) { + s->consolesLoading = true; + s->consolesError.clear(); + } + }); + a->runAsync([a, w] { + std::vector<Console> list; + std::string err; + auto fetch = [&](bool* authRejected) { + if (a->config().tokens().xhomeGs.empty()) return true; + return a->catalog().fetchConsoles(list, err, authRejected); + }; + bool authRejected = false; + bool ok = fetch(&authRejected); + if (authRejected) ok = reauthorizeAndRetry(a, err, [&] { return fetch(nullptr); }); + const bool signedOut = !a->auth().isLoggedIn(); + a->postToMain([a, w, ok, signedOut, list = std::move(list), err]() mutable { + auto s = w.lock(); + if (!s) return; + if (signedOut) { + goToLogin(a); + return; + } + s->consolesLoading = false; + s->consolesLoaded = true; + if (ok) { + s->consoles = std::move(list); + } else { + s->consolesError = err.empty() ? std::string("Could not load your consoles.") : err; + } + }); + }); + } + + void refresh(App& app) { + std::weak_ptr<HomeState> w = st_; + App* a = &app; + if (tab_ == TabLibrary && !st_->titlesLoading) { + st_->titlesLoading = true; + app.runAsync([a, w] { + std::string err; + a->auth().ensureFresh(err); + loadTitles(a, w, /*force=*/true); + }); + } else if (tab_ == TabConsoles && !st_->consolesLoading) { + st_->consolesLoading = true; + app.runAsync([a, w] { + std::string err; + a->auth().ensureFresh(err); + loadConsoles(a, w); + }); + } + } + + // ---- library ------------------------------------------------------------------------------ + + const std::vector<const Title*>& visible() { + // rebuild the filtered view when the source or filter changed + if (viewVersion_ != st_->titlesVersion || viewFilter_ != filter_) { + view_.clear(); + for (const auto& t : st_->titles) { + if (filter_ == FilterGamePass && !t.gamePass) continue; + // Owned: playable through a purchase ("stream your own game"), not via the plan or ads. + if (filter_ == FilterOwned && !(t.hasEntitlement && !t.gamePass && !t.f2pOnly)) continue; + if (filter_ == FilterFree && !t.f2p) continue; + view_.push_back(&t); + } + viewVersion_ = st_->titlesVersion; + viewFilter_ = filter_; + if (focusTitle_ >= static_cast<int>(view_.size())) focusTitle_ = std::max(0, static_cast<int>(view_.size()) - 1); + } + return view_; + } + + void navLibrary(App& app, NavKey key) { + const auto& list = visible(); + const int n = static_cast<int>(list.size()); + switch (key) { + case NavKey::Left: + if (n && focusTitle_ % kCols > 0) --focusTitle_; + break; + case NavKey::Right: + if (n && focusTitle_ % kCols < kCols - 1 && focusTitle_ + 1 < n) ++focusTitle_; + break; + case NavKey::Up: + if (focusTitle_ >= kCols) focusTitle_ -= kCols; + break; + case NavKey::Down: + if (focusTitle_ + kCols < n) focusTitle_ += kCols; + else if (focusTitle_ / kCols < (n - 1) / kCols) focusTitle_ = n - 1; // partial last row + break; + case NavKey::Accept: + if (n > 0) { + const Title& t = *list[focusTitle_]; + app.push(makeConnectingScreen(SessionKind::Cloud, t.titleId, t.name.empty() ? t.titleId : t.name, t.f2pOnly)); + } else if (!st_->titlesError.empty()) { + refresh(app); + } + break; + case NavKey::Option: + filter_ = (filter_ + 1) % FilterCount; + focusTitle_ = 0; + scroll_ = targetScroll_ = 0; + break; + case NavKey::Back: app.push(ui::makeExitConfirm()); break; + default: break; + } + } + + void renderLibrary(App& app, Ui& ui, uint64_t now) { + const HomeState& s = *st_; + const auto& list = visible(); + // sub header: filter chips + count + int x = kGridLeft; + for (int f = 0; f < FilterCount; ++f) { + const bool on = f == filter_; + const int w = ui.textWidth(filterName(f), 24, on) + 44; + ui.rect(x, 150, w, 48, on ? colors::Accent : colors::Panel, true, 24); + ui.text(filterName(f), x + w / 2, 150 + (48 - ui.lineHeight(24, on)) / 2, 24, on ? colors::White : colors::TextDim, + Align::Center, on); + x += w + 14; + } + if (s.titlesLoaded && s.titlesError.empty()) { + const std::string count = std::to_string(list.size()) + (list.size() == 1 ? " game" : " games"); + ui.text(count, x + 16, 150 + (48 - ui.lineHeight(24)) / 2, 24, colors::TextDim); + } + if (s.titlesLoading && !s.titles.empty()) ui.spinner(Ui::kWidth - kGridLeft - 24, 174, 18, now); + + // focused title details + if (!list.empty()) { + const Title& t = *list[focusTitle_]; + int w = ui.textEllipsized(t.name, kGridLeft, 220, 1300, 34, colors::Text, Align::Left, true); + if (!t.publisher.empty()) + ui.textEllipsized(t.publisher, kGridLeft + w + 20, 228, 1700 - w - 20, 24, colors::TextDim); + } + + if (!s.hasCloud && s.titlesLoaded) { + emptyState(ui, "No cloud gaming access", + "This account has no Xbox Cloud Gaming access (a Game Pass plan with cloud gaming, or the " + "free-to-play offering in your region). You can still stream from your own console under My consoles."); + hintsLibrary(app, ui, false); + return; + } + if (!s.titlesLoaded || (s.titlesLoading && s.titles.empty())) { + ui.spinner(Ui::kWidth / 2, 560, 48, now); + ui.text("Loading your cloud library\xE2\x80\xA6", Ui::kWidth / 2, 640, 30, colors::TextDim, Align::Center); + hintsLibrary(app, ui, false); + return; + } + if (!s.titlesError.empty()) { + emptyState(ui, "Couldn't load the library", s.titlesError); + ui::hintBar(app, ui, {{PadIcon::Cross, "Retry"}, {PadIcon::R1, "Next tab"}, {PadIcon::Circle, "Exit"}}); + return; + } + if (list.empty()) { + emptyState(ui, "Nothing here", filter_ == FilterAll ? "No cloud titles are available for this account." + : "No titles match this filter. Press Y / \xE2\x96\xB3 to change it."); + hintsLibrary(app, ui, false); + return; + } + + // grid + ui.setClip(0, kGridTop - 12, Ui::kWidth, kViewH + 8); + const int scroll = static_cast<int>(std::lround(scroll_)); + const int firstRow = std::max(0, scroll / kRowPitch); + const int lastRow = (scroll + kViewH) / kRowPitch + 1; // +1 row prefetch + const int n = static_cast<int>(list.size()); + for (int row = firstRow; row <= lastRow; ++row) { + for (int col = 0; col < kCols; ++col) { + const int i = row * kCols + col; + if (i >= n) break; + const Title& t = *list[i]; + const int tx = kGridLeft + col * (kTileW + kGap); + const int ty = kGridTop + row * kRowPitch - scroll; + SDL_Texture* img = ui.remoteImage(normalizeUrl(t.imageUrl), kTileW, kImageH); + if (ty >= kGridBottom) continue; // prefetch only + std::string badge; + if (t.gamePass) badge = "Game Pass"; + else if (t.hasEntitlement && !t.f2pOnly) badge = "Owned"; + else if (t.f2p) badge = "Free"; + ui.tile(img, t.name, badge, tx, ty, kTileW, kTileH, i == focusTitle_); + } + } + ui.clearClip(); + // scroll indicator + const int rows = (n + kCols - 1) / kCols; + const int totalH = rows * kRowPitch; + const int viewH = kViewH - 30; + if (totalH > kViewH) { + const int barH = std::max(60, viewH * kViewH / totalH); + const int barY = kGridTop + static_cast<int>((viewH - barH) * (scroll_ / std::max(1, totalH - kViewH))); + ui.rect(Ui::kWidth - 40, kGridTop, 6, viewH, withAlpha(colors::Panel, 200), true, 3); + ui.rect(Ui::kWidth - 40, std::min(kGridTop + viewH - barH, barY), 6, barH, colors::AccentBright, true, 3); + } + hintsLibrary(app, ui, true); + } + + void hintsLibrary(App& app, Ui& ui, bool canPlay) { + std::vector<ui::Hint> h; + h.push_back({PadIcon::L1, ""}); + h.push_back({PadIcon::R1, "Switch tab"}); + h.push_back({PadIcon::Triangle, "Filter"}); + if (canPlay) h.push_back({PadIcon::Cross, "Play"}); + h.push_back({PadIcon::Circle, "Exit"}); + ui::hintBar(app, ui, h); + } + + void emptyState(Ui& ui, const std::string& title, const std::string& msg) { + ui.text(title, Ui::kWidth / 2, 470, 40, colors::Text, Align::Center, true); + const int w = 1100; + ui.textWrapped(msg, (Ui::kWidth - w) / 2, 540, w, 28, colors::TextDim); + } + + // ---- consoles ----------------------------------------------------------------------------- + + void navConsoles(App& app, NavKey key) { + const int n = static_cast<int>(st_->consoles.size()); + switch (key) { + case NavKey::Left: + if (focusConsole_ > 0) --focusConsole_; + break; + case NavKey::Right: + if (focusConsole_ + 1 < n) ++focusConsole_; + break; + case NavKey::Up: + if (focusConsole_ >= 3) focusConsole_ -= 3; + break; + case NavKey::Down: + if (focusConsole_ + 3 < n) focusConsole_ += 3; + break; + case NavKey::Accept: + if (n > 0) { + const Console& c = st_->consoles[focusConsole_]; + app.push(makeConnectingScreen(SessionKind::Home, c.serverId, c.name.empty() ? "your console" : c.name)); + } else { + refresh(app); + } + break; + case NavKey::Option: refresh(app); break; + case NavKey::Back: app.push(ui::makeExitConfirm()); break; + default: break; + } + } + + void renderConsoles(App& app, Ui& ui, uint64_t now) { + const HomeState& s = *st_; + ui.text("Stream from your own Xbox (Remote Play)", kGridLeft, 160, 28, colors::TextDim); + if (s.consolesLoading && !s.consoles.empty()) ui.spinner(Ui::kWidth - kGridLeft - 24, 176, 18, now); + std::vector<ui::Hint> hints = {{PadIcon::L1, ""}, {PadIcon::R1, "Switch tab"}, {PadIcon::Triangle, "Refresh"}}; + + if (!s.hasHome && s.consolesLoaded) { + emptyState(ui, "Remote Play unavailable", + "This account did not receive an Xbox Remote Play token. Sign in with the account that owns " + "the console."); + } else if (!s.consolesLoaded || (s.consolesLoading && s.consoles.empty())) { + ui.spinner(Ui::kWidth / 2, 520, 48, now); + ui.text("Looking for your consoles\xE2\x80\xA6", Ui::kWidth / 2, 600, 30, colors::TextDim, Align::Center); + } else if (!s.consolesError.empty()) { + emptyState(ui, "Couldn't load your consoles", s.consolesError); + hints.push_back({PadIcon::Cross, "Retry"}); + } else if (s.consoles.empty()) { + emptyState(ui, "No consoles found", + "Turn on remote features on your Xbox: Settings \xE2\x80\xBA Devices & connections \xE2\x80\xBA " + "Remote features, and use the \"Sleep\" power mode."); + } else { + const int cw = 520, ch = 300, gap = 40; + for (size_t i = 0; i < s.consoles.size(); ++i) { + const Console& c = s.consoles[i]; + const int col = static_cast<int>(i % 3), row = static_cast<int>(i / 3); + const int x = kGridLeft + col * (cw + gap), y = 240 + row * (ch + gap); + if (y > 1000) break; + const bool f = static_cast<int>(i) == focusConsole_; + if (f) ui.outline(x - 6, y - 6, cw + 12, ch + 12, colors::AccentBright, 4, 26); + ui.rect(x, y, cw, ch, f ? colors::PanelHi : colors::Panel, true, 20); + // console glyph: tower with a power light + const int gx = x + 48, gy = y + 60; + ui.rect(gx, gy, 96, 150, Color{20, 22, 21, 255}, true, 12); + ui.outline(gx, gy, 96, 150, Color{70, 78, 74, 255}, 2, 12); + ui.circle(gx + 48, gy + 26, 14, Color{40, 44, 42, 255}); + const bool on = c.powerState == "On"; + const bool standby = c.powerState == "ConnectedStandby"; + const Color dot = on ? colors::AccentBright : standby ? colors::Warning : colors::TextDim; + ui.circle(gx + 18, gy + 130, 6, dot); + + const int tx = x + 180; + ui.textEllipsized(c.name.empty() ? "Xbox" : c.name, tx, y + 64, cw - 200, 34, colors::Text, Align::Left, + true); + ui.text(ui::prettyConsoleType(c.consoleType), tx, y + 116, 26, colors::TextDim); + const std::string ps = on ? "On" : standby ? "Standby (instant-on)" : (c.powerState.empty() ? "Unknown" : c.powerState); + ui.circle(tx + 8, y + 186, 7, dot); + ui.text(ps, tx + 26, y + 186 - ui.lineHeight(24) / 2, 24, dot); + if (f) ui.text("Press to stream", tx, y + 230, 22, withAlpha(colors::TextDim, 200)); + } + hints.push_back({PadIcon::Cross, "Stream"}); + } + hints.push_back({PadIcon::Circle, "Exit"}); + ui::hintBar(app, ui, hints); + } + + // ---- header ------------------------------------------------------------------------------- + + void renderHeader(App& app, Ui& ui) { + ui::logo(ui, 96, 40, true); + // tabs centred + const int ts = 30; + int total = 0; + for (int t = 0; t < TabCount; ++t) total += ui.textWidth(tabName(t), ts, true) + (t ? 64 : 0); + int x = (Ui::kWidth - total) / 2; + const bool kb = app.gamepad().lastInputKeyboard(); + if (kb) ui.keyCap(ui::keyFor(PadIcon::L1), x - 76, 66, 40); + else ui.padIcon(PadIcon::L1, x - 76, 66, 40); + for (int t = 0; t < TabCount; ++t) { + const bool on = t == tab_; + const int w = ui.textWidth(tabName(t), ts, true); + ui.text(tabName(t), x + w / 2, 66 - ui.lineHeight(ts, true) / 2, ts, on ? colors::White : colors::TextDim, + Align::Center, true); + if (on) ui.rect(x, 96, w, 5, colors::AccentBright, true, 2); + x += w + 64; + } + x -= 64; + if (kb) ui.keyCap(ui::keyFor(PadIcon::R1), x + 24, 66, 40); + else ui.padIcon(PadIcon::R1, x + 24, 66, 40); + + // gamertag + clock + char clock[16] = {0}; + std::time_t tt = std::time(nullptr); + std::tm lt{}; + if (localtime_r(&tt, <)) std::strftime(clock, sizeof(clock), "%H:%M", <); + int rx = Ui::kWidth - 96; + rx -= ui.text(clock, rx, 66 - ui.lineHeight(28) / 2, 28, colors::Text, Align::Right); + const std::string tag = app.config().gamertag(); + if (!tag.empty()) { + rx -= 32; + const int tw = ui.textWidth(tag, 26, true); + ui.text(tag, rx, 66 - ui.lineHeight(26, true) / 2, 26, colors::Text, Align::Right, true); + const int ax = rx - tw - 38; + ui.circle(ax, 66, 22, colors::Accent); + ui.text(tag.substr(0, 1), ax, 66 - ui.lineHeight(24, true) / 2, 24, colors::White, Align::Center, true); + } + } + + std::shared_ptr<HomeState> st_ = std::make_shared<HomeState>(); + ui::SettingsPanel settings_; + int tab_ = TabLibrary; + int filter_ = FilterAll; + int focusTitle_ = 0, focusConsole_ = 0; + float scroll_ = 0, targetScroll_ = 0; + + std::vector<const Title*> view_; + uint64_t viewVersion_ = static_cast<uint64_t>(-1); + int viewFilter_ = -1; +}; + +} // namespace + +std::unique_ptr<Screen> makeHomeScreen() { return std::make_unique<HomeScreen>(); } + +} // namespace xc diff --git a/src/ui/screen_login.cpp b/src/ui/screen_login.cpp new file mode 100644 index 0000000..dfc80a0 --- /dev/null +++ b/src/ui/screen_login.cpp @@ -0,0 +1,261 @@ +// Nubix — Login screen: Microsoft device-code flow with QR code and countdown. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <algorithm> +#include <memory> + +#include "core/log.hpp" +#include "platform/platform.hpp" +#include "ui/app.hpp" +#include "ui/screens.hpp" +#include "ui/screens_internal.hpp" +#include "ui/ui.hpp" + +namespace xc { +namespace { + +constexpr int kMaxTransientPollErrors = 5; + +struct LoginState { + enum Phase { Idle, Requesting, Code, Authorizing, Error } phase = Idle; + DeviceCode dc; + uint64_t expiresAt = 0, nextPoll = 0; + bool polling = false; + int transientErrors = 0; + std::string error; +}; + +std::string displayUri(const std::string& uri) { + std::string s = uri; + for (const char* p : {"https://", "http://"}) + if (s.rfind(p, 0) == 0) s = s.substr(std::char_traits<char>::length(p)); + if (s.rfind("www.", 0) == 0) s = s.substr(4); + while (!s.empty() && s.back() == '/') s.pop_back(); + return s; +} + +class LoginScreen : public Screen { +public: + void onEnter(App& app) override { + if (st_->phase == LoginState::Idle) requestCode(app); + } + + void onNav(App& app, NavKey key) override { + switch (key) { + case NavKey::Accept: + if (st_->phase == LoginState::Error || st_->phase == LoginState::Code) requestCode(app); + break; + case NavKey::Back: app.push(ui::makeExitConfirm()); break; + default: break; + } + } + + void update(App& app, uint32_t dtMs) override { + (void)dtMs; + LoginState& s = *st_; + if (s.phase != LoginState::Code) return; + const uint64_t now = platform::monotonicMs(); + if (now >= s.expiresAt && !s.polling) { + XC_LOGI("login: device code expired, requesting a new one"); + requestCode(app); + return; + } + if (!s.polling && now >= s.nextPoll) poll(app); + } + + void render(App& app, Ui& ui) override { + const LoginState& s = *st_; + const uint64_t now = platform::monotonicMs(); + ui::background(ui); + ui::logo(ui, 96, 56); + // Shown on every sign-in screen: this is a third-party client. + ui.text("Unofficial client \xC2\xB7 not affiliated with Microsoft or Sony", 96, + 1022 - ui.lineHeight(20) / 2, 20, withAlpha(colors::TextDim, 200)); // hint bar row + + switch (s.phase) { + case LoginState::Idle: + case LoginState::Requesting: + ui.spinner(Ui::kWidth / 2, 470, 56, now); + ui.text("Contacting Microsoft\xE2\x80\xA6", Ui::kWidth / 2, 570, 34, colors::Text, Align::Center); + ui::hintBar(app, ui, {{PadIcon::Circle, "Exit"}}); + return; + case LoginState::Authorizing: + ui.spinner(Ui::kWidth / 2, 470, 56, now); + ui.text("Signing in to Xbox\xE2\x80\xA6", Ui::kWidth / 2, 570, 34, colors::Text, Align::Center); + ui.text("Getting your cloud gaming and remote play access", Ui::kWidth / 2, 626, 26, colors::TextDim, + Align::Center); + ui::hintBar(app, ui, {{PadIcon::Circle, "Exit"}}); + return; + case LoginState::Error: { + ui::errorBadge(ui, Ui::kWidth / 2, 380, 52); + ui.text("Sign-in failed", Ui::kWidth / 2, 470, 44, colors::Text, Align::Center, true); + const int w = 1100; + ui.textWrapped(s.error, (Ui::kWidth - w) / 2, 548, w, 28, colors::TextDim); + ui::hintBar(app, ui, {{PadIcon::Cross, "Try again"}, {PadIcon::Circle, "Exit"}}); + return; + } + case LoginState::Code: break; + } + + // ---- code view ---- + const int lx = 160; + ui.text("Sign in", lx, 190, 64, colors::Text, Align::Left, true); + ui.text("Use the Microsoft account with your Game Pass plan or your Xbox consoles.", lx, 278, 26, + colors::TextDim); + + auto step = [&](int n, int y, const std::string& caption) { + ui.circle(lx + 24, y + 24, 24, colors::Accent); + ui.text(std::to_string(n), lx + 24, y + 24 - ui.lineHeight(26, true) / 2, 26, colors::White, Align::Center, + true); + ui.text(caption, lx + 72, y + 24 - ui.lineHeight(28) / 2, 28, colors::TextDim); + }; + step(1, 370, "On your phone or computer, go to"); + ui.text(displayUri(s.dc.verificationUri), lx + 72, 420, 50, colors::White, Align::Left, true); + + step(2, 530, "Enter this code"); + // code box with letter-spaced characters + const int cs = 84, spacing = 16; + int codeW = 0; + for (char c : s.dc.userCode) codeW += ui.textWidth(std::string(1, c), cs, true) + spacing; + codeW = std::max(0, codeW - spacing); + const int boxX = lx + 72, boxY = 586, boxW = std::max(520, codeW + 96), boxH = 148; + ui.rect(boxX, boxY, boxW, boxH, colors::Panel, true, 20); + ui.outline(boxX, boxY, boxW, boxH, withAlpha(colors::AccentBright, 140), 3, 20); + int cx = boxX + (boxW - codeW) / 2; + const int cy = boxY + (boxH - ui.lineHeight(cs, true)) / 2; + for (char c : s.dc.userCode) { + const std::string ch(1, c); + ui.text(ch, cx, cy, cs, colors::White, Align::Left, true); + cx += ui.textWidth(ch, cs, true) + spacing; + } + + // waiting + countdown + ui.spinner(lx + 96, 820, 24, now); + ui.text("Waiting for you to sign in\xE2\x80\xA6", lx + 140, 820 - ui.lineHeight(28) / 2, 28, colors::Text); + const int64_t left = s.expiresAt > now ? static_cast<int64_t>((s.expiresAt - now) / 1000) : 0; + ui.text("Code expires in " + ui::formatDuration(left) + " \xC2\xB7 a new one is created automatically", + lx + 72, 868, 24, colors::TextDim); + + // QR + const int qs = 420, qx = 1260, qy = 300; + ui.qr(s.dc.qrUrl.empty() ? s.dc.verificationUri : s.dc.qrUrl, qx, qy, qs); + ui.text("Scan to open the sign-in page", qx + qs / 2, qy + qs + 40, 26, colors::TextDim, Align::Center); + ui.text("(the code is filled in for you)", qx + qs / 2, qy + qs + 78, 22, withAlpha(colors::TextDim, 190), + Align::Center); + + ui::hintBar(app, ui, {{PadIcon::Cross, "New code"}, {PadIcon::Circle, "Exit"}}); + } + +private: + void requestCode(App& app) { + LoginState& s = *st_; + s.phase = LoginState::Requesting; + s.error.clear(); + s.transientErrors = 0; + const uint64_t gen = ++generation_; + std::weak_ptr<LoginState> w = st_; + App* a = &app; + app.runAsync([a, w, gen, this] { + DeviceCode dc; + std::string err; + const bool ok = a->auth().requestDeviceCode(dc, err); + a->postToMain([a, w, gen, ok, dc, err, this] { + auto s = w.lock(); + if (!s || gen != generation_) return; // screen gone or superseded + if (ok) { + const uint64_t now = platform::monotonicMs(); + s->dc = dc; + s->phase = LoginState::Code; + s->expiresAt = now + static_cast<uint64_t>(std::max(30, dc.expiresIn)) * 1000; + s->nextPoll = now + static_cast<uint64_t>(std::max(1, dc.interval)) * 1000; + s->polling = false; + XC_LOGI("login: user code %s at %s", dc.userCode.c_str(), dc.verificationUri.c_str()); + } else { + s->phase = LoginState::Error; + s->error = "Could not get a sign-in code: " + (err.empty() ? std::string("unknown error") : err); + if (!platform::networkAvailable()) s->error += "\nThe console does not seem to be connected to a network."; + (void)a; + } + }); + }); + } + + void poll(App& app) { + LoginState& s = *st_; + s.polling = true; + const uint64_t gen = generation_; + const DeviceCode dc = s.dc; + std::weak_ptr<LoginState> w = st_; + App* a = &app; + app.runAsync([a, w, gen, dc, this] { + std::string err; + const PollResult r = a->auth().pollToken(dc, err); + a->postToMain([a, w, gen, r, err, this] { + auto s = w.lock(); + if (!s || gen != generation_) return; + s->polling = false; + const uint64_t now = platform::monotonicMs(); + switch (r) { + case PollResult::Pending: + s->transientErrors = 0; + if (err == "slow_down") s->dc.interval += 5; // RFC 8628 §3.5 + s->nextPoll = now + static_cast<uint64_t>(std::max(1, s->dc.interval)) * 1000; + break; + case PollResult::Success: authorize(*a); break; + case PollResult::Expired: requestCode(*a); break; + case PollResult::Denied: + s->phase = LoginState::Error; + s->error = "The sign-in request was declined."; + break; + case PollResult::Error: + if (++s->transientErrors <= kMaxTransientPollErrors) { + XC_LOGW("login: poll error (%d): %s", s->transientErrors, err.c_str()); + s->nextPoll = now + static_cast<uint64_t>(std::max(2, s->dc.interval * 2)) * 1000; + } else { + s->phase = LoginState::Error; + s->error = err.empty() ? std::string("Polling the sign-in status failed.") : err; + } + break; + } + }); + }); + } + + void authorize(App& app) { + st_->phase = LoginState::Authorizing; + const uint64_t gen = generation_; + std::weak_ptr<LoginState> w = st_; + App* a = &app; + app.runAsync([a, w, gen, this] { + std::string err; + const bool ok = a->auth().authorizeStreaming(err); + const std::string tag = ok ? a->config().gamertag() : std::string(); + a->postToMain([a, w, gen, ok, err, tag, this] { + auto s = w.lock(); + if (!s || gen != generation_) return; + if (ok) { + const std::string msg = tag.empty() ? std::string("Signed in") : "Signed in as " + tag; + a->toast(msg); + platform::notify("Nubix: " + msg); + a->replace(makeHomeScreen()); + } else { + s->phase = LoginState::Error; + s->error = "Your Microsoft account is signed in, but Xbox streaming access could not be " + "obtained: " + + (err.empty() ? std::string("unknown error") : err); + } + }); + }); + } + + std::shared_ptr<LoginState> st_ = std::make_shared<LoginState>(); + // Bumped on every new code request; stale async results are ignored. Only touched on the + // main thread (inside postToMain callbacks after the weak_ptr check, the screen is alive). + uint64_t generation_ = 0; +}; + +} // namespace + +std::unique_ptr<Screen> makeLoginScreen() { return std::make_unique<LoginScreen>(); } + +} // namespace xc diff --git a/src/ui/screen_stream.cpp b/src/ui/screen_stream.cpp new file mode 100644 index 0000000..e9a0999 --- /dev/null +++ b/src/ui/screen_stream.cpp @@ -0,0 +1,560 @@ +// Nubix — Connecting screen (session start / queue / provisioning) and Stream screen +// (full-screen video, input forwarding, rumble, hold-PS overlay menu, statistics). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <SDL.h> + +#include <algorithm> +#include <cmath> +#include <cstdio> +#include <memory> + +extern "C" { +#include <libavutil/frame.h> +#include <libavutil/pixfmt.h> +} + +#include "core/log.hpp" +#include "platform/platform.hpp" +#include "ui/app.hpp" +#include "ui/screens.hpp" +#include "ui/screens_internal.hpp" +#include "ui/ui.hpp" + +namespace xc { +namespace { + +constexpr uint32_t kMenuHoldMs = 1000; +constexpr uint64_t kNexusPulseMs = 150; +constexpr uint64_t kEndedReturnMs = 6000; +constexpr uint64_t kStartHintMs = 7000; +constexpr uint64_t kStatsRefreshMs = 500; +constexpr uint16_t kTriggerReleased = 4000; // a suppressed trigger counts as released below this + +// The account was signed out while a session was starting/running (the refresh token was +// rejected on the streamer thread): go to the Login screen instead of a stale Home. +bool leaveIfSignedOut(App& app) { + if (app.auth().isLoggedIn()) return false; + app.toast("Your sign-in expired. Please sign in again."); + app.resetTo(makeLoginScreen()); + return true; +} + +bool isTerminal(StreamState s) { return s == StreamState::Ended || s == StreamState::Failed; } + +// ============================================================================================ +// Connecting +// ============================================================================================ + +class ConnectingScreen : public Screen { +public: + ConnectingScreen(SessionKind kind, std::string id, std::string name, bool f2pOnly) + : kind_(kind), id_(std::move(id)), name_(std::move(name)), f2pOnly_(f2pOnly) {} + + void onEnter(App& app) override { + if (!issued_) begin(app); + } + + void onNav(App& app, NavKey key) override { + if (key == NavKey::Back) { + cancel(app); + return; + } + if (key == NavKey::Accept && failed_) begin(app); + } + + void update(App& app, uint32_t dtMs) override { + (void)dtMs; + if (!started_ || failed_) return; + Streamer& s = app.streamer(); + const StreamState st = s.state(); + lastState_ = st; + if (st == StreamState::Streaming) { + XC_LOGI("connecting: stream is up after %llu ms", + static_cast<unsigned long long>(platform::monotonicMs() - startedAt_)); + app.replace(ui::makeStreamScreenNamed(name_)); + return; + } + if (isTerminal(st)) { + failed_ = true; + error_ = s.statusText(); + if (error_.empty()) error_ = st == StreamState::Ended ? "The session ended." : "The session could not be started."; + XC_LOGW("connecting: %s: %s", streamStateName(st), error_.c_str()); + app.streamer().stopAsync(); // release resources in the background + leaveIfSignedOut(app); + } + } + + void render(App& app, Ui& ui) override { + ui::background(ui); + ui::logo(ui, 96, 56); + const uint64_t now = platform::monotonicMs(); + const int cx = Ui::kWidth / 2; + + if (failed_) { + ui::errorBadge(ui, cx, 360, 52); + ui.text("Couldn't start " + name_, cx, 450, 44, colors::Text, Align::Center, true); + const int w = 1100; + ui.textWrapped(error_, (Ui::kWidth - w) / 2, 528, w, 28, colors::TextDim); + ui::hintBar(app, ui, {{PadIcon::Cross, "Try again"}, {PadIcon::Circle, "Back"}}); + return; + } + + const StreamState st = started_ ? lastState_ : StreamState::Starting; + ui.spinner(cx, 330, 58, now); + ui.textEllipsized((kind_ == SessionKind::Home ? "Connecting to " : "Starting ") + name_, cx, 430, 1500, 50, + colors::Text, Align::Center, true); + std::string status = started_ ? app.streamer().statusText() : std::string("Preparing\xE2\x80\xA6"); + if (status.empty()) status = streamStateName(st); + ui.textEllipsized(status, cx, 506, 1500, 28, colors::TextDim, Align::Center); + + if (st == StreamState::Queued) { + const int pos = app.streamer().queuePosition(); + if (pos > 0) { + ui.rect(cx - 260, 570, 520, 120, colors::Panel, true, 20); + ui.text("Position in queue", cx, 586, 24, colors::TextDim, Align::Center); + ui.text(std::to_string(pos), cx, 620, 50, colors::AccentBright, Align::Center, true); + } + } + + // progress stepper + struct Step { + const char* label; + StreamState state; + }; + const Step steps[] = {{"Session", StreamState::Starting}, + {kind_ == SessionKind::Home ? "Console" : "Queue", StreamState::Queued}, + {kind_ == SessionKind::Home ? "Waking up" : "Provisioning", StreamState::Provisioning}, + {"Connecting", StreamState::Connecting}}; + const int n = 4, sw = 300, sx = cx - (n - 1) * sw / 2, sy = 790; + int cur = 0; + for (int i = 0; i < n; ++i) + if (static_cast<int>(st) >= static_cast<int>(steps[i].state)) cur = i; + for (int i = 0; i < n - 1; ++i) + ui.rect(sx + i * sw + 16, sy - 2, sw - 32, 4, i < cur ? colors::AccentBright : colors::PanelHi, true, 2); + for (int i = 0; i < n; ++i) { + const int x = sx + i * sw; + if (i < cur) { + ui.circle(x, sy, 12, colors::AccentBright); + } else if (i == cur) { + const double pulse = 0.5 + 0.5 * std::sin(now / 180.0); + ui.circle(x, sy, 12 + static_cast<int>(6 * pulse), withAlpha(colors::AccentBright, 70)); + ui.circle(x, sy, 12, colors::AccentBright); + } else { + ui.circle(x, sy, 10, colors::PanelHi); + } + ui.text(steps[i].label, x, sy + 28, 22, i <= cur ? colors::Text : colors::TextDim, Align::Center, i == cur); + } + + const int64_t elapsed = started_ ? static_cast<int64_t>((now - startedAt_) / 1000) : 0; + ui.text("Elapsed " + ui::formatDuration(elapsed), cx, 900, 24, withAlpha(colors::TextDim, 200), Align::Center); + ui::hintBar(app, ui, {{PadIcon::Circle, "Cancel"}}); + } + +private: + void begin(App& app) { + issued_ = true; + started_ = false; + failed_ = false; + error_.clear(); + lastState_ = StreamState::Starting; + startedAt_ = platform::monotonicMs(); + XC_LOGI("connecting: %s session for %s", kind_ == SessionKind::Home ? "home" : "cloud", id_.c_str()); + // Non-blocking: the Streamer releases a session that is still winding down on its own + // lifecycle thread before this one starts. + if (app.streamer().start(kind_, id_, f2pOnly_)) { + started_ = true; + startedAt_ = platform::monotonicMs(); + } else { + failed_ = true; + error_ = "A previous streaming session is still active. Please try again."; + } + } + + void cancel(App& app) { + if (started_ || issued_) app.streamer().stopAsync(); + app.pop(); + } + + SessionKind kind_; + std::string id_, name_; + bool f2pOnly_ = false; + bool issued_ = false, started_ = false, failed_ = false; + std::string error_; + StreamState lastState_ = StreamState::Starting; + uint64_t startedAt_ = 0; +}; + +// ============================================================================================ +// Stream +// ============================================================================================ + +class StreamScreen : public Screen { +public: + explicit StreamScreen(std::string name) : name_(std::move(name)) {} + ~StreamScreen() override { + if (tex_) SDL_DestroyTexture(tex_); + } + + bool capturesGamepad() const override { return !menuOpen_ && !ended_; } + + void onEnter(App& app) override { + if (enteredAt_ == 0) enteredAt_ = platform::monotonicMs(); + deadzone_ = app.config().settings().stickDeadzone; + captureHeld(app); // e.g. Cross still held from starting the title in the library + } + + void onLeave(App& app) override { + app.gamepad().stopRumble(); + app.streamer().setGamepad(GamepadState{}); + } + + void onNav(App& app, NavKey key) override { + if (ended_) { + if (key == NavKey::Accept || key == NavKey::Back) leave(app); + return; + } + if (!menuOpen_) { + if (key == NavKey::Menu) openMenu(app); + return; + } + switch (key) { + case NavKey::Up: menuFocus_ = (menuFocus_ + kMenuItems - 1) % kMenuItems; break; + case NavKey::Down: menuFocus_ = (menuFocus_ + 1) % kMenuItems; break; + case NavKey::Back: + case NavKey::Menu: closeMenu(app); break; + case NavKey::Accept: activate(app, menuFocus_); break; + default: break; + } + } + + void update(App& app, uint32_t dtMs) override { + (void)dtMs; + const uint64_t now = platform::monotonicMs(); + Streamer& s = app.streamer(); + Gamepad& pad = app.gamepad(); + + // session end + const StreamState st = s.state(); + if (!ended_ && isTerminal(st)) { + ended_ = true; + menuOpen_ = false; + endedAt_ = now; + endMsg_ = s.statusText(); + failedEnd_ = st == StreamState::Failed; + pad.stopRumble(); + XC_LOGI("stream: %s (%s)", streamStateName(st), endMsg_.c_str()); + if (failedEnd_) platform::notify("Nubix: stream ended: " + (endMsg_.empty() ? std::string("error") : endMsg_)); + app.streamer().stopAsync(); + } + if (ended_) { + if (now - endedAt_ > kEndedReturnMs) leave(app); + return; + } + + // video + if (AVFrame* f = s.currentFrame()) { + upload(app.ui(), f); + av_frame_free(&f); + ++videoFrames_; + } + + // input (state() every frame: it also advances the Menu/View chord hold-back) + GamepadState gs = pad.state(deadzone_); + const bool chord = pad.menuChordTriggered(kMenuHoldMs); + if (!menuOpen_) { + if (chord) { + openMenu(app); + } else { + suppressHeld(gs); + if (now < nexusUntil_) gs.buttons |= btn::Nexus; + s.setGamepad(gs); + } + } else { + if (chord) closeMenu(app); + s.setGamepad(GamepadState{}); + } + + // rumble + Vibration v; + while (s.takeVibration(v)) + if (!menuOpen_) pad.rumble(v); + + // stats + ++displayFrames_; + if (now - statsAt_ >= kStatsRefreshMs) { + const double secs = (now - statsAt_) / 1000.0; + if (statsAt_ != 0 && secs > 0) displayFps_ = displayFrames_ / secs; + displayFrames_ = 0; + statsAt_ = now; + stats_ = s.stats(); + } + } + + void render(App& app, Ui& ui) override { + const uint64_t now = platform::monotonicMs(); + SDL_Renderer* r = ui.renderer(); + if (tex_ && texW_ > 0 && texH_ > 0) { + const float sc = std::min(static_cast<float>(Ui::kWidth) / texW_, static_cast<float>(Ui::kHeight) / texH_); + const int w = static_cast<int>(std::lround(texW_ * sc)), h = static_cast<int>(std::lround(texH_ * sc)); + const int x = (Ui::kWidth - w) / 2, y = (Ui::kHeight - h) / 2; + // letterbox bars only (the video covers the rest) + if (x > 0) { + ui.rect(0, 0, x, Ui::kHeight, colors::Black); + ui.rect(x + w, 0, Ui::kWidth - x - w, Ui::kHeight, colors::Black); + } + if (y > 0) { + ui.rect(0, 0, Ui::kWidth, y, colors::Black); + ui.rect(0, y + h, Ui::kWidth, Ui::kHeight - y - h, colors::Black); + } + SDL_Rect dst{x, y, w, h}; + SDL_RenderCopy(r, tex_, nullptr, &dst); + } else { + ui.rect(0, 0, Ui::kWidth, Ui::kHeight, colors::Black); + if (!ended_) { + ui.spinner(Ui::kWidth / 2, Ui::kHeight / 2 - 30, 48, now); + ui.text("Waiting for video\xE2\x80\xA6", Ui::kWidth / 2, Ui::kHeight / 2 + 50, 30, colors::TextDim, + Align::Center); + } + } + + if (app.statsOverlay() && !ended_) renderStats(ui); + + if (!ended_ && !menuOpen_ && now - enteredAt_ < kStartHintMs) { + const bool kb = app.gamepad().lastInputKeyboard(); + const std::string msg = kb ? "Press F1 for the stream menu" + : (platform::isPs5() ? "Hold Options + touchpad for 1 s for the stream menu" + : "Hold PS (or Options + Create) for 1 s for the stream menu"); + const uint64_t left = kStartHintMs - (now - enteredAt_); + const uint8_t a = left < 500 ? static_cast<uint8_t>(left * 255 / 500) : 255; + const int w = ui.textWidth(msg, 24) + 60; + ui.rect((Ui::kWidth - w) / 2, 40, w, 56, withAlpha(colors::Panel, static_cast<uint8_t>(a * 220 / 255)), true, 28); + ui.text(msg, Ui::kWidth / 2, 40 + (56 - ui.lineHeight(24)) / 2, 24, withAlpha(colors::Text, a), Align::Center); + } + + if (menuOpen_) renderMenu(app, ui); + if (ended_) renderEnded(app, ui, now); + } + +private: + enum MenuItem { ItemResume, ItemNexus, ItemStats, ItemKeyframe, ItemDisconnect, kMenuItems }; + + void openMenu(App& app) { + menuOpen_ = true; + menuFocus_ = ItemResume; + app.gamepad().stopRumble(); + app.streamer().setGamepad(GamepadState{}); + captureHeld(app); + } + + // Close the overlay. The button that closed it (Cross on an item, Circle, the chord) is + // still physically down: it must not reach the game when forwarding resumes. + void closeMenu(App& app) { + menuOpen_ = false; + captureHeld(app); + } + + // Remember every button / trigger held right now; suppressHeld() keeps each one away from + // the game until it has been released once. + void captureHeld(App& app) { + const GamepadState cur = app.gamepad().state(deadzone_); + suppressMask_ = cur.buttons; + suppressLt_ = cur.lt >= kTriggerReleased; + suppressRt_ = cur.rt >= kTriggerReleased; + } + + void suppressHeld(GamepadState& gs) { + if (suppressMask_) { + suppressMask_ &= gs.buttons; // released bits are forwarded normally from now on + gs.buttons = static_cast<uint16_t>(gs.buttons & ~suppressMask_); + } + if (suppressLt_) { + if (gs.lt < kTriggerReleased) suppressLt_ = false; + else gs.lt = 0; + } + if (suppressRt_) { + if (gs.rt < kTriggerReleased) suppressRt_ = false; + else gs.rt = 0; + } + } + + // Back to the previous screen, or to Login when the account got signed out meanwhile. + void leave(App& app) { + if (!leaveIfSignedOut(app)) app.pop(); + } + + void activate(App& app, int item) { + switch (item) { + case ItemResume: closeMenu(app); break; + case ItemNexus: + closeMenu(app); // Cross is suppressed: the game gets Nexus alone + nexusUntil_ = platform::monotonicMs() + kNexusPulseMs; + break; + case ItemStats: app.setStatsOverlay(!app.statsOverlay()); break; + case ItemKeyframe: + app.streamer().requestKeyframe(); + app.toast("Requested a fresh video frame"); + closeMenu(app); + break; + case ItemDisconnect: { + XC_LOGI("stream: disconnect requested by user"); + app.streamer().stopAsync(); + leave(app); + break; + } + default: break; + } + } + + void upload(Ui& ui, const AVFrame* f) { + const int fmt = f->format; + Uint32 sdlFmt = 0; + if (fmt == AV_PIX_FMT_YUV420P || fmt == AV_PIX_FMT_YUVJ420P) sdlFmt = SDL_PIXELFORMAT_IYUV; + else if (fmt == AV_PIX_FMT_NV12) sdlFmt = SDL_PIXELFORMAT_NV12; + if (sdlFmt == 0 || f->width <= 0 || f->height <= 0) { + if (!warnedFormat_) { + XC_LOGE("stream: unsupported frame format %d (%dx%d)", fmt, f->width, f->height); + warnedFormat_ = true; + } + return; + } + if (!tex_ || f->width != texW_ || f->height != texH_ || sdlFmt != texFmt_) { + if (tex_) SDL_DestroyTexture(tex_); + const bool full = fmt == AV_PIX_FMT_YUVJ420P || f->color_range == AVCOL_RANGE_JPEG; + SDL_SetYUVConversionMode(full ? SDL_YUV_CONVERSION_JPEG : SDL_YUV_CONVERSION_AUTOMATIC); + tex_ = SDL_CreateTexture(ui.renderer(), sdlFmt, SDL_TEXTUREACCESS_STREAMING, f->width, f->height); + if (!tex_) { + XC_LOGE("stream: SDL_CreateTexture %dx%d failed: %s", f->width, f->height, SDL_GetError()); + texW_ = texH_ = 0; + return; + } + texW_ = f->width; + texH_ = f->height; + texFmt_ = sdlFmt; + XC_LOGI("stream: video %dx%d %s", texW_, texH_, sdlFmt == SDL_PIXELFORMAT_NV12 ? "NV12" : "IYUV"); + } + int rc; + if (sdlFmt == SDL_PIXELFORMAT_IYUV) + rc = SDL_UpdateYUVTexture(tex_, nullptr, f->data[0], f->linesize[0], f->data[1], f->linesize[1], f->data[2], + f->linesize[2]); + else + rc = SDL_UpdateNVTexture(tex_, nullptr, f->data[0], f->linesize[0], f->data[1], f->linesize[1]); + if (rc != 0 && !warnedUpload_) { + XC_LOGE("stream: texture upload failed: %s", SDL_GetError()); + warnedUpload_ = true; + } + } + + void renderStats(Ui& ui) { + char l1[96], l2[96], l3[128], l4[64]; + const StreamStats& s = stats_; + std::snprintf(l1, sizeof(l1), "%d \xC3\x97 %d \xC2\xB7 %.1f fps", s.width ? s.width : texW_, + s.height ? s.height : texH_, s.fps); + std::snprintf(l2, sizeof(l2), "%.1f Mbps \xC2\xB7 loss %.1f %%", s.bitrateKbps / 1000.0, s.lossPercent); + if (s.rttMs >= 0) + std::snprintf(l3, sizeof(l3), "decode %.1f ms \xC2\xB7 RTT %d ms \xC2\xB7 audio %d ms", s.decodeMs, s.rttMs, + s.audioBufferMs); + else + std::snprintf(l3, sizeof(l3), "decode %.1f ms \xC2\xB7 audio %d ms", s.decodeMs, s.audioBufferMs); + std::snprintf(l4, sizeof(l4), "display %.0f fps", displayFps_); + const char* lines[] = {l1, l2, l3, l4}; + int w = 0; + for (auto* l : lines) w = std::max(w, ui.textWidth(l, 22)); + const int lh = ui.lineHeight(22); + ui.rect(28, 28, w + 48, lh * 4 + 32, withAlpha(colors::Black, 160), true, 14); + ui.rect(28, 44, 4, lh * 4, colors::AccentBright, true, 2); + int y = 44; + for (int i = 0; i < 4; ++i) { + ui.text(lines[i], 52, y, 22, i == 0 ? colors::White : colors::Text, Align::Left, i == 0); + y += lh; + } + } + + void renderMenu(App& app, Ui& ui) { + ui.rect(0, 0, Ui::kWidth, Ui::kHeight, withAlpha(colors::Black, 150)); + const int w = 640, itemH = 76, gap = 12; + const int h = 150 + kMenuItems * (itemH + gap) + 20; + const int x = (Ui::kWidth - w) / 2, y = (Ui::kHeight - h) / 2; + ui.rect(x, y, w, h, colors::Panel, true, 26); + ui.outline(x, y, w, h, withAlpha(colors::AccentBright, 80), 2, 26); + ui.textEllipsized(name_.empty() ? std::string("Stream") : name_, x + 48, y + 40, w - 96, 36, colors::Text, + Align::Left, true); + const StreamStats& s = stats_; + char sub[96]; + std::snprintf(sub, sizeof(sub), "%dx%d \xC2\xB7 %.0f fps \xC2\xB7 %.1f Mbps", s.width ? s.width : texW_, + s.height ? s.height : texH_, s.fps, s.bitrateKbps / 1000.0); + if (s.width > 0 || texW_ > 0) ui.text(sub, x + 48, y + 92, 22, colors::TextDim); + int iy = y + 150; + for (int i = 0; i < kMenuItems; ++i) { + std::string label; + switch (i) { + case ItemResume: label = "Resume"; break; + case ItemNexus: label = "Press Xbox button"; break; + case ItemStats: label = app.statsOverlay() ? "Hide statistics" : "Show statistics"; break; + case ItemKeyframe: label = "Refresh video"; break; + case ItemDisconnect: label = "Disconnect"; break; + } + const bool f = i == menuFocus_; + if (f) { + ui.rect(x + 32, iy, w - 64, itemH, i == ItemDisconnect ? Color{150, 45, 40, 255} : colors::Accent, true, 14); + } + ui.text(label, x + 64, iy + (itemH - ui.lineHeight(28, f)) / 2, 28, + f ? colors::White : (i == ItemDisconnect ? Color{255, 140, 130, 255} : colors::Text), Align::Left, f); + iy += itemH + gap; + } + ui::hintBar(app, ui, {{PadIcon::Cross, "Select"}, {PadIcon::Circle, "Resume"}}); + } + + void renderEnded(App& app, Ui& ui, uint64_t now) { + ui.rect(0, 0, Ui::kWidth, Ui::kHeight, withAlpha(colors::Black, 170)); + const int w = 1000, h = 330; + const int x = (Ui::kWidth - w) / 2, y = (Ui::kHeight - h) / 2; + ui.rect(x, y, w, h, colors::Panel, true, 24); + if (failedEnd_) ui::errorBadge(ui, x + 80, y + 82, 30); + ui.text(failedEnd_ ? "Stream interrupted" : "Stream ended", x + (failedEnd_ ? 132 : 56), y + 58, 40, + colors::Text, Align::Left, true); + if (!endMsg_.empty()) ui.textWrapped(endMsg_, x + 56, y + 130, w - 112, 26, colors::TextDim); + const int64_t left = static_cast<int64_t>((kEndedReturnMs - std::min(kEndedReturnMs, now - endedAt_)) / 1000) + 1; + ui.text("Returning in " + std::to_string(left) + " s", x + 56, y + h - 70, 24, withAlpha(colors::TextDim, 200)); + ui::hintBar(app, ui, {{PadIcon::Cross, "Back now"}}); + } + + std::string name_; + SDL_Texture* tex_ = nullptr; + int texW_ = 0, texH_ = 0; + Uint32 texFmt_ = 0; + bool warnedFormat_ = false, warnedUpload_ = false; + + bool menuOpen_ = false; + int menuFocus_ = 0; + uint64_t nexusUntil_ = 0; + int deadzone_ = 0; + uint16_t suppressMask_ = 0; + bool suppressLt_ = false, suppressRt_ = false; + + bool ended_ = false, failedEnd_ = false; + uint64_t endedAt_ = 0; + std::string endMsg_; + + uint64_t enteredAt_ = 0; + StreamStats stats_; + uint64_t statsAt_ = 0; + uint64_t videoFrames_ = 0; + int displayFrames_ = 0; + double displayFps_ = 0; +}; + +} // namespace + +namespace ui { +std::unique_ptr<Screen> makeStreamScreenNamed(const std::string& displayName) { + return std::make_unique<StreamScreen>(displayName); +} +} // namespace ui + +std::unique_ptr<Screen> makeConnectingScreen(SessionKind kind, const std::string& titleOrServerId, + const std::string& displayName, bool f2pOnly) { + return std::make_unique<ConnectingScreen>(kind, titleOrServerId, displayName, f2pOnly); +} + +} // namespace xc diff --git a/src/ui/screens.cpp b/src/ui/screens.cpp new file mode 100644 index 0000000..83d92e1 --- /dev/null +++ b/src/ui/screens.cpp @@ -0,0 +1,501 @@ +// Nubix — UI screens: shared widgets, confirmation dialog, settings panel / screen and +// the public factories. Login, Home, Connecting and Stream live in screen_*.cpp. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "ui/screens.hpp" + +#include <algorithm> +#include <cctype> +#include <cstdio> +#include <string> +#include <vector> + +#include "core/log.hpp" +#include "platform/platform.hpp" +#include "ui/app.hpp" +#include "ui/screens_internal.hpp" +#include "ui/ui.hpp" + +namespace xc { +namespace ui { + +namespace { +constexpr int kHintIcon = 40; +constexpr int kHintText = 24; +constexpr int kHintY = 1022; + +// Width padIcon() will use for an icon (mirrors Ui::padIcon's metrics). +int padIconWidth(Ui& ui, PadIcon icon, int size) { + auto pill = [&](const char* label) { + const int fs = std::max(12, size * 9 / 20); + return std::max(size + size / 3, ui.textWidth(label, fs, true) + size / 2); + }; + switch (icon) { + case PadIcon::L1: return pill("L1"); + case PadIcon::R1: return pill("R1"); + case PadIcon::PS: return pill("PS"); + case PadIcon::Options: return size + size / 3; + case PadIcon::Touchpad: return size * 2; + default: return size; + } +} + +int keyCapWidth(Ui& ui, const std::string& label, int size) { + const int fs = std::max(12, size * 9 / 20); + return std::max(size, ui.textWidth(label, fs, true) + size / 2); +} + +int hintWidth(App& app, Ui& ui, const Hint& h) { + const bool kb = app.gamepad().lastInputKeyboard(); + const int iw = kb ? keyCapWidth(ui, keyFor(h.icon), kHintIcon) : padIconWidth(ui, h.icon, kHintIcon); + return h.label.empty() ? iw : iw + 12 + ui.textWidth(h.label, kHintText); +} +} // namespace + +const char* keyFor(PadIcon icon) { + switch (icon) { + case PadIcon::Cross: return "Enter"; + case PadIcon::Circle: return "Esc"; + case PadIcon::Square: return "X"; + case PadIcon::Triangle: return "Y"; + case PadIcon::L1: return "Q"; + case PadIcon::R1: return "E"; + case PadIcon::Options: return "N"; + case PadIcon::Touchpad: return "V"; + case PadIcon::PS: return "F1"; + } + return "?"; +} + +int hintItem(App& app, Ui& ui, const Hint& h, int x, int cy) { + const bool kb = app.gamepad().lastInputKeyboard(); + int w = kb ? ui.keyCap(keyFor(h.icon), x, cy, kHintIcon) : ui.padIcon(h.icon, x, cy, kHintIcon); + if (h.label.empty()) return w; // glyph paired with the next hint (e.g. "L1 R1 Switch tab") + w += 12; + w += ui.text(h.label, x + w, cy - ui.lineHeight(kHintText) / 2, kHintText, colors::TextDim); + return w; +} + +void hintBar(App& app, Ui& ui, const std::vector<Hint>& hints) { + if (hints.empty()) return; + auto gapAfter = [](const Hint& h) { return h.label.empty() ? 10 : 40; }; + int total = 0; + for (auto& h : hints) total += hintWidth(app, ui, h) + gapAfter(h); + total -= gapAfter(hints.back()); + int x = Ui::kWidth - 96 - total; + for (auto& h : hints) x += hintItem(app, ui, h, x, kHintY) + gapAfter(h); +} + +void background(Ui& ui) { + ui.gradient(0, 0, Ui::kWidth, 420, Color{16, 32, 70, 255}, colors::Background); +} + +void logo(Ui& ui, int x, int y, bool compact) { + const int r = 26; + // app icon in miniature: navy rounded square with a two-tone blue X + ui.rect(x, y, 2 * r, 2 * r, Color{18, 38, 82, 255}, true, 14); + const float c = r, k = r * 0.48f; + ui.line(x + c + k, y + c - k, x + c - k, y + c + k, 9.0f, colors::Accent); + ui.line(x + c - k, y + c - k, x + c + k, y + c + k, 9.0f, colors::AccentBright); + const int tx = x + 2 * r + 16; + int w = ui.text("Nubix", tx, y + r - ui.lineHeight(30, true) / 2, 30, colors::Text, Align::Left, true); + if (!compact) ui.text("for Xbox Cloud Gaming", tx + w + 14, y + r - ui.lineHeight(30) / 2, 30, colors::TextDim); +} + +void errorBadge(Ui& ui, int cx, int cy, int radius) { + ui.circle(cx, cy, radius, colors::Error); + const int s = radius * 13 / 10; + ui.text("!", cx, cy - ui.lineHeight(s, true) / 2, s, colors::White, Align::Center, true); +} + +std::string formatDuration(int64_t seconds) { + if (seconds < 0) seconds = 0; + char buf[32]; + if (seconds >= 3600) + std::snprintf(buf, sizeof(buf), "%d:%02d:%02d", static_cast<int>(seconds / 3600), + static_cast<int>((seconds / 60) % 60), static_cast<int>(seconds % 60)); + else + std::snprintf(buf, sizeof(buf), "%d:%02d", static_cast<int>(seconds / 60), static_cast<int>(seconds % 60)); + return buf; +} + +std::string prettyConsoleType(const std::string& t) { + if (t == "XboxSeriesX") return "Xbox Series X"; + if (t == "XboxSeriesS") return "Xbox Series S"; + if (t == "XboxOne") return "Xbox One"; + if (t == "XboxOneS") return "Xbox One S"; + if (t == "XboxOneX") return "Xbox One X"; + if (t.empty()) return "Xbox"; + // generic: insert spaces before capitals ("XboxFooBar" -> "Xbox Foo Bar") + std::string out; + for (size_t i = 0; i < t.size(); ++i) { + if (i > 0 && std::isupper(static_cast<unsigned char>(t[i])) && + !std::isupper(static_cast<unsigned char>(t[i - 1]))) + out.push_back(' '); + out.push_back(t[i]); + } + return out; +} + +// ---- confirmation dialog ------------------------------------------------------------------ + +namespace { + +class ConfirmScreen : public Screen { +public: + ConfirmScreen(std::string title, std::string message, std::string confirm, std::function<void(App&)> fn) + : title_(std::move(title)), message_(std::move(message)), confirm_(std::move(confirm)), fn_(std::move(fn)) {} + + bool isOverlay() const override { return true; } + + void onNav(App& app, NavKey key) override { + switch (key) { + case NavKey::Left: focus_ = 0; break; + case NavKey::Right: focus_ = 1; break; + case NavKey::Back: app.pop(); break; + case NavKey::Accept: + app.pop(); + if (focus_ == 1 && fn_) fn_(app); + break; + default: break; + } + } + void update(App& app, uint32_t dtMs) override { + (void)app; + (void)dtMs; + } + void render(App& app, Ui& ui) override { + ui.rect(0, 0, Ui::kWidth, Ui::kHeight, withAlpha(colors::Black, 170)); + // cover the (dimmed) hint bar of the screen below so only ours is readable + ui.rect(0, 990, Ui::kWidth, 70, Color{5, 7, 6, 255}); + const int w = 900, h = 400; + const int x = (Ui::kWidth - w) / 2, y = (Ui::kHeight - h) / 2; + ui.rect(x, y, w, h, colors::Panel, true, 24); + ui.outline(x, y, w, h, withAlpha(colors::AccentBright, 90), 2, 24); + ui.text(title_, x + 56, y + 48, 42, colors::Text, Align::Left, true); + ui.textWrapped(message_, x + 56, y + 122, w - 112, 28, colors::TextDim); + const int bw = 300, bh = 72, by = y + h - bh - 48; + ui.button("Cancel", x + w - 56 - 2 * bw - 24, by, bw, bh, focus_ == 0); + ui.button(confirm_, x + w - 56 - bw, by, bw, bh, focus_ == 1); + hintBar(app, ui, {{PadIcon::Cross, "Select"}, {PadIcon::Circle, "Cancel"}}); + } + +private: + std::string title_, message_, confirm_; + std::function<void(App&)> fn_; + int focus_ = 1; +}; + +} // namespace + +std::unique_ptr<Screen> makeConfirmScreen(const std::string& title, const std::string& message, + const std::string& confirmLabel, std::function<void(App&)> onConfirm) { + return std::make_unique<ConfirmScreen>(title, message, confirmLabel, std::move(onConfirm)); +} + +std::unique_ptr<Screen> makeExitConfirm() { + return makeConfirmScreen("Exit Nubix?", "Any running stream will be stopped.", "Exit", + [](App& app) { app.quit(); }); +} + +// ---- settings panel ----------------------------------------------------------------------- + +namespace { + +// Fallback gssv region names (offeringSettings.regions[].name), used only until Auth has stored +// the account's real region list (Tokens::xcloudRegions, filled by authorizeStreaming). Auth +// falls back to the offering's default region when the stored name is not offered. +const char* const kFallbackRegions[] = {"WestEurope", "NorthEurope", "UKSouth", "FranceCentral", + "GermanyWestCentral", "SwedenCentral", "EastUS", "EastUS2", + "NorthCentralUS", "SouthCentralUS", "WestUS", "WestUS2", + "CanadaCentral", "BrazilSouth", "JapanEast", "KoreaCentral", + "SoutheastAsia", "AustraliaEast", "AustraliaSouthEast"}; + +// Region choices for the Settings row: "" (Automatic = server default) followed by the account's +// xgpuweb regions (or the f2p ones, or the fallback list). +std::vector<std::string> regionChoices(const Tokens& t) { + std::vector<std::string> out{std::string()}; + const std::vector<Region>& src = !t.xcloudRegions.empty() ? t.xcloudRegions : t.xcloudF2pRegions; + for (const auto& r : src) + if (!r.name.empty() && std::find(out.begin(), out.end(), r.name) == out.end()) out.push_back(r.name); + if (out.size() == 1) + for (const char* n : kFallbackRegions) out.emplace_back(n); + return out; +} + +const char* const kResolutions[] = {"720", "1080", "1080HQ"}; + +enum Row { RowResolution, RowBitrate, RowRegion, RowF2p, RowDeadzone, RowStats, RowSignOut, RowExit, RowCount }; + +} // namespace + +struct SettingsPanel::Impl { + int focus = 0; + + // Per-frame reads go through cached snapshots (Config is shared with the worker threads); + // refreshed when the config revision changes. + uint64_t cachedRev = ~uint64_t(0); + Settings settings; + std::vector<Region> cloudRegions; + std::vector<std::string> regions; // regionChoices() + std::string gamertag; + // getifaddrs() is not a per-frame call: refresh the IP about once per second. + std::string ip; + uint64_t ipAt = 0; + + void refresh(App& app) { + const uint64_t rev = app.config().revision(); + if (rev == cachedRev) return; + cachedRev = rev; + settings = app.config().settings(); + const Tokens t = app.config().tokens(); + cloudRegions = t.xcloudRegions; + regions = regionChoices(t); + gamertag = t.gamertag; + } + + const std::string& localIp() { + const uint64_t now = platform::monotonicMs(); + if (ipAt == 0 || now - ipAt >= 1000) { + ip = platform::localIpv4(); + ipAt = now; + } + return ip; + } + + static void save(App& app) { + Config* cfg = &app.config(); + app.runAsync([cfg] { + if (!cfg->save()) XC_LOGW("settings: saving %s failed", cfg->path().c_str()); + }); + } + + std::string label(int row) const { + switch (row) { + case RowResolution: return "Stream resolution"; + case RowBitrate: return "Maximum bitrate"; + case RowRegion: return "Server region"; + case RowF2p: return "Free-to-play fallback"; + case RowDeadzone: return "Extra stick deadzone"; + case RowStats: return "Show stream statistics"; + case RowSignOut: return "Sign out"; + case RowExit: return "Exit Nubix"; + } + return ""; + } + + std::string value(App& app, int row) const { + const Settings& s = settings; + char buf[64]; + switch (row) { + case RowResolution: + if (s.resolution == "720") return "720p"; + if (s.resolution == "1080HQ") return "1080p (high quality)"; + return "1080p"; + case RowBitrate: + std::snprintf(buf, sizeof(buf), "%d Mbps", (s.bitrateKbps + 500) / 1000); + return buf; + case RowRegion: { + if (s.region.empty()) return "Automatic"; + for (const auto& r : cloudRegions) + if (r.name == s.region) return s.region; + // Not (yet) known to be offered: Auth uses the server default in that case. + return cloudRegions.empty() ? s.region : s.region + " (unavailable)"; + } + case RowF2p: return s.f2pFallback ? "On" : "Off"; + case RowDeadzone: + if (s.stickDeadzone <= 0) return "Off"; + std::snprintf(buf, sizeof(buf), "%d %%", s.stickDeadzone); + return buf; + case RowStats: return app.statsOverlay() ? "On" : "Off"; + default: return ""; + } + } + + std::string help(int row) const { + switch (row) { + case RowResolution: return "720p decodes fastest. 1080p (high quality) asks the server for a higher-quality profile."; + case RowBitrate: return "Upper limit the server may use. Lower it on Wi-Fi or if the picture stutters."; + case RowRegion: return "Automatic uses the region Xbox assigns to your account."; + case RowF2p: return "Retry with the free-to-play offering when a title is not in your Game Pass library."; + case RowDeadzone: return "Ignore small stick movements (helps with drifting sticks)."; + case RowStats: return "Resolution, frame rate, bitrate and decode time while streaming."; + case RowSignOut: return "Forget the stored Microsoft account on this console."; + case RowExit: return "Close the app."; + } + return ""; + } + + // dir: -1 / +1 (Left / Right), 0 = Accept + void change(App& app, int row, int dir) { + refresh(app); + Settings s = settings; // edit a copy, publish it with one locked write + const int step = dir == 0 ? 1 : dir; + auto commit = [&] { + app.config().setSettings(s); + refresh(app); + }; + switch (row) { + case RowResolution: { + int i = 1; + for (int k = 0; k < 3; ++k) + if (s.resolution == kResolutions[k]) i = k; + i = (i + step + 3) % 3; + s.resolution = kResolutions[i]; + commit(); + save(app); + break; + } + case RowBitrate: { + int mb = (s.bitrateKbps + 500) / 1000 + step; + if (dir == 0 && mb > 20) mb = 5; + s.bitrateKbps = std::max(5, std::min(20, mb)) * 1000; + commit(); + save(app); + break; + } + case RowRegion: { + const std::vector<std::string> choices = regions; + const int n = static_cast<int>(choices.size()); + int i = 0; + for (int k = 0; k < n; ++k) + if (s.region == choices[static_cast<size_t>(k)]) i = k; + i = (i + step + n) % n; + s.region = choices[static_cast<size_t>(i)]; + commit(); + // Re-pick the cloud base URIs for the new region (no network) and persist. + Auth* auth = &app.auth(); + app.runAsync([auth] { auth->applyRegion(); }); + break; + } + case RowF2p: + s.f2pFallback = !s.f2pFallback; + commit(); + save(app); + break; + case RowDeadzone: { + int v = s.stickDeadzone + step * 5; + if (dir == 0 && v > 30) v = 0; + s.stickDeadzone = std::max(0, std::min(30, v)); + commit(); + save(app); + break; + } + case RowStats: app.setStatsOverlay(!app.statsOverlay()); break; + case RowSignOut: + if (dir != 0) break; + app.push(makeConfirmScreen("Sign out?", "You will need to sign in again with a code to stream.", + "Sign out", [](App& a) { + a.runAsync([&a] { + a.auth().logout(); + a.postToMain([&a] { + a.toast("Signed out"); + a.resetTo(makeLoginScreen()); + }); + }); + })); + break; + case RowExit: + if (dir == 0) app.push(makeExitConfirm()); + break; + } + } +}; + +SettingsPanel::SettingsPanel() : d_(new Impl) {} +SettingsPanel::~SettingsPanel() = default; + +void SettingsPanel::resetFocus() { d_->focus = 0; } + +bool SettingsPanel::onNav(App& app, NavKey key) { + d_->refresh(app); + switch (key) { + case NavKey::Up: d_->focus = (d_->focus + RowCount - 1) % RowCount; return true; + case NavKey::Down: d_->focus = (d_->focus + 1) % RowCount; return true; + case NavKey::Left: d_->change(app, d_->focus, -1); return true; + case NavKey::Right: d_->change(app, d_->focus, +1); return true; + case NavKey::Accept: d_->change(app, d_->focus, 0); return true; + default: return false; + } +} + +void SettingsPanel::render(App& app, Ui& ui, int top) { + d_->refresh(app); + const int x = 360, w = 1200, h = 74, gap = 8; + int y = top; + for (int row = 0; row < RowCount; ++row) { + const bool f = row == d_->focus; + const bool action = row == RowSignOut || row == RowExit; + if (row == RowSignOut) y += 16; // separate actions from settings + ui.rect(x, y, w, h, f ? colors::PanelHi : colors::Panel, true, 14); + if (f) { + ui.outline(x - 4, y - 4, w + 8, h + 8, colors::AccentBright, 3, 18); + ui.rect(x, y + 14, 6, h - 28, colors::AccentBright, true, 3); + } + const int ty = y + (h - ui.lineHeight(28)) / 2; + const Color lc = action && row == RowSignOut ? Color{255, 140, 130, 255} : colors::Text; + ui.text(d_->label(row), x + 36, ty, 28, lc, Align::Left, f); + if (!action) { + const std::string v = d_->value(app, row); + const int vx = x + w - 36; + if (f) { + const int vw = ui.textWidth(v, 28, true); + ui.text("\xE2\x80\xBA", vx, ty, 28, colors::AccentBright, Align::Right, true); // › + ui.text(v, vx - 34, ty, 28, colors::White, Align::Right, true); + ui.text("\xE2\x80\xB9", vx - 34 - vw - 18, ty, 28, colors::AccentBright, Align::Right, true); // ‹ + } else { + ui.text(v, vx, ty, 28, colors::TextDim, Align::Right); + } + } + y += h + gap; + } + // help line for the focused row + account info + ui.text(d_->help(d_->focus), x, y + 16, 24, colors::TextDim); + std::string info; + const std::string& tag = d_->gamertag; + info = tag.empty() ? std::string("Signed in") : "Signed in as " + tag; + info += " \xC2\xB7 v" XC_VERSION; + const std::string ip = d_->localIp(); + if (!ip.empty()) info += " \xC2\xB7 IP " + ip; + if (app.gamepad().connected()) info += " \xC2\xB7 " + app.gamepad().name(); + ui.text(info, x, y + 60, 22, withAlpha(colors::TextDim, 200)); +} + +} // namespace ui + +// ---- standalone settings screen ----------------------------------------------------------- + +namespace { + +class SettingsScreen : public Screen { +public: + void onNav(App& app, NavKey key) override { + if (key == NavKey::Back) { + app.pop(); + return; + } + panel_.onNav(app, key); + } + void update(App& app, uint32_t dtMs) override { + (void)app; + (void)dtMs; + } + void render(App& app, Ui& ui) override { + ui::background(ui); + ui::logo(ui, 96, 56); + ui.text("Settings", 360, 150, 48, colors::Text, Align::Left, true); + panel_.render(app, ui, 236); + ui::hintBar(app, ui, {{PadIcon::Cross, "Change"}, {PadIcon::Circle, "Back"}}); + } + +private: + ui::SettingsPanel panel_; +}; + +} // namespace + +std::unique_ptr<Screen> makeSettingsScreen() { return std::make_unique<SettingsScreen>(); } + +std::unique_ptr<Screen> makeStreamScreen() { return ui::makeStreamScreenNamed(""); } + +} // namespace xc diff --git a/src/ui/screens.hpp b/src/ui/screens.hpp new file mode 100644 index 0000000..1f46a48 --- /dev/null +++ b/src/ui/screens.hpp @@ -0,0 +1,52 @@ +// Nubix — UI screens: Login, Home (Cloud library / My consoles / Settings tabs), +// Settings, Connecting, Stream (video + overlay menu + stats). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <memory> +#include <string> + +#include "core/gssv.hpp" +#include "platform/gamepad.hpp" + +namespace xc { + +class App; +class Ui; + +// Base class for a screen on the App's stack. All methods run on the main thread. +class Screen { +public: + virtual ~Screen() = default; + // Became the top screen. + virtual void onEnter(App& app) { (void)app; } + // Another screen was pushed on top / this one is popped. + virtual void onLeave(App& app) { (void)app; } + // A navigation key was pressed. + virtual void onNav(App& app, NavKey key) = 0; + // Per-frame logic (poll async results, streamer state, ...). dtMs = frame time. + virtual void update(App& app, uint32_t dtMs) = 0; + // Draw the screen. + virtual void render(App& app, Ui& ui) = 0; + // True while the screen forwards raw pad state to the stream instead of NavKeys. + virtual bool capturesGamepad() const { return false; } + // True for modal dialogs: the screen below is rendered first (dimmed by the dialog). + virtual bool isOverlay() const { return false; } +}; + +// Device-code login with QR; on success replaces itself with the Home screen. +std::unique_ptr<Screen> makeLoginScreen(); +// Tabbed home: Cloud library grid, My consoles list, Settings. +std::unique_ptr<Screen> makeHomeScreen(); +// Settings list (resolution, bitrate, region, F2P fallback, logout). +std::unique_ptr<Screen> makeSettingsScreen(); +// Starts the Streamer and shows status/queue until Streaming, then replaces itself with the +// Stream screen. displayName is shown in the status text. +// f2pOnly: cloud title playable only via the free-to-play offering (Title::f2pOnly). +std::unique_ptr<Screen> makeConnectingScreen(SessionKind kind, const std::string& titleOrServerId, + const std::string& displayName, bool f2pOnly = false); +// Full-screen video, input forwarding, hold-PS overlay menu and stats. +std::unique_ptr<Screen> makeStreamScreen(); + +} // namespace xc diff --git a/src/ui/screens_internal.hpp b/src/ui/screens_internal.hpp new file mode 100644 index 0000000..3733d6b --- /dev/null +++ b/src/ui/screens_internal.hpp @@ -0,0 +1,78 @@ +// Nubix — helpers shared by the screen implementations (not part of the public UI API). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstdint> +#include <functional> +#include <memory> +#include <string> +#include <vector> + +#include "platform/gamepad.hpp" +#include "ui/screens.hpp" +#include "ui/ui.hpp" + +namespace xc { + +class App; + +namespace ui { + +// One entry of the bottom hint bar: a controller glyph (or the matching key on keyboard). +struct Hint { + PadIcon icon; + std::string label; +}; + +// Bottom-right hint bar ("✕ Select ○ Back"), keyboard key names when no pad is in use. +void hintBar(App& app, Ui& ui, const std::vector<Hint>& hints); + +// Keyboard key that corresponds to a pad glyph in the UI ("Enter", "Esc", ...). +const char* keyFor(PadIcon icon); + +// Draw one glyph-or-key + label pair; returns the width used. +int hintItem(App& app, Ui& ui, const Hint& h, int x, int cy); + +// Background with a subtle green glow at the top. +void background(Ui& ui); + +// Product logo (navy square with a blue X + "Nubix" wordmark, plus "for Xbox Cloud Gaming" unless compact). +void logo(Ui& ui, int x, int y, bool compact = false); + +// Error badge (red roundel with "!") centred at (cx, cy). +void errorBadge(Ui& ui, int cx, int cy, int radius); + +// "m:ss" / "h:mm:ss". +std::string formatDuration(int64_t seconds); + +// Pretty console model name ("XboxSeriesX" -> "Xbox Series X"). +std::string prettyConsoleType(const std::string& t); + +// Modal confirmation dialog over the current screen. onConfirm runs on the main thread. +std::unique_ptr<Screen> makeConfirmScreen(const std::string& title, const std::string& message, + const std::string& confirmLabel, std::function<void(App&)> onConfirm); + +// Confirm + quit the app. +std::unique_ptr<Screen> makeExitConfirm(); + +// Stream screen with a title for the overlay menu. +std::unique_ptr<Screen> makeStreamScreenNamed(const std::string& displayName); + +// Settings list used by the Home "Settings" tab and the standalone Settings screen. +class SettingsPanel { +public: + SettingsPanel(); + ~SettingsPanel(); + // Returns true if the key was consumed. + bool onNav(App& app, NavKey key); + void render(App& app, Ui& ui, int top); + void resetFocus(); + +private: + struct Impl; + std::unique_ptr<Impl> d_; +}; + +} // namespace ui +} // namespace xc diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp new file mode 100644 index 0000000..8d268b5 --- /dev/null +++ b/src/ui/ui.cpp @@ -0,0 +1,1051 @@ +// Nubix — drawing helpers (SDL_Renderer + SDL2_ttf). +// +// Rendering notes: on PS5 the SDL port renders in software into a 1920x1080 framebuffer, so +// everything here is designed for 1:1 blits: text is rasterised at the output pixel size +// (logical size * output scale), rounded corners come from small cached anti-aliased circle +// textures, and box art is pre-scaled to the exact tile size on a loader thread. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "ui/ui.hpp" + + +#include <SDL.h> +#include <SDL_ttf.h> + +#include <algorithm> +#include <atomic> +#include <cctype> +#include <cmath> +#include <condition_variable> +#include <deque> +#include <map> +#include <mutex> +#include <thread> +#include <unordered_map> + +#include "core/catalog.hpp" +#include "core/http.hpp" +#include "core/log.hpp" +#include "platform/platform.hpp" +#include "ui/image.hpp" +#include "ui/qr.hpp" + +namespace xc { +namespace { + +constexpr uint64_t kTextEvictFrames = 240; // drop text textures unused for ~4 s +constexpr size_t kTextCacheSoftCap = 1200; +constexpr size_t kImageCacheCap = 120; // box-art textures kept (~360 KB each at 1080p) +constexpr uint64_t kImageStaleFrames = 90; // queued requests not refreshed for this long are dropped +constexpr uint64_t kImageRetryFrames = 60 * 30; +constexpr int kLoaderThreads = 2; +constexpr int kMaxUploadsPerFrame = 6; +constexpr double kPi = 3.14159265358979323846; + +struct TextEntry { + SDL_Texture* tex = nullptr; + int pw = 0, ph = 0; // pixel size + uint64_t lastUsed = 0; +}; + +struct ImageEntry { + enum State { Queued, Ready, Failed } state = Queued; + SDL_Texture* tex = nullptr; + uint64_t lastUsed = 0; + uint64_t failedAt = 0; +}; + +// Shared between the main thread and the box-art loader threads (kept alive by shared_ptr so +// shutdown never races a thread that is finishing an HTTP request). +struct LoaderShared { + struct Job { + std::string url; + int pw = 0, ph = 0; + uint64_t wanted = 0; // frame of the last request + }; + struct Done { + std::string key; + image::Rgba rgba; + enum { Ok, Failed, Dropped } result = Failed; + }; + std::mutex m; + std::condition_variable cv; + std::map<std::string, Job> jobs; // key -> job (not yet started) + std::deque<Done> done; + int inFlight = 0; + bool stop = false; + // Set at shutdown together with stop: an image request in flight aborts within ~1 s + // (Http per-thread abort flag), so the loader threads can always be joined. + std::atomic<bool> abort{false}; + std::atomic<uint64_t> frame{0}; +}; + +void loaderThread(std::shared_ptr<LoaderShared> sh) { + Http::AbortScope abortOnShutdown(&sh->abort); + for (;;) { + std::string key; + LoaderShared::Job job; + { + std::unique_lock<std::mutex> lk(sh->m); + sh->cv.wait(lk, [&] { return sh->stop || !sh->jobs.empty(); }); + if (sh->stop) return; + // Most recently requested job first (visible tiles beat stale ones); drop stale. + const uint64_t now = sh->frame.load(); + auto best = sh->jobs.end(); + for (auto it = sh->jobs.begin(); it != sh->jobs.end();) { + if (it->second.wanted + kImageStaleFrames < now) { + LoaderShared::Done d; + d.key = it->first; + d.result = LoaderShared::Done::Dropped; + sh->done.push_back(std::move(d)); + it = sh->jobs.erase(it); + continue; + } + if (best == sh->jobs.end() || it->second.wanted > best->second.wanted) best = it; + ++it; + } + if (best == sh->jobs.end()) continue; + key = best->first; + job = best->second; + sh->jobs.erase(best); + ++sh->inFlight; + } + LoaderShared::Done d; + d.key = key; + std::vector<uint8_t> bytes; + image::Rgba full; + if (Catalog::fetchImage(job.url, bytes) && image::decode(bytes.data(), bytes.size(), full)) { + image::resize(full, job.pw, job.ph, true, d.rgba); + d.result = d.rgba.empty() ? LoaderShared::Done::Failed : LoaderShared::Done::Ok; + } else { + XC_LOGD("ui: image load failed: %s", job.url.c_str()); + d.result = LoaderShared::Done::Failed; + } + std::lock_guard<std::mutex> lk(sh->m); + --sh->inFlight; + sh->done.push_back(std::move(d)); + } +} + +// Coverage of the pixel (px, py) by a disc of radius r centred at (r, r): 4x4 supersampling. +float discCoverage(int px, int py, float r) { + int inside = 0; + for (int sy = 0; sy < 4; ++sy) + for (int sx = 0; sx < 4; ++sx) { + const float x = px + (sx + 0.5f) / 4.0f - r, y = py + (sy + 0.5f) / 4.0f - r; + if (x * x + y * y <= r * r) ++inside; + } + return inside / 16.0f; +} + +// Split UTF-8 string into code-point boundaries (for ellipsizing). +std::vector<size_t> utf8Boundaries(const std::string& s) { + std::vector<size_t> b; + for (size_t i = 0; i < s.size(); ++i) + if ((static_cast<unsigned char>(s[i]) & 0xC0) != 0x80) b.push_back(i); + b.push_back(s.size()); + return b; +} + +} // namespace + +struct Ui::Impl { + SDL_Window* window = nullptr; + SDL_Renderer* renderer = nullptr; + std::string fontPath, boldPath; + bool ttfInited = false; + float scale = 1.0f; + uint64_t frame = 0; + + std::unordered_map<int, TTF_Font*> fonts; // key: px * 2 + bold + std::unordered_map<std::string, TextEntry> text; + std::unordered_map<std::string, int> widths; // text-width cache (pixels), cleared when large + std::unordered_map<std::string, TextEntry> qr; + std::unordered_map<int, SDL_Texture*> discs; // pixel radius -> filled disc + std::unordered_map<int, SDL_Texture*> masks; // pixel radius -> inverted quarter (corner mask) + std::unordered_map<int64_t, SDL_Texture*> rings; // (radius << 16 | thickness) -> ring + + std::unordered_map<std::string, ImageEntry> images; + std::shared_ptr<LoaderShared> loader = std::make_shared<LoaderShared>(); + std::vector<std::thread> loaderThreads; + + int px(float logical) const { return std::max(1, static_cast<int>(std::lround(logical * scale))); } + + TTF_Font* font(int size, bool bold) { + const int p = px(static_cast<float>(size)); + const int key = p * 2 + (bold ? 1 : 0); + auto it = fonts.find(key); + if (it != fonts.end()) return it->second; + TTF_Font* f = TTF_OpenFont((bold ? boldPath : fontPath).c_str(), p); + if (!f) { + XC_LOGE("ui: TTF_OpenFont(%s, %d) failed: %s", (bold ? boldPath : fontPath).c_str(), p, TTF_GetError()); + return nullptr; + } + TTF_SetFontHinting(f, TTF_HINTING_LIGHT); + fonts[key] = f; + return f; + } + + TextEntry* textEntry(const std::string& s, int size, bool bold) { + const int p = px(static_cast<float>(size)); + std::string key; + key.reserve(s.size() + 8); + key.push_back(bold ? 'B' : 'R'); + key += std::to_string(p); + key.push_back('\x1f'); + key += s; + auto it = text.find(key); + if (it != text.end()) { + it->second.lastUsed = frame; + return &it->second; + } + TTF_Font* f = font(size, bold); + if (!f) return nullptr; + SDL_Surface* surf = TTF_RenderUTF8_Blended(f, s.c_str(), SDL_Color{255, 255, 255, 255}); + if (!surf) return nullptr; + SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf); + TextEntry e; + e.pw = surf->w; + e.ph = surf->h; + SDL_FreeSurface(surf); + if (!tex) return nullptr; + SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND); + e.tex = tex; + e.lastUsed = frame; + return &(text[key] = e); + } + + SDL_Texture* makeAlphaTexture(int w, int h, const std::vector<uint8_t>& alpha) { + std::vector<uint32_t> pix(static_cast<size_t>(w) * h); + for (size_t i = 0; i < pix.size(); ++i) pix[i] = (static_cast<uint32_t>(alpha[i]) << 24) | 0x00FFFFFFu; + SDL_Texture* t = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, w, h); + if (!t) return nullptr; + SDL_UpdateTexture(t, nullptr, pix.data(), w * 4); + SDL_SetTextureBlendMode(t, SDL_BLENDMODE_BLEND); + return t; + } + + SDL_Texture* disc(int r) { + auto it = discs.find(r); + if (it != discs.end()) return it->second; + const int d = r * 2; + std::vector<uint8_t> a(static_cast<size_t>(d) * d); + for (int y = 0; y < d; ++y) + for (int x = 0; x < d; ++x) a[y * d + x] = static_cast<uint8_t>(std::lround(discCoverage(x, y, r) * 255)); + return discs[r] = makeAlphaTexture(d, d, a); + } + + // Top-left quarter of (1 - disc): paints the area outside a rounded corner. + SDL_Texture* mask(int r) { + auto it = masks.find(r); + if (it != masks.end()) return it->second; + std::vector<uint8_t> a(static_cast<size_t>(r) * r); + for (int y = 0; y < r; ++y) + for (int x = 0; x < r; ++x) a[y * r + x] = static_cast<uint8_t>(std::lround((1.0f - discCoverage(x, y, r)) * 255)); + return masks[r] = makeAlphaTexture(r, r, a); + } + + SDL_Texture* ring(int r, int t) { + const int64_t key = (static_cast<int64_t>(r) << 16) | t; + auto it = rings.find(key); + if (it != rings.end()) return it->second; + const int d = r * 2; + const float ri = std::max(0.0f, static_cast<float>(r - t)); + std::vector<uint8_t> a(static_cast<size_t>(d) * d); + for (int y = 0; y < d; ++y) + for (int x = 0; x < d; ++x) { + int inO = 0, inI = 0; + for (int sy = 0; sy < 4; ++sy) + for (int sx = 0; sx < 4; ++sx) { + const float fx = x + (sx + 0.5f) / 4.0f - r, fy = y + (sy + 0.5f) / 4.0f - r; + const float dd = fx * fx + fy * fy; + if (dd <= static_cast<float>(r) * r) ++inO; + if (dd <= ri * ri) ++inI; + } + a[y * d + x] = static_cast<uint8_t>(std::lround((inO - inI) / 16.0f * 255)); + } + return rings[key] = makeAlphaTexture(d, d, a); + } + + void setColor(SDL_Texture* t, Color c) { + SDL_SetTextureColorMod(t, c.r, c.g, c.b); + SDL_SetTextureAlphaMod(t, c.a); + } + + void fill(float x, float y, float w, float h, Color c) { + if (w <= 0 || h <= 0) return; + SDL_SetRenderDrawBlendMode(renderer, c.a == 255 ? SDL_BLENDMODE_NONE : SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a); + SDL_FRect r{x, y, w, h}; + SDL_RenderFillRectF(renderer, &r); + } + + // Draw the four quarters of a (2r x 2r)-pixel texture at the corners of a rectangle. + void corners(SDL_Texture* t, int rp, float x, float y, float w, float h, float r) { + const SDL_Rect src[4] = {{0, 0, rp, rp}, {rp, 0, rp, rp}, {0, rp, rp, rp}, {rp, rp, rp, rp}}; + const SDL_FRect dst[4] = {{x, y, r, r}, {x + w - r, y, r, r}, {x, y + h - r, r, r}, {x + w - r, y + h - r, r, r}}; + for (int i = 0; i < 4; ++i) SDL_RenderCopyF(renderer, t, &src[i], &dst[i]); + } + + void destroyMap(std::unordered_map<std::string, TextEntry>& m) { + for (auto& kv : m) + if (kv.second.tex) SDL_DestroyTexture(kv.second.tex); + m.clear(); + } + + void drainImages() { + std::deque<LoaderShared::Done> ready; + { + std::lock_guard<std::mutex> lk(loader->m); + int n = 0; + while (!loader->done.empty() && n < kMaxUploadsPerFrame) { + if (loader->done.front().result == LoaderShared::Done::Ok) ++n; + ready.push_back(std::move(loader->done.front())); + loader->done.pop_front(); + } + } + for (auto& d : ready) { + auto it = images.find(d.key); + if (it == images.end()) continue; + if (d.result == LoaderShared::Done::Dropped) { + images.erase(it); + continue; + } + if (d.result == LoaderShared::Done::Failed) { + it->second.state = ImageEntry::Failed; + it->second.failedAt = frame; + continue; + } + SDL_Texture* t = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, d.rgba.width, + d.rgba.height); + if (!t) { + it->second.state = ImageEntry::Failed; + it->second.failedAt = frame; + continue; + } + SDL_UpdateTexture(t, nullptr, d.rgba.pixels.data(), d.rgba.width * 4); + SDL_SetTextureBlendMode(t, SDL_BLENDMODE_BLEND); + it->second.tex = t; + it->second.state = ImageEntry::Ready; + } + } + + void evict() { + // text: unused for a while (or everything not used this frame when over the soft cap) + const uint64_t limit = text.size() > kTextCacheSoftCap ? 1 : kTextEvictFrames; + for (auto it = text.begin(); it != text.end();) { + if (frame - it->second.lastUsed > limit) { + SDL_DestroyTexture(it->second.tex); + it = text.erase(it); + } else { + ++it; + } + } + for (auto it = qr.begin(); it != qr.end();) { + if (frame - it->second.lastUsed > kTextEvictFrames * 4) { + SDL_DestroyTexture(it->second.tex); + it = qr.erase(it); + } else { + ++it; + } + } + // images: LRU beyond the cap + size_t ready = 0; + for (auto& kv : images) + if (kv.second.state == ImageEntry::Ready) ++ready; + if (ready > kImageCacheCap) { + std::vector<std::pair<uint64_t, std::string>> order; + for (auto& kv : images) + if (kv.second.state == ImageEntry::Ready) order.emplace_back(kv.second.lastUsed, kv.first); + std::sort(order.begin(), order.end()); + size_t toDrop = ready - kImageCacheCap; + for (auto& o : order) { + if (toDrop == 0 || o.first + 2 >= frame) break; + auto it = images.find(o.second); + SDL_DestroyTexture(it->second.tex); + images.erase(it); + --toDrop; + } + } + } +}; + +Ui::Ui() : d_(new Impl) {} +Ui::~Ui() { shutdown(); } + +bool Ui::init(const std::string& fontPath, const std::string& boldFontPath, const std::string& title) { + Impl& d = *d_; + d.fontPath = fontPath; + d.boldPath = boldFontPath; + if (SDL_WasInit(SDL_INIT_VIDEO) == 0 && SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { + XC_LOGE("ui: SDL video init failed: %s", SDL_GetError()); + return false; + } + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); + SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); + + Uint32 flags = SDL_WINDOW_SHOWN; + int w = 1280, h = 720; + if (platform::isPs5()) { + flags |= SDL_WINDOW_FULLSCREEN; + w = kWidth; + h = kHeight; + } else { + flags |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; + if (const char* fs = SDL_getenv("XC_FULLSCREEN"); fs && *fs == '1') flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } + d.window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, flags); + if (!d.window) { + XC_LOGE("ui: SDL_CreateWindow failed: %s", SDL_GetError()); + return false; + } + if (!platform::isPs5()) SDL_SetWindowMinimumSize(d.window, 640, 360); + d.renderer = SDL_CreateRenderer(d.window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!d.renderer) { + XC_LOGI("ui: no accelerated vsync renderer (%s), falling back", SDL_GetError()); + d.renderer = SDL_CreateRenderer(d.window, -1, 0); + } + if (!d.renderer) { + XC_LOGE("ui: SDL_CreateRenderer failed: %s", SDL_GetError()); + return false; + } + SDL_RendererInfo info{}; + if (SDL_GetRendererInfo(d.renderer, &info) == 0) { + XC_LOGI("ui: renderer %s (flags 0x%x, max texture %dx%d)", info.name ? info.name : "?", info.flags, + info.max_texture_width, info.max_texture_height); + } + SDL_RenderSetLogicalSize(d.renderer, kWidth, kHeight); + + if (TTF_WasInit() == 0 && TTF_Init() != 0) { + XC_LOGE("ui: TTF_Init failed: %s", TTF_GetError()); + return false; + } + d.ttfInited = true; + float sx = 1, sy = 1; + SDL_RenderGetScale(d.renderer, &sx, &sy); + d.scale = sx > 0 ? sx : 1.0f; + if (!d.font(32, false) || !d.font(32, true)) { + XC_LOGE("ui: cannot load fonts %s / %s", fontPath.c_str(), boldFontPath.c_str()); + return false; + } + + d.loader = std::make_shared<LoaderShared>(); + for (int i = 0; i < kLoaderThreads; ++i) { + try { + d.loaderThreads.emplace_back(loaderThread, d.loader); + } catch (const std::exception& e) { // thread limit: fewer loaders still work + XC_LOGW("ui: image loader thread %d not started: %s", i, e.what()); + break; + } + } + return true; +} + +void Ui::shutdown() { + if (!d_) return; + Impl& d = *d_; + if (!d.loaderThreads.empty()) { + { + std::lock_guard<std::mutex> lk(d.loader->m); + d.loader->stop = true; + d.loader->jobs.clear(); + } + d.loader->abort = true; + d.loader->cv.notify_all(); + // A thread inside an HTTP request sees the abort flag from curl's progress callback and + // returns within ~1 s: join them all, so none outlives curl_global_cleanup / SDL_Quit. + for (auto& t : d.loaderThreads) + if (t.joinable()) t.join(); + d.loaderThreads.clear(); + } + clearCache(); + for (auto& kv : d.discs) SDL_DestroyTexture(kv.second); + for (auto& kv : d.masks) SDL_DestroyTexture(kv.second); + for (auto& kv : d.rings) SDL_DestroyTexture(kv.second); + d.discs.clear(); + d.masks.clear(); + d.rings.clear(); + for (auto& kv : d.fonts) TTF_CloseFont(kv.second); + d.fonts.clear(); + if (d.ttfInited) { + TTF_Quit(); + d.ttfInited = false; + } + if (d.renderer) { + SDL_DestroyRenderer(d.renderer); + d.renderer = nullptr; + } + if (d.window) { + SDL_DestroyWindow(d.window); + d.window = nullptr; + } +} + +SDL_Renderer* Ui::renderer() const { return d_->renderer; } +SDL_Window* Ui::window() const { return d_->window; } +uint64_t Ui::frameCount() const { return d_->frame; } +float Ui::scale() const { return d_->scale; } + +void Ui::beginFrame() { + Impl& d = *d_; + float sx = 1, sy = 1; + SDL_RenderGetScale(d.renderer, &sx, &sy); + if (sx > 0.01f && std::fabs(sx - d.scale) > 0.001f) d.scale = sx; + d.loader->frame.store(d.frame); + d.drainImages(); + SDL_RenderSetClipRect(d.renderer, nullptr); + SDL_SetRenderDrawBlendMode(d.renderer, SDL_BLENDMODE_NONE); + SDL_SetRenderDrawColor(d.renderer, colors::Background.r, colors::Background.g, colors::Background.b, 255); + SDL_RenderClear(d.renderer); +} + +void Ui::endFrame() { + Impl& d = *d_; + SDL_RenderPresent(d.renderer); + ++d.frame; + if (d.frame % 30 == 0) d.evict(); +} + +int Ui::text(const std::string& s, int x, int y, int size, Color c, Align align, bool bold) { + if (s.empty()) return 0; + TextEntry* e = d_->textEntry(s, size, bold); + if (!e) return 0; + const float w = e->pw / d_->scale, h = e->ph / d_->scale; + float dx = static_cast<float>(x); + if (align == Align::Center) dx -= w / 2; + else if (align == Align::Right) dx -= w; + // Snap to output pixels so glyphs stay crisp. + dx = std::round(dx * d_->scale) / d_->scale; + const float dy = std::round(y * d_->scale) / d_->scale; + d_->setColor(e->tex, c); + SDL_FRect dst{dx, dy, w, h}; + SDL_RenderCopyF(d_->renderer, e->tex, nullptr, &dst); + return static_cast<int>(std::lround(w)); +} + +int Ui::textWidth(const std::string& s, int size, bool bold) { + if (s.empty()) return 0; + Impl& d = *d_; + const int p = d.px(static_cast<float>(size)); + std::string key; + key.reserve(s.size() + 8); + key.push_back(bold ? 'B' : 'R'); + key += std::to_string(p); + key.push_back('\x1f'); + key += s; + int w = 0; + auto it = d.widths.find(key); + if (it != d.widths.end()) { + w = it->second; + } else { + TTF_Font* f = d.font(size, bold); + if (!f) return 0; + int h = 0; + if (TTF_SizeUTF8(f, s.c_str(), &w, &h) != 0) return 0; + if (d.widths.size() > 8192) d.widths.clear(); + d.widths.emplace(std::move(key), w); + } + return static_cast<int>(std::lround(w / d.scale)); +} + +int Ui::lineHeight(int size, bool bold) { + TTF_Font* f = d_->font(size, bold); + if (!f) return size; + return static_cast<int>(std::lround(TTF_FontLineSkip(f) / d_->scale)); +} + +int Ui::textWrapped(const std::string& s, int x, int y, int maxWidth, int size, Color c, bool bold) { + if (s.empty()) return 0; + const int lh = lineHeight(size, bold); + std::vector<std::string> lines; + size_t start = 0; + while (start <= s.size()) { + size_t nl = s.find('\n', start); + std::string para = s.substr(start, nl == std::string::npos ? std::string::npos : nl - start); + // greedy word wrap + std::string cur; + size_t i = 0; + while (i < para.size()) { + size_t sp = para.find(' ', i); + std::string word = para.substr(i, sp == std::string::npos ? std::string::npos : sp - i); + std::string cand = cur.empty() ? word : cur + " " + word; + if (!cur.empty() && textWidth(cand, size, bold) > maxWidth) { + lines.push_back(cur); + cur = word; + } else { + cur = cand; + } + // hard-break words wider than the line + while (textWidth(cur, size, bold) > maxWidth && cur.size() > 1) { + auto b = utf8Boundaries(cur); + size_t cut = 1; + for (size_t k = 1; k < b.size(); ++k) { + if (textWidth(cur.substr(0, b[k]), size, bold) > maxWidth) break; + cut = k; + } + lines.push_back(cur.substr(0, b[cut])); + cur = cur.substr(b[cut]); + } + if (sp == std::string::npos) break; + i = sp + 1; + } + lines.push_back(cur); + if (nl == std::string::npos) break; + start = nl + 1; + } + int yy = y; + for (auto& l : lines) { + text(l, x, yy, size, c, Align::Left, bold); + yy += lh; + } + return yy - y; +} + +int Ui::textEllipsized(const std::string& s, int x, int y, int maxWidth, int size, Color c, Align align, bool bold) { + if (textWidth(s, size, bold) <= maxWidth) return text(s, x, y, size, c, align, bold); + static const std::string ell = "\xE2\x80\xA6"; // … + auto b = utf8Boundaries(s); + // binary search the longest prefix that fits with the ellipsis + size_t lo = 0, hi = b.size() - 1; + while (lo < hi) { + size_t mid = (lo + hi + 1) / 2; + if (textWidth(s.substr(0, b[mid]) + ell, size, bold) <= maxWidth) lo = mid; + else hi = mid - 1; + } + std::string cut = s.substr(0, b[lo]); + while (!cut.empty() && cut.back() == ' ') cut.pop_back(); + return text(cut + ell, x, y, size, c, align, bold); +} + +void Ui::rect(int x, int y, int w, int h, Color c, bool filled, int radius) { + if (w <= 0 || h <= 0) return; + if (!filled) { + outline(x, y, w, h, c, 2, radius); + return; + } + Impl& d = *d_; + const float r = static_cast<float>(std::min(radius, std::min(w, h) / 2)); + if (r < 1.0f) { + d.fill(x, y, w, h, c); + return; + } + const int rp = d.px(r); + SDL_Texture* t = d.disc(rp); + if (!t) { + d.fill(x, y, w, h, c); + return; + } + d.setColor(t, c); + d.corners(t, rp, x, y, w, h, r); + d.fill(x + r, y, w - 2 * r, h, c); + d.fill(x, y + r, r, h - 2 * r, c); + d.fill(x + w - r, y + r, r, h - 2 * r, c); +} + +void Ui::outline(int x, int y, int w, int h, Color c, int thickness, int radius) { + if (w <= 0 || h <= 0 || thickness <= 0) return; + Impl& d = *d_; + const float t = static_cast<float>(thickness); + const float r = static_cast<float>(std::min(radius, std::min(w, h) / 2)); + if (r < t) { + d.fill(x, y, w, t, c); + d.fill(x, y + h - t, w, t, c); + d.fill(x, y + t, t, h - 2 * t, c); + d.fill(x + w - t, y + t, t, h - 2 * t, c); + return; + } + const int rp = d.px(r); + SDL_Texture* ring = d.ring(rp, d.px(t)); + if (ring) { + d.setColor(ring, c); + d.corners(ring, rp, x, y, w, h, r); + } + d.fill(x + r, y, w - 2 * r, t, c); + d.fill(x + r, y + h - t, w - 2 * r, t, c); + d.fill(x, y + r, t, h - 2 * r, c); + d.fill(x + w - t, y + r, t, h - 2 * r, c); +} + +void Ui::roundCorners(int x, int y, int w, int h, int radius, Color bg) { + Impl& d = *d_; + const float r = static_cast<float>(std::min(radius, std::min(w, h) / 2)); + if (r < 1.0f) return; + const int rp = d.px(r); + SDL_Texture* m = d.mask(rp); + if (!m) return; + d.setColor(m, bg); + const SDL_FRect dst[4] = {{(float)x, (float)y, r, r}, + {x + w - r, (float)y, r, r}, + {(float)x, y + h - r, r, r}, + {x + w - r, y + h - r, r, r}}; + const SDL_RendererFlip flips[4] = {SDL_FLIP_NONE, SDL_FLIP_HORIZONTAL, SDL_FLIP_VERTICAL, + static_cast<SDL_RendererFlip>(SDL_FLIP_HORIZONTAL | SDL_FLIP_VERTICAL)}; + for (int i = 0; i < 4; ++i) SDL_RenderCopyExF(d.renderer, m, nullptr, &dst[i], 0.0, nullptr, flips[i]); +} + +void Ui::gradient(int x, int y, int w, int h, Color top, Color bottom) { + if (w <= 0 || h <= 0) return; + SDL_Vertex v[4]; + const SDL_Color ct{top.r, top.g, top.b, top.a}, cb{bottom.r, bottom.g, bottom.b, bottom.a}; + v[0] = {{(float)x, (float)y}, ct, {0, 0}}; + v[1] = {{(float)(x + w), (float)y}, ct, {0, 0}}; + v[2] = {{(float)x, (float)(y + h)}, cb, {0, 0}}; + v[3] = {{(float)(x + w), (float)(y + h)}, cb, {0, 0}}; + const int idx[6] = {0, 1, 2, 1, 3, 2}; + SDL_SetRenderDrawBlendMode(d_->renderer, SDL_BLENDMODE_BLEND); + SDL_RenderGeometry(d_->renderer, nullptr, v, 4, idx, 6); +} + +void Ui::circle(int cx, int cy, int radius, Color c) { + if (radius <= 0) return; + Impl& d = *d_; + const int rp = d.px(static_cast<float>(radius)); + SDL_Texture* t = d.disc(rp); + if (!t) return; + d.setColor(t, c); + SDL_FRect dst{(float)(cx - radius), (float)(cy - radius), (float)radius * 2, (float)radius * 2}; + SDL_RenderCopyF(d.renderer, t, nullptr, &dst); +} + +void Ui::line(float x0, float y0, float x1, float y1, float thickness, Color c) { + const float dx = x1 - x0, dy = y1 - y0; + const float len = std::sqrt(dx * dx + dy * dy); + if (len < 0.001f) return; + const float nx = -dy / len * thickness / 2, ny = dx / len * thickness / 2; + const SDL_Color col{c.r, c.g, c.b, c.a}; + SDL_Vertex v[4] = {{{x0 + nx, y0 + ny}, col, {0, 0}}, + {{x1 + nx, y1 + ny}, col, {0, 0}}, + {{x0 - nx, y0 - ny}, col, {0, 0}}, + {{x1 - nx, y1 - ny}, col, {0, 0}}}; + const int idx[6] = {0, 1, 2, 1, 3, 2}; + SDL_SetRenderDrawBlendMode(d_->renderer, SDL_BLENDMODE_BLEND); + SDL_RenderGeometry(d_->renderer, nullptr, v, 4, idx, 6); +} + +void Ui::button(const std::string& label, int x, int y, int w, int h, bool focused) { + const int r = std::min(14, h / 2); + const int size = std::max(18, std::min(30, h * 2 / 5)); + if (focused) { + outline(x - 5, y - 5, w + 10, h + 10, withAlpha(colors::AccentBright, 200), 3, r + 5); + rect(x, y, w, h, colors::Accent, true, r); + text(label, x + w / 2, y + (h - lineHeight(size, true)) / 2, size, colors::White, Align::Center, true); + } else { + rect(x, y, w, h, colors::PanelHi, true, r); + text(label, x + w / 2, y + (h - lineHeight(size, true)) / 2, size, colors::Text, Align::Center, true); + } +} + +void Ui::tile(SDL_Texture* img, const std::string& caption, const std::string& badge, int x, int y, int w, int h, + bool focused) { + const int captionH = caption.empty() ? 0 : 58; + const int ih = h - captionH; + const int r = 12; + if (focused) outline(x - 6, y - 6, w + 12, ih + 12, colors::AccentBright, 4, r + 6); + if (img) { + // Box art from remoteImage() is already exactly w x ih; other textures get a centred + // cover crop so they are never distorted. + int tw = 0, th = 0; + SDL_QueryTexture(img, nullptr, nullptr, &tw, &th); + SDL_SetTextureColorMod(img, 255, 255, 255); + SDL_SetTextureAlphaMod(img, 255); + SDL_FRect dst{(float)x, (float)y, (float)w, (float)ih}; + if (tw > 0 && th > 0 && std::abs(static_cast<double>(tw) / th - static_cast<double>(w) / ih) > 0.01) { + SDL_Rect src{0, 0, tw, th}; + if (static_cast<double>(tw) / th > static_cast<double>(w) / ih) { + src.w = static_cast<int>(static_cast<double>(th) * w / ih); + src.x = (tw - src.w) / 2; + } else { + src.h = static_cast<int>(static_cast<double>(tw) * ih / w); + src.y = (th - src.h) / 2; + } + SDL_RenderCopyF(d_->renderer, img, &src, &dst); + } else { + SDL_RenderCopyF(d_->renderer, img, nullptr, &dst); + } + } else { + // Placeholder: panel with the title's initials. + gradient(x, y, w, ih, colors::PanelHi, colors::Panel); + std::string initials; + bool takeNext = true; + for (size_t i = 0; i < caption.size() && initials.size() < 2; ++i) { + const unsigned char ch = static_cast<unsigned char>(caption[i]); + if (ch == ' ' || ch == ':' || ch == '-') { + takeNext = true; + } else if (takeNext && std::isalnum(ch)) { + initials.push_back(static_cast<char>(std::toupper(ch))); + takeNext = false; + } else { + takeNext = false; + } + } + text(initials, x + w / 2, y + ih / 2 - 36, 64, withAlpha(colors::TextDim, 160), Align::Center, true); + } + roundCorners(x, y, w, ih, r, colors::Background); + if (!badge.empty()) { + const int bs = 18; + const int bw = textWidth(badge, bs, true) + 20; + rect(x + 10, y + 10, bw, 30, withAlpha(colors::Accent, 235), true, 15); + text(badge, x + 20, y + 10 + (30 - lineHeight(bs, true)) / 2, bs, colors::White, Align::Left, true); + } + if (captionH > 0) + textEllipsized(caption, x + 2, y + ih + 14, w - 4, 24, focused ? colors::White : colors::TextDim, + Align::Left, focused); +} + +void Ui::spinner(int cx, int cy, int radius, uint64_t tMs) { + constexpr int kDots = 12; + const int dot = std::max(3, radius / 7); + const double phase = (tMs % 1000) / 1000.0 * kDots; + for (int i = 0; i < kDots; ++i) { + const double a = (static_cast<double>(i) / kDots) * 2 * kPi - kPi / 2; + double age = phase - i; + while (age < 0) age += kDots; + const double k = 1.0 - age / kDots; // 1 = head + const uint8_t alpha = static_cast<uint8_t>(40 + 215 * k * k); + const int px = cx + static_cast<int>(std::lround(std::cos(a) * (radius - dot))); + const int py = cy + static_cast<int>(std::lround(std::sin(a) * (radius - dot))); + circle(px, py, dot, withAlpha(colors::AccentBright, alpha)); + } +} + +void Ui::qr(const std::string& data, int x, int y, int size) { + Impl& d = *d_; + const int p = d.px(static_cast<float>(size)); + const std::string key = std::to_string(p) + "|" + data; + auto it = d.qr.find(key); + if (it == d.qr.end()) { + std::vector<bool> modules; + int n = 0; + if (!qr::encode(data, modules, n) || n <= 0) { + rect(x, y, size, size, colors::White, true, 12); + text("QR unavailable", x + size / 2, y + size / 2 - 14, 24, colors::Error, Align::Center); + return; + } + // 2-module quiet zone inside the texture; the white card drawn around it adds the rest. + const int total = n + 4; + const int mod = std::max(1, p / total); + const int side = mod * total; + const int off = (p - side) / 2 + 2 * mod; + std::vector<uint32_t> pix(static_cast<size_t>(p) * p, 0xFFFFFFFFu); + for (int my = 0; my < n; ++my) + for (int mx = 0; mx < n; ++mx) { + if (!modules[static_cast<size_t>(my) * n + mx]) continue; + for (int yy = 0; yy < mod; ++yy) { + const int py = off + my * mod + yy; + if (py < 0 || py >= p) continue; + uint32_t* row = pix.data() + static_cast<size_t>(py) * p; + for (int xx = 0; xx < mod; ++xx) { + const int pxx = off + mx * mod + xx; + if (pxx >= 0 && pxx < p) row[pxx] = 0xFF000000u; + } + } + } + SDL_Texture* t = SDL_CreateTexture(d.renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, p, p); + if (!t) return; + SDL_UpdateTexture(t, nullptr, pix.data(), p * 4); + TextEntry e; + e.tex = t; + e.pw = e.ph = p; + it = d.qr.emplace(key, e).first; + } + it->second.lastUsed = d.frame; + // white rounded backing so the quiet zone reads as a card + rect(x - 12, y - 12, size + 24, size + 24, colors::White, true, 16); + SDL_FRect dst{(float)x, (float)y, (float)size, (float)size}; + SDL_RenderCopyF(d.renderer, it->second.tex, nullptr, &dst); +} + +void Ui::image(SDL_Texture* tex, int x, int y, int w, int h, bool fit) { + if (!tex || w <= 0 || h <= 0) return; + SDL_SetTextureColorMod(tex, 255, 255, 255); + SDL_SetTextureAlphaMod(tex, 255); + SDL_FRect dst{(float)x, (float)y, (float)w, (float)h}; + if (fit) { + int tw = 0, th = 0; + if (SDL_QueryTexture(tex, nullptr, nullptr, &tw, &th) != 0 || tw <= 0 || th <= 0) return; + const float s = std::min(static_cast<float>(w) / tw, static_cast<float>(h) / th); + dst.w = tw * s; + dst.h = th * s; + dst.x = x + (w - dst.w) / 2; + dst.y = y + (h - dst.h) / 2; + } + SDL_RenderCopyF(d_->renderer, tex, nullptr, &dst); +} + +SDL_Texture* Ui::textureFromImage(const std::vector<uint8_t>& bytes) { + image::Rgba img; + if (!image::decode(bytes.data(), bytes.size(), img)) return nullptr; + SDL_Texture* t = + SDL_CreateTexture(d_->renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, img.width, img.height); + if (!t) { + XC_LOGW("ui: texture %dx%d failed: %s", img.width, img.height, SDL_GetError()); + return nullptr; + } + SDL_UpdateTexture(t, nullptr, img.pixels.data(), img.width * 4); + SDL_SetTextureBlendMode(t, SDL_BLENDMODE_BLEND); + return t; +} + +SDL_Texture* Ui::remoteImage(const std::string& url, int w, int h) { + if (url.empty() || w <= 0 || h <= 0) return nullptr; + Impl& d = *d_; + const int pw = d.px(static_cast<float>(w)), ph = d.px(static_cast<float>(h)); + std::string key = url; + key += '#'; + key += std::to_string(pw); + key += 'x'; + key += std::to_string(ph); + auto it = d.images.find(key); + if (it != d.images.end()) { + ImageEntry& e = it->second; + e.lastUsed = d.frame; + if (e.state == ImageEntry::Ready) return e.tex; + if (e.state == ImageEntry::Failed) { + if (d.frame - e.failedAt < kImageRetryFrames) return nullptr; + e.state = ImageEntry::Queued; + } + } else { + ImageEntry e; + e.lastUsed = d.frame; + d.images.emplace(key, e); + } + { + std::lock_guard<std::mutex> lk(d.loader->m); + auto& job = d.loader->jobs[key]; + if (job.url.empty()) { + job.url = url; + job.pw = pw; + job.ph = ph; + } + job.wanted = d.frame; + } + d.loader->cv.notify_one(); + return nullptr; +} + +int Ui::remoteImagesPending() const { + std::lock_guard<std::mutex> lk(d_->loader->m); + return static_cast<int>(d_->loader->jobs.size()) + d_->loader->inFlight; +} + +void Ui::setClip(int x, int y, int w, int h) { + SDL_Rect r{x, y, w, h}; + SDL_RenderSetClipRect(d_->renderer, &r); +} + +void Ui::clearClip() { SDL_RenderSetClipRect(d_->renderer, nullptr); } + +int Ui::padIcon(PadIcon icon, int x, int cy, int size) { + const float r = size / 2.0f; + const float cx = x + r; + const Color bg{58, 66, 62, 255}; + const float t = std::max(2.0f, size / 11.0f); + auto pill = [&](const std::string& label) { + const int fs = std::max(12, size * 9 / 20); + const int w = std::max(size + size / 3, textWidth(label, fs, true) + size / 2); + rect(x, cy - size / 2, w, size, bg, true, size / 2); + text(label, x + w / 2, cy - lineHeight(fs, true) / 2, fs, colors::Text, Align::Center, true); + return w; + }; + switch (icon) { + case PadIcon::Cross: { + circle(static_cast<int>(cx), cy, size / 2, bg); + const float k = r * 0.42f; + const Color c{140, 170, 255, 255}; + line(cx - k, cy - k, cx + k, cy + k, t, c); + line(cx - k, cy + k, cx + k, cy - k, t, c); + return size; + } + case PadIcon::Circle: { + circle(static_cast<int>(cx), cy, size / 2, bg); + const int rr = static_cast<int>(r * 0.5f); + Impl& d = *d_; + const int rp = d.px(static_cast<float>(rr)); + if (SDL_Texture* ring = d.ring(rp, d.px(t))) { + d.setColor(ring, Color{255, 110, 110, 255}); + SDL_FRect dst{cx - rr, (float)(cy - rr), (float)rr * 2, (float)rr * 2}; + SDL_RenderCopyF(d.renderer, ring, nullptr, &dst); + } + return size; + } + case PadIcon::Square: { + circle(static_cast<int>(cx), cy, size / 2, bg); + const int k = static_cast<int>(r * 0.42f); + outline(static_cast<int>(cx) - k, cy - k, 2 * k, 2 * k, Color{240, 140, 210, 255}, static_cast<int>(t), 0); + return size; + } + case PadIcon::Triangle: { + circle(static_cast<int>(cx), cy, size / 2, bg); + const float k = r * 0.48f; + const Color c{90, 220, 180, 255}; + const float ax = cx, ay = cy - k, bx = cx - k * 0.95f, by = cy + k * 0.62f, qx = cx + k * 0.95f, qy = by; + line(ax, ay, bx, by, t, c); + line(bx, by, qx, qy, t, c); + line(qx, qy, ax, ay, t, c); + return size; + } + case PadIcon::L1: return pill("L1"); + case PadIcon::R1: return pill("R1"); + case PadIcon::PS: return pill("PS"); + case PadIcon::Options: { + const int w = size + size / 3; + rect(x, cy - size / 2, w, size, bg, true, size / 2); + const float k = size * 0.26f; + for (int i = -1; i <= 1; ++i) + line(x + w / 2.0f - k, cy + i * k * 0.7f, x + w / 2.0f + k, cy + i * k * 0.7f, t * 0.8f, colors::Text); + return w; + } + case PadIcon::Touchpad: { + const int w = size * 2; + rect(x, cy - size / 2, w, size, bg, true, size / 2); + outline(x + size / 3, cy - size / 4, w - 2 * size / 3, size / 2, colors::Text, static_cast<int>(t * 0.8f), 4); + return w; + } + } + return size; +} + +int Ui::keyCap(const std::string& label, int x, int cy, int size) { + const int fs = std::max(12, size * 9 / 20); + const int w = std::max(size, textWidth(label, fs, true) + size / 2); + rect(x, cy - size / 2, w, size, Color{58, 66, 62, 255}, true, 8); + outline(x, cy - size / 2, w, size, Color{90, 100, 95, 255}, 2, 8); + text(label, x + w / 2, cy - lineHeight(fs, true) / 2, fs, colors::Text, Align::Center, true); + return w; +} + +bool Ui::saveScreenshot(const std::string& path) { + SDL_Renderer* r = d_->renderer; + // Read the whole output in real pixels: drop the logical size (resets scale + viewport, + // the back buffer is untouched), read, restore. + SDL_RenderSetLogicalSize(r, 0, 0); + int w = 0, h = 0; + bool ok = false; + if (SDL_GetRendererOutputSize(r, &w, &h) == 0 && w > 0 && h > 0) { + if (SDL_Surface* s = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_ARGB8888)) { + ok = SDL_RenderReadPixels(r, nullptr, SDL_PIXELFORMAT_ARGB8888, s->pixels, s->pitch) == 0 && + SDL_SaveBMP(s, path.c_str()) == 0; + SDL_FreeSurface(s); + } + } + SDL_RenderSetLogicalSize(r, kWidth, kHeight); + if (ok) XC_LOGI("ui: screenshot saved to %s", path.c_str()); + else XC_LOGW("ui: screenshot failed: %s", SDL_GetError()); + return ok; +} + +void Ui::clearCache() { + Impl& d = *d_; + d.destroyMap(d.text); + d.destroyMap(d.qr); + d.widths.clear(); + for (auto& kv : d.images) + if (kv.second.tex) SDL_DestroyTexture(kv.second.tex); + d.images.clear(); + if (d.loader) { + std::lock_guard<std::mutex> lk(d.loader->m); + d.loader->jobs.clear(); + } +} + +} // namespace xc diff --git a/src/ui/ui.hpp b/src/ui/ui.hpp new file mode 100644 index 0000000..1599449 --- /dev/null +++ b/src/ui/ui.hpp @@ -0,0 +1,163 @@ +// Nubix — immediate-mode drawing helpers on SDL_Renderer + SDL2_ttf: text, rects, +// buttons, grid tiles, spinner, QR code, images. Logical resolution 1920x1080. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +struct SDL_Renderer; +struct SDL_Texture; +struct SDL_Window; + +namespace xc { + +struct Color { + uint8_t r = 255, g = 255, b = 255, a = 255; +}; + +namespace colors { +constexpr Color Background{16, 20, 18, 255}; +constexpr Color Panel{32, 38, 35, 255}; +constexpr Color Accent{36, 110, 240, 255}; // Nubix blue +constexpr Color Text{240, 245, 240, 255}; +constexpr Color TextDim{150, 160, 155, 255}; +constexpr Color Error{220, 70, 60, 255}; +// additional palette +constexpr Color PanelHi{44, 52, 48, 255}; // hovered / raised panel +constexpr Color PanelLo{24, 29, 27, 255}; // sunken panel, header bar +constexpr Color AccentBright{92, 170, 255, 255}; // focus ring, highlights +constexpr Color Warning{232, 170, 40, 255}; +constexpr Color Black{0, 0, 0, 255}; +constexpr Color White{255, 255, 255, 255}; +} // namespace colors + +// Returns c with alpha replaced. +constexpr Color withAlpha(Color c, uint8_t a) { return Color{c.r, c.g, c.b, a}; } + +enum class Align { Left, Center, Right }; + +// Controller / keyboard hint glyphs drawn by Ui::padIcon. +enum class PadIcon { Cross, Circle, Square, Triangle, L1, R1, Options, Touchpad, PS }; + +class Ui { +public: + static constexpr int kWidth = 1920; // logical width + static constexpr int kHeight = 1080; // logical height + + Ui(); + ~Ui(); + Ui(const Ui&) = delete; + Ui& operator=(const Ui&) = delete; + + // Create the window + renderer (fullscreen on PS5, resizable window on host), set logical + // size, init SDL_ttf and load the regular + bold fonts. + bool init(const std::string& fontPath, const std::string& boldFontPath, const std::string& title); + + // Destroy textures, fonts, renderer and window. + void shutdown(); + + SDL_Renderer* renderer() const; + SDL_Window* window() const; + + // Clear to the background color / present the frame. + void beginFrame(); + void endFrame(); + + // Draw UTF-8 text with its top edge at y. Glyph textures are cached per (text, size, bold, color). + // Returns the rendered width. + int text(const std::string& s, int x, int y, int size, Color c = colors::Text, Align align = Align::Left, + bool bold = false); + + // Draw text wrapped to maxWidth; returns the total height used. + int textWrapped(const std::string& s, int x, int y, int maxWidth, int size, Color c = colors::Text, + bool bold = false); + + // Width in pixels of s at the given size. + int textWidth(const std::string& s, int size, bool bold = false); + + // Filled or outlined rectangle; radius > 0 rounds the corners. + void rect(int x, int y, int w, int h, Color c, bool filled = true, int radius = 0); + + // Button with label; focused draws the highlight. + void button(const std::string& label, int x, int y, int w, int h, bool focused); + + // Grid tile with optional image (box art), caption and badge text (e.g. "Game Pass"). + void tile(SDL_Texture* image, const std::string& caption, const std::string& badge, int x, int y, int w, int h, + bool focused); + + // Animated loading spinner centred at (cx, cy); tMs = monotonic time. + void spinner(int cx, int cy, int radius, uint64_t tMs); + + // QR code of data inside a white square of side `size` at (x, y). Cached per data string. + void qr(const std::string& data, int x, int y, int size); + + // Draw a texture scaled to the rectangle (aspect-fit if fit == true). + void image(SDL_Texture* tex, int x, int y, int w, int h, bool fit = true); + + // Decode PNG/JPEG bytes (via libavcodec) into a new texture; nullptr on failure. Caller owns it. + SDL_Texture* textureFromImage(const std::vector<uint8_t>& bytes); + + // Drop cached text/QR textures (call on screen change if memory is tight). + void clearCache(); + + // ---- additions ------------------------------------------------------------------------- + + // Outlined rectangle with the given line thickness (logical px); radius > 0 rounds corners. + void outline(int x, int y, int w, int h, Color c, int thickness, int radius = 0); + + // Vertical gradient fill (top color -> bottom color). + void gradient(int x, int y, int w, int h, Color top, Color bottom); + + // Filled circle centred at (cx, cy). + void circle(int cx, int cy, int radius, Color c); + + // Thick line segment (anti-aliasing not guaranteed). + void line(float x0, float y0, float x1, float y1, float thickness, Color c); + + // Text truncated with "…" to fit maxWidth. Returns the drawn width. + int textEllipsized(const std::string& s, int x, int y, int maxWidth, int size, Color c = colors::Text, + Align align = Align::Left, bool bold = false); + + // Line height (distance between baselines) for the font size. + int lineHeight(int size, bool bold = false); + + // Controller glyph (PlayStation style) in a circle/pill of height `size`, centred + // vertically at cy, starting at x. Returns the width used. + int padIcon(PadIcon icon, int x, int cy, int size); + + // Keyboard key cap with a label (host hints), same metrics as padIcon. Returns width used. + int keyCap(const std::string& label, int x, int cy, int size); + + // Box art / remote image, decoded + scaled to exactly (w x h) logical px (cover crop) on a + // background loader thread and cached (LRU). Returns nullptr until loaded (or on failure). + // Call every frame for visible items; requests not repeated for a while are dropped. + SDL_Texture* remoteImage(const std::string& url, int w, int h); + + // Number of remote images queued or downloading. + int remoteImagesPending() const; + + // Mask the corners of a rectangle with `bg` so that content drawn inside looks rounded. + void roundCorners(int x, int y, int w, int h, int radius, Color bg); + + // Clip subsequent drawing to the rectangle (logical coords); clearClip() resets. + void setClip(int x, int y, int w, int h); + void clearClip(); + + // Save the current back buffer (call before endFrame) as a BMP file. + bool saveScreenshot(const std::string& path); + + // Monotonic frame counter (incremented by endFrame) and the output scale (pixels per + // logical px; 1.0 on PS5). + uint64_t frameCount() const; + float scale() const; + +private: + struct Impl; + std::unique_ptr<Impl> d_; +}; + +} // namespace xc diff --git a/tests/test.hpp b/tests/test.hpp new file mode 100644 index 0000000..add290c --- /dev/null +++ b/tests/test.hpp @@ -0,0 +1,79 @@ +// Nubix — tiny self-contained unit test framework (host only). +// +// XC_TEST(suite, name) { XC_CHECK(cond); XC_CHECK_EQ(a, b); XC_REQUIRE(cond); } +// +// Run: xc-tests [suite ...] (no args = all suites). Exit code = number of failed tests. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +#include <cstdio> +#include <functional> +#include <sstream> +#include <string> +#include <vector> + +namespace xctest { + +struct Failure {}; // thrown by XC_REQUIRE to abort the current test + +struct TestCase { + const char* suite; + const char* name; + std::function<void()> fn; +}; + +std::vector<TestCase>& registry(); +// Record a failed check for the currently running test. +void fail(const char* file, int line, const std::string& msg); + +struct Registrar { + Registrar(const char* suite, const char* name, std::function<void()> fn) { + registry().push_back({suite, name, std::move(fn)}); + } +}; + +template <typename T> +std::string show(const T& v) { + std::ostringstream os; + os << v; + return os.str(); +} +inline std::string show(const std::string& v) { return "\"" + v + "\""; } +inline std::string show(const char* v) { return v ? "\"" + std::string(v) + "\"" : "(null)"; } +inline std::string show(unsigned char v) { return std::to_string(static_cast<unsigned>(v)); } +inline std::string show(signed char v) { return std::to_string(static_cast<int>(v)); } +inline std::string show(bool v) { return v ? "true" : "false"; } + +} // namespace xctest + +#define XC_CAT2(a, b) a##b +#define XC_CAT(a, b) XC_CAT2(a, b) + +#define XC_TEST(suite, name) \ + static void XC_CAT(xc_test_##suite##_, name)(); \ + static ::xctest::Registrar XC_CAT(xc_reg_##suite##_, name)(#suite, #name, \ + &XC_CAT(xc_test_##suite##_, name)); \ + static void XC_CAT(xc_test_##suite##_, name)() + +#define XC_CHECK(cond) \ + do { \ + if (!(cond)) ::xctest::fail(__FILE__, __LINE__, "CHECK(" #cond ")"); \ + } while (0) + +#define XC_REQUIRE(cond) \ + do { \ + if (!(cond)) { \ + ::xctest::fail(__FILE__, __LINE__, "REQUIRE(" #cond ")"); \ + throw ::xctest::Failure{}; \ + } \ + } while (0) + +#define XC_CHECK_EQ(a, b) \ + do { \ + const auto& xc_a_ = (a); \ + const auto& xc_b_ = (b); \ + if (!(xc_a_ == xc_b_)) \ + ::xctest::fail(__FILE__, __LINE__, \ + "CHECK_EQ(" #a ", " #b "): " + ::xctest::show(xc_a_) + " != " + ::xctest::show(xc_b_)); \ + } while (0) diff --git a/tests/test_config.cpp b/tests/test_config.cpp new file mode 100644 index 0000000..21891f9 --- /dev/null +++ b/tests/test_config.cpp @@ -0,0 +1,123 @@ +// Nubix — config persistence tests. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <unistd.h> + +#include <atomic> +#include <cstdio> +#include <cstdlib> +#include <string> +#include <thread> +#include <vector> + +#include "core/config.hpp" +#include "core/gssv.hpp" +#include "test.hpp" + +using namespace xc; + +namespace { +std::string tempPath() { + char tmpl[] = "/tmp/xc-config-test-XXXXXX"; + int fd = mkstemp(tmpl); + if (fd >= 0) close(fd); + std::remove(tmpl); + return tmpl; +} +} // namespace + +XC_TEST(config, missing_file_gives_defaults_and_install_id) { + Config c(tempPath()); + XC_CHECK(c.load()); + XC_CHECK_EQ(c.settings().resolution, std::string("1080")); + XC_CHECK_EQ(c.installId().size(), size_t(36)); +} + +XC_TEST(config, roundtrip) { + const std::string path = tempPath(); + { + Config c(path); + c.load(); + c.updateSettings([](Settings& s) { + s.resolution = "720"; + s.bitrateKbps = 8000; + s.f2pFallback = false; + }); + c.updateTokens([](Tokens& t) { + t.msaRefresh = "refresh-token"; + t.gsExpiry = 1234567890123LL; + t.gamertag = "Tester"; + }); + XC_REQUIRE(c.save()); + } + Config d(path); + XC_REQUIRE(d.load()); + XC_CHECK_EQ(d.settings().resolution, std::string("720")); + XC_CHECK_EQ(d.settings().bitrateKbps, 8000); + XC_CHECK(!d.settings().f2pFallback); + XC_CHECK_EQ(d.tokens().msaRefresh, std::string("refresh-token")); + XC_CHECK_EQ(d.tokens().gsExpiry, int64_t(1234567890123LL)); + XC_CHECK_EQ(d.tokens().gamertag, std::string("Tester")); + std::remove(path.c_str()); +} + +XC_TEST(config, uuid_format) { + const std::string a = generateUuid(), b = generateUuid(); + XC_REQUIRE(a.size() == 36u); + XC_CHECK(a != b); + XC_CHECK_EQ(a[8], '-'); + XC_CHECK_EQ(a[14], '4'); +} + +// Readers take snapshots while another thread replaces the region lists and saves: no torn +// state, every snapshot is one of the published versions. +XC_TEST(config, concurrent_snapshots_and_updates) { + const std::string path = tempPath(); + Config c(path); + XC_REQUIRE(c.load()); + const uint64_t rev0 = c.revision(); + std::atomic<bool> stop{false}; + std::atomic<int> bad{0}; + std::thread writer([&] { + for (int i = 0; i < 300; ++i) { + c.updateTokens([i](Tokens& t) { + t.xcloudRegions.assign(static_cast<size_t>(1 + i % 7), Region{"R" + std::to_string(i), "https://x", false}); + t.gamertag = "G" + std::to_string(i); + }); + c.updateSettings([i](Settings& s) { s.region = "R" + std::to_string(i); }); + if (i % 50 == 0) c.save(); + } + stop = true; + }); + while (!stop) { + const Tokens t = c.tokens(); + for (const auto& r : t.xcloudRegions) + if (r.name != t.xcloudRegions.front().name) ++bad; + if (!t.xcloudRegions.empty() && "G" + t.xcloudRegions.front().name.substr(1) != t.gamertag) ++bad; + (void)c.settings().region.size(); + } + writer.join(); + XC_CHECK_EQ(bad.load(), 0); + XC_CHECK(c.revision() >= rev0 + 600); + XC_REQUIRE(c.save()); + Config d(path); + XC_REQUIRE(d.load()); + XC_CHECK_EQ(d.tokens().gamertag, std::string("G299")); + XC_CHECK_EQ(d.settings().region, std::string("R299")); + std::remove(path.c_str()); +} + +XC_TEST(config, gssv_device_tier_and_auth_rejection) { + // xhome: always the android (720) fingerprint, cloud: the user's tier + XC_CHECK_EQ(gssv::deviceTierFor(true, "1080"), std::string("720")); + XC_CHECK_EQ(gssv::deviceTierFor(true, "1080HQ"), std::string("720")); + XC_CHECK_EQ(gssv::deviceTierFor(false, "1080HQ"), std::string("1080HQ")); + XC_CHECK_EQ(std::string(gssv::osNameForResolution(gssv::deviceTierFor(true, "1080"))), std::string("android")); + XC_CHECK(gssv::deviceInfoJson(gssv::deviceTierFor(true, "1080")).find("\"android\"") != std::string::npos); + XC_CHECK(gssv::isAuthRejection(401, "")); + XC_CHECK(gssv::isAuthRejection(403, "")); + XC_CHECK(gssv::isAuthRejection(403, "<html>Forbidden</html>")); + XC_CHECK(!gssv::isAuthRejection(403, "{\"code\":\"OfferingAccessDenied\",\"message\":\"no\"}")); + XC_CHECK(!gssv::isAuthRejection(400, "")); + XC_CHECK(!gssv::isAuthRejection(200, "")); +} diff --git a/tests/test_main.cpp b/tests/test_main.cpp new file mode 100644 index 0000000..a87e1d8 --- /dev/null +++ b/tests/test_main.cpp @@ -0,0 +1,63 @@ +// Nubix — unit test runner. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <cstring> +#include <exception> +#include <set> + +#include "test.hpp" + +namespace xctest { + +namespace { +int g_currentFailures = 0; +} + +std::vector<TestCase>& registry() { + static std::vector<TestCase> r; + return r; +} + +void fail(const char* file, int line, const std::string& msg) { + ++g_currentFailures; + std::fprintf(stderr, " %s:%d: %s\n", file, line, msg.c_str()); +} + +} // namespace xctest + +int main(int argc, char** argv) { + std::set<std::string> suites; + for (int i = 1; i < argc; ++i) suites.insert(argv[i]); + if (suites.count("--list")) { + for (const auto& t : xctest::registry()) std::printf("%s.%s\n", t.suite, t.name); + return 0; + } + + int run = 0, failed = 0; + for (const auto& t : xctest::registry()) { + if (!suites.empty() && !suites.count(t.suite)) continue; + ++run; + xctest::g_currentFailures = 0; + std::printf("[ RUN ] %s.%s\n", t.suite, t.name); + try { + t.fn(); + } catch (const xctest::Failure&) { + } catch (const std::exception& e) { + xctest::fail(__FILE__, __LINE__, std::string("unexpected exception: ") + e.what()); + } catch (...) { + xctest::fail(__FILE__, __LINE__, "unexpected non-std exception"); + } + if (xctest::g_currentFailures) { + ++failed; + std::printf("[ FAIL ] %s.%s\n", t.suite, t.name); + } else { + std::printf("[ OK ] %s.%s\n", t.suite, t.name); + } + } + std::printf("%d tests, %d failed\n", run, failed); + if (run == 0) { + std::fprintf(stderr, "no tests matched\n"); + return 1; + } + return failed > 255 ? 255 : failed; +} diff --git a/tests/test_net.cpp b/tests/test_net.cpp new file mode 100644 index 0000000..d6b8e78 --- /dev/null +++ b/tests/test_net.cpp @@ -0,0 +1,73 @@ +// Nubix — resolver fallbacks used by libjuice/libdatachannel on PS5 (net_compat.c). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/in.h> + +#include <cstring> +#include <string> + +#include "platform/net_compat.h" +#include "test.hpp" + +XC_TEST(net, passive_null_node_gives_ipv4_any) { + addrinfo hints{}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; + addrinfo* res = nullptr; + XC_CHECK_EQ(xc_getaddrinfo(nullptr, "0", &hints, &res), 0); + XC_CHECK(res != nullptr); + XC_CHECK_EQ(res->ai_family, AF_INET); + XC_CHECK_EQ(res->ai_socktype, SOCK_DGRAM); + auto* sin = reinterpret_cast<sockaddr_in*>(res->ai_addr); + XC_CHECK_EQ(sin->sin_addr.s_addr, htonl(INADDR_ANY)); + XC_CHECK_EQ(sin->sin_port, htons(0)); + XC_CHECK(res->ai_next == nullptr); + xc_freeaddrinfo(res); +} + +XC_TEST(net, numeric_hosts) { + addrinfo hints{}; + hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; + hints.ai_socktype = SOCK_DGRAM; + addrinfo* res = nullptr; + XC_CHECK_EQ(xc_getaddrinfo("20.40.60.80", "9002", &hints, &res), 0); + XC_CHECK_EQ(res->ai_family, AF_INET); + XC_CHECK_EQ(reinterpret_cast<sockaddr_in*>(res->ai_addr)->sin_port, htons(9002)); + char host[64], serv[16]; + XC_CHECK_EQ(xc_getnameinfo(res->ai_addr, res->ai_addrlen, host, sizeof host, serv, sizeof serv, + NI_NUMERICHOST | NI_NUMERICSERV), 0); + XC_CHECK_EQ(std::string(host), std::string("20.40.60.80")); + XC_CHECK_EQ(std::string(serv), std::string("9002")); + xc_freeaddrinfo(res); + + res = nullptr; + XC_CHECK_EQ(xc_getaddrinfo("2001:0:4137:9e76::1", "1", &hints, &res), 0); + XC_CHECK_EQ(res->ai_family, AF_INET6); + xc_freeaddrinfo(res); + + res = nullptr; + XC_CHECK(xc_getaddrinfo("example.com", "1", &hints, &res) != 0); // AI_NUMERICHOST: no lookup + XC_CHECK(res == nullptr); +} + +XC_TEST(net, family_filter) { + addrinfo hints{}; + hints.ai_family = AF_INET6; + hints.ai_flags = AI_NUMERICHOST; + addrinfo* res = nullptr; + XC_CHECK(xc_getaddrinfo("1.2.3.4", "1", &hints, &res) != 0); +} + +XC_TEST(net, ifaddrs_has_usable_ipv4) { + ifaddrs* list = nullptr; + XC_CHECK_EQ(xc_getifaddrs(&list), 0); + bool found = false; + for (ifaddrs* i = list; i; i = i->ifa_next) + if (i->ifa_addr && i->ifa_addr->sa_family == AF_INET && !(i->ifa_flags & IFF_LOOPBACK)) found = true; + XC_CHECK(found); + xc_freeifaddrs(list); +} diff --git a/tests/test_protocol.cpp b/tests/test_protocol.cpp new file mode 100644 index 0000000..d9f0f71 --- /dev/null +++ b/tests/test_protocol.cpp @@ -0,0 +1,544 @@ +// Nubix — protocol byte-level tests (input packets, vibration, teredo, JSON messages). +// Expected values derived from green-nx and xbox-xcloud-player. +// Portions derived from green-nx (https://github.com/rmrf404/green-nx), Copyright (C) the green-nx authors, GPL-3.0; modified by Nubix contributors, 2026. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <cstring> + +#include <nlohmann/json.hpp> + +#include "core/protocol.hpp" +#include "test.hpp" + +using namespace xc; +using nlohmann::json; + +namespace { +uint16_t u16(const std::vector<uint8_t>& b, size_t o) { return uint16_t(b[o] | (b[o + 1] << 8)); } +uint32_t u32(const std::vector<uint8_t>& b, size_t o) { + return uint32_t(b[o]) | (uint32_t(b[o + 1]) << 8) | (uint32_t(b[o + 2]) << 16) | (uint32_t(b[o + 3]) << 24); +} +} // namespace + +XC_TEST(protocol, client_metadata_layout) { + proto::InputSerializer s; + auto p = s.clientMetadata(1); + XC_REQUIRE(p.size() == 15u); + XC_CHECK_EQ(u16(p, 0), uint16_t(proto::report::ClientMetadata)); + XC_CHECK_EQ(p[14], uint8_t(1)); +} + +XC_TEST(protocol, gamepad_layout) { + proto::InputSerializer s; + s.clientMetadata(1); + GamepadState st; + st.buttons = btn::A | btn::DUp | btn::RS; + st.lx = 1000; st.ly = -2000; st.rx = 32767; st.ry = -32767; + st.lt = 65535; st.rt = 1234; + auto p = s.gamepad(st, 0); + XC_REQUIRE(p.size() == 38u); + XC_CHECK_EQ(u16(p, 0), uint16_t(proto::report::Gamepad)); + XC_CHECK_EQ(p[14], uint8_t(1)); // count + XC_CHECK_EQ(p[15], uint8_t(0)); // gamepad index + XC_CHECK_EQ(u16(p, 16), uint16_t(btn::A | btn::DUp | btn::RS)); + XC_CHECK_EQ(int16_t(u16(p, 18)), int16_t(1000)); + XC_CHECK_EQ(int16_t(u16(p, 20)), int16_t(-2000)); + XC_CHECK_EQ(int16_t(u16(p, 22)), int16_t(32767)); + XC_CHECK_EQ(int16_t(u16(p, 24)), int16_t(-32767)); + XC_CHECK_EQ(u16(p, 26), uint16_t(65535)); + XC_CHECK_EQ(u16(p, 28), uint16_t(1234)); + XC_CHECK_EQ(u32(p, 30), 1u); // PhysicalPhysicality, LE 1 + // VirtualPhysicality: big-endian 1 -> bytes 00 00 00 01 + XC_CHECK_EQ(p[34], uint8_t(0)); + XC_CHECK_EQ(p[35], uint8_t(0)); + XC_CHECK_EQ(p[36], uint8_t(0)); + XC_CHECK_EQ(p[37], uint8_t(1)); +} + +XC_TEST(protocol, sequence_increments_and_rollback) { + proto::InputSerializer s; + auto a = s.clientMetadata(1); + auto b = s.gamepad(GamepadState{}, 0); + auto c = s.gamepad(GamepadState{}, 0); + XC_REQUIRE(a.size() >= 6 && b.size() >= 6 && c.size() >= 6); + XC_CHECK_EQ(u32(b, 2), u32(a, 2) + 1); + XC_CHECK_EQ(u32(c, 2), u32(b, 2) + 1); + s.rollback(); + auto d = s.gamepad(GamepadState{}, 0); + XC_REQUIRE(d.size() >= 6); + XC_CHECK_EQ(u32(d, 2), u32(c, 2)); +} + +XC_TEST(protocol, timestamp_is_monotonic_double) { + proto::InputSerializer s; + auto a = s.gamepad(GamepadState{}, 0); + auto b = s.gamepad(GamepadState{}, 0); + XC_REQUIRE(a.size() >= 14 && b.size() >= 14); + double ta, tb; + std::memcpy(&ta, &a[6], 8); + std::memcpy(&tb, &b[6], 8); + XC_CHECK(ta >= 0.0); + XC_CHECK(tb >= ta); +} + +XC_TEST(protocol, parse_vibration) { + const uint8_t pkt[] = {0x80, 0x00, 0x00, 0x00, 100, 50, 10, 20, 0xE8, 0x03, 0x05, 0x00, 0x02}; + Vibration v; + XC_REQUIRE(proto::parseVibration(pkt, sizeof(pkt), v)); + XC_CHECK_EQ(v.pad, uint8_t(0)); + XC_CHECK_EQ(v.left, uint8_t(100)); + XC_CHECK_EQ(v.right, uint8_t(50)); + XC_CHECK_EQ(v.lt, uint8_t(10)); + XC_CHECK_EQ(v.rt, uint8_t(20)); + XC_CHECK_EQ(v.durationMs, uint16_t(1000)); + XC_CHECK_EQ(v.delayMs, uint16_t(5)); + XC_CHECK_EQ(v.repeat, uint8_t(2)); +} + +XC_TEST(protocol, parse_vibration_rejects_other_reports) { + const uint8_t pkt[] = {0x10, 0x00, 0x38, 0x04, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00}; + Vibration v; + XC_CHECK(!proto::parseVibration(pkt, sizeof(pkt), v)); + XC_CHECK(!proto::parseVibration(pkt, 1, v)); +} + +XC_TEST(protocol, parse_server_metadata) { + // reportType 16, u32 height @2, u32 width @6 + const uint8_t pkt[] = {0x10, 0x00, 0x38, 0x04, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00}; + uint32_t w = 0, h = 0; + XC_REQUIRE(proto::parseServerMetadata(pkt, sizeof(pkt), w, h)); + XC_CHECK_EQ(w, 1920u); + XC_CHECK_EQ(h, 1080u); +} + +XC_TEST(protocol, teredo_rfc4380_example) { + std::string ip; + uint16_t port = 0; + XC_REQUIRE(proto::teredoDecode("2001:0000:4136:e378:8000:63bf:3fff:fdd2", ip, port)); + XC_CHECK_EQ(ip, std::string("192.0.2.45")); + XC_CHECK_EQ(port, uint16_t(40000)); + XC_REQUIRE(proto::teredoDecode("2001:0:4136:e378:8000:63bf:3fff:fdd2", ip, port)); + XC_CHECK_EQ(ip, std::string("192.0.2.45")); + XC_CHECK_EQ(port, uint16_t(40000)); +} + +XC_TEST(protocol, teredo_rejects_non_teredo) { + std::string ip; + uint16_t port = 0; + XC_CHECK(!proto::teredoDecode("2a01:4f8::1", ip, port)); + XC_CHECK(!proto::teredoDecode("192.168.1.1", ip, port)); + XC_CHECK(!proto::teredoDecode("garbage", ip, port)); +} + +XC_TEST(protocol, teredo_candidate_expansion) { + std::vector<std::string> in = { + "a=candidate:1 1 UDP 100 2001:0:4136:e378:8000:63bf:3fff:fdd2 1234 typ host", + "a=candidate:2 1 UDP 50 20.1.2.3 1000 typ host"}; + auto out = proto::expandTeredoCandidates(in); + bool sawOrig = false, saw40000 = false, saw9002 = false, sawPlain = false; + for (const auto& c : out) { + if (c.find("2001:0:4136:e378") != std::string::npos) sawOrig = true; + if (c.find(" 192.0.2.45 40000 ") != std::string::npos) saw40000 = true; + if (c.find(" 192.0.2.45 9002 ") != std::string::npos) saw9002 = true; + if (c.find(" 20.1.2.3 1000 ") != std::string::npos) sawPlain = true; + } + XC_CHECK(sawOrig); + XC_CHECK(saw40000); + XC_CHECK(saw9002); + XC_CHECK(sawPlain); +} + +XC_TEST(protocol, control_messages_are_json) { + auto kf = json::parse(proto::controlKeyframeRequest(), nullptr, false); + XC_REQUIRE(!kf.is_discarded()); + XC_CHECK_EQ(kf.value("message", std::string()), std::string("videoKeyframeRequested")); + XC_CHECK(kf.value("ifrRequested", false)); + XC_CHECK(!json::parse(proto::controlAuthorization(), nullptr, false).is_discarded()); + XC_CHECK(!json::parse(proto::controlGamepadChanged(0, true), nullptr, false).is_discarded()); + XC_CHECK(!json::parse(proto::controlResolution("1080"), nullptr, false).is_discarded()); + XC_CHECK(!json::parse(proto::messageHandshake(), nullptr, false).is_discarded()); +} + +XC_TEST(protocol, message_init_sequence) { + Settings s; + auto msgs = proto::messageInitSequence(s, "00000000-0000-4000-8000-000000000000", 1920, 1080); + XC_REQUIRE(!msgs.empty()); + for (const auto& m : msgs) XC_CHECK(!json::parse(m, nullptr, false).is_discarded()); + auto tc = json::parse(proto::transactionComplete("abc", "{}"), nullptr, false); + XC_CHECK(!tc.is_discarded()); +} + +XC_TEST(protocol, ice_post_body_green_nx_shape) { + std::vector<std::string> cands = {"candidate:1 1 UDP 2122260223 192.168.1.2 50000 typ host"}; + auto body = json::parse(proto::icePostBody(cands, "UFRAG"), nullptr, false); + XC_REQUIRE(!body.is_discarded()); + XC_CHECK_EQ(body.value("messageType", std::string()), std::string("iceCandidate")); + XC_REQUIRE(body.contains("candidate") && body["candidate"].is_array()); + XC_REQUIRE(body["candidate"].size() == 2u); // candidate + end-of-candidates + auto first = json::parse(body["candidate"][0].get<std::string>(), nullptr, false); + XC_REQUIRE(!first.is_discarded()); + XC_CHECK_EQ(first.value("candidate", std::string()), cands[0]); + XC_CHECK_EQ(first.value("sdpMid", std::string()), std::string("0")); + XC_CHECK_EQ(first.value("sdpMLineIndex", -1), 0); + XC_CHECK_EQ(first.value("usernameFragment", std::string()), std::string("UFRAG")); + auto last = json::parse(body["candidate"][1].get<std::string>(), nullptr, false); + XC_CHECK_EQ(last.value("candidate", std::string()), std::string("a=end-of-candidates")); +} + +// --------------------------------------------------------------------------------------------- +// Byte-exact vectors (derived from green-nx xcloud_protocol.cpp / xcloud-player packet.ts) +// --------------------------------------------------------------------------------------------- + +namespace { +// Compare everything except the 8 timestamp bytes at [6, 14). +bool sameIgnoringTimestamp(const std::vector<uint8_t>& got, const std::vector<uint8_t>& want) { + if (got.size() != want.size()) return false; + for (size_t i = 0; i < got.size(); ++i) { + if (i >= 6 && i < 14) continue; + if (got[i] != want[i]) return false; + } + return true; +} +std::string hex(const std::vector<uint8_t>& b) { + static const char* d = "0123456789abcdef"; + std::string s; + for (uint8_t c : b) { + s += d[c >> 4]; + s += d[c & 15]; + s += ' '; + } + return s; +} +} // namespace + +XC_TEST(protocol, client_metadata_exact_bytes_green_nx_sequence) { + proto::InputSerializer s; + XC_CHECK_EQ(s.nextSequence(), 1u); + auto p = s.clientMetadata(0); + // u16 8 | u32 seq=1 (green-nx pre-increments) | f64 ts | u8 maxTouchpoints + const std::vector<uint8_t> want = {0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0x00}; + XC_CHECK(sameIgnoringTimestamp(p, want)); + if (!sameIgnoringTimestamp(p, want)) std::printf(" got %s\n want %s\n", hex(p).c_str(), hex(want).c_str()); + XC_CHECK_EQ(s.nextSequence(), 2u); +} + +XC_TEST(protocol, gamepad_exact_bytes) { + proto::InputSerializer s; + s.clientMetadata(1); // seq 1 + GamepadState st; + st.buttons = btn::Nexus | btn::A | btn::LB | btn::RS; // 0x9012 + st.lx = 0x1234; st.ly = -1; st.rx = -32767; st.ry = 32767; + st.lt = 0xABCD; st.rt = 0x0001; + auto p = s.gamepad(st, 2); + const std::vector<uint8_t> want = { + 0x02, 0x00, // reportType Gamepad + 0x02, 0x00, 0x00, 0x00, // seq 2 + 0, 0, 0, 0, 0, 0, 0, 0, // timestamp (ignored) + 0x01, // count + 0x02, // +0 index + 0x12, 0x90, // +1 buttons + 0x34, 0x12, // +3 lx + 0xFF, 0xFF, // +5 ly = -1 + 0x01, 0x80, // +7 rx = -32767 + 0xFF, 0x7F, // +9 ry = 32767 + 0xCD, 0xAB, // +11 lt + 0x01, 0x00, // +13 rt + 0x01, 0x00, 0x00, 0x00, // +15 PhysicalPhysicality LE 1 + 0x00, 0x00, 0x00, 0x01, // +19 VirtualPhysicality BE 1 + }; + XC_REQUIRE(want.size() == proto::kGamepadPacketSize); + XC_CHECK(sameIgnoringTimestamp(p, want)); + if (!sameIgnoringTimestamp(p, want)) std::printf(" got %s\n want %s\n", hex(p).c_str(), hex(want).c_str()); +} + +XC_TEST(protocol, rollback_never_underflows_and_reset) { + proto::InputSerializer s; + s.rollback(); + XC_CHECK_EQ(s.nextSequence(), 1u); + s.gamepad(GamepadState{}, 0); + s.gamepad(GamepadState{}, 0); + XC_CHECK_EQ(s.nextSequence(), 3u); + s.rollback(); + XC_CHECK_EQ(s.nextSequence(), 2u); + auto p = s.gamepad(GamepadState{}, 0); + XC_CHECK_EQ(u32(p, 2), 2u); + s.reset(); + XC_CHECK_EQ(s.nextSequence(), 1u); + XC_CHECK_EQ(u32(s.clientMetadata(), 2), 1u); +} + +XC_TEST(protocol, control_messages_exact) { + XC_CHECK_EQ(proto::controlAuthorization(), + std::string(R"({"accessKey":"4BDB3609-C1F1-4195-9B37-FEFF45DA8B8E","message":"authorizationRequest"})")); + XC_CHECK_EQ(proto::controlGamepadChanged(0, true), + std::string(R"({"gamepadIndex":0,"message":"gamepadChanged","wasAdded":true})")); + XC_CHECK_EQ(proto::controlGamepadChanged(1, false), + std::string(R"({"gamepadIndex":1,"message":"gamepadChanged","wasAdded":false})")); + XC_CHECK_EQ(proto::controlResolution("1080HQ"), + std::string(R"({"message":"userRequestedResolutionUpdate","resolutionAlias":"1080HQ"})")); + XC_CHECK_EQ(proto::controlKeyframeRequest(), + std::string(R"({"ifrRequested":true,"message":"videoKeyframeRequested"})")); +} + +namespace { +bool isUuidV4(const std::string& u) { + if (u.size() != 36) return false; + for (size_t i = 0; i < u.size(); ++i) { + if (i == 8 || i == 13 || i == 18 || i == 23) { + if (u[i] != '-') return false; + } else if (!((u[i] >= '0' && u[i] <= '9') || (u[i] >= 'a' && u[i] <= 'f'))) { + return false; + } + } + return u[14] == '4' && (u[19] == '8' || u[19] == '9' || u[19] == 'a' || u[19] == 'b'); +} +} // namespace + +XC_TEST(protocol, uuid_and_handshake) { + auto a = proto::newUuid(), b = proto::newUuid(); + XC_CHECK(isUuidV4(a)); + XC_CHECK(isUuidV4(b)); + XC_CHECK(a != b); + auto h = json::parse(proto::messageHandshake()); + XC_CHECK_EQ(h["type"].get<std::string>(), std::string("Handshake")); + XC_CHECK_EQ(h["version"].get<std::string>(), std::string("messageV1")); + XC_CHECK_EQ(h["cv"].get<std::string>(), std::string("0")); + XC_CHECK(isUuidV4(h["id"].get<std::string>())); + XC_CHECK_EQ(h.size(), size_t(4)); +} + +XC_TEST(protocol, message_init_sequence_exact_content) { + Settings s; + s.bitrateKbps = 15000; + const std::string install = "c97d7ee0-73b2-4239-bf1d-9d805a338429"; + auto msgs = proto::messageInitSequence(s, install, 1280, 720); + XC_REQUIRE(msgs.size() == 6u); + const char* targets[] = {"/streaming/systemUi/configuration", + "/streaming/properties/clientappinstallidchanged", + "/streaming/characteristics/orientationchanged", + "/streaming/characteristics/touchinputenabledchanged", + "/streaming/characteristics/clientdevicecapabilities", + "/streaming/characteristics/dimensionschanged"}; + const char* contents[] = { + R"({"systemUis":[],"version":[0,2,0]})", + R"({"clientAppInstallId":"c97d7ee0-73b2-4239-bf1d-9d805a338429"})", + R"({"orientation":0})", + R"({"touchInputEnabled":false})", + R"({"maxBitrateKbps":15000,"maxHeight":720,"maxWidth":1280,"supportsCustomResolution":true,"supportsFps":60,"supportsHdr":false,"supportsHevc":false,"video":{"height":720,"maxBitrateKbps":15000,"maxHeight":720,"maxWidth":1280,"width":1280}})", + R"({"horizontal":1280,"preferredHeight":720,"preferredWidth":1280,"safeAreaBottom":720,"safeAreaLeft":0,"safeAreaRight":1280,"safeAreaTop":0,"supportsCustomResolution":true,"vertical":720})", + }; + std::vector<std::string> ids; + for (size_t i = 0; i < msgs.size(); ++i) { + auto j = json::parse(msgs[i]); + XC_CHECK_EQ(j.size(), size_t(5)); + XC_CHECK_EQ(j["type"].get<std::string>(), std::string("Message")); + XC_CHECK_EQ(j["target"].get<std::string>(), std::string(targets[i])); + XC_CHECK_EQ(j["cv"].get<std::string>(), std::string("")); + XC_REQUIRE(j["content"].is_string()); // content is a JSON *string* + XC_CHECK_EQ(j["content"].get<std::string>(), std::string(contents[i])); + XC_CHECK(isUuidV4(j["id"].get<std::string>())); + ids.push_back(j["id"].get<std::string>()); + } + for (size_t i = 0; i < ids.size(); ++i) + for (size_t k = i + 1; k < ids.size(); ++k) XC_CHECK(ids[i] != ids[k]); + + // empty install id -> generated uuid + auto gen = proto::messageInitSequence(s, "", 1920, 1080); + auto c = json::parse(json::parse(gen[1])["content"].get<std::string>()); + XC_CHECK(isUuidV4(c["clientAppInstallId"].get<std::string>())); +} + +XC_TEST(protocol, transaction_complete_and_unhandled_exact) { + XC_CHECK_EQ(proto::transactionComplete("abc", R"({"Result":0})"), + std::string(R"({"content":"{\"Result\":0}","cv":"","id":"abc","type":"TransactionComplete"})")); + XC_CHECK_EQ(proto::transactionComplete("x", "{}", "cv.1"), + std::string(R"({"content":"{}","cv":"cv.1","id":"x","type":"TransactionComplete"})")); + XC_CHECK_EQ(proto::messageUnhandled("i", "/t", "c"), + std::string(R"({"cv":"c","id":"i","target":"/t","type":"Unhandled"})")); + auto env = json::parse(proto::messageEnvelope("/a/b", R"({"k":1})")); + XC_CHECK_EQ(env["content"].get<std::string>(), std::string(R"({"k":1})")); + XC_CHECK_EQ(env["target"].get<std::string>(), std::string("/a/b")); +} + +XC_TEST(protocol, parse_channel_message) { + proto::ChannelMessage m; + XC_REQUIRE(proto::parseChannelMessage( + R"({"type":"TransactionStart","id":"42","target":"/streaming/systemUi/messages/ShowMessageDialog","content":"{\"TitleText\":\"Hi\"}","cv":"abc.1"})", + m)); + XC_CHECK_EQ(m.type, std::string("TransactionStart")); + XC_CHECK_EQ(m.id, std::string("42")); + XC_CHECK_EQ(m.target, std::string(proto::target::kShowMessageDialog)); + XC_CHECK_EQ(m.content, std::string(R"({"TitleText":"Hi"})")); + XC_CHECK_EQ(m.cv, std::string("abc.1")); + XC_REQUIRE(proto::parseChannelMessage(R"({"type":"Message","content":{"reason":"x"}})", m)); + XC_CHECK_EQ(m.content, std::string(R"({"reason":"x"})")); + XC_CHECK(m.id.empty()); + XC_CHECK(proto::isHandshakeAck(R"({"type":"HandshakeAck","version":"messageV1","id":"x","cv":"0"})")); + XC_CHECK(!proto::isHandshakeAck(R"({"type":"Handshake"})")); + XC_CHECK(!proto::isHandshakeAck("not json")); + XC_CHECK(!proto::parseChannelMessage("[1,2]", m)); + XC_CHECK(!proto::parseChannelMessage(R"({"type":5})", m)); +} + +XC_TEST(protocol, parse_vibration_after_server_metadata) { + // reportType 0x90 = ServerMetadata | Vibration: 8 bytes of size, then the vibration section + const uint8_t pkt[] = {0x90, 0x00, 0xD0, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x01, 7, 8, 9, 10, 0x10, 0x00, 0x20, 0x00, 3}; + Vibration v; + XC_REQUIRE(proto::parseVibration(pkt, sizeof(pkt), v)); + XC_CHECK_EQ(v.pad, uint8_t(1)); + XC_CHECK_EQ(v.left, uint8_t(7)); + XC_CHECK_EQ(v.right, uint8_t(8)); + XC_CHECK_EQ(v.lt, uint8_t(9)); + XC_CHECK_EQ(v.rt, uint8_t(10)); + XC_CHECK_EQ(v.durationMs, uint16_t(16)); + XC_CHECK_EQ(v.delayMs, uint16_t(32)); + XC_CHECK_EQ(v.repeat, uint8_t(3)); + uint32_t w = 0, h = 0; + XC_REQUIRE(proto::parseServerMetadata(pkt, sizeof(pkt), w, h)); + XC_CHECK_EQ(w, 1280u); + XC_CHECK_EQ(h, 720u); + XC_CHECK(!proto::parseVibration(pkt, sizeof(pkt) - 1, v)); // truncated + const uint8_t only[] = {0x80, 0x00, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0}; // 12 bytes, one short + XC_CHECK(!proto::parseVibration(only, sizeof(only), v)); + XC_CHECK(!proto::parseServerMetadata(only, sizeof(only), w, h)); + XC_CHECK(!proto::parseVibration(nullptr, 0, v)); +} + +XC_TEST(protocol, teredo_forms) { + std::string ip; + uint16_t port = 0; + // compressed zero run + upper-case + zone id + XC_REQUIRE(proto::teredoDecode("2001::4136:E378:8000:63BF:3FFF:FDD2", ip, port)); + XC_CHECK_EQ(ip, std::string("192.0.2.45")); + XC_CHECK_EQ(port, uint16_t(40000)); + XC_REQUIRE(proto::teredoDecode(" 2001:0:4136:e378:8000:63bf:3fff:fdd2%en0 ", ip, port)); + XC_CHECK_EQ(ip, std::string("192.0.2.45")); + // all-ones fields invert to zero + XC_REQUIRE(proto::teredoDecode("2001:0:0:0:0:ffff:ffff:ffff", ip, port)); + XC_CHECK_EQ(ip, std::string("0.0.0.0")); + XC_CHECK_EQ(port, uint16_t(0)); + // 2001:db8::/32 is documentation space, not Teredo + XC_CHECK(!proto::teredoDecode("2001:db8::1", ip, port)); + XC_CHECK(ip.empty()); + XC_CHECK(!proto::teredoDecode("", ip, port)); +} + +XC_TEST(protocol, teredo_expansion_exact) { + std::vector<std::string> in = { + "a=candidate:1 1 UDP 100 13.104.1.2 1234 typ host", + "a=candidate:2 1 UDP 2130706431 2001:0:4136:e378:8000:63bf:3fff:fdd2 9002 typ host", + "candidate:3 1 udp 50 2001:0:4136:e378:8000:63bf:3fff:fdd2 9003 typ srflx raddr 0.0.0.0 rport 0", + "a=end-of-candidates", + }; + auto out = proto::expandTeredoCandidates(in); + const std::vector<std::string> want = { + "a=candidate:1 1 UDP 100 13.104.1.2 1234 typ host", + "a=candidate:20 1 UDP 1 192.0.2.45 40000 typ host", + "a=candidate:21 1 UDP 1 192.0.2.45 9002 typ host", + "a=candidate:2 1 UDP 2130706431 2001:0:4136:e378:8000:63bf:3fff:fdd2 9002 typ host", + "candidate:22 1 UDP 1 192.0.2.45 40000 typ host", + "candidate:23 1 UDP 1 192.0.2.45 9002 typ host", + "candidate:3 1 udp 50 2001:0:4136:e378:8000:63bf:3fff:fdd2 9003 typ srflx raddr 0.0.0.0 rport 0", + "a=end-of-candidates", + }; + XC_REQUIRE(out.size() == want.size()); + for (size_t i = 0; i < want.size(); ++i) XC_CHECK_EQ(out[i], want[i]); +} + +XC_TEST(protocol, ice_post_body_exact) { + std::vector<std::string> cands = { + "a=candidate:1 1 UDP 2122260223 192.168.1.2 50000 typ host\r\n", + "candidate:2 1 TCP 1518280447 192.168.1.2 9 typ host tcptype active", + "garbage", + }; + const std::string want = + R"({"candidate":[)" + R"("{\"candidate\":\"candidate:1 1 UDP 2122260223 192.168.1.2 50000 typ host\",\"sdpMLineIndex\":0,\"sdpMid\":\"0\",\"usernameFragment\":\"Uf\"}",)" + R"("{\"candidate\":\"a=end-of-candidates\",\"sdpMLineIndex\":0,\"sdpMid\":\"0\",\"usernameFragment\":\"Uf\"}"],)" + R"("messageType":"iceCandidate"})"; + XC_CHECK_EQ(proto::icePostBody(cands, "Uf"), want); +} + +XC_TEST(protocol, sdp_post_body_exact) { + const std::string body = proto::sdpPostBody("v=0\r\n"); + const std::string want = + R"({"configuration":{"chat":{"maxVersion":1,"minVersion":1},)" + R"("chatConfiguration":{"bytesPerSample":2,"expectedClipDurationMs":20,"format":{"codec":"opus","container":"webm"},"numChannels":1,"sampleFrequencyHz":24000},)" + R"("control":{"maxVersion":3,"minVersion":1},"input":{"maxVersion":9,"minVersion":1},"message":{"maxVersion":1,"minVersion":1},)" + R"("reliableinput":{"maxVersion":9,"minVersion":9},"unreliableinput":{"maxVersion":9,"minVersion":9}},)" + R"("messageType":"offer","requestId":"1","sdp":"v=0\r\n"})"; + XC_CHECK_EQ(body, want); +} + +namespace { +std::string expectedOffer(const char* profile, const char* caps) { + return std::string("v=0\r\n" + "o=- 4611731400430051 2 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "t=0 0\r\n" + "a=group:BUNDLE video audio 0\r\n" + "a=ice-ufrag:uFrAg\r\n" + "a=ice-pwd:pWd\r\n" + "a=fingerprint:sha-256 AB:CD\r\n" + "a=setup:actpass\r\n" + "m=video 9 UDP/TLS/RTP/SAVPF 102\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=mid:video\r\n" + "a=rtcp-mux\r\n" + "a=rtcp-rsize\r\n" + "a=rtpmap:102 H264/90000\r\n" + "a=fmtp:102 level-asymmetry-allowed=0;packetization-mode=1;profile-level-id=") + + profile + ";" + caps + + "\r\n" + "a=rtcp-fb:102 goog-remb\r\n" + "a=rtcp-fb:102 ccm fir\r\n" + "a=rtcp-fb:102 nack\r\n" + "a=rtcp-fb:102 nack pli\r\n" + "a=recvonly\r\n" + "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=mid:audio\r\n" + "a=rtcp-mux\r\n" + "a=rtcp-rsize\r\n" + "a=rtpmap:111 opus/48000/2\r\n" + "a=fmtp:111 minptime=10;useinbandfec=1;stereo=1\r\n" + "a=recvonly\r\n" + "m=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=mid:0\r\n" + "a=sctp-port:5000\r\n" + "a=max-message-size:262144\r\n"; +} +} // namespace + +XC_TEST(protocol, offer_exact_template) { + XC_CHECK_EQ(proto::buildOffer("uFrAg", "pWd", "AB:CD", false, "1080"), + expectedOffer("42e01f", "max-fs=8160;max-mbps=489600")); + XC_CHECK_EQ(proto::buildOffer("uFrAg", "pWd", "AB:CD", false, "1080HQ"), + expectedOffer("42e01f", "max-fs=8160;max-mbps=489600")); + XC_CHECK_EQ(proto::buildOffer("uFrAg", "pWd", "AB:CD", false, "720"), + expectedOffer("42e01f", "max-fs=3600;max-mbps=108000")); + XC_CHECK_EQ(proto::buildOffer("uFrAg", "pWd", "AB:CD", true, "1080"), + expectedOffer("42e020", "max-fs=3600;max-mbps=108000")); + XC_CHECK_EQ(proto::buildOffer("uFrAg\r\n", " pWd", "sha-256 AB:CD", true, "720"), + expectedOffer("42e020", "max-fs=3600;max-mbps=108000")); + XC_CHECK(proto::buildOffer("", "p", "AB", false, "720").empty()); + XC_CHECK(proto::buildOffer("u", "", "AB", false, "720").empty()); + XC_CHECK(proto::buildOffer("u", "p", "", false, "720").empty()); +} + +XC_TEST(protocol, invalid_utf8_never_throws) { + bool threw = false; + std::string out; + try { + out = proto::transactionComplete(std::string("id\xff"), std::string("{\"x\":\"\xc3\"}")); + out += proto::messageUnhandled(std::string("\xfe"), "/t"); + out += proto::controlResolution(std::string("\xff")); + out += proto::icePostBody({std::string("candidate:1 1 UDP 1 1.2.3.4 5 typ host \xff")}, std::string("\xff")); + } catch (...) { + threw = true; + } + XC_CHECK(!threw); + XC_CHECK(!out.empty()); +} diff --git a/tests/test_ratelimit.cpp b/tests/test_ratelimit.cpp new file mode 100644 index 0000000..4fc0a9d --- /dev/null +++ b/tests/test_ratelimit.cpp @@ -0,0 +1,96 @@ +// Nubix — request pacing tests (token bucket, 429 cooldown/backoff, session start cap). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <string> + +#include "core/ratelimit.hpp" +#include "test.hpp" + +using namespace xc; + +XC_TEST(ratelimit, url_host) { + XC_CHECK_EQ(urlHost("https://WEU.core.gssv-play-prod.xboxlive.com/v5/sessions?x=1"), + std::string("weu.core.gssv-play-prod.xboxlive.com")); + XC_CHECK_EQ(urlHost("https://login.live.com:443/oauth20_token.srf"), std::string("login.live.com")); + XC_CHECK_EQ(urlHost("http://u:p@host.example#frag"), std::string("host.example")); +} + +XC_TEST(ratelimit, retry_after_parsing) { + XC_CHECK_EQ(parseRetryAfter("", 0), -1); + XC_CHECK_EQ(parseRetryAfter(" 7 ", 0), 7); + XC_CHECK_EQ(parseRetryAfter("999999999", 0), 3600); + XC_CHECK_EQ(parseRetryAfter("soon", 0), -1); + // Sun, 06 Nov 1994 08:49:37 GMT = 784111777 + XC_CHECK_EQ(parseRetryAfter("Sun, 06 Nov 1994 08:49:37 GMT", 784111777 - 30), 30); + XC_CHECK_EQ(parseRetryAfter("Sun, 06 Nov 1994 08:49:37 GMT", 784111777 + 30), 0); +} + +XC_TEST(ratelimit, bucket_allows_burst_then_paces) { + RateLimiter rl; + const std::string h = "api.example"; + const auto p = RateLimiter::policyFor(h); + int granted = 0; + for (int i = 0; i < 100; ++i) + if (rl.reserve(h, 1000) == 0) ++granted; + XC_CHECK_EQ(granted, static_cast<int>(p.burst)); + const int64_t wait = rl.reserve(h, 1000); + XC_CHECK(wait > 0 && wait <= static_cast<int64_t>(1000 / p.ratePerSec) + 1); + XC_CHECK_EQ(rl.reserve(h, 1000 + wait), int64_t(0)); + // Other hosts are independent. + XC_CHECK_EQ(rl.reserve("other.example", 1000), int64_t(0)); +} + +XC_TEST(ratelimit, cooldown_honours_retry_after) { + RateLimiter rl; + const std::string h = "api.example"; + rl.onResponse(h, 429, 10, 5000); + bool cooldown = false; + XC_CHECK_EQ(rl.reserve(h, 5000, &cooldown), int64_t(10000)); + XC_CHECK(cooldown); + XC_CHECK_EQ(rl.cooldownRemaining(h, 14000), int64_t(1000)); + XC_CHECK(rl.reserve(h, 15000, &cooldown) > 0); // bucket restarts empty after a 429 + XC_CHECK(!cooldown); +} + +XC_TEST(ratelimit, exponential_backoff_without_header) { + RateLimiter rl; + const std::string h = "api.example"; + rl.onResponse(h, 429, -1, 0); + XC_CHECK_EQ(rl.cooldownRemaining(h, 0), int64_t(2000)); + rl.onResponse(h, 429, -1, 2000); + XC_CHECK_EQ(rl.cooldownRemaining(h, 2000), int64_t(4000)); + rl.onResponse(h, 429, -1, 6000); + XC_CHECK_EQ(rl.cooldownRemaining(h, 6000), int64_t(8000)); + for (int i = 0; i < 20; ++i) rl.onResponse(h, 429, -1, 100000); + XC_CHECK_EQ(rl.cooldownRemaining(h, 100000), RateLimiter::kMaxBackoffMs); + // A normal answer resets the backoff ladder. + rl.onResponse(h, 200, -1, 300000); + rl.onResponse(h, 429, -1, 300000); + XC_CHECK_EQ(rl.cooldownRemaining(h, 300000), int64_t(2000)); +} + +XC_TEST(ratelimit, plain_503_and_transport_errors_do_not_cool_down) { + RateLimiter rl; + rl.onResponse("a", 503, -1, 0); + rl.onResponse("a", 0, -1, 0); + XC_CHECK_EQ(rl.cooldownRemaining("a", 0), int64_t(0)); + rl.onResponse("a", 503, 5, 0); + XC_CHECK_EQ(rl.cooldownRemaining("a", 0), int64_t(5000)); +} + +XC_TEST(ratelimit, session_start_cap) { + SessionStartLimiter sl; + int wait = 0; + int64_t t = 0; + for (int i = 0; i < SessionStartLimiter::kMaxCloud; ++i) { + XC_CHECK(sl.allow(false, t, wait)); + t += 3000; + } + XC_CHECK(!sl.allow(false, t, wait)); + XC_CHECK(wait > 0 && wait <= 300); + // Spacing applies across kinds. + XC_CHECK(!sl.allow(true, t - 3000 + 500, wait)); + XC_CHECK(sl.allow(true, t, wait)); + // After the window the oldest start expires. + XC_CHECK(sl.allow(false, SessionStartLimiter::kWindowMs + 1, wait)); +} diff --git a/tests/test_rtp.cpp b/tests/test_rtp.cpp new file mode 100644 index 0000000..3252aba --- /dev/null +++ b/tests/test_rtp.cpp @@ -0,0 +1,419 @@ +// Nubix — RTP H.264 depacketizer / Opus receiver tests. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <cstring> + +#include "stream/rtp.hpp" +#include "test.hpp" + +using namespace xc; + +namespace { + +std::vector<uint8_t> rtp(uint16_t seq, uint32_t ts, bool marker, const std::vector<uint8_t>& payload, + uint8_t pt = 102, uint32_t ssrc = 0x11223344) { + std::vector<uint8_t> p(12); + p[0] = 0x80; + p[1] = uint8_t((marker ? 0x80 : 0) | pt); + p[2] = uint8_t(seq >> 8); p[3] = uint8_t(seq); + p[4] = uint8_t(ts >> 24); p[5] = uint8_t(ts >> 16); p[6] = uint8_t(ts >> 8); p[7] = uint8_t(ts); + p[8] = uint8_t(ssrc >> 24); p[9] = uint8_t(ssrc >> 16); p[10] = uint8_t(ssrc >> 8); p[11] = uint8_t(ssrc); + p.insert(p.end(), payload.begin(), payload.end()); + return p; +} + +void push(RtpH264Depacketizer& d, const std::vector<uint8_t>& p) { d.push(p.data(), p.size()); } + +const std::vector<uint8_t> kSps = {0x67, 0x42, 0xE0, 0x1F, 0xAA}; +const std::vector<uint8_t> kPps = {0x68, 0xCE, 0x3C, 0x80}; + +bool contains(const std::vector<uint8_t>& hay, const std::vector<uint8_t>& needle) { + if (needle.size() > hay.size()) return false; + for (size_t i = 0; i + needle.size() <= hay.size(); ++i) + if (std::memcmp(&hay[i], needle.data(), needle.size()) == 0) return true; + return false; +} + +std::vector<uint8_t> withStart(const std::vector<uint8_t>& nal) { + std::vector<uint8_t> v = {0, 0, 0, 1}; + v.insert(v.end(), nal.begin(), nal.end()); + return v; +} + +} // namespace + +XC_TEST(rtp, single_nal_frame) { + RtpH264Depacketizer d; + const std::vector<uint8_t> idr = {0x65, 0x88, 0x01, 0x02}; // IDR slice (first_mb = 0) + const std::vector<uint8_t> nal = {0x41, 0x9A, 0x01, 0x02, 0x03}; // non-IDR slice + push(d, rtp(9, 6000, true, idr)); + push(d, rtp(10, 9000, true, nal)); + std::vector<uint8_t> au; + uint32_t ts = 0; + bool key = false; + XC_REQUIRE(d.popFrame(au, ts, key)); + XC_CHECK_EQ(ts, 6000u); + XC_CHECK(key); + XC_CHECK(au == withStart(idr)); + XC_REQUIRE(d.popFrame(au, ts, key)); + XC_CHECK_EQ(ts, 9000u); + XC_CHECK(!key); + XC_CHECK(au == withStart(nal)); + XC_CHECK(!d.popFrame(au, ts, key)); +} + +XC_TEST(rtp, stap_a_and_fu_a_keyframe) { + RtpH264Depacketizer d; + // STAP-A (type 24) with SPS + PPS + std::vector<uint8_t> stap = {0x18}; + stap.push_back(0); stap.push_back(uint8_t(kSps.size())); stap.insert(stap.end(), kSps.begin(), kSps.end()); + stap.push_back(0); stap.push_back(uint8_t(kPps.size())); stap.insert(stap.end(), kPps.begin(), kPps.end()); + push(d, rtp(100, 3000, false, stap)); + // IDR (type 5, nri 3) split into 3 FU-A fragments + const std::vector<uint8_t> idr = {0x65, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + const uint8_t fuInd = (idr[0] & 0xE0) | 28; + push(d, rtp(101, 3000, false, {fuInd, uint8_t(0x80 | 5), 1, 2, 3})); + push(d, rtp(102, 3000, false, {fuInd, uint8_t(5), 4, 5, 6})); + push(d, rtp(103, 3000, true, {fuInd, uint8_t(0x40 | 5), 7, 8, 9})); + std::vector<uint8_t> au; + uint32_t ts = 0; + bool key = false; + XC_REQUIRE(d.popFrame(au, ts, key)); + XC_CHECK(key); + XC_CHECK_EQ(ts, 3000u); + XC_CHECK(contains(au, withStart(kSps))); + XC_CHECK(contains(au, withStart(kPps))); + XC_CHECK(contains(au, withStart(idr))); +} + +XC_TEST(rtp, reorders_within_window) { + RtpH264Depacketizer d; + const std::vector<uint8_t> idr = {0x65, 1, 2, 3, 4, 5, 6}; + const uint8_t fuInd = (idr[0] & 0xE0) | 28; + push(d, rtp(201, 6000, false, {fuInd, uint8_t(0x80 | 5), 1, 2})); + push(d, rtp(203, 6000, true, {fuInd, uint8_t(0x40 | 5), 5, 6})); + push(d, rtp(202, 6000, false, {fuInd, uint8_t(5), 3, 4})); + std::vector<uint8_t> au; + uint32_t ts = 0; + bool key = false; + XC_REQUIRE(d.popFrame(au, ts, key)); + XC_CHECK(contains(au, withStart(idr))); + XC_CHECK(d.takeNackList().empty()); +} + +XC_TEST(rtp, gap_produces_nack) { + RtpH264Depacketizer d; + push(d, rtp(65534, 1000, true, {0x41, 1})); + // 65535 and 0 missing (wraparound) + push(d, rtp(1, 2000, true, {0x41, 2})); + push(d, rtp(2, 3000, true, {0x41, 3})); + auto nacks = d.takeNackList(); + bool s65535 = false, s0 = false; + for (auto s : nacks) { + if (s == 65535) s65535 = true; + if (s == 0) s0 = true; + } + XC_CHECK(s65535); + XC_CHECK(s0); + XC_CHECK(d.takeNackList().empty()); +} + +XC_TEST(rtp, opus_in_order_and_loss) { + RtpOpusReceiver r; + auto a = rtp(500, 960, false, {0xF8, 0x01}, 111); + auto b = rtp(501, 1920, false, {0xF8, 0x02}, 111); + auto c = rtp(503, 3840, false, {0xF8, 0x04}, 111); + r.push(a.data(), a.size()); + r.push(b.data(), b.size()); + r.push(c.data(), c.size()); + std::vector<uint8_t> opus; + uint32_t ts = 0; + int lost = -1; + XC_REQUIRE(r.pop(opus, ts, lost)); + XC_CHECK_EQ(ts, 960u); + XC_CHECK_EQ(lost, 0); + XC_CHECK_EQ(opus.size(), size_t(2)); + XC_REQUIRE(r.pop(opus, ts, lost)); + XC_CHECK_EQ(ts, 1920u); + // 502 missing: depending on the reorder window it may be reported now or after more packets + for (int i = 0; i < 10; ++i) { + auto p = rtp(uint16_t(504 + i), 4800u + 960u * i, false, {0xF8, 0x05}, 111); + r.push(p.data(), p.size()); + } + XC_REQUIRE(r.pop(opus, ts, lost)); + XC_CHECK_EQ(ts, 3840u); + XC_CHECK_EQ(lost, 1); +} + +XC_TEST(rtp, stats_tracker_counts_loss) { + RtpStatsTracker t; + for (uint16_t s = 0; s < 100; ++s) + if (s % 10 != 5) t.onPacket(0xABCD, s, s * 3000u, 90000, 1000); + RtpReceiveStats st = t.snapshotForReport(); + XC_CHECK_EQ(st.ssrc, 0xABCDu); + XC_CHECK_EQ(st.cumulativeLost, 10); + XC_CHECK_EQ(st.extHighestSeq & 0xFFFFu, 99u); + XC_CHECK(st.fractionLost > 0); + XC_CHECK_EQ(st.packets, uint64_t(90)); +} + +// ---- time-controlled scenarios (pushAt / takeNackListAt / needKeyframeAt) ----------------------- + +namespace { + +void pushAt(RtpH264Depacketizer& d, const std::vector<uint8_t>& p, uint64_t t) { d.pushAt(p.data(), p.size(), t); } + +const std::vector<uint8_t> kIdr = {0x65, 0x88, 0x10, 0x20}; +const std::vector<uint8_t> kP = {0x41, 0x9A, 0x30, 0x40}; + +int drainFrames(RtpH264Depacketizer& d, std::vector<uint32_t>* tss = nullptr, int* keys = nullptr) { + std::vector<uint8_t> au; + uint32_t ts = 0; + bool key = false; + int n = 0; + while (d.popFrame(au, ts, key)) { + ++n; + if (tss) tss->push_back(ts); + if (keys && key) ++*keys; + } + return n; +} + +bool has(const std::vector<uint16_t>& v, uint16_t s) { + for (auto x : v) + if (x == s) return true; + return false; +} + +} // namespace + +XC_TEST(rtp, drops_p_frames_until_first_idr) { + RtpH264Depacketizer d; + pushAt(d, rtp(1, 1000, true, kP), 0); + pushAt(d, rtp(2, 2500, true, kP), 16); + XC_CHECK_EQ(drainFrames(d), 0); + pushAt(d, rtp(3, 4000, true, kIdr), 33); + pushAt(d, rtp(4, 5500, true, kP), 50); + int keys = 0; + XC_CHECK_EQ(drainFrames(d, nullptr, &keys), 2); + XC_CHECK_EQ(keys, 1); + XC_CHECK(!d.counters().waitingForKeyframe); +} + +XC_TEST(rtp, fu_a_across_sequence_wrap) { + RtpH264Depacketizer d; + const uint8_t ind = 0x60 | 28; + pushAt(d, rtp(65534, 7000, false, {ind, uint8_t(0x80 | 5), 0x88, 1}), 0); + pushAt(d, rtp(65535, 7000, false, {ind, 5, 2, 3}), 0); + pushAt(d, rtp(0, 7000, false, {ind, 5, 4, 5}), 0); + pushAt(d, rtp(1, 7000, true, {ind, uint8_t(0x40 | 5), 6}), 0); + std::vector<uint8_t> au; + uint32_t ts = 0; + bool key = false; + XC_REQUIRE(d.popFrame(au, ts, key)); + XC_CHECK(key); + XC_CHECK(au == withStart({0x65, 0x88, 1, 2, 3, 4, 5, 6})); + XC_CHECK(d.takeNackListAt(1).empty()); +} + +XC_TEST(rtp, retransmission_completes_frame) { + RtpH264Depacketizer d; + pushAt(d, rtp(10, 1000, true, kIdr), 0); + const uint8_t ind = 0x40 | 28; + pushAt(d, rtp(11, 2500, false, {ind, uint8_t(0x80 | 1), 0x9A, 1}), 16); + // 12 lost + pushAt(d, rtp(13, 2500, true, {ind, uint8_t(0x40 | 1), 3}), 17); + XC_CHECK_EQ(drainFrames(d), 1); // only the IDR so far + auto n = d.takeNackListAt(18); + XC_CHECK(has(n, 12)); + pushAt(d, rtp(12, 2500, false, {ind, 1, 2}), 30); // retransmission, 12 ms RTT + std::vector<uint8_t> au; + uint32_t ts = 0; + bool key = true; + XC_REQUIRE(d.popFrame(au, ts, key)); + XC_CHECK_EQ(ts, 2500u); + XC_CHECK(!key); + XC_CHECK(au == withStart({0x41, 0x9A, 1, 2, 3})); + XC_CHECK(!d.needKeyframeAt(31)); + XC_CHECK_EQ(d.counters().recovered, uint64_t(1)); + XC_CHECK(d.counters().rttMs > 0); +} + +XC_TEST(rtp, unrecoverable_loss_requests_keyframe_and_drops_until_idr) { + RtpH264Depacketizer d; + pushAt(d, rtp(100, 1000, true, kIdr), 0); + XC_CHECK_EQ(drainFrames(d), 1); + const uint8_t ind = 0x40 | 28; + pushAt(d, rtp(101, 2500, false, {ind, uint8_t(0x80 | 1), 0x9A}), 16); + // 102 (middle fragment) never arrives + pushAt(d, rtp(103, 2500, true, {ind, uint8_t(0x40 | 1), 9}), 16); + pushAt(d, rtp(104, 4000, true, kP), 33); + XC_CHECK(has(d.takeNackListAt(17), 102)); + // NACK retries are limited + int retries = 0; + for (uint64_t t = 20; t < 50; t += 5) retries += static_cast<int>(d.takeNackListAt(t).size()); + XC_CHECK(retries <= 3); + XC_CHECK_EQ(drainFrames(d), 0); // held while a retransmission may still arrive + XC_CHECK(!d.needKeyframeAt(30)); + // after the loss wait (40 ms) the frame is given up + XC_CHECK(d.needKeyframeAt(60)); + XC_CHECK(!d.needKeyframeAt(61)); // once + XC_CHECK_EQ(drainFrames(d), 0); // the following P-frame is not decodable either + pushAt(d, rtp(105, 5500, true, kP), 70); + XC_CHECK_EQ(drainFrames(d), 0); + // still waiting: request is repeated after the retry interval + XC_CHECK(d.needKeyframeAt(1100)); + pushAt(d, rtp(106, 7000, true, kIdr), 1110); + pushAt(d, rtp(107, 8500, true, kP), 1120); + std::vector<uint32_t> tss; + XC_CHECK_EQ(drainFrames(d, &tss), 2); + XC_REQUIRE(tss.size() == 2); + XC_CHECK_EQ(tss[0], 7000u); + XC_CHECK(!d.needKeyframeAt(5000)); + // late retransmission of the given-up packet is ignored + pushAt(d, rtp(102, 2500, false, {ind, 1, 5}), 1200); + XC_CHECK_EQ(drainFrames(d), 0); + XC_CHECK(d.counters().framesDropped >= 2); +} + +XC_TEST(rtp, padding_packets_are_not_loss) { + RtpH264Depacketizer d; + pushAt(d, rtp(1, 1000, true, kIdr), 0); + // padding-only packet (P bit, all payload is padding), then next frame + std::vector<uint8_t> pad = rtp(2, 1000, false, {0, 0, 0, 4}); + pad[0] |= 0x20; + pushAt(d, pad, 5); + pushAt(d, rtp(3, 2500, true, kP), 16); + XC_CHECK_EQ(drainFrames(d), 2); + // a lost padding packet between frames: frame after it is a clean picture start -> no keyframe + pushAt(d, rtp(5, 4000, true, kP), 33); // 4 lost + XC_CHECK(d.takeNackListAt(34).size() == 1); + XC_CHECK_EQ(drainFrames(d), 0); + XC_CHECK(!d.needKeyframeAt(80)); // gap given up after 40 ms, picture after it is clean + XC_CHECK_EQ(drainFrames(d), 1); + XC_CHECK(!d.counters().waitingForKeyframe); +} + +XC_TEST(rtp, header_extension_csrc_and_padding_stripped) { + RtpH264Depacketizer d; + std::vector<uint8_t> p = rtp(50, 3000, true, {}); + p[0] = 0x80 | 0x20 | 0x10 | 0x01; // P, X, CC=1 + std::vector<uint8_t> tail = {0xDE, 0xAD, 0xBE, 0xEF, // CSRC + 0xBE, 0xDE, 0x00, 0x01, 1, 2, 3, 4}; // extension, 1 word + tail.insert(tail.end(), kIdr.begin(), kIdr.end()); + tail.insert(tail.end(), {0, 0, 3}); // 3 bytes padding + p.insert(p.end(), tail.begin(), tail.end()); + pushAt(d, p, 0); + std::vector<uint8_t> au; + uint32_t ts = 0; + bool key = false; + XC_REQUIRE(d.popFrame(au, ts, key)); + XC_CHECK(au == withStart(kIdr)); +} + +XC_TEST(rtp, marker_less_frame_ends_on_timestamp_change) { + RtpH264Depacketizer d; + pushAt(d, rtp(1, 1000, false, kIdr), 0); + XC_CHECK_EQ(drainFrames(d), 0); + pushAt(d, rtp(2, 2500, true, kP), 16); + XC_CHECK_EQ(drainFrames(d), 2); +} + +XC_TEST(rtp, ssrc_latch_ignores_stray_and_follows_switch) { + RtpH264Depacketizer d; + pushAt(d, rtp(1, 1000, true, kIdr, 102, 0xAAAA), 0); + XC_CHECK_EQ(d.ssrc(), 0xAAAAu); + pushAt(d, rtp(500, 9999, true, kIdr, 102, 0xBBBB), 1); // stray packet of another source + XC_CHECK_EQ(d.ssrc(), 0xAAAAu); + XC_CHECK_EQ(drainFrames(d), 1); + for (uint16_t s = 0; s < 3; ++s) pushAt(d, rtp(uint16_t(900 + s), 20000u + s * 1500u, true, kIdr, 102, 0xCCCC), 10 + s); + XC_CHECK_EQ(d.ssrc(), 0xCCCCu); + XC_CHECK(drainFrames(d) >= 1); +} + +XC_TEST(rtp, stats_lsr_dlsr_and_jitter) { + RtpStatsTracker t; + // perfectly paced 90 kHz stream: zero jitter + for (uint32_t i = 0; i < 50; ++i) t.onPacketAt(7, uint16_t(i), i * 1500u, 90000, 100, i * 16667ull); + t.onSenderReportAt(0x0123456789ABCDEFull, 1000); + RtpReceiveStats st = t.snapshotForReportAt(1500); // 0.5 s after SR + XC_CHECK_EQ(st.lastSr, 0x456789ABu); + XC_CHECK_EQ(st.delaySinceLastSr, 32768u); // 0.5 * 65536 + XC_CHECK(st.jitter <= 1u); + XC_CHECK_EQ(st.cumulativeLost, 0); + XC_CHECK_EQ(st.fractionLost, uint8_t(0)); + // wraparound counts a cycle + RtpStatsTracker w; + for (uint32_t i = 0; i < 20; ++i) w.onPacketAt(9, uint16_t(65530 + i), i * 960u, 48000, 10, i * 20000ull); + RtpReceiveStats ws = w.snapshotForReport(); + XC_CHECK_EQ(ws.extHighestSeq, 0x10000u + 13u); + XC_CHECK_EQ(ws.cumulativeLost, 0); + // copyable + RtpStatsTracker c = w; + XC_CHECK(c.hasData()); +} + +XC_TEST(rtp, opus_gap_skipped_after_timeout_and_dtx) { + RtpOpusReceiver r; + auto a = rtp(10, 0, false, {0xF8, 1}, 111); + auto c = rtp(12, 1920, false, {0xF8, 3}, 111); + r.pushAt(a.data(), a.size(), 0); + r.pushAt(c.data(), c.size(), 20); + std::vector<uint8_t> opus; + uint32_t ts = 0; + int lost = -1; + XC_REQUIRE(r.popAt(opus, ts, lost, 20)); + XC_CHECK_EQ(lost, 0); + XC_CHECK(!r.popAt(opus, ts, lost, 30)); // 11 may still arrive + XC_REQUIRE(r.popAt(opus, ts, lost, 61)); // reorder window (20 ms) has passed + XC_CHECK_EQ(ts, 1920u); + XC_CHECK_EQ(lost, 1); + // late packet is dropped + auto b = rtp(11, 960, false, {0xF8, 2}, 111); + r.pushAt(b.data(), b.size(), 62); + XC_CHECK(!r.popAt(opus, ts, lost, 200)); + // empty (DTX) packet carries loss over to the next real packet + auto e = rtp(14, 3840, false, {}, 111); + auto f = rtp(15, 4800, false, {0xF8, 5}, 111); + r.pushAt(e.data(), e.size(), 300); + r.pushAt(f.data(), f.size(), 300); + XC_REQUIRE(r.popAt(opus, ts, lost, 400)); + XC_CHECK_EQ(ts, 4800u); + XC_CHECK_EQ(lost, 1); // 13 missing +} + +XC_TEST(rtp, opus_swapped_pair_played_in_order) { + RtpOpusReceiver r; + auto a = rtp(100, 0, false, {0xF8, 1}, 111); + auto b = rtp(101, 960, false, {0xF8, 2}, 111); + auto c = rtp(102, 1920, false, {0xF8, 3}, 111); + std::vector<uint8_t> opus; + uint32_t ts = 0; + int lost = -1; + r.pushAt(a.data(), a.size(), 0); + XC_REQUIRE(r.popAt(opus, ts, lost, 0)); + // 102 overtakes 101 by one slot: nothing is concealed, both play in order + r.pushAt(c.data(), c.size(), 10); + XC_CHECK(!r.popAt(opus, ts, lost, 10)); + r.pushAt(b.data(), b.size(), 15); + XC_REQUIRE(r.popAt(opus, ts, lost, 15)); + XC_CHECK_EQ(ts, 960u); + XC_CHECK_EQ(lost, 0); + XC_REQUIRE(r.popAt(opus, ts, lost, 15)); + XC_CHECK_EQ(ts, 1920u); + XC_CHECK_EQ(lost, 0); +} + +XC_TEST(rtp, stats_sr_before_first_packet_is_kept) { + RtpStatsTracker t; + t.onSenderReportAt(0x0123456789ABCDEFull, 1000); // SR right after DTLS, before any RTP + for (uint32_t i = 0; i < 10; ++i) t.onPacketAt(5, uint16_t(i), i * 1500u, 90000, 100, 1000000ull + i * 16667ull); + RtpReceiveStats st = t.snapshotForReportAt(1250); + XC_CHECK_EQ(st.lastSr, 0x456789ABu); + XC_CHECK_EQ(st.delaySinceLastSr, 16384u); // 0.25 s + // a different SSRC later drops it again + t.onPacketAt(6, 0, 0, 90000, 100, 2000000ull); + RtpReceiveStats st2 = t.snapshotForReportAt(1300); + XC_CHECK_EQ(st2.lastSr, 0u); + XC_CHECK_EQ(st2.delaySinceLastSr, 0u); +} diff --git a/tests/test_sdp.cpp b/tests/test_sdp.cpp new file mode 100644 index 0000000..9e0349d --- /dev/null +++ b/tests/test_sdp.cpp @@ -0,0 +1,46 @@ +// Nubix — SDP offer template tests. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <nlohmann/json.hpp> + +#include "core/protocol.hpp" +#include "test.hpp" + +using namespace xc; + +namespace { +const char* kFp = "AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89"; +} + +XC_TEST(sdp, offer_contains_credentials_and_bundle) { + for (bool home : {false, true}) { + const std::string o = proto::buildOffer("uFrAg", "pAsSwOrDpAsSwOrDpAsSwOrD", kFp, home, "1080"); + XC_REQUIRE(!o.empty()); + XC_CHECK(o.rfind("v=0\r\n", 0) == 0); + XC_CHECK(o.find("a=group:BUNDLE video audio 0") != std::string::npos); + XC_CHECK(o.find("a=ice-ufrag:uFrAg\r\n") != std::string::npos); + XC_CHECK(o.find("a=ice-pwd:pAsSwOrDpAsSwOrDpAsSwOrD\r\n") != std::string::npos); + XC_CHECK(o.find(std::string("a=fingerprint:sha-256 ") + kFp) != std::string::npos); + const size_t v = o.find("m=video"), a = o.find("m=audio"), d = o.find("m=application"); + XC_REQUIRE(v != std::string::npos && a != std::string::npos && d != std::string::npos); + XC_CHECK(v < a && a < d); + XC_CHECK(o.find("a=mid:video") != std::string::npos); + XC_CHECK(o.find("a=mid:audio") != std::string::npos); + XC_CHECK(o.find("a=mid:0") != std::string::npos); + XC_CHECK(o.find("H264") != std::string::npos); + XC_CHECK(o.find("opus/48000/2") != std::string::npos); + XC_CHECK(o.find("a=recvonly") != std::string::npos); + // every line CRLF terminated + XC_CHECK(o.size() >= 2 && o.substr(o.size() - 2) == "\r\n"); + XC_CHECK(o.find("\n") == o.find("\r\n") + 1); + } +} + +XC_TEST(sdp, post_body) { + const std::string o = proto::buildOffer("u", "p", kFp, false, "720"); + auto j = nlohmann::json::parse(proto::sdpPostBody(o), nullptr, false); + XC_REQUIRE(!j.is_discarded()); + XC_CHECK_EQ(j.value("messageType", std::string()), std::string("offer")); + XC_CHECK_EQ(j.value("sdp", std::string()), o); + XC_CHECK(j.contains("configuration")); +} diff --git a/tests/test_smoke.cpp b/tests/test_smoke.cpp new file mode 100644 index 0000000..52ca22f --- /dev/null +++ b/tests/test_smoke.cpp @@ -0,0 +1,49 @@ +// Nubix — smoke tests for vendored / scaffold pieces (always expected to pass). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <nlohmann/json.hpp> + +#include "core/log.hpp" +#include "platform/platform.hpp" +#include "test.hpp" +#include "ui/qr.hpp" + +XC_TEST(smoke, qr_encodes_microsoft_link) { + std::vector<bool> modules; + int size = 0; + XC_REQUIRE(xc::qr::encode("https://www.microsoft.com/link?otc=ABCD1234", modules, size)); + XC_CHECK(size >= 21); + XC_CHECK_EQ(modules.size(), static_cast<size_t>(size) * static_cast<size_t>(size)); + // finder pattern: top-left 7x7 has dark corners + XC_CHECK(modules[0]); + XC_CHECK(modules[6]); + XC_CHECK(modules[static_cast<size_t>(6) * size]); +} + +XC_TEST(smoke, qr_rejects_oversized_input) { + std::vector<bool> modules; + int size = 0; + XC_CHECK(!xc::qr::encode(std::string(8000, 'x'), modules, size)); + XC_CHECK_EQ(size, 0); +} + +XC_TEST(smoke, json_roundtrip) { + auto j = nlohmann::json::parse(R"({"a":1,"b":"x"})"); + XC_CHECK_EQ(j["a"].get<int>(), 1); + XC_CHECK_EQ(j["b"].get<std::string>(), std::string("x")); +} + +XC_TEST(smoke, log_does_not_crash) { + xc::logInit("", xc::LogLevel::Debug); + XC_LOGD("debug %d", 1); + XC_LOGE("error %s", "x"); + xc::logSetLevel(xc::LogLevel::Info); + XC_CHECK(xc::logLevel() == xc::LogLevel::Info); +} + +XC_TEST(smoke, platform_clock) { + XC_CHECK(xc::platform::unixTime() > 1700000000); + const uint64_t a = xc::platform::monotonicMs(); + const uint64_t b = xc::platform::monotonicMs(); + XC_CHECK(b >= a); +} diff --git a/third_party/nlohmann/json.hpp b/third_party/nlohmann/json.hpp new file mode 100644 index 0000000..8b72ea6 --- /dev/null +++ b/third_party/nlohmann/json.hpp @@ -0,0 +1,24765 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + +/****************************************************************************\ + * Note on documentation: The source files contain links to the online * + * documentation of the public API at https://json.nlohmann.me. This URL * + * contains the most recent documentation and should also be applicable to * + * previous versions; documentation for deprecated functions is not * + * removed, but marked deprecated. See "Generate documentation" section in * + * file docs/README.md. * +\****************************************************************************/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#include <algorithm> // all_of, find, for_each +#include <cstddef> // nullptr_t, ptrdiff_t, size_t +#include <functional> // hash, less +#include <initializer_list> // initializer_list +#ifndef JSON_NO_IO + #include <iosfwd> // istream, ostream +#endif // JSON_NO_IO +#include <iterator> // random_access_iterator_tag +#include <memory> // unique_ptr +#include <string> // string, stoi, to_string +#include <utility> // declval, forward, move, pair, swap +#include <vector> // vector + +// #include <nlohmann/adl_serializer.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <utility> + +// #include <nlohmann/detail/abi_macros.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +// This file contains all macro definitions affecting or depending on the ABI + +#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK + #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3 + #warning "Already included a different version of the library!" + #endif + #endif +#endif + +#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum) + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 0 +#endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_DIAGNOSTICS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp +#else + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION + #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 +#endif + +// Construct the namespace ABI tags component +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) + +#define NLOHMANN_JSON_ABI_TAGS \ + NLOHMANN_JSON_ABI_TAGS_CONCAT( \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + +// Construct the namespace version component +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ + _v ## major ## _ ## minor ## _ ## patch +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) + +#if NLOHMANN_JSON_NAMESPACE_NO_VERSION +#define NLOHMANN_JSON_NAMESPACE_VERSION +#else +#define NLOHMANN_JSON_NAMESPACE_VERSION \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ + NLOHMANN_JSON_VERSION_MINOR, \ + NLOHMANN_JSON_VERSION_PATCH) +#endif + +// Combine namespace components +#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b +#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ + NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) + +#ifndef NLOHMANN_JSON_NAMESPACE +#define NLOHMANN_JSON_NAMESPACE \ + nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN +#define NLOHMANN_JSON_NAMESPACE_BEGIN \ + namespace nlohmann \ + { \ + inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) \ + { +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_END +#define NLOHMANN_JSON_NAMESPACE_END \ + } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ + } // namespace nlohmann +#endif + +// #include <nlohmann/detail/conversions/from_json.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <algorithm> // transform +#include <array> // array +#include <forward_list> // forward_list +#include <iterator> // inserter, front_inserter, end +#include <map> // map +#include <string> // string +#include <tuple> // tuple, make_tuple +#include <type_traits> // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include <unordered_map> // unordered_map +#include <utility> // pair, declval +#include <valarray> // valarray + +// #include <nlohmann/detail/exceptions.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <cstddef> // nullptr_t +#include <exception> // exception +#if JSON_DIAGNOSTICS + #include <numeric> // accumulate +#endif +#include <stdexcept> // runtime_error +#include <string> // to_string +#include <vector> // vector + +// #include <nlohmann/detail/value_t.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <array> // array +#include <cstddef> // size_t +#include <cstdint> // uint8_t +#include <string> // string + +// #include <nlohmann/detail/macro_scope.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <utility> // declval, pair +// #include <nlohmann/detail/meta/detected.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <type_traits> + +// #include <nlohmann/detail/meta/void_t.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +// #include <nlohmann/detail/abi_macros.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template<typename ...Ts> struct make_void +{ + using type = void; +}; +template<typename ...Ts> using void_t = typename make_void<Ts...>::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// https://en.cppreference.com/w/cpp/experimental/is_detected +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template<class Default, + class AlwaysVoid, + template<class...> class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template<class Default, template<class...> class Op, class... Args> +struct detector<Default, void_t<Op<Args...>>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op<Args...>; +}; + +template<template<class...> class Op, class... Args> +using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t; + +template<template<class...> class Op, class... Args> +struct is_detected_lazy : is_detected<Op, Args...> { }; + +template<template<class...> class Op, class... Args> +using detected_t = typename detector<nonesuch, void, Op, Args...>::type; + +template<class Default, template<class...> class Op, class... Args> +using detected_or = detector<Default, void, Op, Args...>; + +template<class Default, template<class...> class Op, class... Args> +using detected_or_t = typename detected_or<Default, Op, Args...>::type; + +template<class Expected, template<class...> class Op, class... Args> +using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>; + +template<class To, template<class...> class Op, class... Args> +using is_detected_convertible = + std::is_convertible<detected_t<Op, Args...>, To>; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/thirdparty/hedley/hedley.hpp> + + +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson <evan@nemerson.com> +// SPDX-License-Identifier: MIT + +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson <evan@nemerson.com> + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 15 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(JSON_HEDLEY_MSVC_VERSION) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #undef JSON_HEDLEY_INTEL_CL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) + #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #undef JSON_HEDLEY_MCST_LCC_VERSION +#endif +#if defined(__LCC__) && defined(__LCC_MINOR__) + #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) + #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_CRAY_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) && \ + !defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if \ + defined(__has_attribute) && \ + ( \ + (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ + ) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast<T>(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast<T>(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast<T>(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif \ + (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include <stdint.h> + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include <stdint.h> + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#else + #define JSON_HEDLEY_FLAGS +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if \ + (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions (except those affecting ABI) +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// #include <nlohmann/detail/abi_macros.hpp> + + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +// if the user manually specified the used c++ version this is skipped +#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 + #endif + // the cpp 11 flag is always specified because it is the minimal required version + #define JSON_HAS_CPP_11 +#endif + +#ifdef __has_include + #if __has_include(<version>) + #include <version> + #endif +#endif + +#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) + #ifdef JSON_HAS_CPP_17 + #if defined(__cpp_lib_filesystem) + #define JSON_HAS_FILESYSTEM 1 + #elif defined(__cpp_lib_experimental_filesystem) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif !defined(__has_include) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif __has_include(<filesystem>) + #define JSON_HAS_FILESYSTEM 1 + #elif __has_include(<experimental/filesystem>) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #endif + + // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ + #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__clang_major__) && __clang_major__ < 7 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support + #if defined(_MSC_VER) && _MSC_VER < 1914 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before iOS 13 + #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before macOS Catalina + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + #endif +#endif + +#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_FILESYSTEM + #define JSON_HAS_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #else + #define JSON_HAS_THREE_WAY_COMPARISON 0 + #endif +#endif + +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifndef JSON_HAS_STATIC_RTTI + #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0 + #define JSON_HAS_STATIC_RTTI 1 + #else + #define JSON_HAS_STATIC_RTTI 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + +#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) + #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else + #define JSON_NO_UNIQUE_ADDRESS +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdocumentation" + #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#endif + +// allow disabling exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include <cstdlib> + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow overriding assert +#if !defined(JSON_ASSERT) + #include <cassert> // assert + #define JSON_ASSERT(x) assert(x) +#endif + +// allow to access some private functions (needed by the test suite) +#if defined(JSON_TESTS_PRIVATE) + #define JSON_PRIVATE_UNLESS_TESTED public +#else + #define JSON_PRIVATE_UNLESS_TESTED private +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template<typename BasicJsonType> \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template<typename BasicJsonType> \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template<template<typename, typename, typename...> class ObjectType, \ + template<typename, typename...> class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template<typename> class AllocatorType, \ + template<typename, typename = void> class JSONSerializer, \ + class BinaryType, \ + class CustomBaseClass> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json<ObjectType, ArrayType, StringType, BooleanType, \ + NumberIntegerType, NumberUnsignedType, NumberFloatType, \ + AllocatorType, JSONSerializer, BinaryType, CustomBaseClass> + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +// inspired from https://stackoverflow.com/a/26745591 +// allows to call any std function as if (e.g. with begin): +// using std::begin; begin(x); +// +// it allows using the detected idiom to retrieve the return type +// of such an expression +#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ + namespace detail { \ + using std::std_name; \ + \ + template<typename... T> \ + using result_of_##std_name = decltype(std_name(std::declval<T>()...)); \ + } \ + \ + namespace detail2 { \ + struct std_name##_tag \ + { \ + }; \ + \ + template<typename... T> \ + std_name##_tag std_name(T&&...); \ + \ + template<typename... T> \ + using result_of_##std_name = decltype(std_name(std::declval<T>()...)); \ + \ + template<typename... T> \ + struct would_call_std_##std_name \ + { \ + static constexpr auto const value = ::nlohmann::detail:: \ + is_detected_exact<std_name##_tag, result_of_##std_name, T...>::value; \ + }; \ + } /* namespace detail2 */ \ + \ + template<typename... T> \ + struct would_call_std_##std_name : detail2::would_call_std_##std_name<T...> \ + { \ + } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif + +#ifndef JSON_USE_GLOBAL_UDLS + #define JSON_USE_GLOBAL_UDLS 1 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include <compare> // partial_ordering +#endif + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array<std::uint8_t, 9> order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast<std::size_t>(lhs); + const auto r_index = static_cast<std::size_t>(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/string_escape.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +// #include <nlohmann/detail/abi_macros.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +template<typename StringType> +inline void replace_substring(StringType& s, const StringType& f, + const StringType& t) +{ + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != StringType::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + */ +template<typename StringType> +inline StringType escape(StringType s) +{ + replace_substring(s, StringType{"~"}, StringType{"~0"}); + replace_substring(s, StringType{"/"}, StringType{"~1"}); + return s; +} + +/*! + * @brief string unescaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to unescape + * @return unescaped string + * + * Note the order of escaping "~1" to "/" and "~0" to "~" is important. + */ +template<typename StringType> +static void unescape(StringType& s) +{ + replace_substring(s, StringType{"~1"}, StringType{"/"}); + replace_substring(s, StringType{"~0"}, StringType{"~"}); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/input/position_t.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <cstddef> // size_t + +// #include <nlohmann/detail/abi_macros.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/meta/cpp_future.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-FileCopyrightText: 2018 The Abseil Authors +// SPDX-License-Identifier: MIT + + + +#include <array> // array +#include <cstddef> // size_t +#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include <utility> // index_sequence, make_index_sequence, index_sequence_for + +// #include <nlohmann/detail/macro_scope.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template<typename T> +using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type; + +#ifdef JSON_HAS_CPP_14 + +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + +// alias templates to reduce boilerplate +template<bool B, typename T = void> +using enable_if_t = typename std::enable_if<B, T>::type; + +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. + +//// START OF CODE FROM GOOGLE ABSEIL + +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence<T, Ints...>` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence<T, Ints...>); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence<int, 5>()); +// } +template <typename T, T... Ints> +struct integer_sequence +{ + using value_type = T; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +// index_sequence +// +// A helper template for an `integer_sequence` of `size_t`, +// `absl::index_sequence` is designed to be a drop-in replacement for C++14's +// `std::index_sequence`. +template <size_t... Ints> +using index_sequence = integer_sequence<size_t, Ints...>; + +namespace utility_internal +{ + +template <typename Seq, size_t SeqSize, size_t Rem> +struct Extend; + +// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. +template <typename T, T... Ints, size_t SeqSize> +struct Extend<integer_sequence<T, Ints...>, SeqSize, 0> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; +}; + +template <typename T, T... Ints, size_t SeqSize> +struct Extend<integer_sequence<T, Ints...>, SeqSize, 1> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; +}; + +// Recursion helper for 'make_integer_sequence<T, N>'. +// 'Gen<T, N>::type' is an alias for 'integer_sequence<T, 0, 1, ... N-1>'. +template <typename T, size_t N> +struct Gen +{ + using type = + typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; +}; + +template <typename T> +struct Gen<T, 0> +{ + using type = integer_sequence<T>; +}; + +} // namespace utility_internal + +// Compile-time sequences of integers + +// make_integer_sequence +// +// This template alias is equivalent to +// `integer_sequence<int, 0, 1, ..., N-1>`, and is designed to be a drop-in +// replacement for C++14's `std::make_integer_sequence`. +template <typename T, T N> +using make_integer_sequence = typename utility_internal::Gen<T, N>::type; + +// make_index_sequence +// +// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, +// and is designed to be a drop-in replacement for C++14's +// `std::make_index_sequence`. +template <size_t N> +using make_index_sequence = make_integer_sequence<size_t, N>; + +// index_sequence_for +// +// Converts a typename pack into an index sequence of the same length, and +// is designed to be a drop-in replacement for C++14's +// `std::index_sequence_for()` +template <typename... Ts> +using index_sequence_for = make_index_sequence<sizeof...(Ts)>; + +//// END OF CODE FROM GOOGLE ABSEIL + +#endif + +// dispatch utility (taken from ranges-v3) +template<unsigned N> struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template<typename T> +struct static_const +{ + static JSON_INLINE_VARIABLE constexpr T value{}; +}; + +#ifndef JSON_HAS_CPP_17 + template<typename T> + constexpr T static_const<T>::value; +#endif + +template<typename T, typename... Args> +inline constexpr std::array<T, sizeof...(Args)> make_array(Args&& ... args) +{ + return std::array<T, sizeof...(Args)> {{static_cast<T>(std::forward<Args>(args))...}}; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/meta/type_traits.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <limits> // numeric_limits +#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type +#include <utility> // declval +#include <tuple> // tuple +#include <string> // char_traits + +// #include <nlohmann/detail/iterators/iterator_traits.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <iterator> // random_access_iterator_tag + +// #include <nlohmann/detail/abi_macros.hpp> + +// #include <nlohmann/detail/meta/void_t.hpp> + +// #include <nlohmann/detail/meta/cpp_future.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template<typename It, typename = void> +struct iterator_types {}; + +template<typename It> +struct iterator_types < + It, + void_t<typename It::difference_type, typename It::value_type, typename It::pointer, + typename It::reference, typename It::iterator_category >> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template<typename T, typename = void> +struct iterator_traits +{ +}; + +template<typename T> +struct iterator_traits < T, enable_if_t < !std::is_pointer<T>::value >> + : iterator_types<T> +{ +}; + +template<typename T> +struct iterator_traits<T*, enable_if_t<std::is_object<T>::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/meta/call_std/begin.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +// #include <nlohmann/detail/macro_scope.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); + +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/meta/call_std/end.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +// #include <nlohmann/detail/macro_scope.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); + +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/meta/cpp_future.hpp> + +// #include <nlohmann/detail/meta/detected.hpp> + +// #include <nlohmann/json_fwd.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ + #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + #include <cstdint> // int64_t, uint64_t + #include <map> // map + #include <memory> // allocator + #include <string> // string + #include <vector> // vector + + // #include <nlohmann/detail/abi_macros.hpp> + + + /*! + @brief namespace for Niels Lohmann + @see https://github.com/nlohmann + @since version 1.0.0 + */ + NLOHMANN_JSON_NAMESPACE_BEGIN + + /*! + @brief default JSONSerializer template argument + + This serializer ignores the template arguments and uses ADL + ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) + for serialization. + */ + template<typename T = void, typename SFINAE = void> + struct adl_serializer; + + /// a class to store JSON values + /// @sa https://json.nlohmann.me/api/basic_json/ + template<template<typename U, typename V, typename... Args> class ObjectType = + std::map, + template<typename U, typename... Args> class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template<typename U> class AllocatorType = std::allocator, + template<typename T, typename SFINAE = void> class JSONSerializer = + adl_serializer, + class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError + class CustomBaseClass = void> + class basic_json; + + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document + /// @sa https://json.nlohmann.me/api/json_pointer/ + template<typename RefStringType> + class json_pointer; + + /*! + @brief default specialization + @sa https://json.nlohmann.me/api/json/ + */ + using json = basic_json<>; + + /// @brief a minimal map-like container that preserves insertion order + /// @sa https://json.nlohmann.me/api/ordered_map/ + template<class Key, class T, class IgnoredLess, class Allocator> + struct ordered_map; + + /// @brief specialization that maintains the insertion order of object keys + /// @sa https://json.nlohmann.me/api/ordered_json/ + using ordered_json = basic_json<nlohmann::ordered_map>; + + NLOHMANN_JSON_NAMESPACE_END + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + +NLOHMANN_JSON_NAMESPACE_BEGIN +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ + +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval<T>()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + +template<typename> struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {}; + +// used by exceptions create() member functions +// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t +// false_type otherwise +template<typename BasicJsonContext> +struct is_basic_json_context : + std::integral_constant < bool, + is_basic_json<typename std::remove_cv<typename std::remove_pointer<BasicJsonContext>::type>::type>::value + || std::is_same<BasicJsonContext, std::nullptr_t>::value > +{}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template<typename> +class json_ref; + +template<typename> +struct is_json_ref : std::false_type {}; + +template<typename T> +struct is_json_ref<json_ref<T>> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template<typename T> +using mapped_type_t = typename T::mapped_type; + +template<typename T> +using key_type_t = typename T::key_type; + +template<typename T> +using value_type_t = typename T::value_type; + +template<typename T> +using difference_type_t = typename T::difference_type; + +template<typename T> +using pointer_t = typename T::pointer; + +template<typename T> +using reference_t = typename T::reference; + +template<typename T> +using iterator_category_t = typename T::iterator_category; + +template<typename T, typename... Args> +using to_json_function = decltype(T::to_json(std::declval<Args>()...)); + +template<typename T, typename... Args> +using from_json_function = decltype(T::from_json(std::declval<Args>()...)); + +template<typename T, typename U> +using get_template_function = decltype(std::declval<T>().template get<U>()); + +// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists +template<typename BasicJsonType, typename T, typename = void> +struct has_from_json : std::false_type {}; + +// trait checking if j.get<T> is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template <typename BasicJsonType, typename T> +struct is_getable +{ + static constexpr bool value = is_detected<get_template_function, const BasicJsonType&, T>::value; +}; + +template<typename BasicJsonType, typename T> +struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >> +{ + using serializer = typename BasicJsonType::template json_serializer<T, void>; + + static constexpr bool value = + is_detected_exact<void, from_json_function, serializer, + const BasicJsonType&, T&>::value; +}; + +// This trait checks if JSONSerializer<T>::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template<typename BasicJsonType, typename T, typename = void> +struct has_non_default_from_json : std::false_type {}; + +template<typename BasicJsonType, typename T> +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >> +{ + using serializer = typename BasicJsonType::template json_serializer<T, void>; + + static constexpr bool value = + is_detected_exact<T, from_json_function, serializer, + const BasicJsonType&>::value; +}; + +// This trait checks if BasicJsonType::json_serializer<T>::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template<typename BasicJsonType, typename T, typename = void> +struct has_to_json : std::false_type {}; + +template<typename BasicJsonType, typename T> +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >> +{ + using serializer = typename BasicJsonType::template json_serializer<T, void>; + + static constexpr bool value = + is_detected_exact<void, to_json_function, serializer, BasicJsonType&, + T>::value; +}; + +template<typename T> +using detect_key_compare = typename T::key_compare; + +template<typename T> +struct has_key_compare : std::integral_constant<bool, is_detected<detect_key_compare, T>::value> {}; + +// obtains the actual object key comparator +template<typename BasicJsonType> +struct actual_object_comparator +{ + using object_t = typename BasicJsonType::object_t; + using object_comparator_t = typename BasicJsonType::default_object_comparator_t; + using type = typename std::conditional < has_key_compare<object_t>::value, + typename object_t::key_compare, object_comparator_t>::type; +}; + +template<typename BasicJsonType> +using actual_object_comparator_t = typename actual_object_comparator<BasicJsonType>::type; + +///////////////// +// char_traits // +///////////////// + +// Primary template of char_traits calls std char_traits +template<typename T> +struct char_traits : std::char_traits<T> +{}; + +// Explicitly define char traits for unsigned char since it is not standard +template<> +struct char_traits<unsigned char> : std::char_traits<char> +{ + using char_type = unsigned char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast<int_type>(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast<char_type>(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast<int_type>(EOF); + } +}; + +// Explicitly define char traits for signed char since it is not standard +template<> +struct char_traits<signed char> : std::char_traits<char> +{ + using char_type = signed char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast<int_type>(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast<char_type>(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast<int_type>(EOF); + } +}; + +/////////////////// +// is_ functions // +/////////////////// + +// https://en.cppreference.com/w/cpp/types/conjunction +template<class...> struct conjunction : std::true_type { }; +template<class B> struct conjunction<B> : B { }; +template<class B, class... Bn> +struct conjunction<B, Bn...> +: std::conditional<static_cast<bool>(B::value), conjunction<Bn...>, B>::type {}; + +// https://en.cppreference.com/w/cpp/types/negation +template<class B> struct negation : std::integral_constant < bool, !B::value > { }; + +// Reimplementation of is_constructible and is_default_constructible, due to them being broken for +// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). +// This causes compile errors in e.g. clang 3.5 or gcc 4.9. +template <typename T> +struct is_default_constructible : std::is_default_constructible<T> {}; + +template <typename T1, typename T2> +struct is_default_constructible<std::pair<T1, T2>> + : conjunction<is_default_constructible<T1>, is_default_constructible<T2>> {}; + +template <typename T1, typename T2> +struct is_default_constructible<const std::pair<T1, T2>> + : conjunction<is_default_constructible<T1>, is_default_constructible<T2>> {}; + +template <typename... Ts> +struct is_default_constructible<std::tuple<Ts...>> + : conjunction<is_default_constructible<Ts>...> {}; + +template <typename... Ts> +struct is_default_constructible<const std::tuple<Ts...>> + : conjunction<is_default_constructible<Ts>...> {}; + +template <typename T, typename... Args> +struct is_constructible : std::is_constructible<T, Args...> {}; + +template <typename T1, typename T2> +struct is_constructible<std::pair<T1, T2>> : is_default_constructible<std::pair<T1, T2>> {}; + +template <typename T1, typename T2> +struct is_constructible<const std::pair<T1, T2>> : is_default_constructible<const std::pair<T1, T2>> {}; + +template <typename... Ts> +struct is_constructible<std::tuple<Ts...>> : is_default_constructible<std::tuple<Ts...>> {}; + +template <typename... Ts> +struct is_constructible<const std::tuple<Ts...>> : is_default_constructible<const std::tuple<Ts...>> {}; + +template<typename T, typename = void> +struct is_iterator_traits : std::false_type {}; + +template<typename T> +struct is_iterator_traits<iterator_traits<T>> +{ + private: + using traits = iterator_traits<T>; + + public: + static constexpr auto value = + is_detected<value_type_t, traits>::value && + is_detected<difference_type_t, traits>::value && + is_detected<pointer_t, traits>::value && + is_detected<iterator_category_t, traits>::value && + is_detected<reference_t, traits>::value; +}; + +template<typename T> +struct is_range +{ + private: + using t_ref = typename std::add_lvalue_reference<T>::type; + + using iterator = detected_t<result_of_begin, t_ref>; + using sentinel = detected_t<result_of_end, t_ref>; + + // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator + // and https://en.cppreference.com/w/cpp/iterator/sentinel_for + // but reimplementing these would be too much work, as a lot of other concepts are used underneath + static constexpr auto is_iterator_begin = + is_iterator_traits<iterator_traits<iterator>>::value; + + public: + static constexpr bool value = !std::is_same<iterator, nonesuch>::value && !std::is_same<sentinel, nonesuch>::value && is_iterator_begin; +}; + +template<typename R> +using iterator_t = enable_if_t<is_range<R>::value, result_of_begin<decltype(std::declval<R&>())>>; + +template<typename T> +using range_value_t = value_type_t<iterator_traits<iterator_t<T>>>; + +// The following implementation of is_complete_type is taken from +// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ +// and is written by Xiang Fan who agreed to using it in this library. + +template<typename T, typename = void> +struct is_complete_type : std::false_type {}; + +template<typename T> +struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {}; + +template<typename BasicJsonType, typename CompatibleObjectType, + typename = void> +struct is_compatible_object_type_impl : std::false_type {}; + +template<typename BasicJsonType, typename CompatibleObjectType> +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected<mapped_type_t, CompatibleObjectType>::value&& + is_detected<key_type_t, CompatibleObjectType>::value >> +{ + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + is_constructible<typename object_t::key_type, + typename CompatibleObjectType::key_type>::value && + is_constructible<typename object_t::mapped_type, + typename CompatibleObjectType::mapped_type>::value; +}; + +template<typename BasicJsonType, typename CompatibleObjectType> +struct is_compatible_object_type + : is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {}; + +template<typename BasicJsonType, typename ConstructibleObjectType, + typename = void> +struct is_constructible_object_type_impl : std::false_type {}; + +template<typename BasicJsonType, typename ConstructibleObjectType> +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected<mapped_type_t, ConstructibleObjectType>::value&& + is_detected<key_type_t, ConstructibleObjectType>::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (is_default_constructible<ConstructibleObjectType>::value && + (std::is_move_assignable<ConstructibleObjectType>::value || + std::is_copy_assignable<ConstructibleObjectType>::value) && + (is_constructible<typename ConstructibleObjectType::key_type, + typename object_t::key_type>::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json<BasicJsonType, + typename ConstructibleObjectType::mapped_type>::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template<typename BasicJsonType, typename ConstructibleObjectType> +struct is_constructible_object_type + : is_constructible_object_type_impl<BasicJsonType, + ConstructibleObjectType> {}; + +template<typename BasicJsonType, typename CompatibleStringType> +struct is_compatible_string_type +{ + static constexpr auto value = + is_constructible<typename BasicJsonType::string_t, CompatibleStringType>::value; +}; + +template<typename BasicJsonType, typename ConstructibleStringType> +struct is_constructible_string_type +{ + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval<ConstructibleStringType>()); +#else + using laundered_type = ConstructibleStringType; +#endif + + static constexpr auto value = + conjunction < + is_constructible<laundered_type, typename BasicJsonType::string_t>, + is_detected_exact<typename BasicJsonType::string_t::value_type, + value_type_t, laundered_type >>::value; +}; + +template<typename BasicJsonType, typename CompatibleArrayType, typename = void> +struct is_compatible_array_type_impl : std::false_type {}; + +template<typename BasicJsonType, typename CompatibleArrayType> +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < + is_detected<iterator_t, CompatibleArrayType>::value&& + is_iterator_traits<iterator_traits<detected_t<iterator_t, CompatibleArrayType>>>::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 + !std::is_same<CompatibleArrayType, detected_t<range_value_t, CompatibleArrayType>>::value >> +{ + static constexpr bool value = + is_constructible<BasicJsonType, + range_value_t<CompatibleArrayType>>::value; +}; + +template<typename BasicJsonType, typename CompatibleArrayType> +struct is_compatible_array_type + : is_compatible_array_type_impl<BasicJsonType, CompatibleArrayType> {}; + +template<typename BasicJsonType, typename ConstructibleArrayType, typename = void> +struct is_constructible_array_type_impl : std::false_type {}; + +template<typename BasicJsonType, typename ConstructibleArrayType> +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t<std::is_same<ConstructibleArrayType, + typename BasicJsonType::value_type>::value >> + : std::true_type {}; + +template<typename BasicJsonType, typename ConstructibleArrayType> +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same<ConstructibleArrayType, + typename BasicJsonType::value_type>::value&& + !is_compatible_string_type<BasicJsonType, ConstructibleArrayType>::value&& + is_default_constructible<ConstructibleArrayType>::value&& +(std::is_move_assignable<ConstructibleArrayType>::value || + std::is_copy_assignable<ConstructibleArrayType>::value)&& +is_detected<iterator_t, ConstructibleArrayType>::value&& +is_iterator_traits<iterator_traits<detected_t<iterator_t, ConstructibleArrayType>>>::value&& +is_detected<range_value_t, ConstructibleArrayType>::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 +!std::is_same<ConstructibleArrayType, detected_t<range_value_t, ConstructibleArrayType>>::value&& + is_complete_type < + detected_t<range_value_t, ConstructibleArrayType >>::value >> +{ + using value_type = range_value_t<ConstructibleArrayType>; + + static constexpr bool value = + std::is_same<value_type, + typename BasicJsonType::array_t::value_type>::value || + has_from_json<BasicJsonType, + value_type>::value || + has_non_default_from_json < + BasicJsonType, + value_type >::value; +}; + +template<typename BasicJsonType, typename ConstructibleArrayType> +struct is_constructible_array_type + : is_constructible_array_type_impl<BasicJsonType, ConstructibleArrayType> {}; + +template<typename RealIntegerType, typename CompatibleNumberIntegerType, + typename = void> +struct is_compatible_integer_type_impl : std::false_type {}; + +template<typename RealIntegerType, typename CompatibleNumberIntegerType> +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral<RealIntegerType>::value&& + std::is_integral<CompatibleNumberIntegerType>::value&& + !std::is_same<bool, CompatibleNumberIntegerType>::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits<RealIntegerType>; + using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>; + + static constexpr auto value = + is_constructible<RealIntegerType, + CompatibleNumberIntegerType>::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template<typename RealIntegerType, typename CompatibleNumberIntegerType> +struct is_compatible_integer_type + : is_compatible_integer_type_impl<RealIntegerType, + CompatibleNumberIntegerType> {}; + +template<typename BasicJsonType, typename CompatibleType, typename = void> +struct is_compatible_type_impl: std::false_type {}; + +template<typename BasicJsonType, typename CompatibleType> +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t<is_complete_type<CompatibleType>::value >> +{ + static constexpr bool value = + has_to_json<BasicJsonType, CompatibleType>::value; +}; + +template<typename BasicJsonType, typename CompatibleType> +struct is_compatible_type + : is_compatible_type_impl<BasicJsonType, CompatibleType> {}; + +template<typename T1, typename T2> +struct is_constructible_tuple : std::false_type {}; + +template<typename T1, typename... Args> +struct is_constructible_tuple<T1, std::tuple<Args...>> : conjunction<is_constructible<T1, Args>...> {}; + +template<typename BasicJsonType, typename T> +struct is_json_iterator_of : std::false_type {}; + +template<typename BasicJsonType> +struct is_json_iterator_of<BasicJsonType, typename BasicJsonType::iterator> : std::true_type {}; + +template<typename BasicJsonType> +struct is_json_iterator_of<BasicJsonType, typename BasicJsonType::const_iterator> : std::true_type +{}; + +// checks if a given type T is a template specialization of Primary +template<template <typename...> class Primary, typename T> +struct is_specialization_of : std::false_type {}; + +template<template <typename...> class Primary, typename... Args> +struct is_specialization_of<Primary, Primary<Args...>> : std::true_type {}; + +template<typename T> +using is_json_pointer = is_specialization_of<::nlohmann::json_pointer, uncvref_t<T>>; + +// checks if A and B are comparable using Compare functor +template<typename Compare, typename A, typename B, typename = void> +struct is_comparable : std::false_type {}; + +template<typename Compare, typename A, typename B> +struct is_comparable<Compare, A, B, void_t< +decltype(std::declval<Compare>()(std::declval<A>(), std::declval<B>())), +decltype(std::declval<Compare>()(std::declval<B>(), std::declval<A>())) +>> : std::true_type {}; + +template<typename T> +using detect_is_transparent = typename T::is_transparent; + +// type trait to check if KeyType can be used as object key (without a BasicJsonType) +// see is_usable_as_basic_json_key_type below +template<typename Comparator, typename ObjectKeyType, typename KeyTypeCVRef, bool RequireTransparentComparator = true, + bool ExcludeObjectKeyType = RequireTransparentComparator, typename KeyType = uncvref_t<KeyTypeCVRef>> +using is_usable_as_key_type = typename std::conditional < + is_comparable<Comparator, ObjectKeyType, KeyTypeCVRef>::value + && !(ExcludeObjectKeyType && std::is_same<KeyType, + ObjectKeyType>::value) + && (!RequireTransparentComparator + || is_detected <detect_is_transparent, Comparator>::value) + && !is_json_pointer<KeyType>::value, + std::true_type, + std::false_type >::type; + +// type trait to check if KeyType can be used as object key +// true if: +// - KeyType is comparable with BasicJsonType::object_t::key_type +// - if ExcludeObjectKeyType is true, KeyType is not BasicJsonType::object_t::key_type +// - the comparator is transparent or RequireTransparentComparator is false +// - KeyType is not a JSON iterator or json_pointer +template<typename BasicJsonType, typename KeyTypeCVRef, bool RequireTransparentComparator = true, + bool ExcludeObjectKeyType = RequireTransparentComparator, typename KeyType = uncvref_t<KeyTypeCVRef>> +using is_usable_as_basic_json_key_type = typename std::conditional < + is_usable_as_key_type<typename BasicJsonType::object_comparator_t, + typename BasicJsonType::object_t::key_type, KeyTypeCVRef, + RequireTransparentComparator, ExcludeObjectKeyType>::value + && !is_json_iterator_of<BasicJsonType, KeyType>::value, + std::true_type, + std::false_type >::type; + +template<typename ObjectType, typename KeyType> +using detect_erase_with_key_type = decltype(std::declval<ObjectType&>().erase(std::declval<KeyType>())); + +// type trait to check if object_t has an erase() member functions accepting KeyType +template<typename BasicJsonType, typename KeyType> +using has_erase_with_key_type = typename std::conditional < + is_detected < + detect_erase_with_key_type, + typename BasicJsonType::object_t, KeyType >::value, + std::true_type, + std::false_type >::type; + +// a naive helper to check if a type is an ordered_map (exploits the fact that +// ordered_map inherits capacity() from std::vector) +template <typename T> +struct is_ordered_map +{ + using one = char; + + struct two + { + char x[2]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + }; + + template <typename C> static one test( decltype(&C::capacity) ) ; + template <typename C> static two test(...); + + enum { value = sizeof(test<T>(nullptr)) == sizeof(char) }; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) +}; + +// to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324) +template < typename T, typename U, enable_if_t < !std::is_same<T, U>::value, int > = 0 > +T conditional_static_cast(U value) +{ + return static_cast<T>(value); +} + +template<typename T, typename U, enable_if_t<std::is_same<T, U>::value, int> = 0> +T conditional_static_cast(U value) +{ + return value; +} + +template<typename... Types> +using all_integral = conjunction<std::is_integral<Types>...>; + +template<typename... Types> +using all_signed = conjunction<std::is_signed<Types>...>; + +template<typename... Types> +using all_unsigned = conjunction<std::is_unsigned<Types>...>; + +// there's a disjunction trait in another PR; replace when merged +template<typename... Types> +using same_sign = std::integral_constant < bool, + all_signed<Types...>::value || all_unsigned<Types...>::value >; + +template<typename OfType, typename T> +using never_out_of_range = std::integral_constant < bool, + (std::is_signed<OfType>::value && (sizeof(T) < sizeof(OfType))) + || (same_sign<OfType, T>::value && sizeof(OfType) == sizeof(T)) >; + +template<typename OfType, typename T, + bool OfTypeSigned = std::is_signed<OfType>::value, + bool TSigned = std::is_signed<T>::value> +struct value_in_range_of_impl2; + +template<typename OfType, typename T> +struct value_in_range_of_impl2<OfType, T, false, false> +{ + static constexpr bool test(T val) + { + using CommonType = typename std::common_type<OfType, T>::type; + return static_cast<CommonType>(val) <= static_cast<CommonType>((std::numeric_limits<OfType>::max)()); + } +}; + +template<typename OfType, typename T> +struct value_in_range_of_impl2<OfType, T, true, false> +{ + static constexpr bool test(T val) + { + using CommonType = typename std::common_type<OfType, T>::type; + return static_cast<CommonType>(val) <= static_cast<CommonType>((std::numeric_limits<OfType>::max)()); + } +}; + +template<typename OfType, typename T> +struct value_in_range_of_impl2<OfType, T, false, true> +{ + static constexpr bool test(T val) + { + using CommonType = typename std::common_type<OfType, T>::type; + return val >= 0 && static_cast<CommonType>(val) <= static_cast<CommonType>((std::numeric_limits<OfType>::max)()); + } +}; + +template<typename OfType, typename T> +struct value_in_range_of_impl2<OfType, T, true, true> +{ + static constexpr bool test(T val) + { + using CommonType = typename std::common_type<OfType, T>::type; + return static_cast<CommonType>(val) >= static_cast<CommonType>((std::numeric_limits<OfType>::min)()) + && static_cast<CommonType>(val) <= static_cast<CommonType>((std::numeric_limits<OfType>::max)()); + } +}; + +template<typename OfType, typename T, + bool NeverOutOfRange = never_out_of_range<OfType, T>::value, + typename = detail::enable_if_t<all_integral<OfType, T>::value>> +struct value_in_range_of_impl1; + +template<typename OfType, typename T> +struct value_in_range_of_impl1<OfType, T, false> +{ + static constexpr bool test(T val) + { + return value_in_range_of_impl2<OfType, T>::test(val); + } +}; + +template<typename OfType, typename T> +struct value_in_range_of_impl1<OfType, T, true> +{ + static constexpr bool test(T /*val*/) + { + return true; + } +}; + +template<typename OfType, typename T> +inline constexpr bool value_in_range_of(T val) +{ + return value_in_range_of_impl1<OfType, T>::test(val); +} + +template<bool Value> +using bool_constant = std::integral_constant<bool, Value>; + +/////////////////////////////////////////////////////////////////////////////// +// is_c_string +/////////////////////////////////////////////////////////////////////////////// + +namespace impl +{ + +template<typename T> +inline constexpr bool is_c_string() +{ + using TUnExt = typename std::remove_extent<T>::type; + using TUnCVExt = typename std::remove_cv<TUnExt>::type; + using TUnPtr = typename std::remove_pointer<T>::type; + using TUnCVPtr = typename std::remove_cv<TUnPtr>::type; + return + (std::is_array<T>::value && std::is_same<TUnCVExt, char>::value) + || (std::is_pointer<T>::value && std::is_same<TUnCVPtr, char>::value); +} + +} // namespace impl + +// checks whether T is a [cv] char */[cv] char[] C string +template<typename T> +struct is_c_string : bool_constant<impl::is_c_string<T>()> {}; + +template<typename T> +using is_c_string_uncvref = is_c_string<uncvref_t<T>>; + +/////////////////////////////////////////////////////////////////////////////// +// is_transparent +/////////////////////////////////////////////////////////////////////////////// + +namespace impl +{ + +template<typename T> +inline constexpr bool is_transparent() +{ + return is_detected<detect_is_transparent, T>::value; +} + +} // namespace impl + +// checks whether T has a member named is_transparent +template<typename T> +struct is_transparent : bool_constant<impl::is_transparent<T>()> {}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/string_concat.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <cstring> // strlen +#include <string> // string +#include <utility> // forward + +// #include <nlohmann/detail/meta/cpp_future.hpp> + +// #include <nlohmann/detail/meta/detected.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +inline std::size_t concat_length() +{ + return 0; +} + +template<typename... Args> +inline std::size_t concat_length(const char* cstr, const Args& ... rest); + +template<typename StringType, typename... Args> +inline std::size_t concat_length(const StringType& str, const Args& ... rest); + +template<typename... Args> +inline std::size_t concat_length(const char /*c*/, const Args& ... rest) +{ + return 1 + concat_length(rest...); +} + +template<typename... Args> +inline std::size_t concat_length(const char* cstr, const Args& ... rest) +{ + // cppcheck-suppress ignoredReturnValue + return ::strlen(cstr) + concat_length(rest...); +} + +template<typename StringType, typename... Args> +inline std::size_t concat_length(const StringType& str, const Args& ... rest) +{ + return str.size() + concat_length(rest...); +} + +template<typename OutStringType> +inline void concat_into(OutStringType& /*out*/) +{} + +template<typename StringType, typename Arg> +using string_can_append = decltype(std::declval<StringType&>().append(std::declval < Arg && > ())); + +template<typename StringType, typename Arg> +using detect_string_can_append = is_detected<string_can_append, StringType, Arg>; + +template<typename StringType, typename Arg> +using string_can_append_op = decltype(std::declval<StringType&>() += std::declval < Arg && > ()); + +template<typename StringType, typename Arg> +using detect_string_can_append_op = is_detected<string_can_append_op, StringType, Arg>; + +template<typename StringType, typename Arg> +using string_can_append_iter = decltype(std::declval<StringType&>().append(std::declval<const Arg&>().begin(), std::declval<const Arg&>().end())); + +template<typename StringType, typename Arg> +using detect_string_can_append_iter = is_detected<string_can_append_iter, StringType, Arg>; + +template<typename StringType, typename Arg> +using string_can_append_data = decltype(std::declval<StringType&>().append(std::declval<const Arg&>().data(), std::declval<const Arg&>().size())); + +template<typename StringType, typename Arg> +using detect_string_can_append_data = is_detected<string_can_append_data, StringType, Arg>; + +template < typename OutStringType, typename Arg, typename... Args, + enable_if_t < !detect_string_can_append<OutStringType, Arg>::value + && detect_string_can_append_op<OutStringType, Arg>::value, int > = 0 > +inline void concat_into(OutStringType& out, Arg && arg, Args && ... rest); + +template < typename OutStringType, typename Arg, typename... Args, + enable_if_t < !detect_string_can_append<OutStringType, Arg>::value + && !detect_string_can_append_op<OutStringType, Arg>::value + && detect_string_can_append_iter<OutStringType, Arg>::value, int > = 0 > +inline void concat_into(OutStringType& out, const Arg& arg, Args && ... rest); + +template < typename OutStringType, typename Arg, typename... Args, + enable_if_t < !detect_string_can_append<OutStringType, Arg>::value + && !detect_string_can_append_op<OutStringType, Arg>::value + && !detect_string_can_append_iter<OutStringType, Arg>::value + && detect_string_can_append_data<OutStringType, Arg>::value, int > = 0 > +inline void concat_into(OutStringType& out, const Arg& arg, Args && ... rest); + +template<typename OutStringType, typename Arg, typename... Args, + enable_if_t<detect_string_can_append<OutStringType, Arg>::value, int> = 0> +inline void concat_into(OutStringType& out, Arg && arg, Args && ... rest) +{ + out.append(std::forward<Arg>(arg)); + concat_into(out, std::forward<Args>(rest)...); +} + +template < typename OutStringType, typename Arg, typename... Args, + enable_if_t < !detect_string_can_append<OutStringType, Arg>::value + && detect_string_can_append_op<OutStringType, Arg>::value, int > > +inline void concat_into(OutStringType& out, Arg&& arg, Args&& ... rest) +{ + out += std::forward<Arg>(arg); + concat_into(out, std::forward<Args>(rest)...); +} + +template < typename OutStringType, typename Arg, typename... Args, + enable_if_t < !detect_string_can_append<OutStringType, Arg>::value + && !detect_string_can_append_op<OutStringType, Arg>::value + && detect_string_can_append_iter<OutStringType, Arg>::value, int > > +inline void concat_into(OutStringType& out, const Arg& arg, Args&& ... rest) +{ + out.append(arg.begin(), arg.end()); + concat_into(out, std::forward<Args>(rest)...); +} + +template < typename OutStringType, typename Arg, typename... Args, + enable_if_t < !detect_string_can_append<OutStringType, Arg>::value + && !detect_string_can_append_op<OutStringType, Arg>::value + && !detect_string_can_append_iter<OutStringType, Arg>::value + && detect_string_can_append_data<OutStringType, Arg>::value, int > > +inline void concat_into(OutStringType& out, const Arg& arg, Args&& ... rest) +{ + out.append(arg.data(), arg.size()); + concat_into(out, std::forward<Args>(rest)...); +} + +template<typename OutStringType = std::string, typename... Args> +inline OutStringType concat(Args && ... args) +{ + OutStringType str; + str.reserve(concat_length(args...)); + concat_into(str, std::forward<Args>(args)...); + return str; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +//////////////// +// exceptions // +//////////////// + +/// @brief general exception of the @ref basic_json class +/// @sa https://json.nlohmann.me/api/basic_json/exception/ +class exception : public std::exception +{ + public: + /// returns the explanatory string + const char* what() const noexcept override + { + return m.what(); + } + + /// the id of the exception + const int id; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes) + + protected: + JSON_HEDLEY_NON_NULL(3) + exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} // NOLINT(bugprone-throw-keyword-missing) + + static std::string name(const std::string& ename, int id_) + { + return concat("[json.exception.", ename, '.', std::to_string(id_), "] "); + } + + static std::string diagnostics(std::nullptr_t /*leaf_element*/) + { + return ""; + } + + template<typename BasicJsonType> + static std::string diagnostics(const BasicJsonType* leaf_element) + { +#if JSON_DIAGNOSTICS + std::vector<std::string> tokens; + for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent) + { + switch (current->m_parent->type()) + { + case value_t::array: + { + for (std::size_t i = 0; i < current->m_parent->m_data.m_value.array->size(); ++i) + { + if (¤t->m_parent->m_data.m_value.array->operator[](i) == current) + { + tokens.emplace_back(std::to_string(i)); + break; + } + } + break; + } + + case value_t::object: + { + for (const auto& element : *current->m_parent->m_data.m_value.object) + { + if (&element.second == current) + { + tokens.emplace_back(element.first.c_str()); + break; + } + } + break; + } + + case value_t::null: // LCOV_EXCL_LINE + case value_t::string: // LCOV_EXCL_LINE + case value_t::boolean: // LCOV_EXCL_LINE + case value_t::number_integer: // LCOV_EXCL_LINE + case value_t::number_unsigned: // LCOV_EXCL_LINE + case value_t::number_float: // LCOV_EXCL_LINE + case value_t::binary: // LCOV_EXCL_LINE + case value_t::discarded: // LCOV_EXCL_LINE + default: // LCOV_EXCL_LINE + break; // LCOV_EXCL_LINE + } + } + + if (tokens.empty()) + { + return ""; + } + + auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{}, + [](const std::string & a, const std::string & b) + { + return concat(a, '/', detail::escape(b)); + }); + return concat('(', str, ") "); +#else + static_cast<void>(leaf_element); + return ""; +#endif + } + + private: + /// an exception object as storage for error messages + std::runtime_error m; +}; + +/// @brief exception indicating a parse error +/// @sa https://json.nlohmann.me/api/basic_json/parse_error/ +class parse_error : public exception +{ + public: + /*! + @brief create a parse error exception + @param[in] id_ the id of the exception + @param[in] pos the position where the error occurred (or with + chars_read_total=0 if the position cannot be + determined) + @param[in] what_arg the explanatory string + @return parse_error object + */ + template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> + static parse_error create(int id_, const position_t& pos, const std::string& what_arg, BasicJsonContext context) + { + const std::string w = concat(exception::name("parse_error", id_), "parse error", + position_string(pos), ": ", exception::diagnostics(context), what_arg); + return {id_, pos.chars_read_total, w.c_str()}; + } + + template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> + static parse_error create(int id_, std::size_t byte_, const std::string& what_arg, BasicJsonContext context) + { + const std::string w = concat(exception::name("parse_error", id_), "parse error", + (byte_ != 0 ? (concat(" at byte ", std::to_string(byte_))) : ""), + ": ", exception::diagnostics(context), what_arg); + return {id_, byte_, w.c_str()}; + } + + /*! + @brief byte index of the parse error + + The byte index of the last read character in the input file. + + @note For an input with n bytes, 1 is the index of the first character and + n+1 is the index of the terminating null byte or the end of file. + This also holds true when reading a byte vector (CBOR or MessagePack). + */ + const std::size_t byte; + + private: + parse_error(int id_, std::size_t byte_, const char* what_arg) + : exception(id_, what_arg), byte(byte_) {} + + static std::string position_string(const position_t& pos) + { + return concat(" at line ", std::to_string(pos.lines_read + 1), + ", column ", std::to_string(pos.chars_read_current_line)); + } +}; + +/// @brief exception indicating errors with iterators +/// @sa https://json.nlohmann.me/api/basic_json/invalid_iterator/ +class invalid_iterator : public exception +{ + public: + template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> + static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context) + { + const std::string w = concat(exception::name("invalid_iterator", id_), exception::diagnostics(context), what_arg); + return {id_, w.c_str()}; + } + + private: + JSON_HEDLEY_NON_NULL(3) + invalid_iterator(int id_, const char* what_arg) + : exception(id_, what_arg) {} +}; + +/// @brief exception indicating executing a member function with a wrong type +/// @sa https://json.nlohmann.me/api/basic_json/type_error/ +class type_error : public exception +{ + public: + template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> + static type_error create(int id_, const std::string& what_arg, BasicJsonContext context) + { + const std::string w = concat(exception::name("type_error", id_), exception::diagnostics(context), what_arg); + return {id_, w.c_str()}; + } + + private: + JSON_HEDLEY_NON_NULL(3) + type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/// @brief exception indicating access out of the defined range +/// @sa https://json.nlohmann.me/api/basic_json/out_of_range/ +class out_of_range : public exception +{ + public: + template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> + static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context) + { + const std::string w = concat(exception::name("out_of_range", id_), exception::diagnostics(context), what_arg); + return {id_, w.c_str()}; + } + + private: + JSON_HEDLEY_NON_NULL(3) + out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/// @brief exception indicating other library errors +/// @sa https://json.nlohmann.me/api/basic_json/other_error/ +class other_error : public exception +{ + public: + template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0> + static other_error create(int id_, const std::string& what_arg, BasicJsonContext context) + { + const std::string w = concat(exception::name("other_error", id_), exception::diagnostics(context), what_arg); + return {id_, w.c_str()}; + } + + private: + JSON_HEDLEY_NON_NULL(3) + other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/meta/cpp_future.hpp> + +// #include <nlohmann/detail/meta/identity_tag.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +// #include <nlohmann/detail/abi_macros.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// dispatching helper struct +template <class T> struct identity_tag {}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/meta/std_fs.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +// #include <nlohmann/detail/macro_scope.hpp> + + +#if JSON_HAS_EXPERIMENTAL_FILESYSTEM +#include <experimental/filesystem> +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace std_fs = std::experimental::filesystem; +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END +#elif JSON_HAS_FILESYSTEM +#include <filesystem> +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace std_fs = std::filesystem; +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END +#endif + +// #include <nlohmann/detail/meta/type_traits.hpp> + +// #include <nlohmann/detail/string_concat.hpp> + +// #include <nlohmann/detail/value_t.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template<typename BasicJsonType> +inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_null())) + { + JSON_THROW(type_error::create(302, concat("type must be null, but is ", j.type_name()), &j)); + } + n = nullptr; +} + +// overloads for basic_json template parameters +template < typename BasicJsonType, typename ArithmeticType, + enable_if_t < std::is_arithmetic<ArithmeticType>::value&& + !std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value, + int > = 0 > +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast<value_t>(j)) + { + case value_t::number_unsigned: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>()); + break; + } + case value_t::number_integer: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>()); + break; + } + case value_t::number_float: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>()); + break; + } + + case value_t::null: + case value_t::object: + case value_t::array: + case value_t::string: + case value_t::boolean: + case value_t::binary: + case value_t::discarded: + default: + JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + } +} + +template<typename BasicJsonType> +inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) + { + JSON_THROW(type_error::create(302, concat("type must be boolean, but is ", j.type_name()), &j)); + } + b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>(); +} + +template<typename BasicJsonType> +inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_string())) + { + JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + } + s = *j.template get_ptr<const typename BasicJsonType::string_t*>(); +} + +template < + typename BasicJsonType, typename StringType, + enable_if_t < + std::is_assignable<StringType&, const typename BasicJsonType::string_t>::value + && is_detected_exact<typename BasicJsonType::string_t::value_type, value_type_t, StringType>::value + && !std::is_same<typename BasicJsonType::string_t, StringType>::value + && !is_json_ref<StringType>::value, int > = 0 > +inline void from_json(const BasicJsonType& j, StringType& s) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_string())) + { + JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + } + + s = *j.template get_ptr<const typename BasicJsonType::string_t*>(); +} + +template<typename BasicJsonType> +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template<typename BasicJsonType> +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template<typename BasicJsonType> +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +#if !JSON_DISABLE_ENUM_SERIALIZATION +template<typename BasicJsonType, typename EnumType, + enable_if_t<std::is_enum<EnumType>::value, int> = 0> +inline void from_json(const BasicJsonType& j, EnumType& e) +{ + typename std::underlying_type<EnumType>::type val; + get_arithmetic_value(j, val); + e = static_cast<EnumType>(val); +} +#endif // JSON_DISABLE_ENUM_SERIALIZATION + +// forward_list doesn't have an insert method +template<typename BasicJsonType, typename T, typename Allocator, + enable_if_t<is_getable<BasicJsonType, T>::value, int> = 0> +inline void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + } + l.clear(); + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType & i) + { + return i.template get<T>(); + }); +} + +// valarray doesn't have an insert method +template<typename BasicJsonType, typename T, + enable_if_t<is_getable<BasicJsonType, T>::value, int> = 0> +inline void from_json(const BasicJsonType& j, std::valarray<T>& l) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + } + l.resize(j.size()); + std::transform(j.begin(), j.end(), std::begin(l), + [](const BasicJsonType & elem) + { + return elem.template get<T>(); + }); +} + +template<typename BasicJsonType, typename T, std::size_t N> +auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get<T>(), void()) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get<T>(); + } +} + +template<typename BasicJsonType> +inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) +{ + arr = *j.template get_ptr<const typename BasicJsonType::array_t*>(); +} + +template<typename BasicJsonType, typename T, std::size_t N> +auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr, + priority_tag<2> /*unused*/) +-> decltype(j.template get<T>(), void()) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get<T>(); + } +} + +template<typename BasicJsonType, typename ConstructibleArrayType, + enable_if_t< + std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value, + int> = 0> +auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) +-> decltype( + arr.reserve(std::declval<typename ConstructibleArrayType::size_type>()), + j.template get<typename ConstructibleArrayType::value_type>(), + void()) +{ + using std::end; + + ConstructibleArrayType ret; + ret.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(ret, end(ret)), [](const BasicJsonType & i) + { + // get<BasicJsonType>() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get<typename ConstructibleArrayType::value_type>(); + }); + arr = std::move(ret); +} + +template<typename BasicJsonType, typename ConstructibleArrayType, + enable_if_t< + std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value, + int> = 0> +inline void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, + priority_tag<0> /*unused*/) +{ + using std::end; + + ConstructibleArrayType ret; + std::transform( + j.begin(), j.end(), std::inserter(ret, end(ret)), + [](const BasicJsonType & i) + { + // get<BasicJsonType>() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get<typename ConstructibleArrayType::value_type>(); + }); + arr = std::move(ret); +} + +template < typename BasicJsonType, typename ConstructibleArrayType, + enable_if_t < + is_constructible_array_type<BasicJsonType, ConstructibleArrayType>::value&& + !is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&& + !is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&& + !std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&& + !is_basic_json<ConstructibleArrayType>::value, + int > = 0 > +auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) +-> decltype(from_json_array_impl(j, arr, priority_tag<3> {}), +j.template get<typename ConstructibleArrayType::value_type>(), +void()) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + } + + from_json_array_impl(j, arr, priority_tag<3> {}); +} + +template < typename BasicJsonType, typename T, std::size_t... Idx > +std::array<T, sizeof...(Idx)> from_json_inplace_array_impl(BasicJsonType&& j, + identity_tag<std::array<T, sizeof...(Idx)>> /*unused*/, index_sequence<Idx...> /*unused*/) +{ + return { { std::forward<BasicJsonType>(j).at(Idx).template get<T>()... } }; +} + +template < typename BasicJsonType, typename T, std::size_t N > +auto from_json(BasicJsonType&& j, identity_tag<std::array<T, N>> tag) +-> decltype(from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag, make_index_sequence<N> {})) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + } + + return from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag, make_index_sequence<N> {}); +} + +template<typename BasicJsonType> +inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) + { + JSON_THROW(type_error::create(302, concat("type must be binary, but is ", j.type_name()), &j)); + } + + bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>(); +} + +template<typename BasicJsonType, typename ConstructibleObjectType, + enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0> +inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_object())) + { + JSON_THROW(type_error::create(302, concat("type must be object, but is ", j.type_name()), &j)); + } + + ConstructibleObjectType ret; + const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>(); + using value_type = typename ConstructibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(ret, ret.begin()), + [](typename BasicJsonType::object_t::value_type const & p) + { + return value_type(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>()); + }); + obj = std::move(ret); +} + +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template < typename BasicJsonType, typename ArithmeticType, + enable_if_t < + std::is_arithmetic<ArithmeticType>::value&& + !std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value&& + !std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value&& + !std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value&& + !std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value, + int > = 0 > +inline void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast<value_t>(j)) + { + case value_t::number_unsigned: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>()); + break; + } + case value_t::number_integer: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>()); + break; + } + case value_t::number_float: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>()); + break; + } + case value_t::boolean: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>()); + break; + } + + case value_t::null: + case value_t::object: + case value_t::array: + case value_t::string: + case value_t::binary: + case value_t::discarded: + default: + JSON_THROW(type_error::create(302, concat("type must be number, but is ", j.type_name()), &j)); + } +} + +template<typename BasicJsonType, typename... Args, std::size_t... Idx> +std::tuple<Args...> from_json_tuple_impl_base(BasicJsonType&& j, index_sequence<Idx...> /*unused*/) +{ + return std::make_tuple(std::forward<BasicJsonType>(j).at(Idx).template get<Args>()...); +} + +template < typename BasicJsonType, class A1, class A2 > +std::pair<A1, A2> from_json_tuple_impl(BasicJsonType&& j, identity_tag<std::pair<A1, A2>> /*unused*/, priority_tag<0> /*unused*/) +{ + return {std::forward<BasicJsonType>(j).at(0).template get<A1>(), + std::forward<BasicJsonType>(j).at(1).template get<A2>()}; +} + +template<typename BasicJsonType, typename A1, typename A2> +inline void from_json_tuple_impl(BasicJsonType&& j, std::pair<A1, A2>& p, priority_tag<1> /*unused*/) +{ + p = from_json_tuple_impl(std::forward<BasicJsonType>(j), identity_tag<std::pair<A1, A2>> {}, priority_tag<0> {}); +} + +template<typename BasicJsonType, typename... Args> +std::tuple<Args...> from_json_tuple_impl(BasicJsonType&& j, identity_tag<std::tuple<Args...>> /*unused*/, priority_tag<2> /*unused*/) +{ + return from_json_tuple_impl_base<BasicJsonType, Args...>(std::forward<BasicJsonType>(j), index_sequence_for<Args...> {}); +} + +template<typename BasicJsonType, typename... Args> +inline void from_json_tuple_impl(BasicJsonType&& j, std::tuple<Args...>& t, priority_tag<3> /*unused*/) +{ + t = from_json_tuple_impl_base<BasicJsonType, Args...>(std::forward<BasicJsonType>(j), index_sequence_for<Args...> {}); +} + +template<typename BasicJsonType, typename TupleRelated> +auto from_json(BasicJsonType&& j, TupleRelated&& t) +-> decltype(from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {})) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + } + + return from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {}); +} + +template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, + typename = enable_if_t < !std::is_constructible < + typename BasicJsonType::string_t, Key >::value >> +inline void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + } + m.clear(); + for (const auto& p : j) + { + if (JSON_HEDLEY_UNLIKELY(!p.is_array())) + { + JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &j)); + } + m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>()); + } +} + +template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, + typename = enable_if_t < !std::is_constructible < + typename BasicJsonType::string_t, Key >::value >> +inline void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j)); + } + m.clear(); + for (const auto& p : j) + { + if (JSON_HEDLEY_UNLIKELY(!p.is_array())) + { + JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &j)); + } + m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>()); + } +} + +#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM +template<typename BasicJsonType> +inline void from_json(const BasicJsonType& j, std_fs::path& p) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_string())) + { + JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); + } + p = *j.template get_ptr<const typename BasicJsonType::string_t*>(); +} +#endif + +struct from_json_fn +{ + template<typename BasicJsonType, typename T> + auto operator()(const BasicJsonType& j, T&& val) const + noexcept(noexcept(from_json(j, std::forward<T>(val)))) + -> decltype(from_json(j, std::forward<T>(val))) + { + return from_json(j, std::forward<T>(val)); + } +}; + +} // namespace detail + +#ifndef JSON_HAS_CPP_17 +/// namespace to hold default `from_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) +{ +#endif +JSON_INLINE_VARIABLE constexpr const auto& from_json = // NOLINT(misc-definitions-in-headers) + detail::static_const<detail::from_json_fn>::value; +#ifndef JSON_HAS_CPP_17 +} // namespace +#endif + +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/conversions/to_json.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <algorithm> // copy +#include <iterator> // begin, end +#include <string> // string +#include <tuple> // tuple, get +#include <type_traits> // is_same, is_constructible, is_floating_point, is_enum, underlying_type +#include <utility> // move, forward, declval, pair +#include <valarray> // valarray +#include <vector> // vector + +// #include <nlohmann/detail/iterators/iteration_proxy.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <cstddef> // size_t +#include <iterator> // input_iterator_tag +#include <string> // string, to_string +#include <tuple> // tuple_size, get, tuple_element +#include <utility> // move + +#if JSON_HAS_RANGES + #include <ranges> // enable_borrowed_range +#endif + +// #include <nlohmann/detail/abi_macros.hpp> + +// #include <nlohmann/detail/meta/type_traits.hpp> + +// #include <nlohmann/detail/value_t.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template<typename string_type> +void int_to_string( string_type& target, std::size_t value ) +{ + // For ADL + using std::to_string; + target = to_string(value); +} +template<typename IteratorType> class iteration_proxy_value +{ + public: + using difference_type = std::ptrdiff_t; + using value_type = iteration_proxy_value; + using pointer = value_type *; + using reference = value_type &; + using iterator_category = std::input_iterator_tag; + using string_type = typename std::remove_cv< typename std::remove_reference<decltype( std::declval<IteratorType>().key() ) >::type >::type; + + private: + /// the iterator + IteratorType anchor{}; + /// an index for arrays (used to create key names) + std::size_t array_index = 0; + /// last stringified array index + mutable std::size_t array_index_last = 0; + /// a string representation of the array index + mutable string_type array_index_str = "0"; + /// an empty string (to return a reference for primitive values) + string_type empty_str{}; + + public: + explicit iteration_proxy_value() = default; + explicit iteration_proxy_value(IteratorType it, std::size_t array_index_ = 0) + noexcept(std::is_nothrow_move_constructible<IteratorType>::value + && std::is_nothrow_default_constructible<string_type>::value) + : anchor(std::move(it)) + , array_index(array_index_) + {} + + iteration_proxy_value(iteration_proxy_value const&) = default; + iteration_proxy_value& operator=(iteration_proxy_value const&) = default; + // older GCCs are a bit fussy and require explicit noexcept specifiers on defaulted functions + iteration_proxy_value(iteration_proxy_value&&) + noexcept(std::is_nothrow_move_constructible<IteratorType>::value + && std::is_nothrow_move_constructible<string_type>::value) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations) + iteration_proxy_value& operator=(iteration_proxy_value&&) + noexcept(std::is_nothrow_move_assignable<IteratorType>::value + && std::is_nothrow_move_assignable<string_type>::value) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations) + ~iteration_proxy_value() = default; + + /// dereference operator (needed for range-based for) + const iteration_proxy_value& operator*() const + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_value& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + iteration_proxy_value operator++(int)& // NOLINT(cert-dcl21-cpp) + { + auto tmp = iteration_proxy_value(anchor, array_index); + ++anchor; + ++array_index; + return tmp; + } + + /// equality operator (needed for InputIterator) + bool operator==(const iteration_proxy_value& o) const + { + return anchor == o.anchor; + } + + /// inequality operator (needed for range-based for) + bool operator!=(const iteration_proxy_value& o) const + { + return anchor != o.anchor; + } + + /// return key of the iterator + const string_type& key() const + { + JSON_ASSERT(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + { + if (array_index != array_index_last) + { + int_to_string( array_index_str, array_index ); + array_index_last = array_index; + } + return array_index_str; + } + + // use key from the object + case value_t::object: + return anchor.key(); + + // use an empty key for all primitive types + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + return empty_str; + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } +}; + +/// proxy class for the items() function +template<typename IteratorType> class iteration_proxy +{ + private: + /// the container to iterate + typename IteratorType::pointer container = nullptr; + + public: + explicit iteration_proxy() = default; + + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) noexcept + : container(&cont) {} + + iteration_proxy(iteration_proxy const&) = default; + iteration_proxy& operator=(iteration_proxy const&) = default; + iteration_proxy(iteration_proxy&&) noexcept = default; + iteration_proxy& operator=(iteration_proxy&&) noexcept = default; + ~iteration_proxy() = default; + + /// return iterator begin (needed for range-based for) + iteration_proxy_value<IteratorType> begin() const noexcept + { + return iteration_proxy_value<IteratorType>(container->begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_value<IteratorType> end() const noexcept + { + return iteration_proxy_value<IteratorType>(container->end()); + } +}; + +// Structured Bindings Support +// For further reference see https://blog.tartanllama.xyz/structured-bindings/ +// And see https://github.com/nlohmann/json/pull/1391 +template<std::size_t N, typename IteratorType, enable_if_t<N == 0, int> = 0> +auto get(const nlohmann::detail::iteration_proxy_value<IteratorType>& i) -> decltype(i.key()) +{ + return i.key(); +} +// Structured Bindings Support +// For further reference see https://blog.tartanllama.xyz/structured-bindings/ +// And see https://github.com/nlohmann/json/pull/1391 +template<std::size_t N, typename IteratorType, enable_if_t<N == 1, int> = 0> +auto get(const nlohmann::detail::iteration_proxy_value<IteratorType>& i) -> decltype(i.value()) +{ + return i.value(); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// The Addition to the STD Namespace is required to add +// Structured Bindings Support to the iteration_proxy_value class +// For further reference see https://blog.tartanllama.xyz/structured-bindings/ +// And see https://github.com/nlohmann/json/pull/1391 +namespace std +{ + +#if defined(__clang__) + // Fix: https://github.com/nlohmann/json/issues/1401 + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +template<typename IteratorType> +class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp) + : public std::integral_constant<std::size_t, 2> {}; + +template<std::size_t N, typename IteratorType> +class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >> // NOLINT(cert-dcl58-cpp) +{ + public: + using type = decltype( + get<N>(std::declval < + ::nlohmann::detail::iteration_proxy_value<IteratorType >> ())); +}; +#if defined(__clang__) + #pragma clang diagnostic pop +#endif + +} // namespace std + +#if JSON_HAS_RANGES + template <typename IteratorType> + inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy<IteratorType>> = true; +#endif + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/meta/cpp_future.hpp> + +// #include <nlohmann/detail/meta/std_fs.hpp> + +// #include <nlohmann/detail/meta/type_traits.hpp> + +// #include <nlohmann/detail/value_t.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +////////////////// +// constructors // +////////////////// + +/* + * Note all external_constructor<>::construct functions need to call + * j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an + * allocated value (e.g., a string). See bug issue + * https://github.com/nlohmann/json/issues/2865 for more information. + */ + +template<value_t> struct external_constructor; + +template<> +struct external_constructor<value_t::boolean> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::boolean; + j.m_data.m_value = b; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::string> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::string; + j.m_data.m_value = s; + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::string; + j.m_data.m_value = std::move(s); + j.assert_invariant(); + } + + template < typename BasicJsonType, typename CompatibleStringType, + enable_if_t < !std::is_same<CompatibleStringType, typename BasicJsonType::string_t>::value, + int > = 0 > + static void construct(BasicJsonType& j, const CompatibleStringType& str) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::string; + j.m_data.m_value.string = j.template create<typename BasicJsonType::string_t>(str); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::binary> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::binary; + j.m_data.m_value = typename BasicJsonType::binary_t(b); + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::binary; + j.m_data.m_value = typename BasicJsonType::binary_t(std::move(b)); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::number_float> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::number_float; + j.m_data.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::number_unsigned> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::number_unsigned; + j.m_data.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::number_integer> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::number_integer; + j.m_data.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::array> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value = arr; + j.set_parents(); + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value = std::move(arr); + j.set_parents(); + j.assert_invariant(); + } + + template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value, + int > = 0 > + static void construct(BasicJsonType& j, const CompatibleArrayType& arr) + { + using std::begin; + using std::end; + + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr)); + j.set_parents(); + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType& j, const std::vector<bool>& arr) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value = value_t::array; + j.m_data.m_value.array->reserve(arr.size()); + for (const bool x : arr) + { + j.m_data.m_value.array->push_back(x); + j.set_parent(j.m_data.m_value.array->back()); + } + j.assert_invariant(); + } + + template<typename BasicJsonType, typename T, + enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> + static void construct(BasicJsonType& j, const std::valarray<T>& arr) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value = value_t::array; + j.m_data.m_value.array->resize(arr.size()); + if (arr.size() > 0) + { + std::copy(std::begin(arr), std::end(arr), j.m_data.m_value.array->begin()); + } + j.set_parents(); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::object> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::object; + j.m_data.m_value = obj; + j.set_parents(); + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) + { + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::object; + j.m_data.m_value = std::move(obj); + j.set_parents(); + j.assert_invariant(); + } + + template < typename BasicJsonType, typename CompatibleObjectType, + enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 > + static void construct(BasicJsonType& j, const CompatibleObjectType& obj) + { + using std::begin; + using std::end; + + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::object; + j.m_data.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj)); + j.set_parents(); + j.assert_invariant(); + } +}; + +///////////// +// to_json // +///////////// + +template<typename BasicJsonType, typename T, + enable_if_t<std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0> +inline void to_json(BasicJsonType& j, T b) noexcept +{ + external_constructor<value_t::boolean>::construct(j, b); +} + +template < typename BasicJsonType, typename BoolRef, + enable_if_t < + ((std::is_same<std::vector<bool>::reference, BoolRef>::value + && !std::is_same <std::vector<bool>::reference, typename BasicJsonType::boolean_t&>::value) + || (std::is_same<std::vector<bool>::const_reference, BoolRef>::value + && !std::is_same <detail::uncvref_t<std::vector<bool>::const_reference>, + typename BasicJsonType::boolean_t >::value)) + && std::is_convertible<const BoolRef&, typename BasicJsonType::boolean_t>::value, int > = 0 > +inline void to_json(BasicJsonType& j, const BoolRef& b) noexcept +{ + external_constructor<value_t::boolean>::construct(j, static_cast<typename BasicJsonType::boolean_t>(b)); +} + +template<typename BasicJsonType, typename CompatibleString, + enable_if_t<std::is_constructible<typename BasicJsonType::string_t, CompatibleString>::value, int> = 0> +inline void to_json(BasicJsonType& j, const CompatibleString& s) +{ + external_constructor<value_t::string>::construct(j, s); +} + +template<typename BasicJsonType> +inline void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +{ + external_constructor<value_t::string>::construct(j, std::move(s)); +} + +template<typename BasicJsonType, typename FloatType, + enable_if_t<std::is_floating_point<FloatType>::value, int> = 0> +inline void to_json(BasicJsonType& j, FloatType val) noexcept +{ + external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val)); +} + +template<typename BasicJsonType, typename CompatibleNumberUnsignedType, + enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0> +inline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val)); +} + +template<typename BasicJsonType, typename CompatibleNumberIntegerType, + enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0> +inline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val)); +} + +#if !JSON_DISABLE_ENUM_SERIALIZATION +template<typename BasicJsonType, typename EnumType, + enable_if_t<std::is_enum<EnumType>::value, int> = 0> +inline void to_json(BasicJsonType& j, EnumType e) noexcept +{ + using underlying_type = typename std::underlying_type<EnumType>::type; + external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e)); +} +#endif // JSON_DISABLE_ENUM_SERIALIZATION + +template<typename BasicJsonType> +inline void to_json(BasicJsonType& j, const std::vector<bool>& e) +{ + external_constructor<value_t::array>::construct(j, e); +} + +template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < is_compatible_array_type<BasicJsonType, + CompatibleArrayType>::value&& + !is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&& + !is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&& + !std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&& + !is_basic_json<CompatibleArrayType>::value, + int > = 0 > +inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +{ + external_constructor<value_t::array>::construct(j, arr); +} + +template<typename BasicJsonType> +inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) +{ + external_constructor<value_t::binary>::construct(j, bin); +} + +template<typename BasicJsonType, typename T, + enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> +inline void to_json(BasicJsonType& j, const std::valarray<T>& arr) +{ + external_constructor<value_t::array>::construct(j, std::move(arr)); +} + +template<typename BasicJsonType> +inline void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +{ + external_constructor<value_t::array>::construct(j, std::move(arr)); +} + +template < typename BasicJsonType, typename CompatibleObjectType, + enable_if_t < is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value&& !is_basic_json<CompatibleObjectType>::value, int > = 0 > +inline void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +{ + external_constructor<value_t::object>::construct(j, obj); +} + +template<typename BasicJsonType> +inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +{ + external_constructor<value_t::object>::construct(j, std::move(obj)); +} + +template < + typename BasicJsonType, typename T, std::size_t N, + enable_if_t < !std::is_constructible<typename BasicJsonType::string_t, + const T(&)[N]>::value, // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + int > = 0 > +inline void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +{ + external_constructor<value_t::array>::construct(j, arr); +} + +template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible<BasicJsonType, T1>::value&& std::is_constructible<BasicJsonType, T2>::value, int > = 0 > +inline void to_json(BasicJsonType& j, const std::pair<T1, T2>& p) +{ + j = { p.first, p.second }; +} + +// for https://github.com/nlohmann/json/pull/1134 +template<typename BasicJsonType, typename T, + enable_if_t<std::is_same<T, iteration_proxy_value<typename BasicJsonType::iterator>>::value, int> = 0> +inline void to_json(BasicJsonType& j, const T& b) +{ + j = { {b.key(), b.value()} }; +} + +template<typename BasicJsonType, typename Tuple, std::size_t... Idx> +inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<Idx...> /*unused*/) +{ + j = { std::get<Idx>(t)... }; +} + +template<typename BasicJsonType, typename T, enable_if_t<is_constructible_tuple<BasicJsonType, T>::value, int > = 0> +inline void to_json(BasicJsonType& j, const T& t) +{ + to_json_tuple_impl(j, t, make_index_sequence<std::tuple_size<T>::value> {}); +} + +#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM +template<typename BasicJsonType> +inline void to_json(BasicJsonType& j, const std_fs::path& p) +{ + j = p.string(); +} +#endif + +struct to_json_fn +{ + template<typename BasicJsonType, typename T> + auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward<T>(val)))) + -> decltype(to_json(j, std::forward<T>(val)), void()) + { + return to_json(j, std::forward<T>(val)); + } +}; +} // namespace detail + +#ifndef JSON_HAS_CPP_17 +/// namespace to hold default `to_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) +{ +#endif +JSON_INLINE_VARIABLE constexpr const auto& to_json = // NOLINT(misc-definitions-in-headers) + detail::static_const<detail::to_json_fn>::value; +#ifndef JSON_HAS_CPP_17 +} // namespace +#endif + +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/meta/identity_tag.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +/// @sa https://json.nlohmann.me/api/adl_serializer/ +template<typename ValueType, typename> +struct adl_serializer +{ + /// @brief convert a JSON value to any value type + /// @sa https://json.nlohmann.me/api/adl_serializer/from_json/ + template<typename BasicJsonType, typename TargetType = ValueType> + static auto from_json(BasicJsonType && j, TargetType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val))) + -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), val), void()) + { + ::nlohmann::from_json(std::forward<BasicJsonType>(j), val); + } + + /// @brief convert a JSON value to any value type + /// @sa https://json.nlohmann.me/api/adl_serializer/from_json/ + template<typename BasicJsonType, typename TargetType = ValueType> + static auto from_json(BasicJsonType && j) noexcept( + noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {}))) + -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {})) + { + return ::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {}); + } + + /// @brief convert any value type to a JSON value + /// @sa https://json.nlohmann.me/api/adl_serializer/to_json/ + template<typename BasicJsonType, typename TargetType = ValueType> + static auto to_json(BasicJsonType& j, TargetType && val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward<TargetType>(val)))) + -> decltype(::nlohmann::to_json(j, std::forward<TargetType>(val)), void()) + { + ::nlohmann::to_json(j, std::forward<TargetType>(val)); + } +}; + +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/byte_container_with_subtype.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <cstdint> // uint8_t, uint64_t +#include <tuple> // tie +#include <utility> // move + +// #include <nlohmann/detail/abi_macros.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +/// @brief an internal type for a backed binary type +/// @sa https://json.nlohmann.me/api/byte_container_with_subtype/ +template<typename BinaryType> +class byte_container_with_subtype : public BinaryType +{ + public: + using container_type = BinaryType; + using subtype_type = std::uint64_t; + + /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/ + byte_container_with_subtype() noexcept(noexcept(container_type())) + : container_type() + {} + + /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/ + byte_container_with_subtype(const container_type& b) noexcept(noexcept(container_type(b))) + : container_type(b) + {} + + /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/ + byte_container_with_subtype(container_type&& b) noexcept(noexcept(container_type(std::move(b)))) + : container_type(std::move(b)) + {} + + /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/ + byte_container_with_subtype(const container_type& b, subtype_type subtype_) noexcept(noexcept(container_type(b))) + : container_type(b) + , m_subtype(subtype_) + , m_has_subtype(true) + {} + + /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/ + byte_container_with_subtype(container_type&& b, subtype_type subtype_) noexcept(noexcept(container_type(std::move(b)))) + : container_type(std::move(b)) + , m_subtype(subtype_) + , m_has_subtype(true) + {} + + bool operator==(const byte_container_with_subtype& rhs) const + { + return std::tie(static_cast<const BinaryType&>(*this), m_subtype, m_has_subtype) == + std::tie(static_cast<const BinaryType&>(rhs), rhs.m_subtype, rhs.m_has_subtype); + } + + bool operator!=(const byte_container_with_subtype& rhs) const + { + return !(rhs == *this); + } + + /// @brief sets the binary subtype + /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/set_subtype/ + void set_subtype(subtype_type subtype_) noexcept + { + m_subtype = subtype_; + m_has_subtype = true; + } + + /// @brief return the binary subtype + /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/subtype/ + constexpr subtype_type subtype() const noexcept + { + return m_has_subtype ? m_subtype : static_cast<subtype_type>(-1); + } + + /// @brief return whether the value has a subtype + /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/has_subtype/ + constexpr bool has_subtype() const noexcept + { + return m_has_subtype; + } + + /// @brief clears the binary subtype + /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/clear_subtype/ + void clear_subtype() noexcept + { + m_subtype = 0; + m_has_subtype = false; + } + + private: + subtype_type m_subtype = 0; + bool m_has_subtype = false; +}; + +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/conversions/from_json.hpp> + +// #include <nlohmann/detail/conversions/to_json.hpp> + +// #include <nlohmann/detail/exceptions.hpp> + +// #include <nlohmann/detail/hash.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <cstdint> // uint8_t +#include <cstddef> // size_t +#include <functional> // hash + +// #include <nlohmann/detail/abi_macros.hpp> + +// #include <nlohmann/detail/value_t.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// boost::hash_combine +inline std::size_t combine(std::size_t seed, std::size_t h) noexcept +{ + seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); + return seed; +} + +/*! +@brief hash a JSON value + +The hash function tries to rely on std::hash where possible. Furthermore, the +type of the JSON value is taken into account to have different hash values for +null, 0, 0U, and false, etc. + +@tparam BasicJsonType basic_json specialization +@param j JSON value to hash +@return hash value of j +*/ +template<typename BasicJsonType> +std::size_t hash(const BasicJsonType& j) +{ + using string_t = typename BasicJsonType::string_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + + const auto type = static_cast<std::size_t>(j.type()); + switch (j.type()) + { + case BasicJsonType::value_t::null: + case BasicJsonType::value_t::discarded: + { + return combine(type, 0); + } + + case BasicJsonType::value_t::object: + { + auto seed = combine(type, j.size()); + for (const auto& element : j.items()) + { + const auto h = std::hash<string_t> {}(element.key()); + seed = combine(seed, h); + seed = combine(seed, hash(element.value())); + } + return seed; + } + + case BasicJsonType::value_t::array: + { + auto seed = combine(type, j.size()); + for (const auto& element : j) + { + seed = combine(seed, hash(element)); + } + return seed; + } + + case BasicJsonType::value_t::string: + { + const auto h = std::hash<string_t> {}(j.template get_ref<const string_t&>()); + return combine(type, h); + } + + case BasicJsonType::value_t::boolean: + { + const auto h = std::hash<bool> {}(j.template get<bool>()); + return combine(type, h); + } + + case BasicJsonType::value_t::number_integer: + { + const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>()); + return combine(type, h); + } + + case BasicJsonType::value_t::number_unsigned: + { + const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>()); + return combine(type, h); + } + + case BasicJsonType::value_t::number_float: + { + const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>()); + return combine(type, h); + } + + case BasicJsonType::value_t::binary: + { + auto seed = combine(type, j.get_binary().size()); + const auto h = std::hash<bool> {}(j.get_binary().has_subtype()); + seed = combine(seed, h); + seed = combine(seed, static_cast<std::size_t>(j.get_binary().subtype())); + for (const auto byte : j.get_binary()) + { + seed = combine(seed, std::hash<std::uint8_t> {}(byte)); + } + return seed; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + return 0; // LCOV_EXCL_LINE + } +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/input/binary_reader.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <algorithm> // generate_n +#include <array> // array +#include <cmath> // ldexp +#include <cstddef> // size_t +#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t +#include <cstdio> // snprintf +#include <cstring> // memcpy +#include <iterator> // back_inserter +#include <limits> // numeric_limits +#include <string> // char_traits, string +#include <utility> // make_pair, move +#include <vector> // vector + +// #include <nlohmann/detail/exceptions.hpp> + +// #include <nlohmann/detail/input/input_adapters.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <array> // array +#include <cstddef> // size_t +#include <cstring> // strlen +#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next +#include <memory> // shared_ptr, make_shared, addressof +#include <numeric> // accumulate +#include <string> // string, char_traits +#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer +#include <utility> // pair, declval + +#ifndef JSON_NO_IO + #include <cstdio> // FILE * + #include <istream> // istream +#endif // JSON_NO_IO + +// #include <nlohmann/detail/iterators/iterator_traits.hpp> + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/meta/type_traits.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// the supported input formats +enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata }; + +//////////////////// +// input adapters // +//////////////////// + +#ifndef JSON_NO_IO +/*! +Input adapter for stdio file access. This adapter read only 1 byte and do not use any + buffer. This adapter is a very low level adapter. +*/ +class file_input_adapter +{ + public: + using char_type = char; + + JSON_HEDLEY_NON_NULL(2) + explicit file_input_adapter(std::FILE* f) noexcept + : m_file(f) + { + JSON_ASSERT(m_file != nullptr); + } + + // make class move-only + file_input_adapter(const file_input_adapter&) = delete; + file_input_adapter(file_input_adapter&&) noexcept = default; + file_input_adapter& operator=(const file_input_adapter&) = delete; + file_input_adapter& operator=(file_input_adapter&&) = delete; + ~file_input_adapter() = default; + + std::char_traits<char>::int_type get_character() noexcept + { + return std::fgetc(m_file); + } + + private: + /// the file pointer to read from + std::FILE* m_file; +}; + +/*! +Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at +beginning of input. Does not support changing the underlying std::streambuf +in mid-input. Maintains underlying std::istream and std::streambuf to support +subsequent use of standard std::istream operations to process any input +characters following those used in parsing the JSON input. Clears the +std::istream flags; any input errors (e.g., EOF) will be detected by the first +subsequent call for input from the std::istream. +*/ +class input_stream_adapter +{ + public: + using char_type = char; + + ~input_stream_adapter() + { + // clear stream flags; we use underlying streambuf I/O, do not + // maintain ifstream flags, except eof + if (is != nullptr) + { + is->clear(is->rdstate() & std::ios::eofbit); + } + } + + explicit input_stream_adapter(std::istream& i) + : is(&i), sb(i.rdbuf()) + {} + + // delete because of pointer members + input_stream_adapter(const input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&&) = delete; + + input_stream_adapter(input_stream_adapter&& rhs) noexcept + : is(rhs.is), sb(rhs.sb) + { + rhs.is = nullptr; + rhs.sb = nullptr; + } + + // std::istream/std::streambuf use std::char_traits<char>::to_int_type, to + // ensure that std::char_traits<char>::eof() and the character 0xFF do not + // end up as the same value, e.g. 0xFFFFFFFF. + std::char_traits<char>::int_type get_character() + { + auto res = sb->sbumpc(); + // set eof manually, as we don't use the istream interface. + if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof())) + { + is->clear(is->rdstate() | std::ios::eofbit); + } + return res; + } + + private: + /// the associated input stream + std::istream* is = nullptr; + std::streambuf* sb = nullptr; +}; +#endif // JSON_NO_IO + +// General-purpose iterator-based adapter. It might not be as fast as +// theoretically possible for some containers, but it is extremely versatile. +template<typename IteratorType> +class iterator_input_adapter +{ + public: + using char_type = typename std::iterator_traits<IteratorType>::value_type; + + iterator_input_adapter(IteratorType first, IteratorType last) + : current(std::move(first)), end(std::move(last)) + {} + + typename char_traits<char_type>::int_type get_character() + { + if (JSON_HEDLEY_LIKELY(current != end)) + { + auto result = char_traits<char_type>::to_int_type(*current); + std::advance(current, 1); + return result; + } + + return char_traits<char_type>::eof(); + } + + private: + IteratorType current; + IteratorType end; + + template<typename BaseInputAdapter, size_t T> + friend struct wide_string_input_helper; + + bool empty() const + { + return current == end; + } +}; + +template<typename BaseInputAdapter, size_t T> +struct wide_string_input_helper; + +template<typename BaseInputAdapter> +struct wide_string_input_helper<BaseInputAdapter, 4> +{ + // UTF-32 + static void fill_buffer(BaseInputAdapter& input, + std::array<std::char_traits<char>::int_type, 4>& utf8_bytes, + size_t& utf8_bytes_index, + size_t& utf8_bytes_filled) + { + utf8_bytes_index = 0; + + if (JSON_HEDLEY_UNLIKELY(input.empty())) + { + utf8_bytes[0] = std::char_traits<char>::eof(); + utf8_bytes_filled = 1; + } + else + { + // get the current character + const auto wc = input.get_character(); + + // UTF-32 to UTF-8 encoding + if (wc < 0x80) + { + utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc); + utf8_bytes_filled = 1; + } + else if (wc <= 0x7FF) + { + utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xC0u | ((static_cast<unsigned int>(wc) >> 6u) & 0x1Fu)); + utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu)); + utf8_bytes_filled = 2; + } + else if (wc <= 0xFFFF) + { + utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xE0u | ((static_cast<unsigned int>(wc) >> 12u) & 0x0Fu)); + utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu)); + utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu)); + utf8_bytes_filled = 3; + } + else if (wc <= 0x10FFFF) + { + utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | ((static_cast<unsigned int>(wc) >> 18u) & 0x07u)); + utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 12u) & 0x3Fu)); + utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu)); + utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu)); + utf8_bytes_filled = 4; + } + else + { + // unknown character + utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc); + utf8_bytes_filled = 1; + } + } + } +}; + +template<typename BaseInputAdapter> +struct wide_string_input_helper<BaseInputAdapter, 2> +{ + // UTF-16 + static void fill_buffer(BaseInputAdapter& input, + std::array<std::char_traits<char>::int_type, 4>& utf8_bytes, + size_t& utf8_bytes_index, + size_t& utf8_bytes_filled) + { + utf8_bytes_index = 0; + + if (JSON_HEDLEY_UNLIKELY(input.empty())) + { + utf8_bytes[0] = std::char_traits<char>::eof(); + utf8_bytes_filled = 1; + } + else + { + // get the current character + const auto wc = input.get_character(); + + // UTF-16 to UTF-8 encoding + if (wc < 0x80) + { + utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc); + utf8_bytes_filled = 1; + } + else if (wc <= 0x7FF) + { + utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xC0u | ((static_cast<unsigned int>(wc) >> 6u))); + utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu)); + utf8_bytes_filled = 2; + } + else if (0xD800 > wc || wc >= 0xE000) + { + utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xE0u | ((static_cast<unsigned int>(wc) >> 12u))); + utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu)); + utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu)); + utf8_bytes_filled = 3; + } + else + { + if (JSON_HEDLEY_UNLIKELY(!input.empty())) + { + const auto wc2 = static_cast<unsigned int>(input.get_character()); + const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); + utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u)); + utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu)); + utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu)); + utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu)); + utf8_bytes_filled = 4; + } + else + { + utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc); + utf8_bytes_filled = 1; + } + } + } + } +}; + +// Wraps another input adapter to convert wide character types into individual bytes. +template<typename BaseInputAdapter, typename WideCharType> +class wide_string_input_adapter +{ + public: + using char_type = char; + + wide_string_input_adapter(BaseInputAdapter base) + : base_adapter(base) {} + + typename std::char_traits<char>::int_type get_character() noexcept + { + // check if buffer needs to be filled + if (utf8_bytes_index == utf8_bytes_filled) + { + fill_buffer<sizeof(WideCharType)>(); + + JSON_ASSERT(utf8_bytes_filled > 0); + JSON_ASSERT(utf8_bytes_index == 0); + } + + // use buffer + JSON_ASSERT(utf8_bytes_filled > 0); + JSON_ASSERT(utf8_bytes_index < utf8_bytes_filled); + return utf8_bytes[utf8_bytes_index++]; + } + + private: + BaseInputAdapter base_adapter; + + template<size_t T> + void fill_buffer() + { + wide_string_input_helper<BaseInputAdapter, T>::fill_buffer(base_adapter, utf8_bytes, utf8_bytes_index, utf8_bytes_filled); + } + + /// a buffer for UTF-8 bytes + std::array<std::char_traits<char>::int_type, 4> utf8_bytes = {{0, 0, 0, 0}}; + + /// index to the utf8_codes array for the next valid byte + std::size_t utf8_bytes_index = 0; + /// number of valid bytes in the utf8_codes array + std::size_t utf8_bytes_filled = 0; +}; + +template<typename IteratorType, typename Enable = void> +struct iterator_input_adapter_factory +{ + using iterator_type = IteratorType; + using char_type = typename std::iterator_traits<iterator_type>::value_type; + using adapter_type = iterator_input_adapter<iterator_type>; + + static adapter_type create(IteratorType first, IteratorType last) + { + return adapter_type(std::move(first), std::move(last)); + } +}; + +template<typename T> +struct is_iterator_of_multibyte +{ + using value_type = typename std::iterator_traits<T>::value_type; + enum + { + value = sizeof(value_type) > 1 + }; +}; + +template<typename IteratorType> +struct iterator_input_adapter_factory<IteratorType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>> +{ + using iterator_type = IteratorType; + using char_type = typename std::iterator_traits<iterator_type>::value_type; + using base_adapter_type = iterator_input_adapter<iterator_type>; + using adapter_type = wide_string_input_adapter<base_adapter_type, char_type>; + + static adapter_type create(IteratorType first, IteratorType last) + { + return adapter_type(base_adapter_type(std::move(first), std::move(last))); + } +}; + +// General purpose iterator-based input +template<typename IteratorType> +typename iterator_input_adapter_factory<IteratorType>::adapter_type input_adapter(IteratorType first, IteratorType last) +{ + using factory_type = iterator_input_adapter_factory<IteratorType>; + return factory_type::create(first, last); +} + +// Convenience shorthand from container to iterator +// Enables ADL on begin(container) and end(container) +// Encloses the using declarations in namespace for not to leak them to outside scope + +namespace container_input_adapter_factory_impl +{ + +using std::begin; +using std::end; + +template<typename ContainerType, typename Enable = void> +struct container_input_adapter_factory {}; + +template<typename ContainerType> +struct container_input_adapter_factory< ContainerType, + void_t<decltype(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>()))>> + { + using adapter_type = decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>()))); + + static adapter_type create(const ContainerType& container) +{ + return input_adapter(begin(container), end(container)); +} + }; + +} // namespace container_input_adapter_factory_impl + +template<typename ContainerType> +typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(const ContainerType& container) +{ + return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(container); +} + +#ifndef JSON_NO_IO +// Special cases with fast paths +inline file_input_adapter input_adapter(std::FILE* file) +{ + return file_input_adapter(file); +} + +inline input_stream_adapter input_adapter(std::istream& stream) +{ + return input_stream_adapter(stream); +} + +inline input_stream_adapter input_adapter(std::istream&& stream) +{ + return input_stream_adapter(stream); +} +#endif // JSON_NO_IO + +using contiguous_bytes_input_adapter = decltype(input_adapter(std::declval<const char*>(), std::declval<const char*>())); + +// Null-delimited strings, and the like. +template < typename CharT, + typename std::enable_if < + std::is_pointer<CharT>::value&& + !std::is_array<CharT>::value&& + std::is_integral<typename std::remove_pointer<CharT>::type>::value&& + sizeof(typename std::remove_pointer<CharT>::type) == 1, + int >::type = 0 > +contiguous_bytes_input_adapter input_adapter(CharT b) +{ + auto length = std::strlen(reinterpret_cast<const char*>(b)); + const auto* ptr = reinterpret_cast<const char*>(b); + return input_adapter(ptr, ptr + length); +} + +template<typename T, std::size_t N> +auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +{ + return input_adapter(array, array + N); +} + +// This class only handles inputs of input_buffer_adapter type. +// It's required so that expressions like {ptr, len} can be implicitly cast +// to the correct adapter. +class span_input_adapter +{ + public: + template < typename CharT, + typename std::enable_if < + std::is_pointer<CharT>::value&& + std::is_integral<typename std::remove_pointer<CharT>::type>::value&& + sizeof(typename std::remove_pointer<CharT>::type) == 1, + int >::type = 0 > + span_input_adapter(CharT b, std::size_t l) + : ia(reinterpret_cast<const char*>(b), reinterpret_cast<const char*>(b) + l) {} + + template<class IteratorType, + typename std::enable_if< + std::is_same<typename iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value, + int>::type = 0> + span_input_adapter(IteratorType first, IteratorType last) + : ia(input_adapter(first, last)) {} + + contiguous_bytes_input_adapter&& get() + { + return std::move(ia); // NOLINT(hicpp-move-const-arg,performance-move-const-arg) + } + + private: + contiguous_bytes_input_adapter ia; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/input/json_sax.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <cstddef> +#include <string> // string +#include <utility> // move +#include <vector> // vector + +// #include <nlohmann/detail/exceptions.hpp> + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/string_concat.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +/*! +@brief SAX interface + +This class describes the SAX interface used by @ref nlohmann::json::sax_parse. +Each function is called in different situations while the input is parsed. The +boolean return value informs the parser whether to continue processing the +input. +*/ +template<typename BasicJsonType> +struct json_sax +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + /*! + @brief a null value was read + @return whether parsing should proceed + */ + virtual bool null() = 0; + + /*! + @brief a boolean value was read + @param[in] val boolean value + @return whether parsing should proceed + */ + virtual bool boolean(bool val) = 0; + + /*! + @brief an integer number was read + @param[in] val integer value + @return whether parsing should proceed + */ + virtual bool number_integer(number_integer_t val) = 0; + + /*! + @brief an unsigned integer number was read + @param[in] val unsigned integer value + @return whether parsing should proceed + */ + virtual bool number_unsigned(number_unsigned_t val) = 0; + + /*! + @brief a floating-point number was read + @param[in] val floating-point value + @param[in] s raw token value + @return whether parsing should proceed + */ + virtual bool number_float(number_float_t val, const string_t& s) = 0; + + /*! + @brief a string value was read + @param[in] val string value + @return whether parsing should proceed + @note It is safe to move the passed string value. + */ + virtual bool string(string_t& val) = 0; + + /*! + @brief a binary value was read + @param[in] val binary value + @return whether parsing should proceed + @note It is safe to move the passed binary value. + */ + virtual bool binary(binary_t& val) = 0; + + /*! + @brief the beginning of an object was read + @param[in] elements number of object elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_object(std::size_t elements) = 0; + + /*! + @brief an object key was read + @param[in] val object key + @return whether parsing should proceed + @note It is safe to move the passed string. + */ + virtual bool key(string_t& val) = 0; + + /*! + @brief the end of an object was read + @return whether parsing should proceed + */ + virtual bool end_object() = 0; + + /*! + @brief the beginning of an array was read + @param[in] elements number of array elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_array(std::size_t elements) = 0; + + /*! + @brief the end of an array was read + @return whether parsing should proceed + */ + virtual bool end_array() = 0; + + /*! + @brief a parse error occurred + @param[in] position the position in the input where the error occurs + @param[in] last_token the last read token + @param[in] ex an exception object describing the error + @return whether parsing should proceed (must return false) + */ + virtual bool parse_error(std::size_t position, + const std::string& last_token, + const detail::exception& ex) = 0; + + json_sax() = default; + json_sax(const json_sax&) = default; + json_sax(json_sax&&) noexcept = default; + json_sax& operator=(const json_sax&) = default; + json_sax& operator=(json_sax&&) noexcept = default; + virtual ~json_sax() = default; +}; + +namespace detail +{ +/*! +@brief SAX implementation to create a JSON value from SAX events + +This class implements the @ref json_sax interface and processes the SAX events +to create a JSON value which makes it basically a DOM parser. The structure or +hierarchy of the JSON value is managed by the stack `ref_stack` which contains +a pointer to the respective array or object for each recursion depth. + +After successful parsing, the value that is passed by reference to the +constructor contains the parsed value. + +@tparam BasicJsonType the JSON type +*/ +template<typename BasicJsonType> +class json_sax_dom_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + /*! + @param[in,out] r reference to a JSON value that is manipulated while + parsing + @param[in] allow_exceptions_ whether parse errors yield exceptions + */ + explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) + : root(r), allow_exceptions(allow_exceptions_) + {} + + // make class move-only + json_sax_dom_parser(const json_sax_dom_parser&) = delete; + json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; + json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~json_sax_dom_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } + + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } + + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } + + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } + + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); + + if (JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); + } + + return true; + } + + bool key(string_t& val) + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); + + // add null at given key and store the reference for later + object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val)); + return true; + } + + bool end_object() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); + + ref_stack.back()->set_parents(); + ref_stack.pop_back(); + return true; + } + + bool start_array(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); + + if (JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + } + + return true; + } + + bool end_array() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_array()); + + ref_stack.back()->set_parents(); + ref_stack.pop_back(); + return true; + } + + template<class Exception> + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast<void>(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + /*! + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + */ + template<typename Value> + JSON_HEDLEY_RETURNS_NON_NULL + BasicJsonType* handle_value(Value&& v) + { + if (ref_stack.empty()) + { + root = BasicJsonType(std::forward<Value>(v)); + return &root; + } + + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->emplace_back(std::forward<Value>(v)); + return &(ref_stack.back()->m_data.m_value.array->back()); + } + + JSON_ASSERT(ref_stack.back()->is_object()); + JSON_ASSERT(object_element); + *object_element = BasicJsonType(std::forward<Value>(v)); + return object_element; + } + + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector<BasicJsonType*> ref_stack {}; + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; + +template<typename BasicJsonType> +class json_sax_dom_callback_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using parser_callback_t = typename BasicJsonType::parser_callback_t; + using parse_event_t = typename BasicJsonType::parse_event_t; + + json_sax_dom_callback_parser(BasicJsonType& r, + const parser_callback_t cb, + const bool allow_exceptions_ = true) + : root(r), callback(cb), allow_exceptions(allow_exceptions_) + { + keep_stack.push_back(true); + } + + // make class move-only + json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~json_sax_dom_callback_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } + + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } + + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } + + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } + + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + // check callback for object start + const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::object, true); + ref_stack.push_back(val.second); + + // check object limit + if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); + } + + return true; + } + + bool key(string_t& val) + { + BasicJsonType k = BasicJsonType(val); + + // check callback for key + const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k); + key_keep_stack.push_back(keep); + + // add discarded value at given key and store the reference for later + if (keep && ref_stack.back()) + { + object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded); + } + + return true; + } + + bool end_object() + { + if (ref_stack.back()) + { + if (!callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) + { + // discard object + *ref_stack.back() = discarded; + } + else + { + ref_stack.back()->set_parents(); + } + } + + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) + { + // remove discarded value + for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) + { + if (it->is_discarded()) + { + ref_stack.back()->erase(it); + break; + } + } + } + + return true; + } + + bool start_array(std::size_t len) + { + const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::array, true); + ref_stack.push_back(val.second); + + // check array limit + if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + } + + return true; + } + + bool end_array() + { + bool keep = true; + + if (ref_stack.back()) + { + keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); + if (keep) + { + ref_stack.back()->set_parents(); + } + else + { + // discard array + *ref_stack.back() = discarded; + } + } + + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + // remove discarded value + if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->pop_back(); + } + + return true; + } + + template<class Exception> + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast<void>(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + /*! + @param[in] v value to add to the JSON value we build during parsing + @param[in] skip_callback whether we should skip calling the callback + function; this is required after start_array() and + start_object() SAX events, because otherwise we would call the + callback function with an empty array or object, respectively. + + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + + @return pair of boolean (whether value should be kept) and pointer (to the + passed value in the ref_stack hierarchy; nullptr if not kept) + */ + template<typename Value> + std::pair<bool, BasicJsonType*> handle_value(Value&& v, const bool skip_callback = false) + { + JSON_ASSERT(!keep_stack.empty()); + + // do not handle this value if we know it would be added to a discarded + // container + if (!keep_stack.back()) + { + return {false, nullptr}; + } + + // create value + auto value = BasicJsonType(std::forward<Value>(v)); + + // check callback + const bool keep = skip_callback || callback(static_cast<int>(ref_stack.size()), parse_event_t::value, value); + + // do not handle this value if we just learnt it shall be discarded + if (!keep) + { + return {false, nullptr}; + } + + if (ref_stack.empty()) + { + root = std::move(value); + return {true, & root}; + } + + // skip this value if we already decided to skip the parent + // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) + if (!ref_stack.back()) + { + return {false, nullptr}; + } + + // we now only expect arrays and objects + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + // array + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value)); + return {true, & (ref_stack.back()->m_data.m_value.array->back())}; + } + + // object + JSON_ASSERT(ref_stack.back()->is_object()); + // check if we should store an element for the current key + JSON_ASSERT(!key_keep_stack.empty()); + const bool store_element = key_keep_stack.back(); + key_keep_stack.pop_back(); + + if (!store_element) + { + return {false, nullptr}; + } + + JSON_ASSERT(object_element); + *object_element = std::move(value); + return {true, object_element}; + } + + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector<BasicJsonType*> ref_stack {}; + /// stack to manage which values to keep + std::vector<bool> keep_stack {}; + /// stack to manage which object keys to keep + std::vector<bool> key_keep_stack {}; + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// callback function + const parser_callback_t callback = nullptr; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// a discarded value for the callback + BasicJsonType discarded = BasicJsonType::value_t::discarded; +}; + +template<typename BasicJsonType> +class json_sax_acceptor +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + bool null() + { + return true; + } + + bool boolean(bool /*unused*/) + { + return true; + } + + bool number_integer(number_integer_t /*unused*/) + { + return true; + } + + bool number_unsigned(number_unsigned_t /*unused*/) + { + return true; + } + + bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) + { + return true; + } + + bool string(string_t& /*unused*/) + { + return true; + } + + bool binary(binary_t& /*unused*/) + { + return true; + } + + bool start_object(std::size_t /*unused*/ = static_cast<std::size_t>(-1)) + { + return true; + } + + bool key(string_t& /*unused*/) + { + return true; + } + + bool end_object() + { + return true; + } + + bool start_array(std::size_t /*unused*/ = static_cast<std::size_t>(-1)) + { + return true; + } + + bool end_array() + { + return true; + } + + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) + { + return false; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/input/lexer.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <array> // array +#include <clocale> // localeconv +#include <cstddef> // size_t +#include <cstdio> // snprintf +#include <cstdlib> // strtof, strtod, strtold, strtoll, strtoull +#include <initializer_list> // initializer_list +#include <string> // char_traits, string +#include <utility> // move +#include <vector> // vector + +// #include <nlohmann/detail/input/input_adapters.hpp> + +// #include <nlohmann/detail/input/position_t.hpp> + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/meta/type_traits.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////// +// lexer // +/////////// + +template<typename BasicJsonType> +class lexer_base +{ + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value + value_integer, ///< a signed integer -- use get_number_integer() for actual value + value_float, ///< an floating point number -- use get_number_float() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input, ///< indicating the end of the input buffer + literal_or_value ///< a literal or the begin of a value (only for diagnostics) + }; + + /// return name of values of type token_type (only used for errors) + JSON_HEDLEY_RETURNS_NON_NULL + JSON_HEDLEY_CONST + static const char* token_type_name(const token_type t) noexcept + { + switch (t) + { + case token_type::uninitialized: + return "<uninitialized>"; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case token_type::value_unsigned: + case token_type::value_integer: + case token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return "<parse error>"; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + // LCOV_EXCL_START + default: // catch non-enum values + return "unknown token"; + // LCOV_EXCL_STOP + } + } +}; +/*! +@brief lexical analysis + +This class organizes the lexical analysis during JSON deserialization. +*/ +template<typename BasicJsonType, typename InputAdapterType> +class lexer : public lexer_base<BasicJsonType> +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using char_type = typename InputAdapterType::char_type; + using char_int_type = typename char_traits<char_type>::int_type; + + public: + using token_type = typename lexer_base<BasicJsonType>::token_type; + + explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept + : ia(std::move(adapter)) + , ignore_comments(ignore_comments_) + , decimal_point_char(static_cast<char_int_type>(get_decimal_point())) + {} + + // delete because of pointer members + lexer(const lexer&) = delete; + lexer(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + lexer& operator=(lexer&) = delete; + lexer& operator=(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~lexer() = default; + + private: + ///////////////////// + // locales + ///////////////////// + + /// return the locale-dependent decimal point + JSON_HEDLEY_PURE + static char get_decimal_point() noexcept + { + const auto* loc = localeconv(); + JSON_ASSERT(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + } + + ///////////////////// + // scan functions + ///////////////////// + + /*! + @brief get codepoint from 4 hex characters following `\u` + + For input "\u c1 c2 c3 c4" the codepoint is: + (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 + = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) + + Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' + must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The + conversion is done by subtracting the offset (0x30, 0x37, and 0x57) + between the ASCII value of the character and the desired integer value. + + @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or + non-hex character) + */ + int get_codepoint() + { + // this function only makes sense after reading `\u` + JSON_ASSERT(current == 'u'); + int codepoint = 0; + + const auto factors = { 12u, 8u, 4u, 0u }; + for (const auto factor : factors) + { + get(); + + if (current >= '0' && current <= '9') + { + codepoint += static_cast<int>((static_cast<unsigned int>(current) - 0x30u) << factor); + } + else if (current >= 'A' && current <= 'F') + { + codepoint += static_cast<int>((static_cast<unsigned int>(current) - 0x37u) << factor); + } + else if (current >= 'a' && current <= 'f') + { + codepoint += static_cast<int>((static_cast<unsigned int>(current) - 0x57u) << factor); + } + else + { + return -1; + } + } + + JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF); + return codepoint; + } + + /*! + @brief check if the next byte(s) are inside a given range + + Adds the current byte and, for each passed range, reads a new byte and + checks if it is inside the range. If a violation was detected, set up an + error message and return false. Otherwise, return true. + + @param[in] ranges list of integers; interpreted as list of pairs of + inclusive lower and upper bound, respectively + + @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, + 1, 2, or 3 pairs. This precondition is enforced by an assertion. + + @return true if and only if no range violation was detected + */ + bool next_byte_in_range(std::initializer_list<char_int_type> ranges) + { + JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); + add(current); + + for (auto range = ranges.begin(); range != ranges.end(); ++range) + { + get(); + if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions) + { + add(current); + } + else + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } + } + + return true; + } + + /*! + @brief scan a string literal + + This function scans a string according to Sect. 7 of RFC 8259. While + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. + + @return token_type::value_string if string could be successfully scanned, + token_type::parse_error otherwise + + @note In case of errors, variable error_message contains a textual + description. + */ + token_type scan_string() + { + // reset token_buffer (ignore opening quote) + reset(); + + // we entered the function by reading an open quote + JSON_ASSERT(current == '\"'); + + while (true) + { + // get next character + switch (get()) + { + // end of file while parsing string + case char_traits<char_type>::eof(): + { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } + + // closing quote + case '\"': + { + return token_type::value_string; + } + + // escapes + case '\\': + { + switch (get()) + { + // quotation mark + case '\"': + add('\"'); + break; + // reverse solidus + case '\\': + add('\\'); + break; + // solidus + case '/': + add('/'); + break; + // backspace + case 'b': + add('\b'); + break; + // form feed + case 'f': + add('\f'); + break; + // line feed + case 'n': + add('\n'); + break; + // carriage return + case 'r': + add('\r'); + break; + // tab + case 't': + add('\t'); + break; + + // unicode escapes + case 'u': + { + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; // start with codepoint1 + + if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if code point is a high surrogate + if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) + { + // expect next \uxxxx entry + if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u')) + { + const int codepoint2 = get_codepoint(); + + if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if codepoint2 is a low surrogate + if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) + { + // overwrite codepoint + codepoint = static_cast<int>( + // high surrogate occupies the most significant 22 bits + (static_cast<unsigned int>(codepoint1) << 10u) + // low surrogate occupies the least significant 15 bits + + static_cast<unsigned int>(codepoint2) + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result, so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00u); + } + else + { + error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + } + + // result of the above calculation yields a proper codepoint + JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); + + // translate codepoint into bytes + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + add(static_cast<char_int_type>(codepoint)); + } + else if (codepoint <= 0x7FF) + { + // 2-byte characters: 110xxxxx 10xxxxxx + add(static_cast<char_int_type>(0xC0u | (static_cast<unsigned int>(codepoint) >> 6u))); + add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>(codepoint) & 0x3Fu))); + } + else if (codepoint <= 0xFFFF) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + add(static_cast<char_int_type>(0xE0u | (static_cast<unsigned int>(codepoint) >> 12u))); + add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>(codepoint) >> 6u) & 0x3Fu))); + add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>(codepoint) & 0x3Fu))); + } + else + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + add(static_cast<char_int_type>(0xF0u | (static_cast<unsigned int>(codepoint) >> 18u))); + add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>(codepoint) >> 12u) & 0x3Fu))); + add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>(codepoint) >> 6u) & 0x3Fu))); + add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>(codepoint) & 0x3Fu))); + } + + break; + } + + // other characters after escape + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } + + break; + } + + // invalid control characters + case 0x00: + { + error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; + return token_type::parse_error; + } + + case 0x01: + { + error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; + return token_type::parse_error; + } + + case 0x02: + { + error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; + return token_type::parse_error; + } + + case 0x03: + { + error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; + return token_type::parse_error; + } + + case 0x04: + { + error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; + return token_type::parse_error; + } + + case 0x05: + { + error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; + return token_type::parse_error; + } + + case 0x06: + { + error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; + return token_type::parse_error; + } + + case 0x07: + { + error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; + return token_type::parse_error; + } + + case 0x08: + { + error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; + return token_type::parse_error; + } + + case 0x09: + { + error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; + return token_type::parse_error; + } + + case 0x0A: + { + error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; + return token_type::parse_error; + } + + case 0x0B: + { + error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; + return token_type::parse_error; + } + + case 0x0C: + { + error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; + return token_type::parse_error; + } + + case 0x0D: + { + error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; + return token_type::parse_error; + } + + case 0x0E: + { + error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; + return token_type::parse_error; + } + + case 0x0F: + { + error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; + return token_type::parse_error; + } + + case 0x10: + { + error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; + return token_type::parse_error; + } + + case 0x11: + { + error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; + return token_type::parse_error; + } + + case 0x12: + { + error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; + return token_type::parse_error; + } + + case 0x13: + { + error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; + return token_type::parse_error; + } + + case 0x14: + { + error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; + return token_type::parse_error; + } + + case 0x15: + { + error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; + return token_type::parse_error; + } + + case 0x16: + { + error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; + return token_type::parse_error; + } + + case 0x17: + { + error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; + return token_type::parse_error; + } + + case 0x18: + { + error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; + return token_type::parse_error; + } + + case 0x19: + { + error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; + return token_type::parse_error; + } + + case 0x1A: + { + error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; + return token_type::parse_error; + } + + case 0x1B: + { + error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; + return token_type::parse_error; + } + + case 0x1C: + { + error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; + return token_type::parse_error; + } + + case 0x1D: + { + error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; + return token_type::parse_error; + } + + case 0x1E: + { + error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; + return token_type::parse_error; + } + + case 0x1F: + { + error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; + return token_type::parse_error; + } + + // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) + case 0x20: + case 0x21: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + { + add(current); + break; + } + + // U+0080..U+07FF: bytes C2..DF 80..BF + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: + { + if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) + { + return token_type::parse_error; + } + break; + } + + // U+0800..U+0FFF: bytes E0 A0..BF 80..BF + case 0xE0: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF + // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+D000..U+D7FF: bytes ED 80..9F 80..BF + case 0xED: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF + case 0xF0: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF + case 0xF1: + case 0xF2: + case 0xF3: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF + case 0xF4: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // remaining bytes (80..C1 and F5..FF) are ill-formed + default: + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } + } + + /*! + * @brief scan a comment + * @return whether comment could be scanned successfully + */ + bool scan_comment() + { + switch (get()) + { + // single-line comments skip input until a newline or EOF is read + case '/': + { + while (true) + { + switch (get()) + { + case '\n': + case '\r': + case char_traits<char_type>::eof(): + case '\0': + return true; + + default: + break; + } + } + } + + // multi-line comments skip input until */ is read + case '*': + { + while (true) + { + switch (get()) + { + case char_traits<char_type>::eof(): + case '\0': + { + error_message = "invalid comment; missing closing '*/'"; + return false; + } + + case '*': + { + switch (get()) + { + case '/': + return true; + + default: + { + unget(); + continue; + } + } + } + + default: + continue; + } + } + } + + // unexpected character after reading '/' + default: + { + error_message = "invalid comment; expecting '/' or '*' after '/'"; + return false; + } + } + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(float& f, const char* str, char** endptr) noexcept + { + f = std::strtof(str, endptr); + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(double& f, const char* str, char** endptr) noexcept + { + f = std::strtod(str, endptr); + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(long double& f, const char* str, char** endptr) noexcept + { + f = std::strtold(str, endptr); + } + + /*! + @brief scan a number literal + + This function scans a string according to Sect. 6 of RFC 8259. + + The function is realized with a deterministic finite state machine derived + from the grammar described in RFC 8259. Starting in state "init", the + input is read and used to determined the next state. Only state "done" + accepts the number. State "error" is a trap state to model errors. In the + table below, "anything" means any character but the ones listed before. + + state | 0 | 1-9 | e E | + | - | . | anything + ---------|----------|----------|----------|---------|---------|----------|----------- + init | zero | any1 | [error] | [error] | minus | [error] | [error] + minus | zero | any1 | [error] | [error] | [error] | [error] | [error] + zero | done | done | exponent | done | done | decimal1 | done + any1 | any1 | any1 | exponent | done | done | decimal1 | done + decimal1 | decimal2 | decimal2 | [error] | [error] | [error] | [error] | [error] + decimal2 | decimal2 | decimal2 | exponent | done | done | done | done + exponent | any2 | any2 | [error] | sign | sign | [error] | [error] + sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] + any2 | any2 | any2 | done | done | done | done | done + + The state machine is realized with one label per state (prefixed with + "scan_number_") and `goto` statements between them. The state machine + contains cycles, but any cycle can be left when EOF is read. Therefore, + the function is guaranteed to terminate. + + During scanning, the read bytes are stored in token_buffer. This string is + then converted to a signed integer, an unsigned integer, or a + floating-point number. + + @return token_type::value_unsigned, token_type::value_integer, or + token_type::value_float if number could be successfully scanned, + token_type::parse_error otherwise + + @note The scanner is independent of the current locale. Internally, the + locale's decimal point is used instead of `.` to work with the + locale-dependent converters. + */ + token_type scan_number() // lgtm [cpp/use-of-goto] + { + // reset token_buffer to store the number's bytes + reset(); + + // the type of the parsed number; initially set to unsigned; will be + // changed if minus sign, decimal point or exponent is read + token_type number_type = token_type::value_unsigned; + + // state (init): we just found out we need to scan a number + switch (current) + { + case '-': + { + add(current); + goto scan_number_minus; + } + + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + // all other characters are rejected outside scan_number() + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + +scan_number_minus: + // state: we just parsed a leading minus sign + number_type = token_type::value_integer; + switch (get()) + { + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + error_message = "invalid number; expected digit after '-'"; + return token_type::parse_error; + } + } + +scan_number_zero: + // state: we just parse a zero (maybe with a leading minus sign) + switch (get()) + { + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_any1: + // state: we just parsed a number 0-9 (maybe with a leading minus sign) + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_decimal1: + // state: we just parsed a decimal point + number_type = token_type::value_float; + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + default: + { + error_message = "invalid number; expected digit after '.'"; + return token_type::parse_error; + } + } + +scan_number_decimal2: + // we just parsed at least one number after a decimal point + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_exponent: + // we just parsed an exponent + number_type = token_type::value_float; + switch (get()) + { + case '+': + case '-': + { + add(current); + goto scan_number_sign; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = + "invalid number; expected '+', '-', or digit after exponent"; + return token_type::parse_error; + } + } + +scan_number_sign: + // we just parsed an exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = "invalid number; expected digit after exponent sign"; + return token_type::parse_error; + } + } + +scan_number_any2: + // we just parsed a number after the exponent or exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + goto scan_number_done; + } + +scan_number_done: + // unget the character after the number (we only read it to know that + // we are done scanning a number) + unget(); + + char* endptr = nullptr; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + errno = 0; + + // try to parse integers first and fall back to floats + if (number_type == token_type::value_unsigned) + { + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_unsigned = static_cast<number_unsigned_t>(x); + if (value_unsigned == x) + { + return token_type::value_unsigned; + } + } + } + else if (number_type == token_type::value_integer) + { + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_integer = static_cast<number_integer_t>(x); + if (value_integer == x) + { + return token_type::value_integer; + } + } + } + + // this code is reached if we parse a floating-point number or if an + // integer conversion above failed + strtof(value_float, token_buffer.data(), &endptr); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + return token_type::value_float; + } + + /*! + @param[in] literal_text the literal text to expect + @param[in] length the length of the passed literal text + @param[in] return_type the token type to return on success + */ + JSON_HEDLEY_NON_NULL(2) + token_type scan_literal(const char_type* literal_text, const std::size_t length, + token_type return_type) + { + JSON_ASSERT(char_traits<char_type>::to_char_type(current) == literal_text[0]); + for (std::size_t i = 1; i < length; ++i) + { + if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != literal_text[i])) + { + error_message = "invalid literal"; + return token_type::parse_error; + } + } + return return_type; + } + + ///////////////////// + // input management + ///////////////////// + + /// reset token_buffer; current character is beginning of token + void reset() noexcept + { + token_buffer.clear(); + token_string.clear(); + token_string.push_back(char_traits<char_type>::to_char_type(current)); + } + + /* + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `char_traits<char>::eof()` in that case. Stores the scanned characters + for use in error messages. + + @return character read from the input + */ + char_int_type get() + { + ++position.chars_read_total; + ++position.chars_read_current_line; + + if (next_unget) + { + // just reset the next_unget variable and work with current + next_unget = false; + } + else + { + current = ia.get_character(); + } + + if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof())) + { + token_string.push_back(char_traits<char_type>::to_char_type(current)); + } + + if (current == '\n') + { + ++position.lines_read; + position.chars_read_current_line = 0; + } + + return current; + } + + /*! + @brief unget current character (read it again on next get) + + We implement unget by setting variable next_unget to true. The input is not + changed - we just simulate ungetting by modifying chars_read_total, + chars_read_current_line, and token_string. The next call to get() will + behave as if the unget character is read again. + */ + void unget() + { + next_unget = true; + + --position.chars_read_total; + + // in case we "unget" a newline, we have to also decrement the lines_read + if (position.chars_read_current_line == 0) + { + if (position.lines_read > 0) + { + --position.lines_read; + } + } + else + { + --position.chars_read_current_line; + } + + if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof())) + { + JSON_ASSERT(!token_string.empty()); + token_string.pop_back(); + } + } + + /// add a character to token_buffer + void add(char_int_type c) + { + token_buffer.push_back(static_cast<typename string_t::value_type>(c)); + } + + public: + ///////////////////// + // value getters + ///////////////////// + + /// return integer value + constexpr number_integer_t get_number_integer() const noexcept + { + return value_integer; + } + + /// return unsigned integer value + constexpr number_unsigned_t get_number_unsigned() const noexcept + { + return value_unsigned; + } + + /// return floating-point value + constexpr number_float_t get_number_float() const noexcept + { + return value_float; + } + + /// return current string value (implicitly resets the token; useful only once) + string_t& get_string() + { + return token_buffer; + } + + ///////////////////// + // diagnostics + ///////////////////// + + /// return position of last read token + constexpr position_t get_position() const noexcept + { + return position; + } + + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. + std::string get_token_string() const + { + // escape control characters + std::string result; + for (const auto c : token_string) + { + if (static_cast<unsigned char>(c) <= '\x1F') + { + // escape control characters + std::array<char, 9> cs{{}}; + static_cast<void>((std::snprintf)(cs.data(), cs.size(), "<U+%.4X>", static_cast<unsigned char>(c))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + result += cs.data(); + } + else + { + // add character as is + result.push_back(static_cast<std::string::value_type>(c)); + } + } + + return result; + } + + /// return syntax error message + JSON_HEDLEY_RETURNS_NON_NULL + constexpr const char* get_error_message() const noexcept + { + return error_message; + } + + ///////////////////// + // actual scanner + ///////////////////// + + /*! + @brief skip the UTF-8 byte order mark + @return true iff there is no BOM or the correct BOM has been skipped + */ + bool skip_bom() + { + if (get() == 0xEF) + { + // check if we completely parse the BOM + return get() == 0xBB && get() == 0xBF; + } + + // the first character is not the beginning of the BOM; unget it to + // process is later + unget(); + return true; + } + + void skip_whitespace() + { + do + { + get(); + } + while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); + } + + token_type scan() + { + // initially, skip the BOM + if (position.chars_read_total == 0 && !skip_bom()) + { + error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; + return token_type::parse_error; + } + + // read next character and ignore whitespace + skip_whitespace(); + + // ignore comments + while (ignore_comments && current == '/') + { + if (!scan_comment()) + { + return token_type::parse_error; + } + + // skip following whitespace + skip_whitespace(); + } + + switch (current) + { + // structural characters + case '[': + return token_type::begin_array; + case ']': + return token_type::end_array; + case '{': + return token_type::begin_object; + case '}': + return token_type::end_object; + case ':': + return token_type::name_separator; + case ',': + return token_type::value_separator; + + // literals + case 't': + { + std::array<char_type, 4> true_literal = {{static_cast<char_type>('t'), static_cast<char_type>('r'), static_cast<char_type>('u'), static_cast<char_type>('e')}}; + return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true); + } + case 'f': + { + std::array<char_type, 5> false_literal = {{static_cast<char_type>('f'), static_cast<char_type>('a'), static_cast<char_type>('l'), static_cast<char_type>('s'), static_cast<char_type>('e')}}; + return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false); + } + case 'n': + { + std::array<char_type, 4> null_literal = {{static_cast<char_type>('n'), static_cast<char_type>('u'), static_cast<char_type>('l'), static_cast<char_type>('l')}}; + return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null); + } + + // string + case '\"': + return scan_string(); + + // number + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); + + // end of input (the null byte is needed when parsing from + // string literals) + case '\0': + case char_traits<char_type>::eof(): + return token_type::end_of_input; + + // error + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } + + private: + /// input adapter + InputAdapterType ia; + + /// whether comments should be ignored (true) or signaled as errors (false) + const bool ignore_comments = false; + + /// the current character + char_int_type current = char_traits<char_type>::eof(); + + /// whether the next get() call should just return current + bool next_unget = false; + + /// the start position of the current token + position_t position {}; + + /// raw input token string (for error messages) + std::vector<char_type> token_string {}; + + /// buffer for variable-length tokens (numbers, strings) + string_t token_buffer {}; + + /// a description of occurred lexer errors + const char* error_message = ""; + + // number values + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; + + /// the decimal point + const char_int_type decimal_point_char = '.'; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/meta/is_sax.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <cstdint> // size_t +#include <utility> // declval +#include <string> // string + +// #include <nlohmann/detail/abi_macros.hpp> + +// #include <nlohmann/detail/meta/detected.hpp> + +// #include <nlohmann/detail/meta/type_traits.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template<typename T> +using null_function_t = decltype(std::declval<T&>().null()); + +template<typename T> +using boolean_function_t = + decltype(std::declval<T&>().boolean(std::declval<bool>())); + +template<typename T, typename Integer> +using number_integer_function_t = + decltype(std::declval<T&>().number_integer(std::declval<Integer>())); + +template<typename T, typename Unsigned> +using number_unsigned_function_t = + decltype(std::declval<T&>().number_unsigned(std::declval<Unsigned>())); + +template<typename T, typename Float, typename String> +using number_float_function_t = decltype(std::declval<T&>().number_float( + std::declval<Float>(), std::declval<const String&>())); + +template<typename T, typename String> +using string_function_t = + decltype(std::declval<T&>().string(std::declval<String&>())); + +template<typename T, typename Binary> +using binary_function_t = + decltype(std::declval<T&>().binary(std::declval<Binary&>())); + +template<typename T> +using start_object_function_t = + decltype(std::declval<T&>().start_object(std::declval<std::size_t>())); + +template<typename T, typename String> +using key_function_t = + decltype(std::declval<T&>().key(std::declval<String&>())); + +template<typename T> +using end_object_function_t = decltype(std::declval<T&>().end_object()); + +template<typename T> +using start_array_function_t = + decltype(std::declval<T&>().start_array(std::declval<std::size_t>())); + +template<typename T> +using end_array_function_t = decltype(std::declval<T&>().end_array()); + +template<typename T, typename Exception> +using parse_error_function_t = decltype(std::declval<T&>().parse_error( + std::declval<std::size_t>(), std::declval<const std::string&>(), + std::declval<const Exception&>())); + +template<typename SAX, typename BasicJsonType> +struct is_sax +{ + private: + static_assert(is_basic_json<BasicJsonType>::value, + "BasicJsonType must be of type basic_json<...>"); + + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using exception_t = typename BasicJsonType::exception; + + public: + static constexpr bool value = + is_detected_exact<bool, null_function_t, SAX>::value && + is_detected_exact<bool, boolean_function_t, SAX>::value && + is_detected_exact<bool, number_integer_function_t, SAX, number_integer_t>::value && + is_detected_exact<bool, number_unsigned_function_t, SAX, number_unsigned_t>::value && + is_detected_exact<bool, number_float_function_t, SAX, number_float_t, string_t>::value && + is_detected_exact<bool, string_function_t, SAX, string_t>::value && + is_detected_exact<bool, binary_function_t, SAX, binary_t>::value && + is_detected_exact<bool, start_object_function_t, SAX>::value && + is_detected_exact<bool, key_function_t, SAX, string_t>::value && + is_detected_exact<bool, end_object_function_t, SAX>::value && + is_detected_exact<bool, start_array_function_t, SAX>::value && + is_detected_exact<bool, end_array_function_t, SAX>::value && + is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value; +}; + +template<typename SAX, typename BasicJsonType> +struct is_sax_static_asserts +{ + private: + static_assert(is_basic_json<BasicJsonType>::value, + "BasicJsonType must be of type basic_json<...>"); + + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using exception_t = typename BasicJsonType::exception; + + public: + static_assert(is_detected_exact<bool, null_function_t, SAX>::value, + "Missing/invalid function: bool null()"); + static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value, + "Missing/invalid function: bool boolean(bool)"); + static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value, + "Missing/invalid function: bool boolean(bool)"); + static_assert( + is_detected_exact<bool, number_integer_function_t, SAX, + number_integer_t>::value, + "Missing/invalid function: bool number_integer(number_integer_t)"); + static_assert( + is_detected_exact<bool, number_unsigned_function_t, SAX, + number_unsigned_t>::value, + "Missing/invalid function: bool number_unsigned(number_unsigned_t)"); + static_assert(is_detected_exact<bool, number_float_function_t, SAX, + number_float_t, string_t>::value, + "Missing/invalid function: bool number_float(number_float_t, const string_t&)"); + static_assert( + is_detected_exact<bool, string_function_t, SAX, string_t>::value, + "Missing/invalid function: bool string(string_t&)"); + static_assert( + is_detected_exact<bool, binary_function_t, SAX, binary_t>::value, + "Missing/invalid function: bool binary(binary_t&)"); + static_assert(is_detected_exact<bool, start_object_function_t, SAX>::value, + "Missing/invalid function: bool start_object(std::size_t)"); + static_assert(is_detected_exact<bool, key_function_t, SAX, string_t>::value, + "Missing/invalid function: bool key(string_t&)"); + static_assert(is_detected_exact<bool, end_object_function_t, SAX>::value, + "Missing/invalid function: bool end_object()"); + static_assert(is_detected_exact<bool, start_array_function_t, SAX>::value, + "Missing/invalid function: bool start_array(std::size_t)"); + static_assert(is_detected_exact<bool, end_array_function_t, SAX>::value, + "Missing/invalid function: bool end_array()"); + static_assert( + is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value, + "Missing/invalid function: bool parse_error(std::size_t, const " + "std::string&, const exception&)"); +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/meta/type_traits.hpp> + +// #include <nlohmann/detail/string_concat.hpp> + +// #include <nlohmann/detail/value_t.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// how to treat CBOR tags +enum class cbor_tag_handler_t +{ + error, ///< throw a parse_error exception in case of a tag + ignore, ///< ignore tags + store ///< store tags as binary type +}; + +/*! +@brief determine system byte order + +@return true if and only if system's byte order is little endian + +@note from https://stackoverflow.com/a/1001328/266378 +*/ +static inline bool little_endianness(int num = 1) noexcept +{ + return *reinterpret_cast<char*>(&num) == 1; +} + +/////////////////// +// binary reader // +/////////////////// + +/*! +@brief deserialization of CBOR, MessagePack, and UBJSON values +*/ +template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType>> +class binary_reader +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using json_sax_t = SAX; + using char_type = typename InputAdapterType::char_type; + using char_int_type = typename char_traits<char_type>::int_type; + + public: + /*! + @brief create a binary reader + + @param[in] adapter input adapter to read from + */ + explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json) noexcept : ia(std::move(adapter)), input_format(format) + { + (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {}; + } + + // make class move-only + binary_reader(const binary_reader&) = delete; + binary_reader(binary_reader&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + binary_reader& operator=(const binary_reader&) = delete; + binary_reader& operator=(binary_reader&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~binary_reader() = default; + + /*! + @param[in] format the binary format to parse + @param[in] sax_ a SAX event processor + @param[in] strict whether to expect the input to be consumed completed + @param[in] tag_handler how to treat CBOR tags + + @return whether parsing was successful + */ + JSON_HEDLEY_NON_NULL(3) + bool sax_parse(const input_format_t format, + json_sax_t* sax_, + const bool strict = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + sax = sax_; + bool result = false; + + switch (format) + { + case input_format_t::bson: + result = parse_bson_internal(); + break; + + case input_format_t::cbor: + result = parse_cbor_internal(true, tag_handler); + break; + + case input_format_t::msgpack: + result = parse_msgpack_internal(); + break; + + case input_format_t::ubjson: + case input_format_t::bjdata: + result = parse_ubjson_internal(); + break; + + case input_format_t::json: // LCOV_EXCL_LINE + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + + // strict mode: next byte must be EOF + if (result && strict) + { + if (input_format == input_format_t::ubjson || input_format == input_format_t::bjdata) + { + get_ignore_noop(); + } + else + { + get(); + } + + if (JSON_HEDLEY_UNLIKELY(current != char_traits<char_type>::eof())) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, + exception_message(input_format, concat("expected end of input; last byte: 0x", get_token_string()), "value"), nullptr)); + } + } + + return result; + } + + private: + ////////// + // BSON // + ////////// + + /*! + @brief Reads in a BSON-object and passes it to the SAX-parser. + @return whether a valid BSON-value was passed to the SAX parser + */ + bool parse_bson_internal() + { + std::int32_t document_size{}; + get_number<std::int32_t, true>(input_format_t::bson, document_size); + + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast<std::size_t>(-1)))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/false))) + { + return false; + } + + return sax->end_object(); + } + + /*! + @brief Parses a C-style string from the BSON input. + @param[in,out] result A reference to the string variable where the read + string is to be stored. + @return `true` if the \x00-byte indicating the end of the string was + encountered before the EOF; false` indicates an unexpected EOF. + */ + bool get_bson_cstr(string_t& result) + { + auto out = std::back_inserter(result); + while (true) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "cstring"))) + { + return false; + } + if (current == 0x00) + { + return true; + } + *out++ = static_cast<typename string_t::value_type>(current); + } + } + + /*! + @brief Parses a zero-terminated string of length @a len from the BSON + input. + @param[in] len The length (including the zero-byte at the end) of the + string to be read. + @param[in,out] result A reference to the string variable where the read + string is to be stored. + @tparam NumberType The type of the length @a len + @pre len >= 1 + @return `true` if the string was successfully parsed + */ + template<typename NumberType> + bool get_bson_string(const NumberType len, string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(len < 1)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format_t::bson, concat("string length must be at least 1, is ", std::to_string(len)), "string"), nullptr)); + } + + return get_string(input_format_t::bson, len - static_cast<NumberType>(1), result) && get() != char_traits<char_type>::eof(); + } + + /*! + @brief Parses a byte array input of length @a len from the BSON input. + @param[in] len The length of the byte array to be read. + @param[in,out] result A reference to the binary variable where the read + array is to be stored. + @tparam NumberType The type of the length @a len + @pre len >= 0 + @return `true` if the byte array was successfully parsed + */ + template<typename NumberType> + bool get_bson_binary(const NumberType len, binary_t& result) + { + if (JSON_HEDLEY_UNLIKELY(len < 0)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format_t::bson, concat("byte array length cannot be negative, is ", std::to_string(len)), "binary"), nullptr)); + } + + // All BSON binary values have a subtype + std::uint8_t subtype{}; + get_number<std::uint8_t>(input_format_t::bson, subtype); + result.set_subtype(subtype); + + return get_binary(input_format_t::bson, len, result); + } + + /*! + @brief Read a BSON document element of the given @a element_type. + @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html + @param[in] element_type_parse_position The position in the input stream, + where the `element_type` was read. + @warning Not all BSON element types are supported yet. An unsupported + @a element_type will give rise to a parse_error.114: + Unsupported BSON record type 0x... + @return whether a valid BSON-object/array was passed to the SAX parser + */ + bool parse_bson_element_internal(const char_int_type element_type, + const std::size_t element_type_parse_position) + { + switch (element_type) + { + case 0x01: // double + { + double number{}; + return get_number<double, true>(input_format_t::bson, number) && sax->number_float(static_cast<number_float_t>(number), ""); + } + + case 0x02: // string + { + std::int32_t len{}; + string_t value; + return get_number<std::int32_t, true>(input_format_t::bson, len) && get_bson_string(len, value) && sax->string(value); + } + + case 0x03: // object + { + return parse_bson_internal(); + } + + case 0x04: // array + { + return parse_bson_array(); + } + + case 0x05: // binary + { + std::int32_t len{}; + binary_t value; + return get_number<std::int32_t, true>(input_format_t::bson, len) && get_bson_binary(len, value) && sax->binary(value); + } + + case 0x08: // boolean + { + return sax->boolean(get() != 0); + } + + case 0x0A: // null + { + return sax->null(); + } + + case 0x10: // int32 + { + std::int32_t value{}; + return get_number<std::int32_t, true>(input_format_t::bson, value) && sax->number_integer(value); + } + + case 0x12: // int64 + { + std::int64_t value{}; + return get_number<std::int64_t, true>(input_format_t::bson, value) && sax->number_integer(value); + } + + default: // anything else not supported (yet) + { + std::array<char, 3> cr{{}}; + static_cast<void>((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast<unsigned char>(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + const std::string cr_str{cr.data()}; + return sax->parse_error(element_type_parse_position, cr_str, + parse_error::create(114, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr)); + } + } + } + + /*! + @brief Read a BSON element list (as specified in the BSON-spec) + + The same binary layout is used for objects and arrays, hence it must be + indicated with the argument @a is_array which one is expected + (true --> array, false --> object). + + @param[in] is_array Determines if the element list being read is to be + treated as an object (@a is_array == false), or as an + array (@a is_array == true). + @return whether a valid BSON-object/array was passed to the SAX parser + */ + bool parse_bson_element_list(const bool is_array) + { + string_t key; + + while (auto element_type = get()) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "element list"))) + { + return false; + } + + const std::size_t element_type_parse_position = chars_read; + if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) + { + return false; + } + + if (!is_array && !sax->key(key)) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_internal(element_type, element_type_parse_position))) + { + return false; + } + + // get_bson_cstr only appends + key.clear(); + } + + return true; + } + + /*! + @brief Reads an array from the BSON input and passes it to the SAX-parser. + @return whether a valid BSON-array was passed to the SAX parser + */ + bool parse_bson_array() + { + std::int32_t document_size{}; + get_number<std::int32_t, true>(input_format_t::bson, document_size); + + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast<std::size_t>(-1)))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/true))) + { + return false; + } + + return sax->end_array(); + } + + ////////// + // CBOR // + ////////// + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true) or whether the last read character should + be considered instead (false) + @param[in] tag_handler how CBOR tags should be treated + + @return whether a valid CBOR value was passed to the SAX parser + */ + bool parse_cbor_internal(const bool get_char, + const cbor_tag_handler_t tag_handler) + { + switch (get_char ? get() : current) + { + // EOF + case char_traits<char_type>::eof(): + return unexpect_eof(input_format_t::cbor, "value"); + + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + return sax->number_unsigned(static_cast<number_unsigned_t>(current)); + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + { + std::uint8_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + { + std::uint16_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x1A: // Unsigned integer (four-byte uint32_t follows) + { + std::uint32_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x1B: // Unsigned integer (eight-byte uint64_t follows) + { + std::uint64_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + return sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - current)); + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + std::uint8_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + std::uint16_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number); + } + + case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) + { + std::uint32_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number); + } + + case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) + { + std::uint64_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) + - static_cast<number_integer_t>(number)); + } + + // Binary data (0x00..0x17 bytes follow) + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: // Binary data (one-byte uint8_t for n follows) + case 0x59: // Binary data (two-byte uint16_t for n follow) + case 0x5A: // Binary data (four-byte uint32_t for n follow) + case 0x5B: // Binary data (eight-byte uint64_t for n follow) + case 0x5F: // Binary data (indefinite length) + { + binary_t b; + return get_cbor_binary(b) && sax->binary(b); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7F: // UTF-8 string (indefinite length) + { + string_t s; + return get_cbor_string(s) && sax->string(s); + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + return get_cbor_array( + conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu), tag_handler); + + case 0x98: // array (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast<std::size_t>(len), tag_handler); + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast<std::size_t>(len), tag_handler); + } + + case 0x9A: // array (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast<std::size_t>(len), tag_handler); + } + + case 0x9B: // array (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast<std::size_t>(len), tag_handler); + } + + case 0x9F: // array (indefinite length) + return get_cbor_array(static_cast<std::size_t>(-1), tag_handler); + + // map (0x00..0x17 pairs of data items follow) + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + return get_cbor_object(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu), tag_handler); + + case 0xB8: // map (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast<std::size_t>(len), tag_handler); + } + + case 0xB9: // map (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast<std::size_t>(len), tag_handler); + } + + case 0xBA: // map (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast<std::size_t>(len), tag_handler); + } + + case 0xBB: // map (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast<std::size_t>(len), tag_handler); + } + + case 0xBF: // map (indefinite length) + return get_cbor_object(static_cast<std::size_t>(-1), tag_handler); + + case 0xC6: // tagged item + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD8: // tagged item (1 bytes follow) + case 0xD9: // tagged item (2 bytes follow) + case 0xDA: // tagged item (4 bytes follow) + case 0xDB: // tagged item (8 bytes follow) + { + switch (tag_handler) + { + case cbor_tag_handler_t::error: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format_t::cbor, concat("invalid byte: 0x", last_token), "value"), nullptr)); + } + + case cbor_tag_handler_t::ignore: + { + // ignore binary subtype + switch (current) + { + case 0xD8: + { + std::uint8_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); + break; + } + case 0xD9: + { + std::uint16_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); + break; + } + case 0xDA: + { + std::uint32_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); + break; + } + case 0xDB: + { + std::uint64_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); + break; + } + default: + break; + } + return parse_cbor_internal(true, tag_handler); + } + + case cbor_tag_handler_t::store: + { + binary_t b; + // use binary subtype and store in binary container + switch (current) + { + case 0xD8: + { + std::uint8_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype)); + break; + } + case 0xD9: + { + std::uint16_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype)); + break; + } + case 0xDA: + { + std::uint32_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype)); + break; + } + case 0xDB: + { + std::uint64_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype)); + break; + } + default: + return parse_cbor_internal(true, tag_handler); + } + get(); + return get_cbor_binary(b) && sax->binary(b); + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + return false; // LCOV_EXCL_LINE + } + } + + case 0xF4: // false + return sax->boolean(false); + + case 0xF5: // true + return sax->boolean(true); + + case 0xF6: // null + return sax->null(); + + case 0xF9: // Half-Precision Float (two-byte IEEE 754) + { + const auto byte1_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) + { + return false; + } + const auto byte2_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) + { + return false; + } + + const auto byte1 = static_cast<unsigned char>(byte1_raw); + const auto byte2 = static_cast<unsigned char>(byte2_raw); + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const auto half = static_cast<unsigned int>((byte1 << 8u) + byte2); + const double val = [&half] + { + const int exp = (half >> 10u) & 0x1Fu; + const unsigned int mant = half & 0x3FFu; + JSON_ASSERT(0 <= exp&& exp <= 32); + JSON_ASSERT(mant <= 1024); + switch (exp) + { + case 0: + return std::ldexp(mant, -24); + case 31: + return (mant == 0) + ? std::numeric_limits<double>::infinity() + : std::numeric_limits<double>::quiet_NaN(); + default: + return std::ldexp(mant + 1024, exp - 25); + } + }(); + return sax->number_float((half & 0x8000u) != 0 + ? static_cast<number_float_t>(-val) + : static_cast<number_float_t>(val), ""); + } + + case 0xFA: // Single-Precision Float (four-byte IEEE 754) + { + float number{}; + return get_number(input_format_t::cbor, number) && sax->number_float(static_cast<number_float_t>(number), ""); + } + + case 0xFB: // Double-Precision Float (eight-byte IEEE 754) + { + double number{}; + return get_number(input_format_t::cbor, number) && sax->number_float(static_cast<number_float_t>(number), ""); + } + + default: // anything else (0xFF is handled inside the other types) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format_t::cbor, concat("invalid byte: 0x", last_token), "value"), nullptr)); + } + } + } + + /*! + @brief reads a CBOR string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + Additionally, CBOR's strings with indefinite lengths are supported. + + @param[out] result created string + + @return whether string creation completed + */ + bool get_cbor_string(string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string"))) + { + return false; + } + + switch (current) + { + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + return get_string(input_format_t::cbor, static_cast<unsigned int>(current) & 0x1Fu, result); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7F: // UTF-8 string (indefinite length) + { + while (get() != 0xFF) + { + string_t chunk; + if (!get_cbor_string(chunk)) + { + return false; + } + result.append(chunk); + } + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, + exception_message(input_format_t::cbor, concat("expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x", last_token), "string"), nullptr)); + } + } + } + + /*! + @brief reads a CBOR byte array + + This function first reads starting bytes to determine the expected + byte array length and then copies this number of bytes into the byte array. + Additionally, CBOR's byte arrays with indefinite lengths are supported. + + @param[out] result created byte array + + @return whether byte array creation completed + */ + bool get_cbor_binary(binary_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary"))) + { + return false; + } + + switch (current) + { + // Binary data (0x00..0x17 bytes follow) + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + { + return get_binary(input_format_t::cbor, static_cast<unsigned int>(current) & 0x1Fu, result); + } + + case 0x58: // Binary data (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x59: // Binary data (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5A: // Binary data (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5B: // Binary data (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5F: // Binary data (indefinite length) + { + while (get() != 0xFF) + { + binary_t chunk; + if (!get_cbor_binary(chunk)) + { + return false; + } + result.insert(result.end(), chunk.begin(), chunk.end()); + } + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, + exception_message(input_format_t::cbor, concat("expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x", last_token), "binary"), nullptr)); + } + } + } + + /*! + @param[in] len the length of the array or static_cast<std::size_t>(-1) for an + array of indefinite size + @param[in] tag_handler how CBOR tags should be treated + @return whether array creation completed + */ + bool get_cbor_array(const std::size_t len, + const cbor_tag_handler_t tag_handler) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) + { + return false; + } + + if (len != static_cast<std::size_t>(-1)) + { + for (std::size_t i = 0; i < len; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + } + } + else + { + while (get() != 0xFF) + { + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(false, tag_handler))) + { + return false; + } + } + } + + return sax->end_array(); + } + + /*! + @param[in] len the length of the object or static_cast<std::size_t>(-1) for an + object of indefinite size + @param[in] tag_handler how CBOR tags should be treated + @return whether object creation completed + */ + bool get_cbor_object(const std::size_t len, + const cbor_tag_handler_t tag_handler) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) + { + return false; + } + + if (len != 0) + { + string_t key; + if (len != static_cast<std::size_t>(-1)) + { + for (std::size_t i = 0; i < len; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + key.clear(); + } + } + else + { + while (get() != 0xFF) + { + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + key.clear(); + } + } + } + + return sax->end_object(); + } + + ///////////// + // MsgPack // + ///////////// + + /*! + @return whether a valid MessagePack value was passed to the SAX parser + */ + bool parse_msgpack_internal() + { + switch (get()) + { + // EOF + case char_traits<char_type>::eof(): + return unexpect_eof(input_format_t::msgpack, "value"); + + // positive fixint + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + return sax->number_unsigned(static_cast<number_unsigned_t>(current)); + + // fixmap + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + return get_msgpack_object(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu)); + + // fixarray + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x98: + case 0x99: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: + return get_msgpack_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu)); + + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + case 0xD9: // str 8 + case 0xDA: // str 16 + case 0xDB: // str 32 + { + string_t s; + return get_msgpack_string(s) && sax->string(s); + } + + case 0xC0: // nil + return sax->null(); + + case 0xC2: // false + return sax->boolean(false); + + case 0xC3: // true + return sax->boolean(true); + + case 0xC4: // bin 8 + case 0xC5: // bin 16 + case 0xC6: // bin 32 + case 0xC7: // ext 8 + case 0xC8: // ext 16 + case 0xC9: // ext 32 + case 0xD4: // fixext 1 + case 0xD5: // fixext 2 + case 0xD6: // fixext 4 + case 0xD7: // fixext 8 + case 0xD8: // fixext 16 + { + binary_t b; + return get_msgpack_binary(b) && sax->binary(b); + } + + case 0xCA: // float 32 + { + float number{}; + return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast<number_float_t>(number), ""); + } + + case 0xCB: // float 64 + { + double number{}; + return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast<number_float_t>(number), ""); + } + + case 0xCC: // uint 8 + { + std::uint8_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCD: // uint 16 + { + std::uint16_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCE: // uint 32 + { + std::uint32_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCF: // uint 64 + { + std::uint64_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xD0: // int 8 + { + std::int8_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD1: // int 16 + { + std::int16_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD2: // int 32 + { + std::int32_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD3: // int 64 + { + std::int64_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xDC: // array 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast<std::size_t>(len)); + } + + case 0xDD: // array 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_array(conditional_static_cast<std::size_t>(len)); + } + + case 0xDE: // map 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast<std::size_t>(len)); + } + + case 0xDF: // map 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_object(conditional_static_cast<std::size_t>(len)); + } + + // negative fixint + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + return sax->number_integer(static_cast<std::int8_t>(current)); + + default: // anything else + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format_t::msgpack, concat("invalid byte: 0x", last_token), "value"), nullptr)); + } + } + } + + /*! + @brief reads a MessagePack string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + + @param[out] result created string + + @return whether string creation completed + */ + bool get_msgpack_string(string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string"))) + { + return false; + } + + switch (current) + { + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + { + return get_string(input_format_t::msgpack, static_cast<unsigned int>(current) & 0x1Fu, result); + } + + case 0xD9: // str 8 + { + std::uint8_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + case 0xDA: // str 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + case 0xDB: // str 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, + exception_message(input_format_t::msgpack, concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", last_token), "string"), nullptr)); + } + } + } + + /*! + @brief reads a MessagePack byte array + + This function first reads starting bytes to determine the expected + byte array length and then copies this number of bytes into a byte array. + + @param[out] result created byte array + + @return whether byte array creation completed + */ + bool get_msgpack_binary(binary_t& result) + { + // helper function to set the subtype + auto assign_and_return_true = [&result](std::int8_t subtype) + { + result.set_subtype(static_cast<std::uint8_t>(subtype)); + return true; + }; + + switch (current) + { + case 0xC4: // bin 8 + { + std::uint8_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC5: // bin 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC6: // bin 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC7: // ext 8 + { + std::uint8_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xC8: // ext 16 + { + std::uint16_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xC9: // ext 32 + { + std::uint32_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xD4: // fixext 1 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 1, result) && + assign_and_return_true(subtype); + } + + case 0xD5: // fixext 2 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 2, result) && + assign_and_return_true(subtype); + } + + case 0xD6: // fixext 4 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 4, result) && + assign_and_return_true(subtype); + } + + case 0xD7: // fixext 8 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 8, result) && + assign_and_return_true(subtype); + } + + case 0xD8: // fixext 16 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 16, result) && + assign_and_return_true(subtype); + } + + default: // LCOV_EXCL_LINE + return false; // LCOV_EXCL_LINE + } + } + + /*! + @param[in] len the length of the array + @return whether array creation completed + */ + bool get_msgpack_array(const std::size_t len) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) + { + return false; + } + + for (std::size_t i = 0; i < len; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) + { + return false; + } + } + + return sax->end_array(); + } + + /*! + @param[in] len the length of the object + @return whether object creation completed + */ + bool get_msgpack_object(const std::size_t len) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) + { + return false; + } + + string_t key; + for (std::size_t i = 0; i < len; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) + { + return false; + } + key.clear(); + } + + return sax->end_object(); + } + + //////////// + // UBJSON // + //////////// + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return whether a valid UBJSON value was passed to the SAX parser + */ + bool parse_ubjson_internal(const bool get_char = true) + { + return get_ubjson_value(get_char ? get_ignore_noop() : current); + } + + /*! + @brief reads a UBJSON string + + This function is either called after reading the 'S' byte explicitly + indicating a string, or in case of an object key where the 'S' byte can be + left out. + + @param[out] result created string + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return whether string creation completed + */ + bool get_ubjson_string(string_t& result, const bool get_char = true) + { + if (get_char) + { + get(); // TODO(niels): may we ignore N here? + } + + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value"))) + { + return false; + } + + switch (current) + { + case 'U': + { + std::uint8_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'i': + { + std::int8_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'I': + { + std::int16_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'l': + { + std::int32_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'L': + { + std::int64_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'u': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint16_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'm': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint32_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'M': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint64_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + default: + break; + } + auto last_token = get_token_string(); + std::string message; + + if (input_format != input_format_t::bjdata) + { + message = "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token; + } + else + { + message = "expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x" + last_token; + } + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, "string"), nullptr)); + } + + /*! + @param[out] dim an integer vector storing the ND array dimensions + @return whether reading ND array size vector is successful + */ + bool get_ubjson_ndarray_size(std::vector<size_t>& dim) + { + std::pair<std::size_t, char_int_type> size_and_type; + size_t dimlen = 0; + bool no_ndarray = true; + + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type, no_ndarray))) + { + return false; + } + + if (size_and_type.first != npos) + { + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second))) + { + return false; + } + dim.push_back(dimlen); + } + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray))) + { + return false; + } + dim.push_back(dimlen); + } + } + } + else + { + while (current != ']') + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, current))) + { + return false; + } + dim.push_back(dimlen); + get_ignore_noop(); + } + } + return true; + } + + /*! + @param[out] result determined size + @param[in,out] is_ndarray for input, `true` means already inside an ndarray vector + or ndarray dimension is not allowed; `false` means ndarray + is allowed; for output, `true` means an ndarray is found; + is_ndarray can only return `true` when its initial value + is `false` + @param[in] prefix type marker if already read, otherwise set to 0 + + @return whether size determination completed + */ + bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0) + { + if (prefix == 0) + { + prefix = get_ignore_noop(); + } + + switch (prefix) + { + case 'U': + { + std::uint8_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + result = static_cast<std::size_t>(number); + return true; + } + + case 'i': + { + std::int8_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } + result = static_cast<std::size_t>(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char + return true; + } + + case 'I': + { + std::int16_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } + result = static_cast<std::size_t>(number); + return true; + } + + case 'l': + { + std::int32_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } + result = static_cast<std::size_t>(number); + return true; + } + + case 'L': + { + std::int64_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } + if (!value_in_range_of<std::size_t>(number)) + { + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + exception_message(input_format, "integer value overflow", "size"), nullptr)); + } + result = static_cast<std::size_t>(number); + return true; + } + + case 'u': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint16_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + result = static_cast<std::size_t>(number); + return true; + } + + case 'm': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint32_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + result = conditional_static_cast<std::size_t>(number); + return true; + } + + case 'M': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint64_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + if (!value_in_range_of<std::size_t>(number)) + { + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + exception_message(input_format, "integer value overflow", "size"), nullptr)); + } + result = detail::conditional_static_cast<std::size_t>(number); + return true; + } + + case '[': + { + if (input_format != input_format_t::bjdata) + { + break; + } + if (is_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimensional vector is not allowed", "size"), nullptr)); + } + std::vector<size_t> dim; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) + { + return false; + } + if (dim.size() == 1 || (dim.size() == 2 && dim.at(0) == 1)) // return normal array size if 1D row vector + { + result = dim.at(dim.size() - 1); + return true; + } + if (!dim.empty()) // if ndarray, convert to an object in JData annotated array format + { + for (auto i : dim) // test if any dimension in an ndarray is 0, if so, return a 1D empty container + { + if ( i == 0 ) + { + result = 0; + return true; + } + } + + string_t key = "_ArraySize_"; + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) || !sax->start_array(dim.size()))) + { + return false; + } + result = 1; + for (auto i : dim) + { + result *= i; + if (result == 0 || result == npos) // because dim elements shall not have zeros, result = 0 means overflow happened; it also can't be npos as it is used to initialize size in get_ubjson_size_type() + { + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message(input_format, "excessive ndarray size caused overflow", "size"), nullptr)); + } + if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast<number_unsigned_t>(i)))) + { + return false; + } + } + is_ndarray = true; + return sax->end_array(); + } + result = 0; + return true; + } + + default: + break; + } + auto last_token = get_token_string(); + std::string message; + + if (input_format != input_format_t::bjdata) + { + message = "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token; + } + else + { + message = "expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x" + last_token; + } + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, "size"), nullptr)); + } + + /*! + @brief determine the type and size for a container + + In the optimized UBJSON format, a type and a size can be provided to allow + for a more compact representation. + + @param[out] result pair of the size and the type + @param[in] inside_ndarray whether the parser is parsing an ND array dimensional vector + + @return whether pair creation completed + */ + bool get_ubjson_size_type(std::pair<std::size_t, char_int_type>& result, bool inside_ndarray = false) + { + result.first = npos; // size + result.second = 0; // type + bool is_ndarray = false; + + get_ignore_noop(); + + if (current == '$') + { + result.second = get(); // must not ignore 'N', because 'N' maybe the type + if (input_format == input_format_t::bjdata + && JSON_HEDLEY_UNLIKELY(std::binary_search(bjd_optimized_type_markers.begin(), bjd_optimized_type_markers.end(), result.second))) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr)); + } + + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "type"))) + { + return false; + } + + get_ignore_noop(); + if (JSON_HEDLEY_UNLIKELY(current != '#')) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value"))) + { + return false; + } + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); + } + + const bool is_error = get_ubjson_size_value(result.first, is_ndarray); + if (input_format == input_format_t::bjdata && is_ndarray) + { + if (inside_ndarray) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, + exception_message(input_format, "ndarray can not be recursive", "size"), nullptr)); + } + result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters + } + return is_error; + } + + if (current == '#') + { + const bool is_error = get_ubjson_size_value(result.first, is_ndarray); + if (input_format == input_format_t::bjdata && is_ndarray) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, + exception_message(input_format, "ndarray requires both type and size", "size"), nullptr)); + } + return is_error; + } + + return true; + } + + /*! + @param prefix the previously read or set type prefix + @return whether value creation completed + */ + bool get_ubjson_value(const char_int_type prefix) + { + switch (prefix) + { + case char_traits<char_type>::eof(): // EOF + return unexpect_eof(input_format, "value"); + + case 'T': // true + return sax->boolean(true); + case 'F': // false + return sax->boolean(false); + + case 'Z': // null + return sax->null(); + + case 'U': + { + std::uint8_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'i': + { + std::int8_t number{}; + return get_number(input_format, number) && sax->number_integer(number); + } + + case 'I': + { + std::int16_t number{}; + return get_number(input_format, number) && sax->number_integer(number); + } + + case 'l': + { + std::int32_t number{}; + return get_number(input_format, number) && sax->number_integer(number); + } + + case 'L': + { + std::int64_t number{}; + return get_number(input_format, number) && sax->number_integer(number); + } + + case 'u': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint16_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'm': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint32_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'M': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint64_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'h': + { + if (input_format != input_format_t::bjdata) + { + break; + } + const auto byte1_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "number"))) + { + return false; + } + const auto byte2_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "number"))) + { + return false; + } + + const auto byte1 = static_cast<unsigned char>(byte1_raw); + const auto byte2 = static_cast<unsigned char>(byte2_raw); + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const auto half = static_cast<unsigned int>((byte2 << 8u) + byte1); + const double val = [&half] + { + const int exp = (half >> 10u) & 0x1Fu; + const unsigned int mant = half & 0x3FFu; + JSON_ASSERT(0 <= exp&& exp <= 32); + JSON_ASSERT(mant <= 1024); + switch (exp) + { + case 0: + return std::ldexp(mant, -24); + case 31: + return (mant == 0) + ? std::numeric_limits<double>::infinity() + : std::numeric_limits<double>::quiet_NaN(); + default: + return std::ldexp(mant + 1024, exp - 25); + } + }(); + return sax->number_float((half & 0x8000u) != 0 + ? static_cast<number_float_t>(-val) + : static_cast<number_float_t>(val), ""); + } + + case 'd': + { + float number{}; + return get_number(input_format, number) && sax->number_float(static_cast<number_float_t>(number), ""); + } + + case 'D': + { + double number{}; + return get_number(input_format, number) && sax->number_float(static_cast<number_float_t>(number), ""); + } + + case 'H': + { + return get_ubjson_high_precision_number(); + } + + case 'C': // char + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "char"))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(current > 127)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, + exception_message(input_format, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr)); + } + string_t s(1, static_cast<typename string_t::value_type>(current)); + return sax->string(s); + } + + case 'S': // string + { + string_t s; + return get_ubjson_string(s) && sax->string(s); + } + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + break; + } + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format, "invalid byte: 0x" + last_token, "value"), nullptr)); + } + + /*! + @return whether array creation completed + */ + bool get_ubjson_array() + { + std::pair<std::size_t, char_int_type> size_and_type; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + { + return false; + } + + // if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata): + // {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]} + + if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0) + { + size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker + auto it = std::lower_bound(bjd_types_map.begin(), bjd_types_map.end(), size_and_type.second, [](const bjd_type & p, char_int_type t) + { + return p.first < t; + }); + string_t key = "_ArrayType_"; + if (JSON_HEDLEY_UNLIKELY(it == bjd_types_map.end() || it->first != size_and_type.second)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, "invalid byte: 0x" + last_token, "type"), nullptr)); + } + + string_t type = it->second; // sax->string() takes a reference + if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->string(type))) + { + return false; + } + + if (size_and_type.second == 'C') + { + size_and_type.second = 'U'; + } + + key = "_ArrayData_"; + if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) )) + { + return false; + } + + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + } + + return (sax->end_array() && sax->end_object()); + } + + if (size_and_type.first != npos) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) + { + return false; + } + + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + } + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + } + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast<std::size_t>(-1)))) + { + return false; + } + + while (current != ']') + { + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal(false))) + { + return false; + } + get_ignore_noop(); + } + } + + return sax->end_array(); + } + + /*! + @return whether object creation completed + */ + bool get_ubjson_object() + { + std::pair<std::size_t, char_int_type> size_and_type; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + { + return false; + } + + // do not accept ND-array size in objects in BJData + if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, "BJData object does not support ND-array size in optimized format", "object"), nullptr)); + } + + string_t key; + if (size_and_type.first != npos) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(size_and_type.first))) + { + return false; + } + + if (size_and_type.second != 0) + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + key.clear(); + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + key.clear(); + } + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast<std::size_t>(-1)))) + { + return false; + } + + while (current != '}') + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + get_ignore_noop(); + key.clear(); + } + } + + return sax->end_object(); + } + + // Note, no reader for UBJSON binary types is implemented because they do + // not exist + + bool get_ubjson_high_precision_number() + { + // get size of following number string + std::size_t size{}; + bool no_ndarray = true; + auto res = get_ubjson_size_value(size, no_ndarray); + if (JSON_HEDLEY_UNLIKELY(!res)) + { + return res; + } + + // get number string + std::vector<char> number_vector; + for (std::size_t i = 0; i < size; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "number"))) + { + return false; + } + number_vector.push_back(static_cast<char>(current)); + } + + // parse number string + using ia_type = decltype(detail::input_adapter(number_vector)); + auto number_lexer = detail::lexer<BasicJsonType, ia_type>(detail::input_adapter(number_vector), false); + const auto result_number = number_lexer.scan(); + const auto number_string = number_lexer.get_token_string(); + const auto result_remainder = number_lexer.scan(); + + using token_type = typename detail::lexer_base<BasicJsonType>::token_type; + + if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) + { + return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, + exception_message(input_format, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); + } + + switch (result_number) + { + case token_type::value_integer: + return sax->number_integer(number_lexer.get_number_integer()); + case token_type::value_unsigned: + return sax->number_unsigned(number_lexer.get_number_unsigned()); + case token_type::value_float: + return sax->number_float(number_lexer.get_number_float(), std::move(number_string)); + case token_type::uninitialized: + case token_type::literal_true: + case token_type::literal_false: + case token_type::literal_null: + case token_type::value_string: + case token_type::begin_array: + case token_type::begin_object: + case token_type::end_array: + case token_type::end_object: + case token_type::name_separator: + case token_type::value_separator: + case token_type::parse_error: + case token_type::end_of_input: + case token_type::literal_or_value: + default: + return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, + exception_message(input_format, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); + } + } + + /////////////////////// + // Utility functions // + /////////////////////// + + /*! + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a -'ve valued + `char_traits<char_type>::eof()` in that case. + + @return character read from the input + */ + char_int_type get() + { + ++chars_read; + return current = ia.get_character(); + } + + /*! + @return character read from the input after ignoring all 'N' entries + */ + char_int_type get_ignore_noop() + { + do + { + get(); + } + while (current == 'N'); + + return current; + } + + /* + @brief read a number from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[out] result number of type @a NumberType + + @return whether conversion completed + + @note This function needs to respect the system's endianness, because + bytes in CBOR, MessagePack, and UBJSON are stored in network order + (big endian) and therefore need reordering on little endian systems. + On the other hand, BSON and BJData use little endian and should reorder + on big endian systems. + */ + template<typename NumberType, bool InputIsLittleEndian = false> + bool get_number(const input_format_t format, NumberType& result) + { + // step 1: read input into array with system's byte order + std::array<std::uint8_t, sizeof(NumberType)> vec{}; + for (std::size_t i = 0; i < sizeof(NumberType); ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number"))) + { + return false; + } + + // reverse byte order prior to conversion if necessary + if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata)) + { + vec[sizeof(NumberType) - i - 1] = static_cast<std::uint8_t>(current); + } + else + { + vec[i] = static_cast<std::uint8_t>(current); // LCOV_EXCL_LINE + } + } + + // step 2: convert array into number of type T and return + std::memcpy(&result, vec.data(), sizeof(NumberType)); + return true; + } + + /*! + @brief create a string by reading characters from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[in] len number of characters to read + @param[out] result string created by reading @a len bytes + + @return whether string creation completed + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of string memory. + */ + template<typename NumberType> + bool get_string(const input_format_t format, + const NumberType len, + string_t& result) + { + bool success = true; + for (NumberType i = 0; i < len; i++) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string"))) + { + success = false; + break; + } + result.push_back(static_cast<typename string_t::value_type>(current)); + } + return success; + } + + /*! + @brief create a byte array by reading bytes from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[in] len number of bytes to read + @param[out] result byte array created by reading @a len bytes + + @return whether byte array creation completed + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of memory. + */ + template<typename NumberType> + bool get_binary(const input_format_t format, + const NumberType len, + binary_t& result) + { + bool success = true; + for (NumberType i = 0; i < len; i++) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary"))) + { + success = false; + break; + } + result.push_back(static_cast<std::uint8_t>(current)); + } + return success; + } + + /*! + @param[in] format the current format (for diagnostics) + @param[in] context further context information (for diagnostics) + @return whether the last read character is not EOF + */ + JSON_HEDLEY_NON_NULL(3) + bool unexpect_eof(const input_format_t format, const char* context) const + { + if (JSON_HEDLEY_UNLIKELY(current == char_traits<char_type>::eof())) + { + return sax->parse_error(chars_read, "<end of file>", + parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context), nullptr)); + } + return true; + } + + /*! + @return a string representation of the last read byte + */ + std::string get_token_string() const + { + std::array<char, 3> cr{{}}; + static_cast<void>((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast<unsigned char>(current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + return std::string{cr.data()}; + } + + /*! + @param[in] format the current format + @param[in] detail a detailed error message + @param[in] context further context information + @return a message string to use in the parse_error exceptions + */ + std::string exception_message(const input_format_t format, + const std::string& detail, + const std::string& context) const + { + std::string error_msg = "syntax error while parsing "; + + switch (format) + { + case input_format_t::cbor: + error_msg += "CBOR"; + break; + + case input_format_t::msgpack: + error_msg += "MessagePack"; + break; + + case input_format_t::ubjson: + error_msg += "UBJSON"; + break; + + case input_format_t::bson: + error_msg += "BSON"; + break; + + case input_format_t::bjdata: + error_msg += "BJData"; + break; + + case input_format_t::json: // LCOV_EXCL_LINE + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + + return concat(error_msg, ' ', context, ": ", detail); + } + + private: + static JSON_INLINE_VARIABLE constexpr std::size_t npos = static_cast<std::size_t>(-1); + + /// input adapter + InputAdapterType ia; + + /// the current character + char_int_type current = char_traits<char_type>::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// whether we can assume little endianness + const bool is_little_endian = little_endianness(); + + /// input format + const input_format_t input_format = input_format_t::json; + + /// the SAX parser + json_sax_t* sax = nullptr; + + // excluded markers in bjdata optimized type +#define JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_ \ + make_array<char_int_type>('F', 'H', 'N', 'S', 'T', 'Z', '[', '{') + +#define JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ \ + make_array<bjd_type>( \ + bjd_type{'C', "char"}, \ + bjd_type{'D', "double"}, \ + bjd_type{'I', "int16"}, \ + bjd_type{'L', "int64"}, \ + bjd_type{'M', "uint64"}, \ + bjd_type{'U', "uint8"}, \ + bjd_type{'d', "single"}, \ + bjd_type{'i', "int8"}, \ + bjd_type{'l', "int32"}, \ + bjd_type{'m', "uint32"}, \ + bjd_type{'u', "uint16"}) + + JSON_PRIVATE_UNLESS_TESTED: + // lookup tables + // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes) + const decltype(JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_) bjd_optimized_type_markers = + JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_; + + using bjd_type = std::pair<char_int_type, string_t>; + // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes) + const decltype(JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_) bjd_types_map = + JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_; + +#undef JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_ +#undef JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ +}; + +#ifndef JSON_HAS_CPP_17 + template<typename BasicJsonType, typename InputAdapterType, typename SAX> + constexpr std::size_t binary_reader<BasicJsonType, InputAdapterType, SAX>::npos; +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/input/input_adapters.hpp> + +// #include <nlohmann/detail/input/lexer.hpp> + +// #include <nlohmann/detail/input/parser.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <cmath> // isfinite +#include <cstdint> // uint8_t +#include <functional> // function +#include <string> // string +#include <utility> // move +#include <vector> // vector + +// #include <nlohmann/detail/exceptions.hpp> + +// #include <nlohmann/detail/input/input_adapters.hpp> + +// #include <nlohmann/detail/input/json_sax.hpp> + +// #include <nlohmann/detail/input/lexer.hpp> + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/meta/is_sax.hpp> + +// #include <nlohmann/detail/string_concat.hpp> + +// #include <nlohmann/detail/value_t.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +//////////// +// parser // +//////////// + +enum class parse_event_t : std::uint8_t +{ + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value +}; + +template<typename BasicJsonType> +using parser_callback_t = + std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>; + +/*! +@brief syntax analysis + +This class implements a recursive descent parser. +*/ +template<typename BasicJsonType, typename InputAdapterType> +class parser +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using lexer_t = lexer<BasicJsonType, InputAdapterType>; + using token_type = typename lexer_t::token_type; + + public: + /// a parser reading from an input adapter + explicit parser(InputAdapterType&& adapter, + const parser_callback_t<BasicJsonType> cb = nullptr, + const bool allow_exceptions_ = true, + const bool skip_comments = false) + : callback(cb) + , m_lexer(std::move(adapter), skip_comments) + , allow_exceptions(allow_exceptions_) + { + // read first token + get_token(); + } + + /*! + @brief public parser interface + + @param[in] strict whether to expect the last token to be EOF + @param[in,out] result parsed JSON value + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse(const bool strict, BasicJsonType& result) + { + if (callback) + { + json_sax_dom_callback_parser<BasicJsonType> sdp(result, callback, allow_exceptions); + sax_parse_internal(&sdp); + + // in strict mode, input must be completely read + if (strict && (get_token() != token_type::end_of_input)) + { + sdp.parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_of_input, "value"), nullptr)); + } + + // in case of an error, return discarded value + if (sdp.is_errored()) + { + result = value_t::discarded; + return; + } + + // set top-level value to null if it was discarded by the callback + // function + if (result.is_discarded()) + { + result = nullptr; + } + } + else + { + json_sax_dom_parser<BasicJsonType> sdp(result, allow_exceptions); + sax_parse_internal(&sdp); + + // in strict mode, input must be completely read + if (strict && (get_token() != token_type::end_of_input)) + { + sdp.parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr)); + } + + // in case of an error, return discarded value + if (sdp.is_errored()) + { + result = value_t::discarded; + return; + } + } + + result.assert_invariant(); + } + + /*! + @brief public accept interface + + @param[in] strict whether to expect the last token to be EOF + @return whether the input is a proper JSON text + */ + bool accept(const bool strict = true) + { + json_sax_acceptor<BasicJsonType> sax_acceptor; + return sax_parse(&sax_acceptor, strict); + } + + template<typename SAX> + JSON_HEDLEY_NON_NULL(2) + bool sax_parse(SAX* sax, const bool strict = true) + { + (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {}; + const bool result = sax_parse_internal(sax); + + // strict mode: next byte must be EOF + if (result && strict && (get_token() != token_type::end_of_input)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr)); + } + + return result; + } + + private: + template<typename SAX> + JSON_HEDLEY_NON_NULL(2) + bool sax_parse_internal(SAX* sax) + { + // stack to remember the hierarchy of structured values we are parsing + // true = array; false = object + std::vector<bool> states; + // value to avoid a goto (see comment where set to true) + bool skip_to_state_evaluation = false; + + while (true) + { + if (!skip_to_state_evaluation) + { + // invariant: get_token() was called before each iteration + switch (last_token) + { + case token_type::begin_object: + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast<std::size_t>(-1)))) + { + return false; + } + + // closing } -> we are done + if (get_token() == token_type::end_object) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) + { + return false; + } + break; + } + + // parse key + if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + } + if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) + { + return false; + } + + // parse separator (:) + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + } + + // remember we are now inside an object + states.push_back(false); + + // parse values + get_token(); + continue; + } + + case token_type::begin_array: + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast<std::size_t>(-1)))) + { + return false; + } + + // closing ] -> we are done + if (get_token() == token_type::end_array) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) + { + return false; + } + break; + } + + // remember we are now inside an array + states.push_back(true); + + // parse values (no need to call get_token) + continue; + } + + case token_type::value_float: + { + const auto res = m_lexer.get_number_float(); + + if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res))) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + out_of_range::create(406, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); + } + + if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) + { + return false; + } + + break; + } + + case token_type::literal_false: + { + if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false))) + { + return false; + } + break; + } + + case token_type::literal_null: + { + if (JSON_HEDLEY_UNLIKELY(!sax->null())) + { + return false; + } + break; + } + + case token_type::literal_true: + { + if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true))) + { + return false; + } + break; + } + + case token_type::value_integer: + { + if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer()))) + { + return false; + } + break; + } + + case token_type::value_string: + { + if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) + { + return false; + } + break; + } + + case token_type::value_unsigned: + { + if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(m_lexer.get_number_unsigned()))) + { + return false; + } + break; + } + + case token_type::parse_error: + { + // using "uninitialized" to avoid "expected" message + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr)); + } + case token_type::end_of_input: + { + if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + } + + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr)); + } + case token_type::uninitialized: + case token_type::end_array: + case token_type::end_object: + case token_type::name_separator: + case token_type::value_separator: + case token_type::literal_or_value: + default: // the last token was unexpected + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr)); + } + } + } + else + { + skip_to_state_evaluation = false; + } + + // we reached this line after we successfully parsed a value + if (states.empty()) + { + // empty stack: we reached the end of the hierarchy: done + return true; + } + + if (states.back()) // array + { + // comma -> next value + if (get_token() == token_type::value_separator) + { + // parse a new value + get_token(); + continue; + } + + // closing ] + if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array)) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) + { + return false; + } + + // We are done with this array. Before we can parse a + // new value, we need to evaluate the new state first. + // By setting skip_to_state_evaluation to false, we + // are effectively jumping to the beginning of this if. + JSON_ASSERT(!states.empty()); + states.pop_back(); + skip_to_state_evaluation = true; + continue; + } + + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr)); + } + + // states.back() is false -> object + + // comma -> next value + if (get_token() == token_type::value_separator) + { + // parse key + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + } + + if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) + { + return false; + } + + // parse separator (:) + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + } + + // parse values + get_token(); + continue; + } + + // closing } + if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object)) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) + { + return false; + } + + // We are done with this object. Before we can parse a + // new value, we need to evaluate the new state first. + // By setting skip_to_state_evaluation to false, we + // are effectively jumping to the beginning of this if. + JSON_ASSERT(!states.empty()); + states.pop_back(); + skip_to_state_evaluation = true; + continue; + } + + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr)); + } + } + + /// get next token from lexer + token_type get_token() + { + return last_token = m_lexer.scan(); + } + + std::string exception_message(const token_type expected, const std::string& context) + { + std::string error_msg = "syntax error "; + + if (!context.empty()) + { + error_msg += concat("while parsing ", context, ' '); + } + + error_msg += "- "; + + if (last_token == token_type::parse_error) + { + error_msg += concat(m_lexer.get_error_message(), "; last read: '", + m_lexer.get_token_string(), '\''); + } + else + { + error_msg += concat("unexpected ", lexer_t::token_type_name(last_token)); + } + + if (expected != token_type::uninitialized) + { + error_msg += concat("; expected ", lexer_t::token_type_name(expected)); + } + + return error_msg; + } + + private: + /// callback function + const parser_callback_t<BasicJsonType> callback = nullptr; + /// the type of the last read token + token_type last_token = token_type::uninitialized; + /// the lexer + lexer_t m_lexer; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/iterators/internal_iterator.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +// #include <nlohmann/detail/abi_macros.hpp> + +// #include <nlohmann/detail/iterators/primitive_iterator.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <cstddef> // ptrdiff_t +#include <limits> // numeric_limits + +// #include <nlohmann/detail/macro_scope.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/* +@brief an iterator for primitive JSON types + +This class models an iterator for primitive JSON types (boolean, number, +string). It's only purpose is to allow the iterator/const_iterator classes +to "iterate" over primitive values. Internally, the iterator is modeled by +a `difference_type` variable. Value begin_value (`0`) models the begin, +end_value (`1`) models past the end. +*/ +class primitive_iterator_t +{ + private: + using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + JSON_PRIVATE_UNLESS_TESTED: + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits<std::ptrdiff_t>::min)(); + + public: + constexpr difference_type get_value() const noexcept + { + return m_it; + } + + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return m_it == begin_value; + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return m_it == end_value; + } + + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + primitive_iterator_t operator+(difference_type n) noexcept + { + auto result = *this; + result += n; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + primitive_iterator_t& operator++() noexcept + { + ++m_it; + return *this; + } + + primitive_iterator_t operator++(int)& noexcept // NOLINT(cert-dcl21-cpp) + { + auto result = *this; + ++m_it; + return result; + } + + primitive_iterator_t& operator--() noexcept + { + --m_it; + return *this; + } + + primitive_iterator_t operator--(int)& noexcept // NOLINT(cert-dcl21-cpp) + { + auto result = *this; + --m_it; + return result; + } + + primitive_iterator_t& operator+=(difference_type n) noexcept + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) noexcept + { + m_it -= n; + return *this; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief an iterator value + +@note This structure could easily be a union, but MSVC currently does not allow +unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. +*/ +template<typename BasicJsonType> struct internal_iterator +{ + /// iterator for JSON objects + typename BasicJsonType::object_t::iterator object_iterator {}; + /// iterator for JSON arrays + typename BasicJsonType::array_t::iterator array_iterator {}; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator {}; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/iterators/iter_impl.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <iterator> // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next +#include <type_traits> // conditional, is_const, remove_const + +// #include <nlohmann/detail/exceptions.hpp> + +// #include <nlohmann/detail/iterators/internal_iterator.hpp> + +// #include <nlohmann/detail/iterators/primitive_iterator.hpp> + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/meta/cpp_future.hpp> + +// #include <nlohmann/detail/meta/type_traits.hpp> + +// #include <nlohmann/detail/value_t.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// forward declare, to be able to friend it later on +template<typename IteratorType> class iteration_proxy; +template<typename IteratorType> class iteration_proxy_value; + +/*! +@brief a template for a bidirectional iterator for the @ref basic_json class +This class implements a both iterators (iterator and const_iterator) for the +@ref basic_json class. +@note An iterator is called *initialized* when a pointer to a JSON value has + been set (e.g., by a constructor or a copy assignment). If the iterator is + default-constructed, it is *uninitialized* and most methods are undefined. + **The library uses assertions to detect calls on uninitialized iterators.** +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +@since version 1.0.0, simplified in version 2.0.9, change to bidirectional + iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) +*/ +template<typename BasicJsonType> +class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) +{ + /// the iterator with BasicJsonType of different const-ness + using other_iter_impl = iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>; + /// allow basic_json to access private members + friend other_iter_impl; + friend BasicJsonType; + friend iteration_proxy<iter_impl>; + friend iteration_proxy_value<iter_impl>; + + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; + // make sure BasicJsonType is basic_json or const basic_json + static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value, + "iter_impl only accepts (const) basic_json"); + // superficial check for the LegacyBidirectionalIterator named requirement + static_assert(std::is_base_of<std::bidirectional_iterator_tag, std::bidirectional_iterator_tag>::value + && std::is_base_of<std::bidirectional_iterator_tag, typename std::iterator_traits<typename array_t::iterator>::iterator_category>::value, + "basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement."); + + public: + /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. + /// The C++ Standard has never required user-defined iterators to derive from std::iterator. + /// A user-defined iterator should provide publicly accessible typedefs named + /// iterator_category, value_type, difference_type, pointer, and reference. + /// Note that value_type is required to be non-const, even for constant iterators. + using iterator_category = std::bidirectional_iterator_tag; + + /// the type of the values when the iterator is dereferenced + using value_type = typename BasicJsonType::value_type; + /// a type to represent differences between iterators + using difference_type = typename BasicJsonType::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional<std::is_const<BasicJsonType>::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = + typename std::conditional<std::is_const<BasicJsonType>::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; + + iter_impl() = default; + ~iter_impl() = default; + iter_impl(iter_impl&&) noexcept = default; + iter_impl& operator=(iter_impl&&) noexcept = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept : m_object(object) + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /*! + @note The conventional copy constructor and copy assignment are implicitly + defined. Combined with the following converting constructor and + assignment, they support: (1) copy from iterator to iterator, (2) + copy from const iterator to const iterator, and (3) conversion from + iterator to const iterator. However conversion from const iterator + to iterator is not defined. + */ + + /*! + @brief const copy constructor + @param[in] other const iterator to copy from + @note This copy constructor had to be defined explicitly to circumvent a bug + occurring on msvc v19.0 compiler (VS 2015) debug build. For more + information refer to: https://github.com/nlohmann/json/issues/1608 + */ + iter_impl(const iter_impl<const BasicJsonType>& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief converting assignment + @param[in] other const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl<const BasicJsonType>& other) noexcept + { + if (&other != this) + { + m_object = other.m_object; + m_it = other.m_it; + } + return *this; + } + + /*! + @brief converting constructor + @param[in] other non-const iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief converting assignment + @param[in] other non-const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept // NOLINT(cert-oop54-cpp) + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_data.m_value.object->begin(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_data.m_value.array->begin(); + break; + } + + case value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_data.m_value.object->end(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_data.m_value.array->end(); + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end()); + return m_it.object_iterator->second; + } + + case value_t::array: + { + JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end()); + return *m_it.array_iterator; + } + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case value_t::array: + { + JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end()); + return &*m_it.array_iterator; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) + { + return m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator++(int)& // NOLINT(cert-dcl21-cpp) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator--(int)& // NOLINT(cert-dcl21-cpp) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + template < typename IterImpl, detail::enable_if_t < (std::is_same<IterImpl, iter_impl>::value || std::is_same<IterImpl, other_iter_impl>::value), std::nullptr_t > = nullptr > + bool operator==(const IterImpl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); + } + + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + return (m_it.object_iterator == other.m_it.object_iterator); + + case value_t::array: + return (m_it.array_iterator == other.m_it.array_iterator); + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + template < typename IterImpl, detail::enable_if_t < (std::is_same<IterImpl, iter_impl>::value || std::is_same<IterImpl, other_iter_impl>::value), std::nullptr_t > = nullptr > + bool operator!=(const IterImpl& other) const + { + return !operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); + } + + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators", m_object)); + + case value_t::array: + return (m_it.array_iterator < other.m_it.array_iterator); + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const iter_impl& other) const + { + return !other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const iter_impl& other) const + { + return !operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const iter_impl& other) const + { + return !operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); + + case value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief addition of distance and iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + friend iter_impl operator+(difference_type i, const iter_impl& it) + { + auto result = it; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); + + case value_t::array: + return m_it.array_iterator - other.m_it.array_iterator; + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators", m_object)); + + case value_t::array: + return *std::next(m_it.array_iterator, n); + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n)) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const typename object_t::key_type& key() const + { + JSON_ASSERT(m_object != nullptr); + + if (JSON_HEDLEY_LIKELY(m_object->is_object())) + { + return m_it.object_iterator->first; + } + + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators", m_object)); + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + JSON_PRIVATE_UNLESS_TESTED: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it {}; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/iterators/iteration_proxy.hpp> + +// #include <nlohmann/detail/iterators/json_reverse_iterator.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <cstddef> // ptrdiff_t +#include <iterator> // reverse_iterator +#include <utility> // declval + +// #include <nlohmann/detail/abi_macros.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +////////////////////// +// reverse_iterator // +////////////////////// + +/*! +@brief a template for a reverse iterator class + +@tparam Base the base iterator type to reverse. Valid types are @ref +iterator (to create @ref reverse_iterator) and @ref const_iterator (to +create @ref const_reverse_iterator). + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +- [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + +@since version 1.0.0 +*/ +template<typename Base> +class json_reverse_iterator : public std::reverse_iterator<Base> +{ + public: + using difference_type = std::ptrdiff_t; + /// shortcut to the reverse iterator adapter + using base_iterator = std::reverse_iterator<Base>; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) {} + + /// create reverse iterator from base class + explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} + + /// post-increment (it++) + json_reverse_iterator operator++(int)& // NOLINT(cert-dcl21-cpp) + { + return static_cast<json_reverse_iterator>(base_iterator::operator++(1)); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + return static_cast<json_reverse_iterator&>(base_iterator::operator++()); + } + + /// post-decrement (it--) + json_reverse_iterator operator--(int)& // NOLINT(cert-dcl21-cpp) + { + return static_cast<json_reverse_iterator>(base_iterator::operator--(1)); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + return static_cast<json_reverse_iterator&>(base_iterator::operator--()); + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + return static_cast<json_reverse_iterator&>(base_iterator::operator+=(i)); + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + return static_cast<json_reverse_iterator>(base_iterator::operator+(i)); + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + return static_cast<json_reverse_iterator>(base_iterator::operator-(i)); + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return base_iterator(*this) - base_iterator(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + auto key() const -> decltype(std::declval<Base>().key()) + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/iterators/primitive_iterator.hpp> + +// #include <nlohmann/detail/json_custom_base_class.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <type_traits> // conditional, is_same + +// #include <nlohmann/detail/abi_macros.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief Default base class of the @ref basic_json class. + +So that the correct implementations of the copy / move ctors / assign operators +of @ref basic_json do not require complex case distinctions +(no base class / custom base class used as customization point), +@ref basic_json always has a base class. +By default, this class is used because it is empty and thus has no effect +on the behavior of @ref basic_json. +*/ +struct json_default_base {}; + +template<class T> +using json_base_class = typename std::conditional < + std::is_same<T, void>::value, + json_default_base, + T + >::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/json_pointer.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <algorithm> // all_of +#include <cctype> // isdigit +#include <cerrno> // errno, ERANGE +#include <cstdlib> // strtoull +#ifndef JSON_NO_IO + #include <iosfwd> // ostream +#endif // JSON_NO_IO +#include <limits> // max +#include <numeric> // accumulate +#include <string> // string +#include <utility> // move +#include <vector> // vector + +// #include <nlohmann/detail/exceptions.hpp> + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/string_concat.hpp> + +// #include <nlohmann/detail/string_escape.hpp> + +// #include <nlohmann/detail/value_t.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document +/// @sa https://json.nlohmann.me/api/json_pointer/ +template<typename RefStringType> +class json_pointer +{ + // allow basic_json to access private members + NLOHMANN_BASIC_JSON_TPL_DECLARATION + friend class basic_json; + + template<typename> + friend class json_pointer; + + template<typename T> + struct string_t_helper + { + using type = T; + }; + + NLOHMANN_BASIC_JSON_TPL_DECLARATION + struct string_t_helper<NLOHMANN_BASIC_JSON_TPL> + { + using type = StringType; + }; + + public: + // for backwards compatibility accept BasicJsonType + using string_t = typename string_t_helper<RefStringType>::type; + + /// @brief create JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/json_pointer/ + explicit json_pointer(const string_t& s = "") + : reference_tokens(split(s)) + {} + + /// @brief return a string representation of the JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/to_string/ + string_t to_string() const + { + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + string_t{}, + [](const string_t& a, const string_t& b) + { + return detail::concat(a, '/', detail::escape(b)); + }); + } + + /// @brief return a string representation of the JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_string/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string()) + operator string_t() const + { + return to_string(); + } + +#ifndef JSON_NO_IO + /// @brief write string representation of the JSON pointer to stream + /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ + friend std::ostream& operator<<(std::ostream& o, const json_pointer& ptr) + { + o << ptr.to_string(); + return o; + } +#endif + + /// @brief append another JSON pointer at the end of this JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ + json_pointer& operator/=(const json_pointer& ptr) + { + reference_tokens.insert(reference_tokens.end(), + ptr.reference_tokens.begin(), + ptr.reference_tokens.end()); + return *this; + } + + /// @brief append an unescaped reference token at the end of this JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ + json_pointer& operator/=(string_t token) + { + push_back(std::move(token)); + return *this; + } + + /// @brief append an array index at the end of this JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ + json_pointer& operator/=(std::size_t array_idx) + { + return *this /= std::to_string(array_idx); + } + + /// @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ + friend json_pointer operator/(const json_pointer& lhs, + const json_pointer& rhs) + { + return json_pointer(lhs) /= rhs; + } + + /// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ + friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param) + { + return json_pointer(lhs) /= std::move(token); + } + + /// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ + friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx) + { + return json_pointer(lhs) /= array_idx; + } + + /// @brief returns the parent of this JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/parent_pointer/ + json_pointer parent_pointer() const + { + if (empty()) + { + return *this; + } + + json_pointer res = *this; + res.pop_back(); + return res; + } + + /// @brief remove last reference token + /// @sa https://json.nlohmann.me/api/json_pointer/pop_back/ + void pop_back() + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + } + + reference_tokens.pop_back(); + } + + /// @brief return last reference token + /// @sa https://json.nlohmann.me/api/json_pointer/back/ + const string_t& back() const + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + } + + return reference_tokens.back(); + } + + /// @brief append an unescaped token at the end of the reference pointer + /// @sa https://json.nlohmann.me/api/json_pointer/push_back/ + void push_back(const string_t& token) + { + reference_tokens.push_back(token); + } + + /// @brief append an unescaped token at the end of the reference pointer + /// @sa https://json.nlohmann.me/api/json_pointer/push_back/ + void push_back(string_t&& token) + { + reference_tokens.push_back(std::move(token)); + } + + /// @brief return whether pointer points to the root document + /// @sa https://json.nlohmann.me/api/json_pointer/empty/ + bool empty() const noexcept + { + return reference_tokens.empty(); + } + + private: + /*! + @param[in] s reference token to be converted into an array index + + @return integer representation of @a s + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index begins not with a digit + @throw out_of_range.404 if string @a s could not be converted to an integer + @throw out_of_range.410 if an array index exceeds size_type + */ + template<typename BasicJsonType> + static typename BasicJsonType::size_type array_index(const string_t& s) + { + using size_type = typename BasicJsonType::size_type; + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) + { + JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); + } + + const char* p = s.c_str(); + char* p_end = nullptr; + errno = 0; // strtoull doesn't reset errno + const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int) + if (p == p_end // invalid input or empty string + || errno == ERANGE // out of range + || JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read + { + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); + } + + // only triggered on special platforms (like 32bit), see also + // https://github.com/nlohmann/json/pull/2203 + if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int) + { + JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); // LCOV_EXCL_LINE + } + + return static_cast<size_type>(res); + } + + JSON_PRIVATE_UNLESS_TESTED: + json_pointer top() const + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + private: + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + + @throw parse_error.109 if array index is not a number + @throw type_error.313 if value cannot be unflattened + */ + template<typename BasicJsonType> + BasicJsonType& get_and_create(BasicJsonType& j) const + { + auto* result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->type()) + { + case detail::value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + result = &result->operator[](array_index<BasicJsonType>(reference_token)); + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + single value; that is, with an empty list of reference tokens. + */ + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j)); + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries to + create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + template<typename BasicJsonType> + BasicJsonType& get_unchecked(BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->is_null()) + { + // check if reference token is a number + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const unsigned char x) + { + return std::isdigit(x); + }); + + // change value to array for numbers or "-" or to object otherwise + *ptr = (nums || reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch (ptr->type()) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_data.m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token)); + } + break; + } + + case detail::value_t::null: + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + template<typename BasicJsonType> + BasicJsonType& get_checked(BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, detail::concat( + "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), + ") is out of range"), ptr)); + } + + // note: at performs range check + ptr = &ptr->at(array_index<BasicJsonType>(reference_token)); + break; + } + + case detail::value_t::null: + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + template<typename BasicJsonType> + const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); + } + + // use unchecked array access + ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token)); + break; + } + + case detail::value_t::null: + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + template<typename BasicJsonType> + const BasicJsonType& get_checked(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, detail::concat( + "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), + ") is out of range"), ptr)); + } + + // note: at performs range check + ptr = &ptr->at(array_index<BasicJsonType>(reference_token)); + break; + } + + case detail::value_t::null: + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + */ + template<typename BasicJsonType> + bool contains(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + if (!ptr->contains(reference_token)) + { + // we did not find the key in the object + return false; + } + + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + return false; + } + if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9"))) + { + // invalid char + return false; + } + if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) + { + if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) + { + // first char should be between '1' and '9' + return false; + } + for (std::size_t i = 1; i < reference_token.size(); i++) + { + if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9'))) + { + // other char should be between '0' and '9' + return false; + } + } + } + + const auto idx = array_index<BasicJsonType>(reference_token); + if (idx >= ptr->size()) + { + // index out of range + return false; + } + + ptr = &ptr->operator[](idx); + break; + } + + case detail::value_t::null: + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + { + // we do not expect primitive values if there is still a + // reference token to process + return false; + } + } + } + + // no reference token left means we found a primitive value + return true; + } + + /*! + @brief split the string input to reference tokens + + @note This function is only called by the json_pointer constructor. + All exceptions below are documented there. + + @throw parse_error.107 if the pointer is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' + */ + static std::vector<string_t> split(const string_t& reference_string) + { + std::vector<string_t> result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) + { + JSON_THROW(detail::parse_error::create(107, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + std::size_t slash = reference_string.find_first_of('/', 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == 0 (if slash == string_t::npos) + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == string_t::npos) + start = (slash == string_t::npos) ? 0 : slash + 1, + // find next slash + slash = reference_string.find_first_of('/', start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (std::size_t pos = reference_token.find_first_of('~'); + pos != string_t::npos; + pos = reference_token.find_first_of('~', pos + 1)) + { + JSON_ASSERT(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 || + (reference_token[pos + 1] != '0' && + reference_token[pos + 1] != '1'))) + { + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); + } + } + + // finally, store the reference token + detail::unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + private: + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + template<typename BasicJsonType> + static void flatten(const string_t& reference_string, + const BasicJsonType& value, + BasicJsonType& result) + { + switch (value.type()) + { + case detail::value_t::array: + { + if (value.m_data.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i) + { + flatten(detail::concat(reference_string, '/', std::to_string(i)), + value.m_data.m_value.array->operator[](i), result); + } + } + break; + } + + case detail::value_t::object: + { + if (value.m_data.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_data.m_value.object) + { + flatten(detail::concat(reference_string, '/', detail::escape(element.first)), element.second, result); + } + } + break; + } + + case detail::value_t::null: + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + + @throw parse_error.109 if array index is not a number + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + @throw type_error.313 if value cannot be unflattened + */ + template<typename BasicJsonType> + static BasicJsonType + unflatten(const BasicJsonType& value) + { + if (JSON_HEDLEY_UNLIKELY(!value.is_object())) + { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", &value)); + } + + BasicJsonType result; + + // iterate the JSON object values + for (const auto& element : *value.m_data.m_value.object) + { + if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) + { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive", &element.second)); + } + + // assign value to reference pointed to by JSON pointer; Note that if + // the JSON pointer is "" (i.e., points to the whole value), function + // get_and_create returns a reference to result itself. An assignment + // will then create a primitive value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + // can't use conversion operator because of ambiguity + json_pointer<string_t> convert() const& + { + json_pointer<string_t> result; + result.reference_tokens = reference_tokens; + return result; + } + + json_pointer<string_t> convert()&& + { + json_pointer<string_t> result; + result.reference_tokens = std::move(reference_tokens); + return result; + } + + public: +#if JSON_HAS_THREE_WAY_COMPARISON + /// @brief compares two JSON pointers for equality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/ + template<typename RefStringTypeRhs> + bool operator==(const json_pointer<RefStringTypeRhs>& rhs) const noexcept + { + return reference_tokens == rhs.reference_tokens; + } + + /// @brief compares JSON pointer and string for equality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer)) + bool operator==(const string_t& rhs) const + { + return *this == json_pointer(rhs); + } + + /// @brief 3-way compares two JSON pointers + template<typename RefStringTypeRhs> + std::strong_ordering operator<=>(const json_pointer<RefStringTypeRhs>& rhs) const noexcept // *NOPAD* + { + return reference_tokens <=> rhs.reference_tokens; // *NOPAD* + } +#else + /// @brief compares two JSON pointers for equality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/ + template<typename RefStringTypeLhs, typename RefStringTypeRhs> + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator==(const json_pointer<RefStringTypeLhs>& lhs, + const json_pointer<RefStringTypeRhs>& rhs) noexcept; + + /// @brief compares JSON pointer and string for equality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/ + template<typename RefStringTypeLhs, typename StringType> + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator==(const json_pointer<RefStringTypeLhs>& lhs, + const StringType& rhs); + + /// @brief compares string and JSON pointer for equality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/ + template<typename RefStringTypeRhs, typename StringType> + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator==(const StringType& lhs, + const json_pointer<RefStringTypeRhs>& rhs); + + /// @brief compares two JSON pointers for inequality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/ + template<typename RefStringTypeLhs, typename RefStringTypeRhs> + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator!=(const json_pointer<RefStringTypeLhs>& lhs, + const json_pointer<RefStringTypeRhs>& rhs) noexcept; + + /// @brief compares JSON pointer and string for inequality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/ + template<typename RefStringTypeLhs, typename StringType> + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator!=(const json_pointer<RefStringTypeLhs>& lhs, + const StringType& rhs); + + /// @brief compares string and JSON pointer for inequality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/ + template<typename RefStringTypeRhs, typename StringType> + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator!=(const StringType& lhs, + const json_pointer<RefStringTypeRhs>& rhs); + + /// @brief compares two JSON pointer for less-than + template<typename RefStringTypeLhs, typename RefStringTypeRhs> + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator<(const json_pointer<RefStringTypeLhs>& lhs, + const json_pointer<RefStringTypeRhs>& rhs) noexcept; +#endif + + private: + /// the reference tokens + std::vector<string_t> reference_tokens; +}; + +#if !JSON_HAS_THREE_WAY_COMPARISON +// functions cannot be defined inside class due to ODR violations +template<typename RefStringTypeLhs, typename RefStringTypeRhs> +inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs, + const json_pointer<RefStringTypeRhs>& rhs) noexcept +{ + return lhs.reference_tokens == rhs.reference_tokens; +} + +template<typename RefStringTypeLhs, + typename StringType = typename json_pointer<RefStringTypeLhs>::string_t> +JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer)) +inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs, + const StringType& rhs) +{ + return lhs == json_pointer<RefStringTypeLhs>(rhs); +} + +template<typename RefStringTypeRhs, + typename StringType = typename json_pointer<RefStringTypeRhs>::string_t> +JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer)) +inline bool operator==(const StringType& lhs, + const json_pointer<RefStringTypeRhs>& rhs) +{ + return json_pointer<RefStringTypeRhs>(lhs) == rhs; +} + +template<typename RefStringTypeLhs, typename RefStringTypeRhs> +inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs, + const json_pointer<RefStringTypeRhs>& rhs) noexcept +{ + return !(lhs == rhs); +} + +template<typename RefStringTypeLhs, + typename StringType = typename json_pointer<RefStringTypeLhs>::string_t> +JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer)) +inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs, + const StringType& rhs) +{ + return !(lhs == rhs); +} + +template<typename RefStringTypeRhs, + typename StringType = typename json_pointer<RefStringTypeRhs>::string_t> +JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer)) +inline bool operator!=(const StringType& lhs, + const json_pointer<RefStringTypeRhs>& rhs) +{ + return !(lhs == rhs); +} + +template<typename RefStringTypeLhs, typename RefStringTypeRhs> +inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs, + const json_pointer<RefStringTypeRhs>& rhs) noexcept +{ + return lhs.reference_tokens < rhs.reference_tokens; +} +#endif + +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/json_ref.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <initializer_list> +#include <utility> + +// #include <nlohmann/detail/abi_macros.hpp> + +// #include <nlohmann/detail/meta/type_traits.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template<typename BasicJsonType> +class json_ref +{ + public: + using value_type = BasicJsonType; + + json_ref(value_type&& value) + : owned_value(std::move(value)) + {} + + json_ref(const value_type& value) + : value_ref(&value) + {} + + json_ref(std::initializer_list<json_ref> init) + : owned_value(init) + {} + + template < + class... Args, + enable_if_t<std::is_constructible<value_type, Args...>::value, int> = 0 > + json_ref(Args && ... args) + : owned_value(std::forward<Args>(args)...) + {} + + // class should be movable only + json_ref(json_ref&&) noexcept = default; + json_ref(const json_ref&) = delete; + json_ref& operator=(const json_ref&) = delete; + json_ref& operator=(json_ref&&) = delete; + ~json_ref() = default; + + value_type moved_or_copied() const + { + if (value_ref == nullptr) + { + return std::move(owned_value); + } + return *value_ref; + } + + value_type const& operator*() const + { + return value_ref ? *value_ref : owned_value; + } + + value_type const* operator->() const + { + return &** this; + } + + private: + mutable value_type owned_value = nullptr; + value_type const* value_ref = nullptr; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/string_concat.hpp> + +// #include <nlohmann/detail/string_escape.hpp> + +// #include <nlohmann/detail/meta/cpp_future.hpp> + +// #include <nlohmann/detail/meta/type_traits.hpp> + +// #include <nlohmann/detail/output/binary_writer.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <algorithm> // reverse +#include <array> // array +#include <map> // map +#include <cmath> // isnan, isinf +#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t +#include <cstring> // memcpy +#include <limits> // numeric_limits +#include <string> // string +#include <utility> // move +#include <vector> // vector + +// #include <nlohmann/detail/input/binary_reader.hpp> + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/output/output_adapters.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <algorithm> // copy +#include <cstddef> // size_t +#include <iterator> // back_inserter +#include <memory> // shared_ptr, make_shared +#include <string> // basic_string +#include <vector> // vector + +#ifndef JSON_NO_IO + #include <ios> // streamsize + #include <ostream> // basic_ostream +#endif // JSON_NO_IO + +// #include <nlohmann/detail/macro_scope.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// abstract output adapter interface +template<typename CharType> struct output_adapter_protocol +{ + virtual void write_character(CharType c) = 0; + virtual void write_characters(const CharType* s, std::size_t length) = 0; + virtual ~output_adapter_protocol() = default; + + output_adapter_protocol() = default; + output_adapter_protocol(const output_adapter_protocol&) = default; + output_adapter_protocol(output_adapter_protocol&&) noexcept = default; + output_adapter_protocol& operator=(const output_adapter_protocol&) = default; + output_adapter_protocol& operator=(output_adapter_protocol&&) noexcept = default; +}; + +/// a type to simplify interfaces +template<typename CharType> +using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>; + +/// output adapter for byte vectors +template<typename CharType, typename AllocatorType = std::allocator<CharType>> +class output_vector_adapter : public output_adapter_protocol<CharType> +{ + public: + explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept + : v(vec) + {} + + void write_character(CharType c) override + { + v.push_back(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + v.insert(v.end(), s, s + length); + } + + private: + std::vector<CharType, AllocatorType>& v; +}; + +#ifndef JSON_NO_IO +/// output adapter for output streams +template<typename CharType> +class output_stream_adapter : public output_adapter_protocol<CharType> +{ + public: + explicit output_stream_adapter(std::basic_ostream<CharType>& s) noexcept + : stream(s) + {} + + void write_character(CharType c) override + { + stream.put(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + stream.write(s, static_cast<std::streamsize>(length)); + } + + private: + std::basic_ostream<CharType>& stream; +}; +#endif // JSON_NO_IO + +/// output adapter for basic_string +template<typename CharType, typename StringType = std::basic_string<CharType>> +class output_string_adapter : public output_adapter_protocol<CharType> +{ + public: + explicit output_string_adapter(StringType& s) noexcept + : str(s) + {} + + void write_character(CharType c) override + { + str.push_back(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + str.append(s, length); + } + + private: + StringType& str; +}; + +template<typename CharType, typename StringType = std::basic_string<CharType>> +class output_adapter +{ + public: + template<typename AllocatorType = std::allocator<CharType>> + output_adapter(std::vector<CharType, AllocatorType>& vec) + : oa(std::make_shared<output_vector_adapter<CharType, AllocatorType>>(vec)) {} + +#ifndef JSON_NO_IO + output_adapter(std::basic_ostream<CharType>& s) + : oa(std::make_shared<output_stream_adapter<CharType>>(s)) {} +#endif // JSON_NO_IO + + output_adapter(StringType& s) + : oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {} + + operator output_adapter_t<CharType>() + { + return oa; + } + + private: + output_adapter_t<CharType> oa = nullptr; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/string_concat.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////// +// binary writer // +/////////////////// + +/*! +@brief serialization to CBOR and MessagePack values +*/ +template<typename BasicJsonType, typename CharType> +class binary_writer +{ + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using number_float_t = typename BasicJsonType::number_float_t; + + public: + /*! + @brief create a binary writer + + @param[in] adapter output adapter to write to + */ + explicit binary_writer(output_adapter_t<CharType> adapter) : oa(std::move(adapter)) + { + JSON_ASSERT(oa); + } + + /*! + @param[in] j JSON value to serialize + @pre j.type() == value_t::object + */ + void write_bson(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::object: + { + write_bson_object(*j.m_data.m_value.object); + break; + } + + case value_t::null: + case value_t::array: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + JSON_THROW(type_error::create(317, concat("to serialize to BSON, top-level type must be object, but is ", j.type_name()), &j)); + } + } + } + + /*! + @param[in] j JSON value to serialize + */ + void write_cbor(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(to_char_type(0xF6)); + break; + } + + case value_t::boolean: + { + oa->write_character(j.m_data.m_value.boolean + ? to_char_type(0xF5) + : to_char_type(0xF4)); + break; + } + + case value_t::number_integer: + { + if (j.m_data.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_data.m_value.number_integer <= 0x17) + { + write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)()) + { + oa->write_character(to_char_type(0x18)); + write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)()) + { + oa->write_character(to_char_type(0x19)); + write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)()) + { + oa->write_character(to_char_type(0x1A)); + write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer)); + } + else + { + oa->write_character(to_char_type(0x1B)); + write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer)); + } + } + else + { + // The conversions below encode the sign in the first + // byte, and the value is converted to a positive number. + const auto positive_number = -1 - j.m_data.m_value.number_integer; + if (j.m_data.m_value.number_integer >= -24) + { + write_number(static_cast<std::uint8_t>(0x20 + positive_number)); + } + else if (positive_number <= (std::numeric_limits<std::uint8_t>::max)()) + { + oa->write_character(to_char_type(0x38)); + write_number(static_cast<std::uint8_t>(positive_number)); + } + else if (positive_number <= (std::numeric_limits<std::uint16_t>::max)()) + { + oa->write_character(to_char_type(0x39)); + write_number(static_cast<std::uint16_t>(positive_number)); + } + else if (positive_number <= (std::numeric_limits<std::uint32_t>::max)()) + { + oa->write_character(to_char_type(0x3A)); + write_number(static_cast<std::uint32_t>(positive_number)); + } + else + { + oa->write_character(to_char_type(0x3B)); + write_number(static_cast<std::uint64_t>(positive_number)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_data.m_value.number_unsigned <= 0x17) + { + write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)()) + { + oa->write_character(to_char_type(0x18)); + write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)()) + { + oa->write_character(to_char_type(0x19)); + write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_unsigned)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)()) + { + oa->write_character(to_char_type(0x1A)); + write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_unsigned)); + } + else + { + oa->write_character(to_char_type(0x1B)); + write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned)); + } + break; + } + + case value_t::number_float: + { + if (std::isnan(j.m_data.m_value.number_float)) + { + // NaN is 0xf97e00 in CBOR + oa->write_character(to_char_type(0xF9)); + oa->write_character(to_char_type(0x7E)); + oa->write_character(to_char_type(0x00)); + } + else if (std::isinf(j.m_data.m_value.number_float)) + { + // Infinity is 0xf97c00, -Infinity is 0xf9fc00 + oa->write_character(to_char_type(0xf9)); + oa->write_character(j.m_data.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC)); + oa->write_character(to_char_type(0x00)); + } + else + { + write_compact_float(j.m_data.m_value.number_float, detail::input_format_t::cbor); + } + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_data.m_value.string->size(); + if (N <= 0x17) + { + write_number(static_cast<std::uint8_t>(0x60 + N)); + } + else if (N <= (std::numeric_limits<std::uint8_t>::max)()) + { + oa->write_character(to_char_type(0x78)); + write_number(static_cast<std::uint8_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint16_t>::max)()) + { + oa->write_character(to_char_type(0x79)); + write_number(static_cast<std::uint16_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint32_t>::max)()) + { + oa->write_character(to_char_type(0x7A)); + write_number(static_cast<std::uint32_t>(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits<std::uint64_t>::max)()) + { + oa->write_character(to_char_type(0x7B)); + write_number(static_cast<std::uint64_t>(N)); + } + // LCOV_EXCL_STOP + + // step 2: write the string + oa->write_characters( + reinterpret_cast<const CharType*>(j.m_data.m_value.string->c_str()), + j.m_data.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_data.m_value.array->size(); + if (N <= 0x17) + { + write_number(static_cast<std::uint8_t>(0x80 + N)); + } + else if (N <= (std::numeric_limits<std::uint8_t>::max)()) + { + oa->write_character(to_char_type(0x98)); + write_number(static_cast<std::uint8_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint16_t>::max)()) + { + oa->write_character(to_char_type(0x99)); + write_number(static_cast<std::uint16_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint32_t>::max)()) + { + oa->write_character(to_char_type(0x9A)); + write_number(static_cast<std::uint32_t>(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits<std::uint64_t>::max)()) + { + oa->write_character(to_char_type(0x9B)); + write_number(static_cast<std::uint64_t>(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_data.m_value.array) + { + write_cbor(el); + } + break; + } + + case value_t::binary: + { + if (j.m_data.m_value.binary->has_subtype()) + { + if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint8_t>::max)()) + { + write_number(static_cast<std::uint8_t>(0xd8)); + write_number(static_cast<std::uint8_t>(j.m_data.m_value.binary->subtype())); + } + else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint16_t>::max)()) + { + write_number(static_cast<std::uint8_t>(0xd9)); + write_number(static_cast<std::uint16_t>(j.m_data.m_value.binary->subtype())); + } + else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint32_t>::max)()) + { + write_number(static_cast<std::uint8_t>(0xda)); + write_number(static_cast<std::uint32_t>(j.m_data.m_value.binary->subtype())); + } + else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)()) + { + write_number(static_cast<std::uint8_t>(0xdb)); + write_number(static_cast<std::uint64_t>(j.m_data.m_value.binary->subtype())); + } + } + + // step 1: write control byte and the binary array size + const auto N = j.m_data.m_value.binary->size(); + if (N <= 0x17) + { + write_number(static_cast<std::uint8_t>(0x40 + N)); + } + else if (N <= (std::numeric_limits<std::uint8_t>::max)()) + { + oa->write_character(to_char_type(0x58)); + write_number(static_cast<std::uint8_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint16_t>::max)()) + { + oa->write_character(to_char_type(0x59)); + write_number(static_cast<std::uint16_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint32_t>::max)()) + { + oa->write_character(to_char_type(0x5A)); + write_number(static_cast<std::uint32_t>(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits<std::uint64_t>::max)()) + { + oa->write_character(to_char_type(0x5B)); + write_number(static_cast<std::uint64_t>(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + oa->write_characters( + reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()), + N); + + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_data.m_value.object->size(); + if (N <= 0x17) + { + write_number(static_cast<std::uint8_t>(0xA0 + N)); + } + else if (N <= (std::numeric_limits<std::uint8_t>::max)()) + { + oa->write_character(to_char_type(0xB8)); + write_number(static_cast<std::uint8_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint16_t>::max)()) + { + oa->write_character(to_char_type(0xB9)); + write_number(static_cast<std::uint16_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint32_t>::max)()) + { + oa->write_character(to_char_type(0xBA)); + write_number(static_cast<std::uint32_t>(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits<std::uint64_t>::max)()) + { + oa->write_character(to_char_type(0xBB)); + write_number(static_cast<std::uint64_t>(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_data.m_value.object) + { + write_cbor(el.first); + write_cbor(el.second); + } + break; + } + + case value_t::discarded: + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + */ + void write_msgpack(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: // nil + { + oa->write_character(to_char_type(0xC0)); + break; + } + + case value_t::boolean: // true and false + { + oa->write_character(j.m_data.m_value.boolean + ? to_char_type(0xC3) + : to_char_type(0xC2)); + break; + } + + case value_t::number_integer: + { + if (j.m_data.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_data.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)()) + { + // uint 8 + oa->write_character(to_char_type(0xCC)); + write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)()) + { + // uint 16 + oa->write_character(to_char_type(0xCD)); + write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)()) + { + // uint 32 + oa->write_character(to_char_type(0xCE)); + write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)()) + { + // uint 64 + oa->write_character(to_char_type(0xCF)); + write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer)); + } + } + else + { + if (j.m_data.m_value.number_integer >= -32) + { + // negative fixnum + write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() && + j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)()) + { + // int 8 + oa->write_character(to_char_type(0xD0)); + write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() && + j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)()) + { + // int 16 + oa->write_character(to_char_type(0xD1)); + write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() && + j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)()) + { + // int 32 + oa->write_character(to_char_type(0xD2)); + write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int64_t>::min)() && + j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)()) + { + // int 64 + oa->write_character(to_char_type(0xD3)); + write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_data.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)()) + { + // uint 8 + oa->write_character(to_char_type(0xCC)); + write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)()) + { + // uint 16 + oa->write_character(to_char_type(0xCD)); + write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)()) + { + // uint 32 + oa->write_character(to_char_type(0xCE)); + write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)()) + { + // uint 64 + oa->write_character(to_char_type(0xCF)); + write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer)); + } + break; + } + + case value_t::number_float: + { + write_compact_float(j.m_data.m_value.number_float, detail::input_format_t::msgpack); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_data.m_value.string->size(); + if (N <= 31) + { + // fixstr + write_number(static_cast<std::uint8_t>(0xA0 | N)); + } + else if (N <= (std::numeric_limits<std::uint8_t>::max)()) + { + // str 8 + oa->write_character(to_char_type(0xD9)); + write_number(static_cast<std::uint8_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint16_t>::max)()) + { + // str 16 + oa->write_character(to_char_type(0xDA)); + write_number(static_cast<std::uint16_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint32_t>::max)()) + { + // str 32 + oa->write_character(to_char_type(0xDB)); + write_number(static_cast<std::uint32_t>(N)); + } + + // step 2: write the string + oa->write_characters( + reinterpret_cast<const CharType*>(j.m_data.m_value.string->c_str()), + j.m_data.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_data.m_value.array->size(); + if (N <= 15) + { + // fixarray + write_number(static_cast<std::uint8_t>(0x90 | N)); + } + else if (N <= (std::numeric_limits<std::uint16_t>::max)()) + { + // array 16 + oa->write_character(to_char_type(0xDC)); + write_number(static_cast<std::uint16_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint32_t>::max)()) + { + // array 32 + oa->write_character(to_char_type(0xDD)); + write_number(static_cast<std::uint32_t>(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_data.m_value.array) + { + write_msgpack(el); + } + break; + } + + case value_t::binary: + { + // step 0: determine if the binary type has a set subtype to + // determine whether or not to use the ext or fixext types + const bool use_ext = j.m_data.m_value.binary->has_subtype(); + + // step 1: write control byte and the byte string length + const auto N = j.m_data.m_value.binary->size(); + if (N <= (std::numeric_limits<std::uint8_t>::max)()) + { + std::uint8_t output_type{}; + bool fixed = true; + if (use_ext) + { + switch (N) + { + case 1: + output_type = 0xD4; // fixext 1 + break; + case 2: + output_type = 0xD5; // fixext 2 + break; + case 4: + output_type = 0xD6; // fixext 4 + break; + case 8: + output_type = 0xD7; // fixext 8 + break; + case 16: + output_type = 0xD8; // fixext 16 + break; + default: + output_type = 0xC7; // ext 8 + fixed = false; + break; + } + + } + else + { + output_type = 0xC4; // bin 8 + fixed = false; + } + + oa->write_character(to_char_type(output_type)); + if (!fixed) + { + write_number(static_cast<std::uint8_t>(N)); + } + } + else if (N <= (std::numeric_limits<std::uint16_t>::max)()) + { + const std::uint8_t output_type = use_ext + ? 0xC8 // ext 16 + : 0xC5; // bin 16 + + oa->write_character(to_char_type(output_type)); + write_number(static_cast<std::uint16_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint32_t>::max)()) + { + const std::uint8_t output_type = use_ext + ? 0xC9 // ext 32 + : 0xC6; // bin 32 + + oa->write_character(to_char_type(output_type)); + write_number(static_cast<std::uint32_t>(N)); + } + + // step 1.5: if this is an ext type, write the subtype + if (use_ext) + { + write_number(static_cast<std::int8_t>(j.m_data.m_value.binary->subtype())); + } + + // step 2: write the byte string + oa->write_characters( + reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()), + N); + + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_data.m_value.object->size(); + if (N <= 15) + { + // fixmap + write_number(static_cast<std::uint8_t>(0x80 | (N & 0xF))); + } + else if (N <= (std::numeric_limits<std::uint16_t>::max)()) + { + // map 16 + oa->write_character(to_char_type(0xDE)); + write_number(static_cast<std::uint16_t>(N)); + } + else if (N <= (std::numeric_limits<std::uint32_t>::max)()) + { + // map 32 + oa->write_character(to_char_type(0xDF)); + write_number(static_cast<std::uint32_t>(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_data.m_value.object) + { + write_msgpack(el.first); + write_msgpack(el.second); + } + break; + } + + case value_t::discarded: + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + @param[in] use_count whether to use '#' prefixes (optimized format) + @param[in] use_type whether to use '$' prefixes (optimized format) + @param[in] add_prefix whether prefixes need to be used for this value + @param[in] use_bjdata whether write in BJData format, default is false + */ + void write_ubjson(const BasicJsonType& j, const bool use_count, + const bool use_type, const bool add_prefix = true, + const bool use_bjdata = false) + { + switch (j.type()) + { + case value_t::null: + { + if (add_prefix) + { + oa->write_character(to_char_type('Z')); + } + break; + } + + case value_t::boolean: + { + if (add_prefix) + { + oa->write_character(j.m_data.m_value.boolean + ? to_char_type('T') + : to_char_type('F')); + } + break; + } + + case value_t::number_integer: + { + write_number_with_ubjson_prefix(j.m_data.m_value.number_integer, add_prefix, use_bjdata); + break; + } + + case value_t::number_unsigned: + { + write_number_with_ubjson_prefix(j.m_data.m_value.number_unsigned, add_prefix, use_bjdata); + break; + } + + case value_t::number_float: + { + write_number_with_ubjson_prefix(j.m_data.m_value.number_float, add_prefix, use_bjdata); + break; + } + + case value_t::string: + { + if (add_prefix) + { + oa->write_character(to_char_type('S')); + } + write_number_with_ubjson_prefix(j.m_data.m_value.string->size(), true, use_bjdata); + oa->write_characters( + reinterpret_cast<const CharType*>(j.m_data.m_value.string->c_str()), + j.m_data.m_value.string->size()); + break; + } + + case value_t::array: + { + if (add_prefix) + { + oa->write_character(to_char_type('[')); + } + + bool prefix_required = true; + if (use_type && !j.m_data.m_value.array->empty()) + { + JSON_ASSERT(use_count); + const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); + const bool same_prefix = std::all_of(j.begin() + 1, j.end(), + [this, first_prefix, use_bjdata](const BasicJsonType & v) + { + return ubjson_prefix(v, use_bjdata) == first_prefix; + }); + + std::vector<CharType> bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type + + if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end())) + { + prefix_required = false; + oa->write_character(to_char_type('$')); + oa->write_character(first_prefix); + } + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_data.m_value.array->size(), true, use_bjdata); + } + + for (const auto& el : *j.m_data.m_value.array) + { + write_ubjson(el, use_count, use_type, prefix_required, use_bjdata); + } + + if (!use_count) + { + oa->write_character(to_char_type(']')); + } + + break; + } + + case value_t::binary: + { + if (add_prefix) + { + oa->write_character(to_char_type('[')); + } + + if (use_type && !j.m_data.m_value.binary->empty()) + { + JSON_ASSERT(use_count); + oa->write_character(to_char_type('$')); + oa->write_character('U'); + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_data.m_value.binary->size(), true, use_bjdata); + } + + if (use_type) + { + oa->write_characters( + reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()), + j.m_data.m_value.binary->size()); + } + else + { + for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i) + { + oa->write_character(to_char_type('U')); + oa->write_character(j.m_data.m_value.binary->data()[i]); + } + } + + if (!use_count) + { + oa->write_character(to_char_type(']')); + } + + break; + } + + case value_t::object: + { + if (use_bjdata && j.m_data.m_value.object->size() == 3 && j.m_data.m_value.object->find("_ArrayType_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArraySize_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArrayData_") != j.m_data.m_value.object->end()) + { + if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) + { + break; + } + } + + if (add_prefix) + { + oa->write_character(to_char_type('{')); + } + + bool prefix_required = true; + if (use_type && !j.m_data.m_value.object->empty()) + { + JSON_ASSERT(use_count); + const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); + const bool same_prefix = std::all_of(j.begin(), j.end(), + [this, first_prefix, use_bjdata](const BasicJsonType & v) + { + return ubjson_prefix(v, use_bjdata) == first_prefix; + }); + + std::vector<CharType> bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type + + if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end())) + { + prefix_required = false; + oa->write_character(to_char_type('$')); + oa->write_character(first_prefix); + } + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_data.m_value.object->size(), true, use_bjdata); + } + + for (const auto& el : *j.m_data.m_value.object) + { + write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata); + oa->write_characters( + reinterpret_cast<const CharType*>(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata); + } + + if (!use_count) + { + oa->write_character(to_char_type('}')); + } + + break; + } + + case value_t::discarded: + default: + break; + } + } + + private: + ////////// + // BSON // + ////////// + + /*! + @return The size of a BSON document entry header, including the id marker + and the entry name size (and its null-terminator). + */ + static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j) + { + const auto it = name.find(static_cast<typename string_t::value_type>(0)); + if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) + { + JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); + static_cast<void>(j); + } + + return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; + } + + /*! + @brief Writes the given @a element_type and @a name to the output adapter + */ + void write_bson_entry_header(const string_t& name, + const std::uint8_t element_type) + { + oa->write_character(to_char_type(element_type)); // boolean + oa->write_characters( + reinterpret_cast<const CharType*>(name.c_str()), + name.size() + 1u); + } + + /*! + @brief Writes a BSON element with key @a name and boolean value @a value + */ + void write_bson_boolean(const string_t& name, + const bool value) + { + write_bson_entry_header(name, 0x08); + oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00)); + } + + /*! + @brief Writes a BSON element with key @a name and double value @a value + */ + void write_bson_double(const string_t& name, + const double value) + { + write_bson_entry_header(name, 0x01); + write_number<double>(value, true); + } + + /*! + @return The size of the BSON-encoded string in @a value + */ + static std::size_t calc_bson_string_size(const string_t& value) + { + return sizeof(std::int32_t) + value.size() + 1ul; + } + + /*! + @brief Writes a BSON element with key @a name and string value @a value + */ + void write_bson_string(const string_t& name, + const string_t& value) + { + write_bson_entry_header(name, 0x02); + + write_number<std::int32_t>(static_cast<std::int32_t>(value.size() + 1ul), true); + oa->write_characters( + reinterpret_cast<const CharType*>(value.c_str()), + value.size() + 1); + } + + /*! + @brief Writes a BSON element with key @a name and null value + */ + void write_bson_null(const string_t& name) + { + write_bson_entry_header(name, 0x0A); + } + + /*! + @return The size of the BSON-encoded integer @a value + */ + static std::size_t calc_bson_integer_size(const std::int64_t value) + { + return (std::numeric_limits<std::int32_t>::min)() <= value && value <= (std::numeric_limits<std::int32_t>::max)() + ? sizeof(std::int32_t) + : sizeof(std::int64_t); + } + + /*! + @brief Writes a BSON element with key @a name and integer @a value + */ + void write_bson_integer(const string_t& name, + const std::int64_t value) + { + if ((std::numeric_limits<std::int32_t>::min)() <= value && value <= (std::numeric_limits<std::int32_t>::max)()) + { + write_bson_entry_header(name, 0x10); // int32 + write_number<std::int32_t>(static_cast<std::int32_t>(value), true); + } + else + { + write_bson_entry_header(name, 0x12); // int64 + write_number<std::int64_t>(static_cast<std::int64_t>(value), true); + } + } + + /*! + @return The size of the BSON-encoded unsigned integer in @a j + */ + static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept + { + return (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)())) + ? sizeof(std::int32_t) + : sizeof(std::int64_t); + } + + /*! + @brief Writes a BSON element with key @a name and unsigned @a value + */ + void write_bson_unsigned(const string_t& name, + const BasicJsonType& j) + { + if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)())) + { + write_bson_entry_header(name, 0x10 /* int32 */); + write_number<std::int32_t>(static_cast<std::int32_t>(j.m_data.m_value.number_unsigned), true); + } + else if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)())) + { + write_bson_entry_header(name, 0x12 /* int64 */); + write_number<std::int64_t>(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned), true); + } + else + { + JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BSON as it does not fit int64"), &j)); + } + } + + /*! + @brief Writes a BSON element with key @a name and object @a value + */ + void write_bson_object_entry(const string_t& name, + const typename BasicJsonType::object_t& value) + { + write_bson_entry_header(name, 0x03); // object + write_bson_object(value); + } + + /*! + @return The size of the BSON-encoded array @a value + */ + static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value) + { + std::size_t array_index = 0ul; + + const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), static_cast<std::size_t>(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el) + { + return result + calc_bson_element_size(std::to_string(array_index++), el); + }); + + return sizeof(std::int32_t) + embedded_document_size + 1ul; + } + + /*! + @return The size of the BSON-encoded binary array @a value + */ + static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t& value) + { + return sizeof(std::int32_t) + value.size() + 1ul; + } + + /*! + @brief Writes a BSON element with key @a name and array @a value + */ + void write_bson_array(const string_t& name, + const typename BasicJsonType::array_t& value) + { + write_bson_entry_header(name, 0x04); // array + write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_array_size(value)), true); + + std::size_t array_index = 0ul; + + for (const auto& el : value) + { + write_bson_element(std::to_string(array_index++), el); + } + + oa->write_character(to_char_type(0x00)); + } + + /*! + @brief Writes a BSON element with key @a name and binary value @a value + */ + void write_bson_binary(const string_t& name, + const binary_t& value) + { + write_bson_entry_header(name, 0x05); + + write_number<std::int32_t>(static_cast<std::int32_t>(value.size()), true); + write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00)); + + oa->write_characters(reinterpret_cast<const CharType*>(value.data()), value.size()); + } + + /*! + @brief Calculates the size necessary to serialize the JSON value @a j with its @a name + @return The calculated size for the BSON document entry for @a j with the given @a name. + */ + static std::size_t calc_bson_element_size(const string_t& name, + const BasicJsonType& j) + { + const auto header_size = calc_bson_entry_header_size(name, j); + switch (j.type()) + { + case value_t::object: + return header_size + calc_bson_object_size(*j.m_data.m_value.object); + + case value_t::array: + return header_size + calc_bson_array_size(*j.m_data.m_value.array); + + case value_t::binary: + return header_size + calc_bson_binary_size(*j.m_data.m_value.binary); + + case value_t::boolean: + return header_size + 1ul; + + case value_t::number_float: + return header_size + 8ul; + + case value_t::number_integer: + return header_size + calc_bson_integer_size(j.m_data.m_value.number_integer); + + case value_t::number_unsigned: + return header_size + calc_bson_unsigned_size(j.m_data.m_value.number_unsigned); + + case value_t::string: + return header_size + calc_bson_string_size(*j.m_data.m_value.string); + + case value_t::null: + return header_size + 0ul; + + // LCOV_EXCL_START + case value_t::discarded: + default: + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) + return 0ul; + // LCOV_EXCL_STOP + } + } + + /*! + @brief Serializes the JSON value @a j to BSON and associates it with the + key @a name. + @param name The name to associate with the JSON entity @a j within the + current BSON document + */ + void write_bson_element(const string_t& name, + const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::object: + return write_bson_object_entry(name, *j.m_data.m_value.object); + + case value_t::array: + return write_bson_array(name, *j.m_data.m_value.array); + + case value_t::binary: + return write_bson_binary(name, *j.m_data.m_value.binary); + + case value_t::boolean: + return write_bson_boolean(name, j.m_data.m_value.boolean); + + case value_t::number_float: + return write_bson_double(name, j.m_data.m_value.number_float); + + case value_t::number_integer: + return write_bson_integer(name, j.m_data.m_value.number_integer); + + case value_t::number_unsigned: + return write_bson_unsigned(name, j); + + case value_t::string: + return write_bson_string(name, *j.m_data.m_value.string); + + case value_t::null: + return write_bson_null(name); + + // LCOV_EXCL_START + case value_t::discarded: + default: + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) + return; + // LCOV_EXCL_STOP + } + } + + /*! + @brief Calculates the size of the BSON serialization of the given + JSON-object @a j. + @param[in] value JSON value to serialize + @pre value.type() == value_t::object + */ + static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value) + { + const std::size_t document_size = std::accumulate(value.begin(), value.end(), static_cast<std::size_t>(0), + [](size_t result, const typename BasicJsonType::object_t::value_type & el) + { + return result += calc_bson_element_size(el.first, el.second); + }); + + return sizeof(std::int32_t) + document_size + 1ul; + } + + /*! + @param[in] value JSON value to serialize + @pre value.type() == value_t::object + */ + void write_bson_object(const typename BasicJsonType::object_t& value) + { + write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_object_size(value)), true); + + for (const auto& el : value) + { + write_bson_element(el.first, el.second); + } + + oa->write_character(to_char_type(0x00)); + } + + ////////// + // CBOR // + ////////// + + static constexpr CharType get_cbor_float_prefix(float /*unused*/) + { + return to_char_type(0xFA); // Single-Precision Float + } + + static constexpr CharType get_cbor_float_prefix(double /*unused*/) + { + return to_char_type(0xFB); // Double-Precision Float + } + + ///////////// + // MsgPack // + ///////////// + + static constexpr CharType get_msgpack_float_prefix(float /*unused*/) + { + return to_char_type(0xCA); // float 32 + } + + static constexpr CharType get_msgpack_float_prefix(double /*unused*/) + { + return to_char_type(0xCB); // float 64 + } + + //////////// + // UBJSON // + //////////// + + // UBJSON: write number (floating point) + template<typename NumberType, typename std::enable_if< + std::is_floating_point<NumberType>::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix, + const bool use_bjdata) + { + if (add_prefix) + { + oa->write_character(get_ubjson_float_prefix(n)); + } + write_number(n, use_bjdata); + } + + // UBJSON: write number (unsigned integer) + template<typename NumberType, typename std::enable_if< + std::is_unsigned<NumberType>::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix, + const bool use_bjdata) + { + if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('i')); // int8 + } + write_number(static_cast<std::uint8_t>(n), use_bjdata); + } + else if (n <= (std::numeric_limits<std::uint8_t>::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('U')); // uint8 + } + write_number(static_cast<std::uint8_t>(n), use_bjdata); + } + else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int16_t>::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('I')); // int16 + } + write_number(static_cast<std::int16_t>(n), use_bjdata); + } + else if (use_bjdata && n <= static_cast<uint64_t>((std::numeric_limits<uint16_t>::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('u')); // uint16 - bjdata only + } + write_number(static_cast<std::uint16_t>(n), use_bjdata); + } + else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('l')); // int32 + } + write_number(static_cast<std::int32_t>(n), use_bjdata); + } + else if (use_bjdata && n <= static_cast<uint64_t>((std::numeric_limits<uint32_t>::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('m')); // uint32 - bjdata only + } + write_number(static_cast<std::uint32_t>(n), use_bjdata); + } + else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('L')); // int64 + } + write_number(static_cast<std::int64_t>(n), use_bjdata); + } + else if (use_bjdata && n <= (std::numeric_limits<uint64_t>::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('M')); // uint64 - bjdata only + } + write_number(static_cast<std::uint64_t>(n), use_bjdata); + } + else + { + if (add_prefix) + { + oa->write_character(to_char_type('H')); // high-precision number + } + + const auto number = BasicJsonType(n).dump(); + write_number_with_ubjson_prefix(number.size(), true, use_bjdata); + for (std::size_t i = 0; i < number.size(); ++i) + { + oa->write_character(to_char_type(static_cast<std::uint8_t>(number[i]))); + } + } + } + + // UBJSON: write number (signed integer) + template < typename NumberType, typename std::enable_if < + std::is_signed<NumberType>::value&& + !std::is_floating_point<NumberType>::value, int >::type = 0 > + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix, + const bool use_bjdata) + { + if ((std::numeric_limits<std::int8_t>::min)() <= n && n <= (std::numeric_limits<std::int8_t>::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('i')); // int8 + } + write_number(static_cast<std::int8_t>(n), use_bjdata); + } + else if (static_cast<std::int64_t>((std::numeric_limits<std::uint8_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint8_t>::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('U')); // uint8 + } + write_number(static_cast<std::uint8_t>(n), use_bjdata); + } + else if ((std::numeric_limits<std::int16_t>::min)() <= n && n <= (std::numeric_limits<std::int16_t>::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('I')); // int16 + } + write_number(static_cast<std::int16_t>(n), use_bjdata); + } + else if (use_bjdata && (static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::max)()))) + { + if (add_prefix) + { + oa->write_character(to_char_type('u')); // uint16 - bjdata only + } + write_number(static_cast<uint16_t>(n), use_bjdata); + } + else if ((std::numeric_limits<std::int32_t>::min)() <= n && n <= (std::numeric_limits<std::int32_t>::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('l')); // int32 + } + write_number(static_cast<std::int32_t>(n), use_bjdata); + } + else if (use_bjdata && (static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::max)()))) + { + if (add_prefix) + { + oa->write_character(to_char_type('m')); // uint32 - bjdata only + } + write_number(static_cast<uint32_t>(n), use_bjdata); + } + else if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('L')); // int64 + } + write_number(static_cast<std::int64_t>(n), use_bjdata); + } + // LCOV_EXCL_START + else + { + if (add_prefix) + { + oa->write_character(to_char_type('H')); // high-precision number + } + + const auto number = BasicJsonType(n).dump(); + write_number_with_ubjson_prefix(number.size(), true, use_bjdata); + for (std::size_t i = 0; i < number.size(); ++i) + { + oa->write_character(to_char_type(static_cast<std::uint8_t>(number[i]))); + } + } + // LCOV_EXCL_STOP + } + + /*! + @brief determine the type prefix of container values + */ + CharType ubjson_prefix(const BasicJsonType& j, const bool use_bjdata) const noexcept + { + switch (j.type()) + { + case value_t::null: + return 'Z'; + + case value_t::boolean: + return j.m_data.m_value.boolean ? 'T' : 'F'; + + case value_t::number_integer: + { + if ((std::numeric_limits<std::int8_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)()) + { + return 'i'; + } + if ((std::numeric_limits<std::uint8_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)()) + { + return 'U'; + } + if ((std::numeric_limits<std::int16_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)()) + { + return 'I'; + } + if (use_bjdata && ((std::numeric_limits<std::uint16_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)())) + { + return 'u'; + } + if ((std::numeric_limits<std::int32_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)()) + { + return 'l'; + } + if (use_bjdata && ((std::numeric_limits<std::uint32_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)())) + { + return 'm'; + } + if ((std::numeric_limits<std::int64_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)()) + { + return 'L'; + } + // anything else is treated as high-precision number + return 'H'; // LCOV_EXCL_LINE + } + + case value_t::number_unsigned: + { + if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)())) + { + return 'i'; + } + if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint8_t>::max)())) + { + return 'U'; + } + if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int16_t>::max)())) + { + return 'I'; + } + if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint16_t>::max)())) + { + return 'u'; + } + if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)())) + { + return 'l'; + } + if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint32_t>::max)())) + { + return 'm'; + } + if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)())) + { + return 'L'; + } + if (use_bjdata && j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)()) + { + return 'M'; + } + // anything else is treated as high-precision number + return 'H'; // LCOV_EXCL_LINE + } + + case value_t::number_float: + return get_ubjson_float_prefix(j.m_data.m_value.number_float); + + case value_t::string: + return 'S'; + + case value_t::array: // fallthrough + case value_t::binary: + return '['; + + case value_t::object: + return '{'; + + case value_t::discarded: + default: // discarded values + return 'N'; + } + } + + static constexpr CharType get_ubjson_float_prefix(float /*unused*/) + { + return 'd'; // float 32 + } + + static constexpr CharType get_ubjson_float_prefix(double /*unused*/) + { + return 'D'; // float 64 + } + + /*! + @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid + */ + bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type) + { + std::map<string_t, CharType> bjdtype = {{"uint8", 'U'}, {"int8", 'i'}, {"uint16", 'u'}, {"int16", 'I'}, + {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, {"char", 'C'} + }; + + string_t key = "_ArrayType_"; + auto it = bjdtype.find(static_cast<string_t>(value.at(key))); + if (it == bjdtype.end()) + { + return true; + } + CharType dtype = it->second; + + key = "_ArraySize_"; + std::size_t len = (value.at(key).empty() ? 0 : 1); + for (const auto& el : value.at(key)) + { + len *= static_cast<std::size_t>(el.m_data.m_value.number_unsigned); + } + + key = "_ArrayData_"; + if (value.at(key).size() != len) + { + return true; + } + + oa->write_character('['); + oa->write_character('$'); + oa->write_character(dtype); + oa->write_character('#'); + + key = "_ArraySize_"; + write_ubjson(value.at(key), use_count, use_type, true, true); + + key = "_ArrayData_"; + if (dtype == 'U' || dtype == 'C') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast<std::uint8_t>(el.m_data.m_value.number_unsigned), true); + } + } + else if (dtype == 'i') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast<std::int8_t>(el.m_data.m_value.number_integer), true); + } + } + else if (dtype == 'u') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast<std::uint16_t>(el.m_data.m_value.number_unsigned), true); + } + } + else if (dtype == 'I') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast<std::int16_t>(el.m_data.m_value.number_integer), true); + } + } + else if (dtype == 'm') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast<std::uint32_t>(el.m_data.m_value.number_unsigned), true); + } + } + else if (dtype == 'l') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast<std::int32_t>(el.m_data.m_value.number_integer), true); + } + } + else if (dtype == 'M') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast<std::uint64_t>(el.m_data.m_value.number_unsigned), true); + } + } + else if (dtype == 'L') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast<std::int64_t>(el.m_data.m_value.number_integer), true); + } + } + else if (dtype == 'd') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast<float>(el.m_data.m_value.number_float), true); + } + } + else if (dtype == 'D') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast<double>(el.m_data.m_value.number_float), true); + } + } + return false; + } + + /////////////////////// + // Utility functions // + /////////////////////// + + /* + @brief write a number to output input + @param[in] n number of type @a NumberType + @param[in] OutputIsLittleEndian Set to true if output data is + required to be little endian + @tparam NumberType the type of the number + + @note This function needs to respect the system's endianness, because bytes + in CBOR, MessagePack, and UBJSON are stored in network order (big + endian) and therefore need reordering on little endian systems. + On the other hand, BSON and BJData use little endian and should reorder + on big endian systems. + */ + template<typename NumberType> + void write_number(const NumberType n, const bool OutputIsLittleEndian = false) + { + // step 1: write number to array of length NumberType + std::array<CharType, sizeof(NumberType)> vec{}; + std::memcpy(vec.data(), &n, sizeof(NumberType)); + + // step 2: write array to output (with possible reordering) + if (is_little_endian != OutputIsLittleEndian) + { + // reverse byte order prior to conversion if necessary + std::reverse(vec.begin(), vec.end()); + } + + oa->write_characters(vec.data(), sizeof(NumberType)); + } + + void write_compact_float(const number_float_t n, detail::input_format_t format) + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + if (static_cast<double>(n) >= static_cast<double>(std::numeric_limits<float>::lowest()) && + static_cast<double>(n) <= static_cast<double>((std::numeric_limits<float>::max)()) && + static_cast<double>(static_cast<float>(n)) == static_cast<double>(n)) + { + oa->write_character(format == detail::input_format_t::cbor + ? get_cbor_float_prefix(static_cast<float>(n)) + : get_msgpack_float_prefix(static_cast<float>(n))); + write_number(static_cast<float>(n)); + } + else + { + oa->write_character(format == detail::input_format_t::cbor + ? get_cbor_float_prefix(n) + : get_msgpack_float_prefix(n)); + write_number(n); + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + + public: + // The following to_char_type functions are implement the conversion + // between uint8_t and CharType. In case CharType is not unsigned, + // such a conversion is required to allow values greater than 128. + // See <https://github.com/nlohmann/json/issues/1286> for a discussion. + template < typename C = CharType, + enable_if_t < std::is_signed<C>::value && std::is_signed<char>::value > * = nullptr > + static constexpr CharType to_char_type(std::uint8_t x) noexcept + { + return *reinterpret_cast<char*>(&x); + } + + template < typename C = CharType, + enable_if_t < std::is_signed<C>::value && std::is_unsigned<char>::value > * = nullptr > + static CharType to_char_type(std::uint8_t x) noexcept + { + static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); + static_assert(std::is_trivial<CharType>::value, "CharType must be trivial"); + CharType result; + std::memcpy(&result, &x, sizeof(x)); + return result; + } + + template<typename C = CharType, + enable_if_t<std::is_unsigned<C>::value>* = nullptr> + static constexpr CharType to_char_type(std::uint8_t x) noexcept + { + return x; + } + + template < typename InputCharType, typename C = CharType, + enable_if_t < + std::is_signed<C>::value && + std::is_signed<char>::value && + std::is_same<char, typename std::remove_cv<InputCharType>::type>::value + > * = nullptr > + static constexpr CharType to_char_type(InputCharType x) noexcept + { + return x; + } + + private: + /// whether we can assume little endianness + const bool is_little_endian = little_endianness(); + + /// the output + output_adapter_t<CharType> oa = nullptr; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/output/output_adapters.hpp> + +// #include <nlohmann/detail/output/serializer.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2008-2009 Björn Hoehrmann <bjoern@hoehrmann.de> +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <algorithm> // reverse, remove, fill, find, none_of +#include <array> // array +#include <clocale> // localeconv, lconv +#include <cmath> // labs, isfinite, isnan, signbit +#include <cstddef> // size_t, ptrdiff_t +#include <cstdint> // uint8_t +#include <cstdio> // snprintf +#include <limits> // numeric_limits +#include <string> // string, char_traits +#include <iomanip> // setfill, setw +#include <type_traits> // is_same +#include <utility> // move + +// #include <nlohmann/detail/conversions/to_chars.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2009 Florian Loitsch <https://florian.loitsch.com/> +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <array> // array +#include <cmath> // signbit, isfinite +#include <cstdint> // intN_t, uintN_t +#include <cstring> // memcpy, memmove +#include <limits> // numeric_limits +#include <type_traits> // conditional + +// #include <nlohmann/detail/macro_scope.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief implements the Grisu2 algorithm for binary to decimal floating-point +conversion. + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). + +The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. + +For a detailed description of the algorithm see: + +[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with + Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming + Language Design and Implementation, PLDI 2010 +[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", + Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language + Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl +{ + +template<typename Target, typename Source> +Target reinterpret_bits(const Source source) +{ + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + std::uint64_t f = 0; + int e = 0; + + constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp& x, const diyfp& y) noexcept + { + JSON_ASSERT(x.e == y.e); + JSON_ASSERT(x.f >= y.f); + + return {x.f - y.f, x.e}; + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp& x, const diyfp& y) noexcept + { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) + // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) + // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) + // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) + // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) + // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const std::uint64_t u_lo = x.f & 0xFFFFFFFFu; + const std::uint64_t u_hi = x.f >> 32u; + const std::uint64_t v_lo = y.f & 0xFFFFFFFFu; + const std::uint64_t v_hi = y.f >> 32u; + + const std::uint64_t p0 = u_lo * v_lo; + const std::uint64_t p1 = u_lo * v_hi; + const std::uint64_t p2 = u_hi * v_lo; + const std::uint64_t p3 = u_hi * v_hi; + + const std::uint64_t p0_hi = p0 >> 32u; + const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu; + const std::uint64_t p1_hi = p1 >> 32u; + const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu; + const std::uint64_t p2_hi = p2 >> 32u; + + std::uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up + + const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u); + + return {h, x.e + y.e + 64}; + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept + { + JSON_ASSERT(x.f != 0); + + while ((x.f >> 63u) == 0) + { + x.f <<= 1u; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept + { + const int delta = x.e - target_exponent; + + JSON_ASSERT(delta >= 0); + JSON_ASSERT(((x.f << delta) >> delta) == x.f); + + return {x.f << delta, target_exponent}; + } +}; + +struct boundaries +{ + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. + +@pre value must be finite and positive +*/ +template<typename FloatType> +boundaries compute_boundaries(FloatType value) +{ + JSON_ASSERT(std::isfinite(value)); + JSON_ASSERT(value > 0); + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits<FloatType>::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits<FloatType>::digits; // = p (includes the hidden bit) + constexpr int kBias = std::numeric_limits<FloatType>::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr std::uint64_t kHiddenBit = std::uint64_t{1} << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type; + + const auto bits = static_cast<std::uint64_t>(reinterpret_bits<bits_type>(value)); + const std::uint64_t E = bits >> (kPrecision - 1); + const std::uint64_t F = bits & (kHiddenBit - 1); + + const bool is_denormal = E == 0; + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F + kHiddenBit, static_cast<int>(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor if v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = F == 0 && E > 1; + const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f - 1, v.e - 2) // (B) + : diyfp(2 * v.f - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + std::uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) +{ + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exact power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr std::array<cached_power, 79> kCachedPowers = + { + { + { 0xAB70FE17C79AC6CA, -1060, -300 }, + { 0xFF77B1FCBEBCDC4F, -1034, -292 }, + { 0xBE5691EF416BD60C, -1007, -284 }, + { 0x8DD01FAD907FFC3C, -980, -276 }, + { 0xD3515C2831559A83, -954, -268 }, + { 0x9D71AC8FADA6C9B5, -927, -260 }, + { 0xEA9C227723EE8BCB, -901, -252 }, + { 0xAECC49914078536D, -874, -244 }, + { 0x823C12795DB6CE57, -847, -236 }, + { 0xC21094364DFB5637, -821, -228 }, + { 0x9096EA6F3848984F, -794, -220 }, + { 0xD77485CB25823AC7, -768, -212 }, + { 0xA086CFCD97BF97F4, -741, -204 }, + { 0xEF340A98172AACE5, -715, -196 }, + { 0xB23867FB2A35B28E, -688, -188 }, + { 0x84C8D4DFD2C63F3B, -661, -180 }, + { 0xC5DD44271AD3CDBA, -635, -172 }, + { 0x936B9FCEBB25C996, -608, -164 }, + { 0xDBAC6C247D62A584, -582, -156 }, + { 0xA3AB66580D5FDAF6, -555, -148 }, + { 0xF3E2F893DEC3F126, -529, -140 }, + { 0xB5B5ADA8AAFF80B8, -502, -132 }, + { 0x87625F056C7C4A8B, -475, -124 }, + { 0xC9BCFF6034C13053, -449, -116 }, + { 0x964E858C91BA2655, -422, -108 }, + { 0xDFF9772470297EBD, -396, -100 }, + { 0xA6DFBD9FB8E5B88F, -369, -92 }, + { 0xF8A95FCF88747D94, -343, -84 }, + { 0xB94470938FA89BCF, -316, -76 }, + { 0x8A08F0F8BF0F156B, -289, -68 }, + { 0xCDB02555653131B6, -263, -60 }, + { 0x993FE2C6D07B7FAC, -236, -52 }, + { 0xE45C10C42A2B3B06, -210, -44 }, + { 0xAA242499697392D3, -183, -36 }, + { 0xFD87B5F28300CA0E, -157, -28 }, + { 0xBCE5086492111AEB, -130, -20 }, + { 0x8CBCCC096F5088CC, -103, -12 }, + { 0xD1B71758E219652C, -77, -4 }, + { 0x9C40000000000000, -50, 4 }, + { 0xE8D4A51000000000, -24, 12 }, + { 0xAD78EBC5AC620000, 3, 20 }, + { 0x813F3978F8940984, 30, 28 }, + { 0xC097CE7BC90715B3, 56, 36 }, + { 0x8F7E32CE7BEA5C70, 83, 44 }, + { 0xD5D238A4ABE98068, 109, 52 }, + { 0x9F4F2726179A2245, 136, 60 }, + { 0xED63A231D4C4FB27, 162, 68 }, + { 0xB0DE65388CC8ADA8, 189, 76 }, + { 0x83C7088E1AAB65DB, 216, 84 }, + { 0xC45D1DF942711D9A, 242, 92 }, + { 0x924D692CA61BE758, 269, 100 }, + { 0xDA01EE641A708DEA, 295, 108 }, + { 0xA26DA3999AEF774A, 322, 116 }, + { 0xF209787BB47D6B85, 348, 124 }, + { 0xB454E4A179DD1877, 375, 132 }, + { 0x865B86925B9BC5C2, 402, 140 }, + { 0xC83553C5C8965D3D, 428, 148 }, + { 0x952AB45CFA97A0B3, 455, 156 }, + { 0xDE469FBD99A05FE3, 481, 164 }, + { 0xA59BC234DB398C25, 508, 172 }, + { 0xF6C69A72A3989F5C, 534, 180 }, + { 0xB7DCBF5354E9BECE, 561, 188 }, + { 0x88FCF317F22241E2, 588, 196 }, + { 0xCC20CE9BD35C78A5, 614, 204 }, + { 0x98165AF37B2153DF, 641, 212 }, + { 0xE2A0B5DC971F303A, 667, 220 }, + { 0xA8D9D1535CE3B396, 694, 228 }, + { 0xFB9B7CD9A4A7443C, 720, 236 }, + { 0xBB764C4CA7A44410, 747, 244 }, + { 0x8BAB8EEFB6409C1A, 774, 252 }, + { 0xD01FEF10A657842C, 800, 260 }, + { 0x9B10A4E5E9913129, 827, 268 }, + { 0xE7109BFBA19C0C9D, 853, 276 }, + { 0xAC2820D9623BF429, 880, 284 }, + { 0x80444B5E7AA7CF85, 907, 292 }, + { 0xBF21E44003ACDD2D, 933, 300 }, + { 0x8E679C2F5E44FF8F, 960, 308 }, + { 0xD433179D9C8CB841, 986, 316 }, + { 0x9E19DB92B4E31BA9, 1013, 324 }, + } + }; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + JSON_ASSERT(e >= -1500); + JSON_ASSERT(e <= 1500); + const int f = kAlpha - e - 1; + const int k = (f * 78913) / (1 << 18) + static_cast<int>(f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; + JSON_ASSERT(index >= 0); + JSON_ASSERT(static_cast<std::size_t>(index) < kCachedPowers.size()); + + const cached_power cached = kCachedPowers[static_cast<std::size_t>(index)]; + JSON_ASSERT(kAlpha <= cached.e + e + 64); + JSON_ASSERT(kGamma >= cached.e + e + 64); + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10) +{ + // LCOV_EXCL_START + if (n >= 1000000000) + { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + if (n >= 100000000) + { + pow10 = 100000000; + return 9; + } + if (n >= 10000000) + { + pow10 = 10000000; + return 8; + } + if (n >= 1000000) + { + pow10 = 1000000; + return 7; + } + if (n >= 100000) + { + pow10 = 100000; + return 6; + } + if (n >= 10000) + { + pow10 = 10000; + return 5; + } + if (n >= 1000) + { + pow10 = 1000; + return 4; + } + if (n >= 100) + { + pow10 = 100; + return 3; + } + if (n >= 10) + { + pow10 = 10; + return 2; + } + + pow10 = 1; + return 1; +} + +inline void grisu2_round(char* buf, int len, std::uint64_t dist, std::uint64_t delta, + std::uint64_t rest, std::uint64_t ten_k) +{ + JSON_ASSERT(len >= 1); + JSON_ASSERT(dist <= delta); + JSON_ASSERT(rest <= delta); + JSON_ASSERT(ten_k > 0); + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist + && delta - rest >= ten_k + && (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) + { + JSON_ASSERT(buf[len - 1] != '0'); + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -32. +*/ +inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) +{ + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + JSON_ASSERT(M_plus.e >= kAlpha); + JSON_ASSERT(M_plus.e <= kGamma); + + std::uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) + std::uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e); + + auto p1 = static_cast<std::uint32_t>(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + JSON_ASSERT(p1 > 0); + + std::uint32_t pow10{}; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) + { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + JSON_ASSERT(d <= 9); + buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2; + if (rest <= delta) + { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead look if the buffer could be + // decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + JSON_ASSERT(p2 > delta); + + int m = 0; + for (;;) + { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e + // + JSON_ASSERT(p2 <= (std::numeric_limits<std::uint64_t>::max)() / 10); + p2 *= 10; + const std::uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + JSON_ASSERT(d <= 9); + buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) + { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const std::uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +JSON_HEDLEY_NON_NULL(1) +inline void grisu2(char* buf, int& len, int& decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) +{ + JSON_ASSERT(m_plus.e == m_minus.e); + JSON_ASSERT(m_plus.e == v.e); + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + const diyfp M_minus(w_minus.f + 1, w_minus.e); + const diyfp M_plus (w_plus.f - 1, w_plus.e ); + + decimal_exponent = -cached.k; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template<typename FloatType> +JSON_HEDLEY_NON_NULL(1) +void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) +{ + static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits + 3, + "internal error: not enough precision"); + + JSON_ASSERT(std::isfinite(value)); + JSON_ASSERT(value > 0); + + // If the neighbors (and boundaries) of 'value' are always computed for double-precision + // numbers, all float's can be recovered using strtod (and strtof). However, the resulting + // decimal representations are not exactly "short". + // + // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars) + // says "value is converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes floats to doubles, I think this is exactly what 'std::to_chars' + // does. + // On the other hand, the documentation for 'std::to_chars' requires that "parsing the + // representation using the corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered using + // 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision + // value is off by 1 ulp. +#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if) + const boundaries w = compute_boundaries(static_cast<double>(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +JSON_HEDLEY_NON_NULL(1) +JSON_HEDLEY_RETURNS_NON_NULL +inline char* append_exponent(char* buf, int e) +{ + JSON_ASSERT(e > -1000); + JSON_ASSERT(e < 1000); + + if (e < 0) + { + e = -e; + *buf++ = '-'; + } + else + { + *buf++ = '+'; + } + + auto k = static_cast<std::uint32_t>(e); + if (k < 10) + { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast<char>('0' + k); + } + else if (k < 100) + { + *buf++ = static_cast<char>('0' + k / 10); + k %= 10; + *buf++ = static_cast<char>('0' + k); + } + else + { + *buf++ = static_cast<char>('0' + k / 100); + k %= 100; + *buf++ = static_cast<char>('0' + k / 10); + k %= 10; + *buf++ = static_cast<char>('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent + +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. + +@pre min_exp < 0 +@pre max_exp > 0 +*/ +JSON_HEDLEY_NON_NULL(1) +JSON_HEDLEY_RETURNS_NON_NULL +inline char* format_buffer(char* buf, int len, int decimal_exponent, + int min_exp, int max_exp) +{ + JSON_ASSERT(min_exp < 0); + JSON_ASSERT(max_exp > 0); + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n && n <= max_exp) + { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast<size_t>(n) - static_cast<size_t>(k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (static_cast<size_t>(n) + 2); + } + + if (0 < n && n <= max_exp) + { + // dig.its + // len <= max_digits10 + 1 + + JSON_ASSERT(k > n); + + std::memmove(buf + (static_cast<size_t>(n) + 1), buf + n, static_cast<size_t>(k) - static_cast<size_t>(n)); + buf[n] = '.'; + return buf + (static_cast<size_t>(k) + 1U); + } + + if (min_exp < n && n <= 0) + { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + static_cast<size_t>(-n)), buf, static_cast<size_t>(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast<size_t>(-n)); + return buf + (2U + static_cast<size_t>(-n) + static_cast<size_t>(k)); + } + + if (k == 1) + { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } + else + { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast<size_t>(k) - 1); + buf[1] = '.'; + buf += 1 + static_cast<size_t>(k); + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +@brief generates a decimal representation of the floating-point number value in [first, last). + +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. + +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +template<typename FloatType> +JSON_HEDLEY_NON_NULL(1, 2) +JSON_HEDLEY_RETURNS_NON_NULL +char* to_chars(char* first, const char* last, FloatType value) +{ + static_cast<void>(last); // maybe unused - fix warning + JSON_ASSERT(std::isfinite(value)); + + // Use signbit(value) instead of (value < 0) since signbit works for -0. + if (std::signbit(value)) + { + value = -value; + *first++ = '-'; + } + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10); + + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e. the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + JSON_ASSERT(len <= std::numeric_limits<FloatType>::max_digits10); + + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + // Use digits10 here to increase compatibility with version 2. + constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10; + + JSON_ASSERT(last - first >= kMaxExp + 2); + JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits<FloatType>::max_digits10); + JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10 + 6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/exceptions.hpp> + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/meta/cpp_future.hpp> + +// #include <nlohmann/detail/output/binary_writer.hpp> + +// #include <nlohmann/detail/output/output_adapters.hpp> + +// #include <nlohmann/detail/string_concat.hpp> + +// #include <nlohmann/detail/value_t.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////// +// serialization // +/////////////////// + +/// how to treat decoding errors +enum class error_handler_t +{ + strict, ///< throw a type_error exception in case of invalid UTF-8 + replace, ///< replace invalid UTF-8 sequences with U+FFFD + ignore ///< ignore invalid UTF-8 sequences +}; + +template<typename BasicJsonType> +class serializer +{ + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using binary_char_t = typename BasicJsonType::binary_t::value_type; + static constexpr std::uint8_t UTF8_ACCEPT = 0; + static constexpr std::uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + @param[in] error_handler_ how to react on decoding errors + */ + serializer(output_adapter_t<char> s, const char ichar, + error_handler_t error_handler_ = error_handler_t::strict) + : o(std::move(s)) + , loc(std::localeconv()) + , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep))) + , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point))) + , indent_char(ichar) + , indent_string(512, indent_char) + , error_handler(error_handler_) + {} + + // delete because of pointer members + serializer(const serializer&) = delete; + serializer& operator=(const serializer&) = delete; + serializer(serializer&&) = delete; + serializer& operator=(serializer&&) = delete; + ~serializer() = default; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + - binary values are serialized as objects containing the subtype and the + byte array + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(const BasicJsonType& val, + const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_data.m_type) + { + case value_t::object: + { + if (val.m_data.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_data.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + JSON_ASSERT(i != val.m_data.m_value.object->cend()); + JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_data.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + JSON_ASSERT(i != val.m_data.m_value.object->cend()); + JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_data.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (pretty_print) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_data.m_value.array->cbegin(); + i != val.m_data.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + JSON_ASSERT(!val.m_data.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_data.m_value.array->cbegin(); + i != val.m_data.m_value.array->cend() - 1; ++i) + { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + JSON_ASSERT(!val.m_data.m_value.array->empty()); + dump(val.m_data.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: + { + o->write_character('\"'); + dump_escaped(*val.m_data.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::binary: + { + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + o->write_characters(indent_string.c_str(), new_indent); + + o->write_characters("\"bytes\": [", 10); + + if (!val.m_data.m_value.binary->empty()) + { + for (auto i = val.m_data.m_value.binary->cbegin(); + i != val.m_data.m_value.binary->cend() - 1; ++i) + { + dump_integer(*i); + o->write_characters(", ", 2); + } + dump_integer(val.m_data.m_value.binary->back()); + } + + o->write_characters("],\n", 3); + o->write_characters(indent_string.c_str(), new_indent); + + o->write_characters("\"subtype\": ", 11); + if (val.m_data.m_value.binary->has_subtype()) + { + dump_integer(val.m_data.m_value.binary->subtype()); + } + else + { + o->write_characters("null", 4); + } + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_characters("{\"bytes\":[", 10); + + if (!val.m_data.m_value.binary->empty()) + { + for (auto i = val.m_data.m_value.binary->cbegin(); + i != val.m_data.m_value.binary->cend() - 1; ++i) + { + dump_integer(*i); + o->write_character(','); + } + dump_integer(val.m_data.m_value.binary->back()); + } + + o->write_characters("],\"subtype\":", 12); + if (val.m_data.m_value.binary->has_subtype()) + { + dump_integer(val.m_data.m_value.binary->subtype()); + o->write_character('}'); + } + else + { + o->write_characters("null}", 5); + } + } + return; + } + + case value_t::boolean: + { + if (val.m_data.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + dump_integer(val.m_data.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + dump_integer(val.m_data.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + dump_float(val.m_data.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("<discarded>", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + } + + JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t& s, const bool ensure_ascii) + { + std::uint32_t codepoint{}; + std::uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer + + // number of bytes written at the point of the last valid byte + std::size_t bytes_after_last_accept = 0; + std::size_t undumped_chars = 0; + + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto byte = static_cast<std::uint8_t>(s[i]); + + switch (decode(state, codepoint, byte)) + { + case UTF8_ACCEPT: // decode found a new code point + { + switch (codepoint) + { + case 0x08: // backspace + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast<std::uint16_t>(codepoint))); + bytes += 6; + } + else + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)), + static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + + // remember the byte position of this accept + bytes_after_last_accept = bytes; + undumped_chars = 0; + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + switch (error_handler) + { + case error_handler_t::strict: + { + JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_bytes(byte | 0)), nullptr)); + } + + case error_handler_t::ignore: + case error_handler_t::replace: + { + // in case we saw this character the first time, we + // would like to read it again, because the byte + // may be OK for itself, but just not OK for the + // previous sequence + if (undumped_chars > 0) + { + --i; + } + + // reset length buffer to the last accepted index; + // thus removing/ignoring the invalid characters + bytes = bytes_after_last_accept; + + if (error_handler == error_handler_t::replace) + { + // add a replacement character + if (ensure_ascii) + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'u'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'd'; + } + else + { + string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xEF'); + string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xBF'); + string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xBD'); + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + + bytes_after_last_accept = bytes; + } + + undumped_chars = 0; + + // continue processing the string + state = UTF8_ACCEPT; + break; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + break; + } + + default: // decode found yet incomplete multi-byte code point + { + if (!ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; + } + ++undumped_chars; + break; + } + } + } + + // we finished processing the string + if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + switch (error_handler) + { + case error_handler_t::strict: + { + JSON_THROW(type_error::create(316, concat("incomplete UTF-8 string; last byte: 0x", hex_bytes(static_cast<std::uint8_t>(s.back() | 0))), nullptr)); + } + + case error_handler_t::ignore: + { + // write all accepted bytes + o->write_characters(string_buffer.data(), bytes_after_last_accept); + break; + } + + case error_handler_t::replace: + { + // write all accepted bytes + o->write_characters(string_buffer.data(), bytes_after_last_accept); + // add a replacement character + if (ensure_ascii) + { + o->write_characters("\\ufffd", 6); + } + else + { + o->write_characters("\xEF\xBF\xBD", 3); + } + break; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + } + } + + private: + /*! + @brief count digits + + Count the number of decimal (base 10) digits for an input unsigned integer. + + @param[in] x unsigned integer number to count its digits + @return number of decimal digits + */ + inline unsigned int count_digits(number_unsigned_t x) noexcept + { + unsigned int n_digits = 1; + for (;;) + { + if (x < 10) + { + return n_digits; + } + if (x < 100) + { + return n_digits + 1; + } + if (x < 1000) + { + return n_digits + 2; + } + if (x < 10000) + { + return n_digits + 3; + } + x = x / 10000u; + n_digits += 4; + } + } + + /*! + * @brief convert a byte to a uppercase hex representation + * @param[in] byte byte to represent + * @return representation ("00".."FF") + */ + static std::string hex_bytes(std::uint8_t byte) + { + std::string result = "FF"; + constexpr const char* nibble_to_hex = "0123456789ABCDEF"; + result[0] = nibble_to_hex[byte / 16]; + result[1] = nibble_to_hex[byte % 16]; + return result; + } + + // templates to avoid warnings about useless casts + template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0> + bool is_negative_number(NumberType x) + { + return x < 0; + } + + template < typename NumberType, enable_if_t <std::is_unsigned<NumberType>::value, int > = 0 > + bool is_negative_number(NumberType /*unused*/) + { + return false; + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template < typename NumberType, detail::enable_if_t < + std::is_integral<NumberType>::value || + std::is_same<NumberType, number_unsigned_t>::value || + std::is_same<NumberType, number_integer_t>::value || + std::is_same<NumberType, binary_char_t>::value, + int > = 0 > + void dump_integer(NumberType x) + { + static constexpr std::array<std::array<char, 2>, 100> digits_to_99 + { + { + {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}}, + {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}}, + {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}}, + {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}}, + {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}}, + {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}}, + {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}}, + {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}}, + {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}}, + {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}}, + } + }; + + // special case for "0" + if (x == 0) + { + o->write_character('0'); + return; + } + + // use a pointer to fill the buffer + auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg) + + number_unsigned_t abs_value; + + unsigned int n_chars{}; + + if (is_negative_number(x)) + { + *buffer_ptr = '-'; + abs_value = remove_sign(static_cast<number_integer_t>(x)); + + // account one more byte for the minus sign + n_chars = 1 + count_digits(abs_value); + } + else + { + abs_value = static_cast<number_unsigned_t>(x); + n_chars = count_digits(abs_value); + } + + // spare 1 byte for '\0' + JSON_ASSERT(n_chars < number_buffer.size() - 1); + + // jump to the end to generate the string from backward, + // so we later avoid reversing the result + buffer_ptr += n_chars; + + // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu + // See: https://www.youtube.com/watch?v=o4-CwDo2zpg + while (abs_value >= 100) + { + const auto digits_index = static_cast<unsigned>((abs_value % 100)); + abs_value /= 100; + *(--buffer_ptr) = digits_to_99[digits_index][1]; + *(--buffer_ptr) = digits_to_99[digits_index][0]; + } + + if (abs_value >= 10) + { + const auto digits_index = static_cast<unsigned>(abs_value); + *(--buffer_ptr) = digits_to_99[digits_index][1]; + *(--buffer_ptr) = digits_to_99[digits_index][0]; + } + else + { + *(--buffer_ptr) = static_cast<char>('0' + abs_value); + } + + o->write_characters(number_buffer.data(), n_chars); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (!std::isfinite(x)) + { + o->write_characters("null", 4); + return; + } + + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if <long double> == <double>. + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128) || + (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024); + + dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + auto* begin = number_buffer.data(); + auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast<size_t>(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10; + + // the actual conversion + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + JSON_ASSERT(len > 0); + // check if buffer was large enough + JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size()); + + // erase thousands separator + if (thousands_sep != '\0') + { + // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081 + const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + JSON_ASSERT((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' && decimal_point != '.') + { + // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081 + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) + { + *dec_pos = '.'; + } + } + + o->write_characters(number_buffer.data(), static_cast<std::size_t>(len)); + + // determine if we need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return c == '.' || c == 'e'; + }); + + if (value_is_int_like) + { + o->write_characters(".0", 2); + } + } + + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de> + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept + { + static const std::array<std::uint8_t, 400> utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + JSON_ASSERT(byte < utf8d.size()); + const std::uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6u) + : (0xFFu >> type) & (byte); + + const std::size_t index = 256u + static_cast<size_t>(state) * 16u + static_cast<size_t>(type); + JSON_ASSERT(index < utf8d.size()); + state = utf8d[index]; + return state; + } + + /* + * Overload to make the compiler happy while it is instantiating + * dump_integer for number_unsigned_t. + * Must never be called. + */ + number_unsigned_t remove_sign(number_unsigned_t x) + { + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + return x; // LCOV_EXCL_LINE + } + + /* + * Helper function for dump_integer + * + * This function takes a negative signed integer and returns its absolute + * value as unsigned integer. The plus/minus shuffling is necessary as we can + * not directly remove the sign of an arbitrary signed integer as the + * absolute values of INT_MIN and INT_MAX are usually not the same. See + * #1708 for details. + */ + inline number_unsigned_t remove_sign(number_integer_t x) noexcept + { + JSON_ASSERT(x < 0 && x < (std::numeric_limits<number_integer_t>::max)()); // NOLINT(misc-redundant-expression) + return static_cast<number_unsigned_t>(-(x + 1)) + 1; + } + + private: + /// the output of the serializer + output_adapter_t<char> o = nullptr; + + /// a (hopefully) large enough character buffer + std::array<char, 64> number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// string buffer + std::array<char, 512> string_buffer{{}}; + + /// the indentation character + const char indent_char; + /// the indentation string + string_t indent_string; + + /// error_handler how to react on decoding errors + const error_handler_t error_handler; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include <nlohmann/detail/value_t.hpp> + +// #include <nlohmann/json_fwd.hpp> + +// #include <nlohmann/ordered_map.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#include <functional> // equal_to, less +#include <initializer_list> // initializer_list +#include <iterator> // input_iterator_tag, iterator_traits +#include <memory> // allocator +#include <stdexcept> // for out_of_range +#include <type_traits> // enable_if, is_convertible +#include <utility> // pair +#include <vector> // vector + +// #include <nlohmann/detail/macro_scope.hpp> + +// #include <nlohmann/detail/meta/type_traits.hpp> + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +/// ordered_map: a minimal map-like container that preserves insertion order +/// for use within nlohmann::basic_json<ordered_map> +template <class Key, class T, class IgnoredLess = std::less<Key>, + class Allocator = std::allocator<std::pair<const Key, T>>> + struct ordered_map : std::vector<std::pair<const Key, T>, Allocator> +{ + using key_type = Key; + using mapped_type = T; + using Container = std::vector<std::pair<const Key, T>, Allocator>; + using iterator = typename Container::iterator; + using const_iterator = typename Container::const_iterator; + using size_type = typename Container::size_type; + using value_type = typename Container::value_type; +#ifdef JSON_HAS_CPP_14 + using key_compare = std::equal_to<>; +#else + using key_compare = std::equal_to<Key>; +#endif + + // Explicit constructors instead of `using Container::Container` + // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4) + ordered_map() noexcept(noexcept(Container())) : Container{} {} + explicit ordered_map(const Allocator& alloc) noexcept(noexcept(Container(alloc))) : Container{alloc} {} + template <class It> + ordered_map(It first, It last, const Allocator& alloc = Allocator()) + : Container{first, last, alloc} {} + ordered_map(std::initializer_list<value_type> init, const Allocator& alloc = Allocator() ) + : Container{init, alloc} {} + + std::pair<iterator, bool> emplace(const key_type& key, T&& t) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return {it, false}; + } + } + Container::emplace_back(key, std::forward<T>(t)); + return {std::prev(this->end()), true}; + } + + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> + std::pair<iterator, bool> emplace(KeyType && key, T && t) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return {it, false}; + } + } + Container::emplace_back(std::forward<KeyType>(key), std::forward<T>(t)); + return {std::prev(this->end()), true}; + } + + T& operator[](const key_type& key) + { + return emplace(key, T{}).first->second; + } + + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> + T & operator[](KeyType && key) + { + return emplace(std::forward<KeyType>(key), T{}).first->second; + } + + const T& operator[](const key_type& key) const + { + return at(key); + } + + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> + const T & operator[](KeyType && key) const + { + return at(std::forward<KeyType>(key)); + } + + T& at(const key_type& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it->second; + } + } + + JSON_THROW(std::out_of_range("key not found")); + } + + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> + T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it->second; + } + } + + JSON_THROW(std::out_of_range("key not found")); + } + + const T& at(const key_type& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it->second; + } + } + + JSON_THROW(std::out_of_range("key not found")); + } + + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> + const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it->second; + } + } + + JSON_THROW(std::out_of_range("key not found")); + } + + size_type erase(const key_type& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return 1; + } + } + return 0; + } + + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> + size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return 1; + } + } + return 0; + } + + iterator erase(iterator pos) + { + return erase(pos, std::next(pos)); + } + + iterator erase(iterator first, iterator last) + { + if (first == last) + { + return first; + } + + const auto elements_affected = std::distance(first, last); + const auto offset = std::distance(Container::begin(), first); + + // This is the start situation. We need to delete elements_affected + // elements (3 in this example: e, f, g), and need to return an + // iterator past the last deleted element (h in this example). + // Note that offset is the distance from the start of the vector + // to first. We will need this later. + + // [ a, b, c, d, e, f, g, h, i, j ] + // ^ ^ + // first last + + // Since we cannot move const Keys, we re-construct them in place. + // We start at first and re-construct (viz. copy) the elements from + // the back of the vector. Example for first iteration: + + // ,--------. + // v | destroy e and re-construct with h + // [ a, b, c, d, e, f, g, h, i, j ] + // ^ ^ + // it it + elements_affected + + for (auto it = first; std::next(it, elements_affected) != Container::end(); ++it) + { + it->~value_type(); // destroy but keep allocation + new (&*it) value_type{std::move(*std::next(it, elements_affected))}; // "move" next element to it + } + + // [ a, b, c, d, h, i, j, h, i, j ] + // ^ ^ + // first last + + // remove the unneeded elements at the end of the vector + Container::resize(this->size() - static_cast<size_type>(elements_affected)); + + // [ a, b, c, d, h, i, j ] + // ^ ^ + // first last + + // first is now pointing past the last deleted element, but we cannot + // use this iterator, because it may have been invalidated by the + // resize call. Instead, we can return begin() + offset. + return Container::begin() + offset; + } + + size_type count(const key_type& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return 1; + } + } + return 0; + } + + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> + size_type count(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return 1; + } + } + return 0; + } + + iterator find(const key_type& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it; + } + } + return Container::end(); + } + + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> + iterator find(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it; + } + } + return Container::end(); + } + + const_iterator find(const key_type& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it; + } + } + return Container::end(); + } + + std::pair<iterator, bool> insert( value_type&& value ) + { + return emplace(value.first, std::move(value.second)); + } + + std::pair<iterator, bool> insert( const value_type& value ) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, value.first)) + { + return {it, false}; + } + } + Container::push_back(value); + return {--this->end(), true}; + } + + template<typename InputIt> + using require_input_iter = typename std::enable_if<std::is_convertible<typename std::iterator_traits<InputIt>::iterator_category, + std::input_iterator_tag>::value>::type; + + template<typename InputIt, typename = require_input_iter<InputIt>> + void insert(InputIt first, InputIt last) + { + for (auto it = first; it != last; ++it) + { + insert(*it); + } + } + +private: + JSON_NO_UNIQUE_ADDRESS key_compare m_compare = key_compare(); +}; + +NLOHMANN_JSON_NAMESPACE_END + + +#if defined(JSON_HAS_CPP_17) + #if JSON_HAS_STATIC_RTTI + #include <any> + #endif + #include <string_view> +#endif + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +NLOHMANN_JSON_NAMESPACE_BEGIN + +/*! +@brief a class to store JSON values + +@internal +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@note ObjectType trick from https://stackoverflow.com/a/9860911 +@endinternal + +@since version 1.0.0 + +@nosubgrouping +*/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + : public ::nlohmann::detail::json_base_class<CustomBaseClass> +{ + private: + template<detail::value_t> friend struct detail::external_constructor; + + template<typename> + friend class ::nlohmann::json_pointer; + // can be restored when json_pointer backwards compatibility is removed + // friend ::nlohmann::json_pointer<StringType>; + + template<typename BasicJsonType, typename InputType> + friend class ::nlohmann::detail::parser; + friend ::nlohmann::detail::serializer<basic_json>; + template<typename BasicJsonType> + friend class ::nlohmann::detail::iter_impl; + template<typename BasicJsonType, typename CharType> + friend class ::nlohmann::detail::binary_writer; + template<typename BasicJsonType, typename InputType, typename SAX> + friend class ::nlohmann::detail::binary_reader; + template<typename BasicJsonType> + friend class ::nlohmann::detail::json_sax_dom_parser; + template<typename BasicJsonType> + friend class ::nlohmann::detail::json_sax_dom_callback_parser; + friend class ::nlohmann::detail::exception; + + /// workaround type for MSVC + using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>; + + JSON_PRIVATE_UNLESS_TESTED: + // convenience aliases for types residing in namespace detail; + using lexer = ::nlohmann::detail::lexer_base<basic_json>; + + template<typename InputAdapterType> + static ::nlohmann::detail::parser<basic_json, InputAdapterType> parser( + InputAdapterType adapter, + detail::parser_callback_t<basic_json>cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false + ) + { + return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter), + std::move(cb), allow_exceptions, ignore_comments); + } + + private: + using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; + template<typename BasicJsonType> + using internal_iterator = ::nlohmann::detail::internal_iterator<BasicJsonType>; + template<typename BasicJsonType> + using iter_impl = ::nlohmann::detail::iter_impl<BasicJsonType>; + template<typename Iterator> + using iteration_proxy = ::nlohmann::detail::iteration_proxy<Iterator>; + template<typename Base> using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator<Base>; + + template<typename CharType> + using output_adapter_t = ::nlohmann::detail::output_adapter_t<CharType>; + + template<typename InputType> + using binary_reader = ::nlohmann::detail::binary_reader<basic_json, InputType>; + template<typename CharType> using binary_writer = ::nlohmann::detail::binary_writer<basic_json, CharType>; + + JSON_PRIVATE_UNLESS_TESTED: + using serializer = ::nlohmann::detail::serializer<basic_json>; + + public: + using value_t = detail::value_t; + /// JSON Pointer, see @ref nlohmann::json_pointer + using json_pointer = ::nlohmann::json_pointer<StringType>; + template<typename T, typename SFINAE> + using json_serializer = JSONSerializer<T, SFINAE>; + /// how to treat decoding errors + using error_handler_t = detail::error_handler_t; + /// how to treat CBOR tags + using cbor_tag_handler_t = detail::cbor_tag_handler_t; + /// helper type for initializer lists of basic_json values + using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>; + + using input_format_t = detail::input_format_t; + /// SAX interface type, see @ref nlohmann::json_sax + using json_sax_t = json_sax<basic_json>; + + //////////////// + // exceptions // + //////////////// + + /// @name exceptions + /// Classes to implement user-defined exceptions. + /// @{ + + using exception = detail::exception; + using parse_error = detail::parse_error; + using invalid_iterator = detail::invalid_iterator; + using type_error = detail::type_error; + using out_of_range = detail::out_of_range; + using other_error = detail::other_error; + + /// @} + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType<basic_json>; + + /// the type of an element pointer + using pointer = typename std::allocator_traits<allocator_type>::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer; + + /// an iterator for a basic_json container + using iterator = iter_impl<basic_json>; + /// a const iterator for a basic_json container + using const_iterator = iter_impl<const basic_json>; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>; + + /// @} + + /// @brief returns the allocator associated with the container + /// @sa https://json.nlohmann.me/api/basic_json/get_allocator/ + static allocator_type get_allocator() + { + return allocator_type(); + } + + /// @brief returns version information on the library + /// @sa https://json.nlohmann.me/api/basic_json/meta/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json meta() + { + basic_json result; + + result["copyright"] = "(C) 2013-2023 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["url"] = "https://github.com/nlohmann/json"; + result["version"]["string"] = + detail::concat(std::to_string(NLOHMANN_JSON_VERSION_MAJOR), '.', + std::to_string(NLOHMANN_JSON_VERSION_MINOR), '.', + std::to_string(NLOHMANN_JSON_VERSION_PATCH)); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; + +#ifdef _WIN32 + result["platform"] = "win32"; +#elif defined __linux__ + result["platform"] = "linux"; +#elif defined __APPLE__ + result["platform"] = "apple"; +#elif defined __unix__ + result["platform"] = "unix"; +#else + result["platform"] = "unknown"; +#endif + +#if defined(__ICC) || defined(__INTEL_COMPILER) + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__clang__) + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; +#elif defined(__GNUC__) || defined(__GNUG__) + result["compiler"] = {{"family", "gcc"}, {"version", detail::concat( + std::to_string(__GNUC__), '.', + std::to_string(__GNUC_MINOR__), '.', + std::to_string(__GNUC_PATCHLEVEL__)) + } + }; +#elif defined(__HP_cc) || defined(__HP_aCC) + result["compiler"] = "hp" +#elif defined(__IBMCPP__) + result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; +#elif defined(_MSC_VER) + result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; +#elif defined(__PGI) + result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; +#elif defined(__SUNPRO_CC) + result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; +#else + result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; +#endif + +#if defined(_MSVC_LANG) + result["compiler"]["c++"] = std::to_string(_MSVC_LANG); +#elif defined(__cplusplus) + result["compiler"]["c++"] = std::to_string(__cplusplus); +#else + result["compiler"]["c++"] = "unknown"; +#endif + return result; + } + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + + /// @brief default object key comparator type + /// The actual object key comparator type (@ref object_comparator_t) may be + /// different. + /// @sa https://json.nlohmann.me/api/basic_json/default_object_comparator_t/ +#if defined(JSON_HAS_CPP_14) + // use of transparent comparator avoids unnecessary repeated construction of temporaries + // in functions involving lookup by key with types other than object_t::key_type (aka. StringType) + using default_object_comparator_t = std::less<>; +#else + using default_object_comparator_t = std::less<StringType>; +#endif + + /// @brief a type for an object + /// @sa https://json.nlohmann.me/api/basic_json/object_t/ + using object_t = ObjectType<StringType, + basic_json, + default_object_comparator_t, + AllocatorType<std::pair<const StringType, + basic_json>>>; + + /// @brief a type for an array + /// @sa https://json.nlohmann.me/api/basic_json/array_t/ + using array_t = ArrayType<basic_json, AllocatorType<basic_json>>; + + /// @brief a type for a string + /// @sa https://json.nlohmann.me/api/basic_json/string_t/ + using string_t = StringType; + + /// @brief a type for a boolean + /// @sa https://json.nlohmann.me/api/basic_json/boolean_t/ + using boolean_t = BooleanType; + + /// @brief a type for a number (integer) + /// @sa https://json.nlohmann.me/api/basic_json/number_integer_t/ + using number_integer_t = NumberIntegerType; + + /// @brief a type for a number (unsigned) + /// @sa https://json.nlohmann.me/api/basic_json/number_unsigned_t/ + using number_unsigned_t = NumberUnsignedType; + + /// @brief a type for a number (floating-point) + /// @sa https://json.nlohmann.me/api/basic_json/number_float_t/ + using number_float_t = NumberFloatType; + + /// @brief a type for a packed binary type + /// @sa https://json.nlohmann.me/api/basic_json/binary_t/ + using binary_t = nlohmann::byte_container_with_subtype<BinaryType>; + + /// @brief object key comparator type + /// @sa https://json.nlohmann.me/api/basic_json/object_comparator_t/ + using object_comparator_t = detail::actual_object_comparator_t<basic_json>; + + /// @} + + private: + + /// helper for exception-safe object creation + template<typename T, typename... Args> + JSON_HEDLEY_RETURNS_NON_NULL + static T* create(Args&& ... args) + { + AllocatorType<T> alloc; + using AllocatorTraits = std::allocator_traits<AllocatorType<T>>; + + auto deleter = [&](T * obj) + { + AllocatorTraits::deallocate(alloc, obj, 1); + }; + std::unique_ptr<T, decltype(deleter)> obj(AllocatorTraits::allocate(alloc, 1), deleter); + AllocatorTraits::construct(alloc, obj.get(), std::forward<Args>(args)...); + JSON_ASSERT(obj != nullptr); + return obj.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + binary | binary | pointer to @ref binary_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// binary (stored with pointer to save storage) + binary_t* binary; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create<object_t>(); + break; + } + + case value_t::array: + { + array = create<array_t>(); + break; + } + + case value_t::string: + { + string = create<string_t>(""); + break; + } + + case value_t::binary: + { + binary = create<binary_t>(); + break; + } + + case value_t::boolean: + { + boolean = static_cast<boolean_t>(false); + break; + } + + case value_t::number_integer: + { + number_integer = static_cast<number_integer_t>(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = static_cast<number_unsigned_t>(0); + break; + } + + case value_t::number_float: + { + number_float = static_cast<number_float_t>(0.0); + break; + } + + case value_t::null: + { + object = nullptr; // silence warning, see #821 + break; + } + + case value_t::discarded: + default: + { + object = nullptr; // silence warning, see #821 + if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) + { + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.11.3", nullptr)); // LCOV_EXCL_LINE + } + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) : string(create<string_t>(value)) {} + + /// constructor for rvalue strings + json_value(string_t&& value) : string(create<string_t>(std::move(value))) {} + + /// constructor for objects + json_value(const object_t& value) : object(create<object_t>(value)) {} + + /// constructor for rvalue objects + json_value(object_t&& value) : object(create<object_t>(std::move(value))) {} + + /// constructor for arrays + json_value(const array_t& value) : array(create<array_t>(value)) {} + + /// constructor for rvalue arrays + json_value(array_t&& value) : array(create<array_t>(std::move(value))) {} + + /// constructor for binary arrays + json_value(const typename binary_t::container_type& value) : binary(create<binary_t>(value)) {} + + /// constructor for rvalue binary arrays + json_value(typename binary_t::container_type&& value) : binary(create<binary_t>(std::move(value))) {} + + /// constructor for binary arrays (internal type) + json_value(const binary_t& value) : binary(create<binary_t>(value)) {} + + /// constructor for rvalue binary arrays (internal type) + json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {} + + void destroy(value_t t) + { + if ( + (t == value_t::object && object == nullptr) || + (t == value_t::array && array == nullptr) || + (t == value_t::string && string == nullptr) || + (t == value_t::binary && binary == nullptr) + ) + { + //not initialized (e.g. due to exception in the ctor) + return; + } + if (t == value_t::array || t == value_t::object) + { + // flatten the current json_value to a heap-allocated stack + std::vector<basic_json> stack; + + // move the top-level items to stack + if (t == value_t::array) + { + stack.reserve(array->size()); + std::move(array->begin(), array->end(), std::back_inserter(stack)); + } + else + { + stack.reserve(object->size()); + for (auto&& it : *object) + { + stack.push_back(std::move(it.second)); + } + } + + while (!stack.empty()) + { + // move the last item to local variable to be processed + basic_json current_item(std::move(stack.back())); + stack.pop_back(); + + // if current_item is array/object, move + // its children to the stack to be processed later + if (current_item.is_array()) + { + std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack)); + + current_item.m_data.m_value.array->clear(); + } + else if (current_item.is_object()) + { + for (auto&& it : *current_item.m_data.m_value.object) + { + stack.push_back(std::move(it.second)); + } + + current_item.m_data.m_value.object->clear(); + } + + // it's now safe that current_item get destructed + // since it doesn't have any children + } + } + + switch (t) + { + case value_t::object: + { + AllocatorType<object_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, object); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1); + break; + } + + case value_t::array: + { + AllocatorType<array_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, array); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1); + break; + } + + case value_t::string: + { + AllocatorType<string_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, string); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1); + break; + } + + case value_t::binary: + { + AllocatorType<binary_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, binary); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1); + break; + } + + case value_t::null: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::discarded: + default: + { + break; + } + } + } + }; + + private: + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + + Furthermore, the parent relation is checked for arrays and objects: If + @a check_parents true and the value is an array or object, then the + container's elements must have the current value as parent. + + @param[in] check_parents whether the parent relation should be checked. + The value is true by default and should only be set to false + during destruction of objects when the invariant does not + need to hold. + */ + void assert_invariant(bool check_parents = true) const noexcept + { + JSON_ASSERT(m_data.m_type != value_t::object || m_data.m_value.object != nullptr); + JSON_ASSERT(m_data.m_type != value_t::array || m_data.m_value.array != nullptr); + JSON_ASSERT(m_data.m_type != value_t::string || m_data.m_value.string != nullptr); + JSON_ASSERT(m_data.m_type != value_t::binary || m_data.m_value.binary != nullptr); + +#if JSON_DIAGNOSTICS + JSON_TRY + { + // cppcheck-suppress assertWithSideEffect + JSON_ASSERT(!check_parents || !is_structured() || std::all_of(begin(), end(), [this](const basic_json & j) + { + return j.m_parent == this; + })); + } + JSON_CATCH(...) {} // LCOV_EXCL_LINE +#endif + static_cast<void>(check_parents); + } + + void set_parents() + { +#if JSON_DIAGNOSTICS + switch (m_data.m_type) + { + case value_t::array: + { + for (auto& element : *m_data.m_value.array) + { + element.m_parent = this; + } + break; + } + + case value_t::object: + { + for (auto& element : *m_data.m_value.object) + { + element.second.m_parent = this; + } + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + break; + } +#endif + } + + iterator set_parents(iterator it, typename iterator::difference_type count_set_parents) + { +#if JSON_DIAGNOSTICS + for (typename iterator::difference_type i = 0; i < count_set_parents; ++i) + { + (it + i)->m_parent = this; + } +#else + static_cast<void>(count_set_parents); +#endif + return it; + } + + reference set_parent(reference j, std::size_t old_capacity = static_cast<std::size_t>(-1)) + { +#if JSON_DIAGNOSTICS + if (old_capacity != static_cast<std::size_t>(-1)) + { + // see https://github.com/nlohmann/json/issues/2838 + JSON_ASSERT(type() == value_t::array); + if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity)) + { + // capacity has changed: update all parents + set_parents(); + return j; + } + } + + // ordered_json uses a vector internally, so pointers could have + // been invalidated; see https://github.com/nlohmann/json/issues/2962 +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning(push ) +#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr +#endif + if (detail::is_ordered_map<object_t>::value) + { + set_parents(); + return j; + } +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning( pop ) +#endif + + j.m_parent = this; +#else + static_cast<void>(j); + static_cast<void>(old_capacity); +#endif + return j; + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /// @brief parser event types + /// @sa https://json.nlohmann.me/api/basic_json/parse_event_t/ + using parse_event_t = detail::parse_event_t; + + /// @brief per-element parser callback type + /// @sa https://json.nlohmann.me/api/basic_json/parser_callback_t/ + using parser_callback_t = detail::parser_callback_t<basic_json>; + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /// @brief create an empty value with a given type + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + basic_json(const value_t v) + : m_data(v) + { + assert_invariant(); + } + + /// @brief create a null object + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + basic_json(std::nullptr_t = nullptr) noexcept // NOLINT(bugprone-exception-escape) + : basic_json(value_t::null) + { + assert_invariant(); + } + + /// @brief create a JSON value from compatible types + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + template < typename CompatibleType, + typename U = detail::uncvref_t<CompatibleType>, + detail::enable_if_t < + !detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 > + basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape) + JSONSerializer<U>::to_json(std::declval<basic_json_t&>(), + std::forward<CompatibleType>(val)))) + { + JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val)); + set_parents(); + assert_invariant(); + } + + /// @brief create a JSON value from an existing one + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + template < typename BasicJsonType, + detail::enable_if_t < + detail::is_basic_json<BasicJsonType>::value&& !std::is_same<basic_json, BasicJsonType>::value, int > = 0 > + basic_json(const BasicJsonType& val) + { + using other_boolean_t = typename BasicJsonType::boolean_t; + using other_number_float_t = typename BasicJsonType::number_float_t; + using other_number_integer_t = typename BasicJsonType::number_integer_t; + using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using other_string_t = typename BasicJsonType::string_t; + using other_object_t = typename BasicJsonType::object_t; + using other_array_t = typename BasicJsonType::array_t; + using other_binary_t = typename BasicJsonType::binary_t; + + switch (val.type()) + { + case value_t::boolean: + JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>()); + break; + case value_t::number_float: + JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>()); + break; + case value_t::number_integer: + JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>()); + break; + case value_t::number_unsigned: + JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>()); + break; + case value_t::string: + JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>()); + break; + case value_t::object: + JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>()); + break; + case value_t::array: + JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>()); + break; + case value_t::binary: + JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>()); + break; + case value_t::null: + *this = nullptr; + break; + case value_t::discarded: + m_data.m_type = value_t::discarded; + break; + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + JSON_ASSERT(m_data.m_type == val.type()); + set_parents(); + assert_invariant(); + } + + /// @brief create a container (array or object) from an initializer list + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + basic_json(initializer_list_t init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const detail::json_ref<basic_json>& element_ref) + { + // The cast is to ensure op[size_type] is called, bearing in mind size_type may not be int; + // (many string types can be constructed from 0 via its null-pointer guise, so we get a + // broken call to op[key_type], the wrong semantics and a 4804 warning on Windows) + return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[static_cast<size_type>(0)].is_string(); + }); + + // adjust type if type deduction is not wanted + if (!type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) + { + JSON_THROW(type_error::create(301, "cannot create object from initializer list", nullptr)); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_data.m_type = value_t::object; + m_data.m_value = value_t::object; + + for (auto& element_ref : init) + { + auto element = element_ref.moved_or_copied(); + m_data.m_value.object->emplace( + std::move(*((*element.m_data.m_value.array)[0].m_data.m_value.string)), + std::move((*element.m_data.m_value.array)[1])); + } + } + else + { + // the initializer list describes an array -> create array + m_data.m_type = value_t::array; + m_data.m_value.array = create<array_t>(init.begin(), init.end()); + } + + set_parents(); + assert_invariant(); + } + + /// @brief explicitly create a binary array (without subtype) + /// @sa https://json.nlohmann.me/api/basic_json/binary/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(const typename binary_t::container_type& init) + { + auto res = basic_json(); + res.m_data.m_type = value_t::binary; + res.m_data.m_value = init; + return res; + } + + /// @brief explicitly create a binary array (with subtype) + /// @sa https://json.nlohmann.me/api/basic_json/binary/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype) + { + auto res = basic_json(); + res.m_data.m_type = value_t::binary; + res.m_data.m_value = binary_t(init, subtype); + return res; + } + + /// @brief explicitly create a binary array + /// @sa https://json.nlohmann.me/api/basic_json/binary/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(typename binary_t::container_type&& init) + { + auto res = basic_json(); + res.m_data.m_type = value_t::binary; + res.m_data.m_value = std::move(init); + return res; + } + + /// @brief explicitly create a binary array (with subtype) + /// @sa https://json.nlohmann.me/api/basic_json/binary/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype) + { + auto res = basic_json(); + res.m_data.m_type = value_t::binary; + res.m_data.m_value = binary_t(std::move(init), subtype); + return res; + } + + /// @brief explicitly create an array from an initializer list + /// @sa https://json.nlohmann.me/api/basic_json/array/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json array(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::array); + } + + /// @brief explicitly create an object from an initializer list + /// @sa https://json.nlohmann.me/api/basic_json/object/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json object(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::object); + } + + /// @brief construct an array with count copies of given value + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + basic_json(size_type cnt, const basic_json& val): + m_data{cnt, val} + { + set_parents(); + assert_invariant(); + } + + /// @brief construct a JSON container given an iterator range + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + template < class InputIT, typename std::enable_if < + std::is_same<InputIT, typename basic_json_t::iterator>::value || + std::is_same<InputIT, typename basic_json_t::const_iterator>::value, int >::type = 0 > + basic_json(InputIT first, InputIT last) + { + JSON_ASSERT(first.m_object != nullptr); + JSON_ASSERT(last.m_object != nullptr); + + // make sure iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(201, "iterators are not compatible", nullptr)); + } + + // copy type from first iterator + m_data.m_type = first.m_object->m_data.m_type; + + // check if iterator range is complete for primitive values + switch (m_data.m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() + || !last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range", first.m_object)); + } + break; + } + + case value_t::null: + case value_t::object: + case value_t::array: + case value_t::binary: + case value_t::discarded: + default: + break; + } + + switch (m_data.m_type) + { + case value_t::number_integer: + { + m_data.m_value.number_integer = first.m_object->m_data.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_data.m_value.number_unsigned = first.m_object->m_data.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_data.m_value.number_float = first.m_object->m_data.m_value.number_float; + break; + } + + case value_t::boolean: + { + m_data.m_value.boolean = first.m_object->m_data.m_value.boolean; + break; + } + + case value_t::string: + { + m_data.m_value = *first.m_object->m_data.m_value.string; + break; + } + + case value_t::object: + { + m_data.m_value.object = create<object_t>(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_data.m_value.array = create<array_t>(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + case value_t::binary: + { + m_data.m_value = *first.m_object->m_data.m_value.binary; + break; + } + + case value_t::null: + case value_t::discarded: + default: + JSON_THROW(invalid_iterator::create(206, detail::concat("cannot construct with iterators from ", first.m_object->type_name()), first.m_object)); + } + + set_parents(); + assert_invariant(); + } + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + template<typename JsonRef, + detail::enable_if_t<detail::conjunction<detail::is_json_ref<JsonRef>, + std::is_same<typename JsonRef::value_type, basic_json>>::value, int> = 0 > + basic_json(const JsonRef& ref) : basic_json(ref.moved_or_copied()) {} + + /// @brief copy constructor + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + basic_json(const basic_json& other) + : json_base_class_t(other) + { + m_data.m_type = other.m_data.m_type; + // check of passed value is valid + other.assert_invariant(); + + switch (m_data.m_type) + { + case value_t::object: + { + m_data.m_value = *other.m_data.m_value.object; + break; + } + + case value_t::array: + { + m_data.m_value = *other.m_data.m_value.array; + break; + } + + case value_t::string: + { + m_data.m_value = *other.m_data.m_value.string; + break; + } + + case value_t::boolean: + { + m_data.m_value = other.m_data.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_data.m_value = other.m_data.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_data.m_value = other.m_data.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_data.m_value = other.m_data.m_value.number_float; + break; + } + + case value_t::binary: + { + m_data.m_value = *other.m_data.m_value.binary; + break; + } + + case value_t::null: + case value_t::discarded: + default: + break; + } + + set_parents(); + assert_invariant(); + } + + /// @brief move constructor + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + basic_json(basic_json&& other) noexcept + : json_base_class_t(std::forward<json_base_class_t>(other)), + m_data(std::move(other.m_data)) + { + // check that passed value is valid + other.assert_invariant(false); + + // invalidate payload + other.m_data.m_type = value_t::null; + other.m_data.m_value = {}; + + set_parents(); + assert_invariant(); + } + + /// @brief copy assignment + /// @sa https://json.nlohmann.me/api/basic_json/operator=/ + basic_json& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible<value_t>::value&& + std::is_nothrow_move_assignable<value_t>::value&& + std::is_nothrow_move_constructible<json_value>::value&& + std::is_nothrow_move_assignable<json_value>::value&& + std::is_nothrow_move_assignable<json_base_class_t>::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_data.m_type, other.m_data.m_type); + swap(m_data.m_value, other.m_data.m_value); + json_base_class_t::operator=(std::move(other)); + + set_parents(); + assert_invariant(); + return *this; + } + + /// @brief destructor + /// @sa https://json.nlohmann.me/api/basic_json/~basic_json/ + ~basic_json() noexcept + { + assert_invariant(false); + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /// @brief serialization + /// @sa https://json.nlohmann.me/api/basic_json/dump/ + string_t dump(const int indent = -1, + const char indent_char = ' ', + const bool ensure_ascii = false, + const error_handler_t error_handler = error_handler_t::strict) const + { + string_t result; + serializer s(detail::output_adapter<char, string_t>(result), indent_char, error_handler); + + if (indent >= 0) + { + s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent)); + } + else + { + s.dump(*this, false, ensure_ascii, 0); + } + + return result; + } + + /// @brief return the type of the JSON value (explicit) + /// @sa https://json.nlohmann.me/api/basic_json/type/ + constexpr value_t type() const noexcept + { + return m_data.m_type; + } + + /// @brief return whether type is primitive + /// @sa https://json.nlohmann.me/api/basic_json/is_primitive/ + constexpr bool is_primitive() const noexcept + { + return is_null() || is_string() || is_boolean() || is_number() || is_binary(); + } + + /// @brief return whether type is structured + /// @sa https://json.nlohmann.me/api/basic_json/is_structured/ + constexpr bool is_structured() const noexcept + { + return is_array() || is_object(); + } + + /// @brief return whether value is null + /// @sa https://json.nlohmann.me/api/basic_json/is_null/ + constexpr bool is_null() const noexcept + { + return m_data.m_type == value_t::null; + } + + /// @brief return whether value is a boolean + /// @sa https://json.nlohmann.me/api/basic_json/is_boolean/ + constexpr bool is_boolean() const noexcept + { + return m_data.m_type == value_t::boolean; + } + + /// @brief return whether value is a number + /// @sa https://json.nlohmann.me/api/basic_json/is_number/ + constexpr bool is_number() const noexcept + { + return is_number_integer() || is_number_float(); + } + + /// @brief return whether value is an integer number + /// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/ + constexpr bool is_number_integer() const noexcept + { + return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned; + } + + /// @brief return whether value is an unsigned integer number + /// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/ + constexpr bool is_number_unsigned() const noexcept + { + return m_data.m_type == value_t::number_unsigned; + } + + /// @brief return whether value is a floating-point number + /// @sa https://json.nlohmann.me/api/basic_json/is_number_float/ + constexpr bool is_number_float() const noexcept + { + return m_data.m_type == value_t::number_float; + } + + /// @brief return whether value is an object + /// @sa https://json.nlohmann.me/api/basic_json/is_object/ + constexpr bool is_object() const noexcept + { + return m_data.m_type == value_t::object; + } + + /// @brief return whether value is an array + /// @sa https://json.nlohmann.me/api/basic_json/is_array/ + constexpr bool is_array() const noexcept + { + return m_data.m_type == value_t::array; + } + + /// @brief return whether value is a string + /// @sa https://json.nlohmann.me/api/basic_json/is_string/ + constexpr bool is_string() const noexcept + { + return m_data.m_type == value_t::string; + } + + /// @brief return whether value is a binary array + /// @sa https://json.nlohmann.me/api/basic_json/is_binary/ + constexpr bool is_binary() const noexcept + { + return m_data.m_type == value_t::binary; + } + + /// @brief return whether value is discarded + /// @sa https://json.nlohmann.me/api/basic_json/is_discarded/ + constexpr bool is_discarded() const noexcept + { + return m_data.m_type == value_t::discarded; + } + + /// @brief return the type of the JSON value (implicit) + /// @sa https://json.nlohmann.me/api/basic_json/operator_value_t/ + constexpr operator value_t() const noexcept + { + return m_data.m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get a boolean (explicit) + boolean_t get_impl(boolean_t* /*unused*/) const + { + if (JSON_HEDLEY_LIKELY(is_boolean())) + { + return m_data.m_value.boolean; + } + + JSON_THROW(type_error::create(302, detail::concat("type must be boolean, but is ", type_name()), this)); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t* /*unused*/) noexcept + { + return is_object() ? m_data.m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept + { + return is_object() ? m_data.m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t* /*unused*/) noexcept + { + return is_array() ? m_data.m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept + { + return is_array() ? m_data.m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t* /*unused*/) noexcept + { + return is_string() ? m_data.m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept + { + return is_string() ? m_data.m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept + { + return is_boolean() ? &m_data.m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept + { + return is_boolean() ? &m_data.m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept + { + return is_number_integer() ? &m_data.m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept + { + return is_number_integer() ? &m_data.m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept + { + return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept + { + return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept + { + return is_number_float() ? &m_data.m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept + { + return is_number_float() ? &m_data.m_value.number_float : nullptr; + } + + /// get a pointer to the value (binary) + binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept + { + return is_binary() ? m_data.m_value.binary : nullptr; + } + + /// get a pointer to the value (binary) + constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept + { + return is_binary() ? m_data.m_value.binary : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This function helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw type_error.303 if ReferenceType does not match underlying value + type of the current JSON + */ + template<typename ReferenceType, typename ThisType> + static ReferenceType get_ref_impl(ThisType& obj) + { + // delegate the call to get_ptr<>() + auto* ptr = obj.template get_ptr<typename std::add_pointer<ReferenceType>::type>(); + + if (JSON_HEDLEY_LIKELY(ptr != nullptr)) + { + return *ptr; + } + + JSON_THROW(type_error::create(303, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); + } + + public: + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /// @brief get a pointer value (implicit) + /// @sa https://json.nlohmann.me/api/basic_json/get_ptr/ + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value, int>::type = 0> + auto get_ptr() noexcept -> decltype(std::declval<basic_json_t&>().get_impl_ptr(std::declval<PointerType>())) + { + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast<PointerType>(nullptr)); + } + + /// @brief get a pointer value (implicit) + /// @sa https://json.nlohmann.me/api/basic_json/get_ptr/ + template < typename PointerType, typename std::enable_if < + std::is_pointer<PointerType>::value&& + std::is_const<typename std::remove_pointer<PointerType>::type>::value, int >::type = 0 > + constexpr auto get_ptr() const noexcept -> decltype(std::declval<const basic_json_t&>().get_impl_ptr(std::declval<PointerType>())) + { + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast<PointerType>(nullptr)); + } + + private: + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). + The value is converted by calling the @ref json_serializer<ValueType> + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer<ValueType>::from_json(*this, ret); + return ret; + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer<ValueType> has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + - @ref json_serializer<ValueType> does not have a `from_json()` method of + the form `ValueType from_json(const basic_json&)` + + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer<ValueType> `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector<short>`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map<std::string\, + json>`.,get__ValueType_const} + + @since version 2.1.0 + */ + template < typename ValueType, + detail::enable_if_t < + detail::is_default_constructible<ValueType>::value&& + detail::has_from_json<basic_json_t, ValueType>::value, + int > = 0 > + ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept( + JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>()))) + { + auto ret = ValueType(); + JSONSerializer<ValueType>::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). + The value is converted by calling the @ref json_serializer<ValueType> + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer<ValueType>::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer<ValueType> has a `from_json()` method of the form + `ValueType from_json(const basic_json&)` + + @note If @ref json_serializer<ValueType> has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer<ValueType> `from_json()` method throws + + @since version 2.1.0 + */ + template < typename ValueType, + detail::enable_if_t < + detail::has_non_default_from_json<basic_json_t, ValueType>::value, + int > = 0 > + ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept( + JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>()))) + { + return JSONSerializer<ValueType>::from_json(*this); + } + + /*! + @brief get special-case overload + + This overloads converts the current @ref basic_json in a different + @ref basic_json type + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this, converted into @a BasicJsonType + + @complexity Depending on the implementation of the called `from_json()` + method. + + @since version 3.2.0 + */ + template < typename BasicJsonType, + detail::enable_if_t < + detail::is_basic_json<BasicJsonType>::value, + int > = 0 > + BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const + { + return *this; + } + + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template<typename BasicJsonType, + detail::enable_if_t< + std::is_same<BasicJsonType, basic_json_t>::value, + int> = 0> + basic_json get_impl(detail::priority_tag<3> /*unused*/) const + { + return *this; + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template<typename PointerType, + detail::enable_if_t< + std::is_pointer<PointerType>::value, + int> = 0> + constexpr auto get_impl(detail::priority_tag<4> /*unused*/) const noexcept + -> decltype(std::declval<const basic_json_t&>().template get_ptr<PointerType>()) + { + // delegate the call to get_ptr + return get_ptr<PointerType>(); + } + + public: + /*! + @brief get a (pointer) value (explicit) + + Performs explicit type conversion between the JSON value and a compatible value if required. + + - If the requested type is a pointer to the internally stored JSON value that pointer is returned. + No copies are made. + + - If the requested type is the current @ref basic_json, or a different @ref basic_json convertible + from the current @ref basic_json. + + - Otherwise the value is converted by calling the @ref json_serializer<ValueType> `from_json()` + method. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @tparam ValueType if necessary + + @throw what @ref json_serializer<ValueType> `from_json()` method throws if conversion is required + + @since version 2.1.0 + */ + template < typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>> +#if defined(JSON_HAS_CPP_14) + constexpr +#endif + auto get() const noexcept( + noexcept(std::declval<const basic_json_t&>().template get_impl<ValueType>(detail::priority_tag<4> {}))) + -> decltype(std::declval<const basic_json_t&>().template get_impl<ValueType>(detail::priority_tag<4> {})) + { + // we cannot static_assert on ValueTypeCV being non-const, because + // there is support for get<const basic_json_t>(), which is why we + // still need the uncvref + static_assert(!std::is_reference<ValueTypeCV>::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return get_impl<ValueType>(detail::priority_tag<4> {}); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa see @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value, int>::type = 0> + auto get() noexcept -> decltype(std::declval<basic_json_t&>().template get_ptr<PointerType>()) + { + // delegate the call to get_ptr + return get_ptr<PointerType>(); + } + + /// @brief get a value (explicit) + /// @sa https://json.nlohmann.me/api/basic_json/get_to/ + template < typename ValueType, + detail::enable_if_t < + !detail::is_basic_json<ValueType>::value&& + detail::has_from_json<basic_json_t, ValueType>::value, + int > = 0 > + ValueType & get_to(ValueType& v) const noexcept(noexcept( + JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v))) + { + JSONSerializer<ValueType>::from_json(*this, v); + return v; + } + + // specialization to allow calling get_to with a basic_json value + // see https://github.com/nlohmann/json/issues/2175 + template<typename ValueType, + detail::enable_if_t < + detail::is_basic_json<ValueType>::value, + int> = 0> + ValueType & get_to(ValueType& v) const + { + v = *this; + return v; + } + + template < + typename T, std::size_t N, + typename Array = T (&)[N], // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + detail::enable_if_t < + detail::has_from_json<basic_json_t, Array>::value, int > = 0 > + Array get_to(T (&v)[N]) const // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + noexcept(noexcept(JSONSerializer<Array>::from_json( + std::declval<const basic_json_t&>(), v))) + { + JSONSerializer<Array>::from_json(*this, v); + return v; + } + + /// @brief get a reference value (implicit) + /// @sa https://json.nlohmann.me/api/basic_json/get_ref/ + template<typename ReferenceType, typename std::enable_if< + std::is_reference<ReferenceType>::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl<ReferenceType>(*this); + } + + /// @brief get a reference value (implicit) + /// @sa https://json.nlohmann.me/api/basic_json/get_ref/ + template < typename ReferenceType, typename std::enable_if < + std::is_reference<ReferenceType>::value&& + std::is_const<typename std::remove_reference<ReferenceType>::type>::value, int >::type = 0 > + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl<ReferenceType>(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw type_error.302 in case passed type @a ValueType is incompatible + to the JSON value type (e.g., the JSON value is of type boolean, but a + string is requested); see example below + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector<short>`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map<std::string\, + json>`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + detail::conjunction < + detail::negation<std::is_pointer<ValueType>>, + detail::negation<std::is_same<ValueType, std::nullptr_t>>, + detail::negation<std::is_same<ValueType, detail::json_ref<basic_json>>>, + detail::negation<std::is_same<ValueType, typename string_t::value_type>>, + detail::negation<detail::is_basic_json<ValueType>>, + detail::negation<std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>>, +#if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) + detail::negation<std::is_same<ValueType, std::string_view>>, +#endif +#if defined(JSON_HAS_CPP_17) && JSON_HAS_STATIC_RTTI + detail::negation<std::is_same<ValueType, std::any>>, +#endif + detail::is_detected_lazy<detail::get_template_function, const basic_json_t&, ValueType> + >::value, int >::type = 0 > + JSON_EXPLICIT operator ValueType() const + { + // delegate the call to get<>() const + return get<ValueType>(); + } + + /// @brief get a binary value + /// @sa https://json.nlohmann.me/api/basic_json/get_binary/ + binary_t& get_binary() + { + if (!is_binary()) + { + JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + } + + return *get_ptr<binary_t*>(); + } + + /// @brief get a binary value + /// @sa https://json.nlohmann.me/api/basic_json/get_binary/ + const binary_t& get_binary() const + { + if (!is_binary()) + { + JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + } + + return *get_ptr<const binary_t*>(); + } + + /// @} + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /// @brief access specified array element with bounds checking + /// @sa https://json.nlohmann.me/api/basic_json/at/ + reference at(size_type idx) + { + // at only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + JSON_TRY + { + return set_parent(m_data.m_value.array->at(idx)); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); + } + } + else + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); + } + } + + /// @brief access specified array element with bounds checking + /// @sa https://json.nlohmann.me/api/basic_json/at/ + const_reference at(size_type idx) const + { + // at only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + JSON_TRY + { + return m_data.m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); + } + } + else + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); + } + } + + /// @brief access specified object element with bounds checking + /// @sa https://json.nlohmann.me/api/basic_json/at/ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); + } + + auto it = m_data.m_value.object->find(key); + if (it == m_data.m_value.object->end()) + { + JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this)); + } + return set_parent(it->second); + } + + /// @brief access specified object element with bounds checking + /// @sa https://json.nlohmann.me/api/basic_json/at/ + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0> + reference at(KeyType && key) + { + // at only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); + } + + auto it = m_data.m_value.object->find(std::forward<KeyType>(key)); + if (it == m_data.m_value.object->end()) + { + JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this)); + } + return set_parent(it->second); + } + + /// @brief access specified object element with bounds checking + /// @sa https://json.nlohmann.me/api/basic_json/at/ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); + } + + auto it = m_data.m_value.object->find(key); + if (it == m_data.m_value.object->end()) + { + JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this)); + } + return it->second; + } + + /// @brief access specified object element with bounds checking + /// @sa https://json.nlohmann.me/api/basic_json/at/ + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0> + const_reference at(KeyType && key) const + { + // at only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); + } + + auto it = m_data.m_value.object->find(std::forward<KeyType>(key)); + if (it == m_data.m_value.object->end()) + { + JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward<KeyType>(key)), "' not found"), this)); + } + return it->second; + } + + /// @brief access specified array element + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_data.m_type = value_t::array; + m_data.m_value.array = create<array_t>(); + assert_invariant(); + } + + // operator[] only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // fill up array with null values if given idx is outside range + if (idx >= m_data.m_value.array->size()) + { +#if JSON_DIAGNOSTICS + // remember array size & capacity before resizing + const auto old_size = m_data.m_value.array->size(); + const auto old_capacity = m_data.m_value.array->capacity(); +#endif + m_data.m_value.array->resize(idx + 1); + +#if JSON_DIAGNOSTICS + if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity)) + { + // capacity has changed: update all parents + set_parents(); + } + else + { + // set parent for values added above + set_parents(begin() + static_cast<typename iterator::difference_type>(old_size), static_cast<typename iterator::difference_type>(idx + 1 - old_size)); + } +#endif + assert_invariant(); + } + + return m_data.m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); + } + + /// @brief access specified array element + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + return m_data.m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); + } + + /// @brief access specified object element + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + reference operator[](typename object_t::key_type key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_data.m_type = value_t::object; + m_data.m_value.object = create<object_t>(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + auto result = m_data.m_value.object->emplace(std::move(key), nullptr); + return set_parent(result.first->second); + } + + JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + } + + /// @brief access specified object element + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + auto it = m_data.m_value.object->find(key); + JSON_ASSERT(it != m_data.m_value.object->end()); + return it->second; + } + + JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + } + + // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC + // (they seemingly cannot be constrained to resolve the ambiguity) + template<typename T> + reference operator[](T* key) + { + return operator[](typename object_t::key_type(key)); + } + + template<typename T> + const_reference operator[](T* key) const + { + return operator[](typename object_t::key_type(key)); + } + + /// @brief access specified object element + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int > = 0 > + reference operator[](KeyType && key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_data.m_type = value_t::object; + m_data.m_value.object = create<object_t>(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + auto result = m_data.m_value.object->emplace(std::forward<KeyType>(key), nullptr); + return set_parent(result.first->second); + } + + JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + } + + /// @brief access specified object element + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int > = 0 > + const_reference operator[](KeyType && key) const + { + // const operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + auto it = m_data.m_value.object->find(std::forward<KeyType>(key)); + JSON_ASSERT(it != m_data.m_value.object->end()); + return it->second; + } + + JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + } + + private: + template<typename KeyType> + using is_comparable_with_object_key = detail::is_comparable < + object_comparator_t, const typename object_t::key_type&, KeyType >; + + template<typename ValueType> + using value_return_type = std::conditional < + detail::is_c_string_uncvref<ValueType>::value, + string_t, typename std::decay<ValueType>::type >; + + public: + /// @brief access specified object element with default value + /// @sa https://json.nlohmann.me/api/basic_json/value/ + template < class ValueType, detail::enable_if_t < + !detail::is_transparent<object_comparator_t>::value + && detail::is_getable<basic_json_t, ValueType>::value + && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 > + ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const + { + // value only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return it->template get<ValueType>(); + } + + return default_value; + } + + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + /// @brief access specified object element with default value + /// @sa https://json.nlohmann.me/api/basic_json/value/ + template < class ValueType, class ReturnType = typename value_return_type<ValueType>::type, + detail::enable_if_t < + !detail::is_transparent<object_comparator_t>::value + && detail::is_getable<basic_json_t, ReturnType>::value + && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 > + ReturnType value(const typename object_t::key_type& key, ValueType && default_value) const + { + // value only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return it->template get<ReturnType>(); + } + + return std::forward<ValueType>(default_value); + } + + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + /// @brief access specified object element with default value + /// @sa https://json.nlohmann.me/api/basic_json/value/ + template < class ValueType, class KeyType, detail::enable_if_t < + detail::is_transparent<object_comparator_t>::value + && !detail::is_json_pointer<KeyType>::value + && is_comparable_with_object_key<KeyType>::value + && detail::is_getable<basic_json_t, ValueType>::value + && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 > + ValueType value(KeyType && key, const ValueType& default_value) const + { + // value only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // if key is found, return value and given default value otherwise + const auto it = find(std::forward<KeyType>(key)); + if (it != end()) + { + return it->template get<ValueType>(); + } + + return default_value; + } + + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + /// @brief access specified object element via JSON Pointer with default value + /// @sa https://json.nlohmann.me/api/basic_json/value/ + template < class ValueType, class KeyType, class ReturnType = typename value_return_type<ValueType>::type, + detail::enable_if_t < + detail::is_transparent<object_comparator_t>::value + && !detail::is_json_pointer<KeyType>::value + && is_comparable_with_object_key<KeyType>::value + && detail::is_getable<basic_json_t, ReturnType>::value + && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 > + ReturnType value(KeyType && key, ValueType && default_value) const + { + // value only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // if key is found, return value and given default value otherwise + const auto it = find(std::forward<KeyType>(key)); + if (it != end()) + { + return it->template get<ReturnType>(); + } + + return std::forward<ValueType>(default_value); + } + + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + /// @brief access specified object element via JSON Pointer with default value + /// @sa https://json.nlohmann.me/api/basic_json/value/ + template < class ValueType, detail::enable_if_t < + detail::is_getable<basic_json_t, ValueType>::value + && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 > + ValueType value(const json_pointer& ptr, const ValueType& default_value) const + { + // value only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // if pointer resolves a value, return it or use default value + JSON_TRY + { + return ptr.get_checked(this).template get<ValueType>(); + } + JSON_INTERNAL_CATCH (out_of_range&) + { + return default_value; + } + } + + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + /// @brief access specified object element via JSON Pointer with default value + /// @sa https://json.nlohmann.me/api/basic_json/value/ + template < class ValueType, class ReturnType = typename value_return_type<ValueType>::type, + detail::enable_if_t < + detail::is_getable<basic_json_t, ReturnType>::value + && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 > + ReturnType value(const json_pointer& ptr, ValueType && default_value) const + { + // value only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // if pointer resolves a value, return it or use default value + JSON_TRY + { + return ptr.get_checked(this).template get<ReturnType>(); + } + JSON_INTERNAL_CATCH (out_of_range&) + { + return std::forward<ValueType>(default_value); + } + } + + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + template < class ValueType, class BasicJsonType, detail::enable_if_t < + detail::is_basic_json<BasicJsonType>::value + && detail::is_getable<basic_json_t, ValueType>::value + && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 > + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens) + ValueType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, const ValueType& default_value) const + { + return value(ptr.convert(), default_value); + } + + template < class ValueType, class BasicJsonType, class ReturnType = typename value_return_type<ValueType>::type, + detail::enable_if_t < + detail::is_basic_json<BasicJsonType>::value + && detail::is_getable<basic_json_t, ReturnType>::value + && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 > + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens) + ReturnType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, ValueType && default_value) const + { + return value(ptr.convert(), std::forward<ValueType>(default_value)); + } + + /// @brief access the first element + /// @sa https://json.nlohmann.me/api/basic_json/front/ + reference front() + { + return *begin(); + } + + /// @brief access the first element + /// @sa https://json.nlohmann.me/api/basic_json/front/ + const_reference front() const + { + return *cbegin(); + } + + /// @brief access the last element + /// @sa https://json.nlohmann.me/api/basic_json/back/ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /// @brief access the last element + /// @sa https://json.nlohmann.me/api/basic_json/back/ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /// @brief remove element given an iterator + /// @sa https://json.nlohmann.me/api/basic_json/erase/ + template < class IteratorType, detail::enable_if_t < + std::is_same<IteratorType, typename basic_json_t::iterator>::value || + std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int > = 0 > + IteratorType erase(IteratorType pos) + { + // make sure iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + } + + IteratorType result = end(); + + switch (m_data.m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + case value_t::binary: + { + if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) + { + JSON_THROW(invalid_iterator::create(205, "iterator out of range", this)); + } + + if (is_string()) + { + AllocatorType<string_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.string); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.string, 1); + m_data.m_value.string = nullptr; + } + else if (is_binary()) + { + AllocatorType<binary_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.binary); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.binary, 1); + m_data.m_value.binary = nullptr; + } + + m_data.m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_data.m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_data.m_value.array->erase(pos.m_it.array_iterator); + break; + } + + case value_t::null: + case value_t::discarded: + default: + JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + } + + return result; + } + + /// @brief remove elements given an iterator range + /// @sa https://json.nlohmann.me/api/basic_json/erase/ + template < class IteratorType, detail::enable_if_t < + std::is_same<IteratorType, typename basic_json_t::iterator>::value || + std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int > = 0 > + IteratorType erase(IteratorType first, IteratorType last) + { + // make sure iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) + { + JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value", this)); + } + + IteratorType result = end(); + + switch (m_data.m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + case value_t::binary: + { + if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin() + || !last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range", this)); + } + + if (is_string()) + { + AllocatorType<string_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.string); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.string, 1); + m_data.m_value.string = nullptr; + } + else if (is_binary()) + { + AllocatorType<binary_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.binary); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.binary, 1); + m_data.m_value.binary = nullptr; + } + + m_data.m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_data.m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_data.m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + case value_t::null: + case value_t::discarded: + default: + JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + } + + return result; + } + + private: + template < typename KeyType, detail::enable_if_t < + detail::has_erase_with_key_type<basic_json_t, KeyType>::value, int > = 0 > + size_type erase_internal(KeyType && key) + { + // this erase only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + } + + return m_data.m_value.object->erase(std::forward<KeyType>(key)); + } + + template < typename KeyType, detail::enable_if_t < + !detail::has_erase_with_key_type<basic_json_t, KeyType>::value, int > = 0 > + size_type erase_internal(KeyType && key) + { + // this erase only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + } + + const auto it = m_data.m_value.object->find(std::forward<KeyType>(key)); + if (it != m_data.m_value.object->end()) + { + m_data.m_value.object->erase(it); + return 1; + } + return 0; + } + + public: + + /// @brief remove element from a JSON object given a key + /// @sa https://json.nlohmann.me/api/basic_json/erase/ + size_type erase(const typename object_t::key_type& key) + { + // the indirection via erase_internal() is added to avoid making this + // function a template and thus de-rank it during overload resolution + return erase_internal(key); + } + + /// @brief remove element from a JSON object given a key + /// @sa https://json.nlohmann.me/api/basic_json/erase/ + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0> + size_type erase(KeyType && key) + { + return erase_internal(std::forward<KeyType>(key)); + } + + /// @brief remove element from a JSON array given an index + /// @sa https://json.nlohmann.me/api/basic_json/erase/ + void erase(const size_type idx) + { + // this erase only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + if (JSON_HEDLEY_UNLIKELY(idx >= size())) + { + JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); + } + + m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast<difference_type>(idx)); + } + else + { + JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + } + } + + /// @} + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /// @brief find an element in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/find/ + iterator find(const typename object_t::key_type& key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_data.m_value.object->find(key); + } + + return result; + } + + /// @brief find an element in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/find/ + const_iterator find(const typename object_t::key_type& key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_data.m_value.object->find(key); + } + + return result; + } + + /// @brief find an element in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/find/ + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0> + iterator find(KeyType && key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_data.m_value.object->find(std::forward<KeyType>(key)); + } + + return result; + } + + /// @brief find an element in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/find/ + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0> + const_iterator find(KeyType && key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_data.m_value.object->find(std::forward<KeyType>(key)); + } + + return result; + } + + /// @brief returns the number of occurrences of a key in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/count/ + size_type count(const typename object_t::key_type& key) const + { + // return 0 for all nonobject types + return is_object() ? m_data.m_value.object->count(key) : 0; + } + + /// @brief returns the number of occurrences of a key in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/count/ + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0> + size_type count(KeyType && key) const + { + // return 0 for all nonobject types + return is_object() ? m_data.m_value.object->count(std::forward<KeyType>(key)) : 0; + } + + /// @brief check the existence of an element in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/contains/ + bool contains(const typename object_t::key_type& key) const + { + return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end(); + } + + /// @brief check the existence of an element in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/contains/ + template<class KeyType, detail::enable_if_t< + detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0> + bool contains(KeyType && key) const + { + return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end(); + } + + /// @brief check the existence of an element in a JSON object given a JSON pointer + /// @sa https://json.nlohmann.me/api/basic_json/contains/ + bool contains(const json_pointer& ptr) const + { + return ptr.contains(this); + } + + template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0> + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens) + bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const + { + return ptr.contains(this); + } + + /// @} + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /// @brief returns an iterator to the first element + /// @sa https://json.nlohmann.me/api/basic_json/begin/ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /// @brief returns an iterator to the first element + /// @sa https://json.nlohmann.me/api/basic_json/begin/ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /// @brief returns a const iterator to the first element + /// @sa https://json.nlohmann.me/api/basic_json/cbegin/ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /// @brief returns an iterator to one past the last element + /// @sa https://json.nlohmann.me/api/basic_json/end/ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /// @brief returns an iterator to one past the last element + /// @sa https://json.nlohmann.me/api/basic_json/end/ + const_iterator end() const noexcept + { + return cend(); + } + + /// @brief returns an iterator to one past the last element + /// @sa https://json.nlohmann.me/api/basic_json/cend/ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /// @brief returns an iterator to the reverse-beginning + /// @sa https://json.nlohmann.me/api/basic_json/rbegin/ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /// @brief returns an iterator to the reverse-beginning + /// @sa https://json.nlohmann.me/api/basic_json/rbegin/ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /// @brief returns an iterator to the reverse-end + /// @sa https://json.nlohmann.me/api/basic_json/rend/ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /// @brief returns an iterator to the reverse-end + /// @sa https://json.nlohmann.me/api/basic_json/rend/ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /// @brief returns a const reverse iterator to the last element + /// @sa https://json.nlohmann.me/api/basic_json/crbegin/ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /// @brief returns a const reverse iterator to one before the first + /// @sa https://json.nlohmann.me/api/basic_json/crend/ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + public: + /// @brief wrapper to access iterator member functions in range-based for + /// @sa https://json.nlohmann.me/api/basic_json/items/ + /// @deprecated This function is deprecated since 3.1.0 and will be removed in + /// version 4.0.0 of the library. Please use @ref items() instead; + /// that is, replace `json::iterator_wrapper(j)` with `j.items()`. + JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) + static iteration_proxy<iterator> iterator_wrapper(reference ref) noexcept + { + return ref.items(); + } + + /// @brief wrapper to access iterator member functions in range-based for + /// @sa https://json.nlohmann.me/api/basic_json/items/ + /// @deprecated This function is deprecated since 3.1.0 and will be removed in + /// version 4.0.0 of the library. Please use @ref items() instead; + /// that is, replace `json::iterator_wrapper(j)` with `j.items()`. + JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) + static iteration_proxy<const_iterator> iterator_wrapper(const_reference ref) noexcept + { + return ref.items(); + } + + /// @brief helper to access iterator member functions in range-based for + /// @sa https://json.nlohmann.me/api/basic_json/items/ + iteration_proxy<iterator> items() noexcept + { + return iteration_proxy<iterator>(*this); + } + + /// @brief helper to access iterator member functions in range-based for + /// @sa https://json.nlohmann.me/api/basic_json/items/ + iteration_proxy<const_iterator> items() const noexcept + { + return iteration_proxy<const_iterator>(*this); + } + + /// @} + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /// @brief checks whether the container is empty. + /// @sa https://json.nlohmann.me/api/basic_json/empty/ + bool empty() const noexcept + { + switch (m_data.m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_data.m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_data.m_value.object->empty(); + } + + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + // all other types are nonempty + return false; + } + } + } + + /// @brief returns the number of elements + /// @sa https://json.nlohmann.me/api/basic_json/size/ + size_type size() const noexcept + { + switch (m_data.m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_data.m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_data.m_value.object->size(); + } + + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + // all other types have size 1 + return 1; + } + } + } + + /// @brief returns the maximum possible number of elements + /// @sa https://json.nlohmann.me/api/basic_json/max_size/ + size_type max_size() const noexcept + { + switch (m_data.m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_data.m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_data.m_value.object->max_size(); + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /// @brief clears the contents + /// @sa https://json.nlohmann.me/api/basic_json/clear/ + void clear() noexcept + { + switch (m_data.m_type) + { + case value_t::number_integer: + { + m_data.m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_data.m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_data.m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_data.m_value.boolean = false; + break; + } + + case value_t::string: + { + m_data.m_value.string->clear(); + break; + } + + case value_t::binary: + { + m_data.m_value.binary->clear(); + break; + } + + case value_t::array: + { + m_data.m_value.array->clear(); + break; + } + + case value_t::object: + { + m_data.m_value.object->clear(); + break; + } + + case value_t::null: + case value_t::discarded: + default: + break; + } + } + + /// @brief add an object to an array + /// @sa https://json.nlohmann.me/api/basic_json/push_back/ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + } + + // transform null object into an array + if (is_null()) + { + m_data.m_type = value_t::array; + m_data.m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + const auto old_capacity = m_data.m_value.array->capacity(); + m_data.m_value.array->push_back(std::move(val)); + set_parent(m_data.m_value.array->back(), old_capacity); + // if val is moved from, basic_json move constructor marks it null, so we do not call the destructor + } + + /// @brief add an object to an array + /// @sa https://json.nlohmann.me/api/basic_json/operator+=/ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /// @brief add an object to an array + /// @sa https://json.nlohmann.me/api/basic_json/push_back/ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + } + + // transform null object into an array + if (is_null()) + { + m_data.m_type = value_t::array; + m_data.m_value = value_t::array; + assert_invariant(); + } + + // add element to array + const auto old_capacity = m_data.m_value.array->capacity(); + m_data.m_value.array->push_back(val); + set_parent(m_data.m_value.array->back(), old_capacity); + } + + /// @brief add an object to an array + /// @sa https://json.nlohmann.me/api/basic_json/operator+=/ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /// @brief add an object to an object + /// @sa https://json.nlohmann.me/api/basic_json/push_back/ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) + { + JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + } + + // transform null object into an object + if (is_null()) + { + m_data.m_type = value_t::object; + m_data.m_value = value_t::object; + assert_invariant(); + } + + // add element to object + auto res = m_data.m_value.object->insert(val); + set_parent(res.first->second); + } + + /// @brief add an object to an object + /// @sa https://json.nlohmann.me/api/basic_json/operator+=/ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /// @brief add an object to an object + /// @sa https://json.nlohmann.me/api/basic_json/push_back/ + void push_back(initializer_list_t init) + { + if (is_object() && init.size() == 2 && (*init.begin())->is_string()) + { + basic_json&& key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref<string_t&>()), (init.begin() + 1)->moved_or_copied())); + } + else + { + push_back(basic_json(init)); + } + } + + /// @brief add an object to an object + /// @sa https://json.nlohmann.me/api/basic_json/operator+=/ + reference operator+=(initializer_list_t init) + { + push_back(init); + return *this; + } + + /// @brief add an object to an array + /// @sa https://json.nlohmann.me/api/basic_json/emplace_back/ + template<class... Args> + reference emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(311, detail::concat("cannot use emplace_back() with ", type_name()), this)); + } + + // transform null object into an array + if (is_null()) + { + m_data.m_type = value_t::array; + m_data.m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) + const auto old_capacity = m_data.m_value.array->capacity(); + m_data.m_value.array->emplace_back(std::forward<Args>(args)...); + return set_parent(m_data.m_value.array->back(), old_capacity); + } + + /// @brief add an object to an object if key does not exist + /// @sa https://json.nlohmann.me/api/basic_json/emplace/ + template<class... Args> + std::pair<iterator, bool> emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) + { + JSON_THROW(type_error::create(311, detail::concat("cannot use emplace() with ", type_name()), this)); + } + + // transform null object into an object + if (is_null()) + { + m_data.m_type = value_t::object; + m_data.m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + auto res = m_data.m_value.object->emplace(std::forward<Args>(args)...); + set_parent(res.first->second); + + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + + /// Helper for insertion of an iterator + /// @note: This uses std::distance to support GCC 4.8, + /// see https://github.com/nlohmann/json/pull/1257 + template<typename... Args> + iterator insert_iterator(const_iterator pos, Args&& ... args) + { + iterator result(this); + JSON_ASSERT(m_data.m_value.array != nullptr); + + auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator); + m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...); + result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos; + + // This could have been written as: + // result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val); + // but the return value of insert is missing in GCC 4.8, so it is written this way instead. + + set_parents(); + return result; + } + + /// @brief inserts element into array + /// @sa https://json.nlohmann.me/api/basic_json/insert/ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + } + + // insert to array and return iterator + return insert_iterator(pos, val); + } + + JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + } + + /// @brief inserts element into array + /// @sa https://json.nlohmann.me/api/basic_json/insert/ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /// @brief inserts copies of element into array + /// @sa https://json.nlohmann.me/api/basic_json/insert/ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + } + + // insert to array and return iterator + return insert_iterator(pos, cnt, val); + } + + JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + } + + /// @brief inserts range of elements into array + /// @sa https://json.nlohmann.me/api/basic_json/insert/ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (JSON_HEDLEY_UNLIKELY(!is_array())) + { + JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + } + + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + } + + if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) + { + JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container", this)); + } + + // insert to array and return iterator + return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator); + } + + /// @brief inserts elements from initializer list into array + /// @sa https://json.nlohmann.me/api/basic_json/insert/ + iterator insert(const_iterator pos, initializer_list_t ilist) + { + // insert only works for arrays + if (JSON_HEDLEY_UNLIKELY(!is_array())) + { + JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + } + + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + } + + // insert to array and return iterator + return insert_iterator(pos, ilist.begin(), ilist.end()); + } + + /// @brief inserts range of elements into object + /// @sa https://json.nlohmann.me/api/basic_json/insert/ + void insert(const_iterator first, const_iterator last) + { + // insert only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + } + + // passed iterators must belong to objects + if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", this)); + } + + m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + } + + /// @brief updates a JSON object from another object, overwriting existing keys + /// @sa https://json.nlohmann.me/api/basic_json/update/ + void update(const_reference j, bool merge_objects = false) + { + update(j.begin(), j.end(), merge_objects); + } + + /// @brief updates a JSON object from another object, overwriting existing keys + /// @sa https://json.nlohmann.me/api/basic_json/update/ + void update(const_iterator first, const_iterator last, bool merge_objects = false) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_data.m_type = value_t::object; + m_data.m_value.object = create<object_t>(); + assert_invariant(); + } + + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this)); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + } + + // passed iterators must belong to objects + if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) + { + JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object)); + } + + for (auto it = first; it != last; ++it) + { + if (merge_objects && it.value().is_object()) + { + auto it2 = m_data.m_value.object->find(it.key()); + if (it2 != m_data.m_value.object->end()) + { + it2->second.update(it.value(), true); + continue; + } + } + m_data.m_value.object->operator[](it.key()) = it.value(); +#if JSON_DIAGNOSTICS + m_data.m_value.object->operator[](it.key()).m_parent = this; +#endif + } + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible<value_t>::value&& + std::is_nothrow_move_assignable<value_t>::value&& + std::is_nothrow_move_constructible<json_value>::value&& // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + std::is_nothrow_move_assignable<json_value>::value + ) + { + std::swap(m_data.m_type, other.m_data.m_type); + std::swap(m_data.m_value, other.m_data.m_value); + + set_parents(); + other.set_parents(); + assert_invariant(); + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + friend void swap(reference left, reference right) noexcept ( + std::is_nothrow_move_constructible<value_t>::value&& + std::is_nothrow_move_assignable<value_t>::value&& + std::is_nothrow_move_constructible<json_value>::value&& // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + std::is_nothrow_move_assignable<json_value>::value + ) + { + left.swap(right); + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + void swap(array_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + { + // swap only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + using std::swap; + swap(*(m_data.m_value.array), other); + } + else + { + JSON_THROW(type_error::create(310, detail::concat("cannot use swap(array_t&) with ", type_name()), this)); + } + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + void swap(object_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + { + // swap only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + using std::swap; + swap(*(m_data.m_value.object), other); + } + else + { + JSON_THROW(type_error::create(310, detail::concat("cannot use swap(object_t&) with ", type_name()), this)); + } + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + void swap(string_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_string())) + { + using std::swap; + swap(*(m_data.m_value.string), other); + } + else + { + JSON_THROW(type_error::create(310, detail::concat("cannot use swap(string_t&) with ", type_name()), this)); + } + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + void swap(binary_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_binary())) + { + using std::swap; + swap(*(m_data.m_value.binary), other); + } + else + { + JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t&) with ", type_name()), this)); + } + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + void swap(typename binary_t::container_type& other) // NOLINT(bugprone-exception-escape) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_binary())) + { + using std::swap; + swap(*(m_data.m_value.binary), other); + } + else + { + JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t::container_type&) with ", type_name()), this)); + } + } + + /// @} + + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + // note parentheses around operands are necessary; see + // https://github.com/nlohmann/json/issues/1530 +#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \ + const auto lhs_type = lhs.type(); \ + const auto rhs_type = rhs.type(); \ + \ + if (lhs_type == rhs_type) /* NOLINT(readability/braces) */ \ + { \ + switch (lhs_type) \ + { \ + case value_t::array: \ + return (*lhs.m_data.m_value.array) op (*rhs.m_data.m_value.array); \ + \ + case value_t::object: \ + return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \ + \ + case value_t::null: \ + return (null_result); \ + \ + case value_t::string: \ + return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string); \ + \ + case value_t::boolean: \ + return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean); \ + \ + case value_t::number_integer: \ + return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer); \ + \ + case value_t::number_unsigned: \ + return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned); \ + \ + case value_t::number_float: \ + return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float); \ + \ + case value_t::binary: \ + return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary); \ + \ + case value_t::discarded: \ + default: \ + return (unordered_result); \ + } \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) \ + { \ + return static_cast<number_float_t>(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) \ + { \ + return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_integer); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) \ + { \ + return static_cast<number_float_t>(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_unsigned); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ + { \ + return static_cast<number_integer_t>(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_integer; \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_data.m_value.number_integer op static_cast<number_integer_t>(rhs.m_data.m_value.number_unsigned); \ + } \ + else if(compares_unordered(lhs, rhs))\ + {\ + return (unordered_result);\ + }\ + \ + return (default_result); + + JSON_PRIVATE_UNLESS_TESTED: + // returns true if: + // - any operand is NaN and the other operand is of number type + // - any operand is discarded + // in legacy mode, discarded values are considered ordered if + // an operation is computed as an odd number of inverses of others + static bool compares_unordered(const_reference lhs, const_reference rhs, bool inverse = false) noexcept + { + if ((lhs.is_number_float() && std::isnan(lhs.m_data.m_value.number_float) && rhs.is_number()) + || (rhs.is_number_float() && std::isnan(rhs.m_data.m_value.number_float) && lhs.is_number())) + { + return true; + } +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + return (lhs.is_discarded() || rhs.is_discarded()) && !inverse; +#else + static_cast<void>(inverse); + return lhs.is_discarded() || rhs.is_discarded(); +#endif + } + + private: + bool compares_unordered(const_reference rhs, bool inverse = false) const noexcept + { + return compares_unordered(*this, rhs, inverse); + } + + public: +#if JSON_HAS_THREE_WAY_COMPARISON + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + bool operator==(const_reference rhs) const noexcept + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + const_reference lhs = *this; + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + template<typename ScalarType> + requires std::is_scalar_v<ScalarType> + bool operator==(ScalarType rhs) const noexcept + { + return *this == basic_json(rhs); + } + + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + bool operator!=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !operator==(rhs); + } + + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD* + { + const_reference lhs = *this; + // default_result is used if we cannot compare values. In that case, + // we compare types. + JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD* + std::partial_ordering::equivalent, + std::partial_ordering::unordered, + lhs_type <=> rhs_type) // *NOPAD* + } + + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + template<typename ScalarType> + requires std::is_scalar_v<ScalarType> + std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD* + { + return *this <=> basic_json(rhs); // *NOPAD* + } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + // all operators that are computed as an odd number of inverses of others + // need to be overloaded to emulate the legacy comparison behavior + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator<=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !(rhs < *this); + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template<typename ScalarType> + requires std::is_scalar_v<ScalarType> + bool operator<=(ScalarType rhs) const noexcept + { + return *this <= basic_json(rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator>=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !(*this < rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template<typename ScalarType> + requires std::is_scalar_v<ScalarType> + bool operator>=(ScalarType rhs) const noexcept + { + return *this >= basic_json(rhs); + } +#endif +#else + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator==(const_reference lhs, ScalarType rhs) noexcept + { + return lhs == basic_json(rhs); + } + + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator==(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) == rhs; + } + + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } + return !(lhs == rhs); + } + + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept + { + return lhs != basic_json(rhs); + } + + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) != rhs; + } + + /// @brief comparison: less than + /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + // default_result is used if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type)) + } + + /// @brief comparison: less than + /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<(const_reference lhs, ScalarType rhs) noexcept + { + return lhs < basic_json(rhs); + } + + /// @brief comparison: less than + /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) < rhs; + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } + return !(rhs < lhs); + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept + { + return lhs <= basic_json(rhs); + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) <= rhs; + } + + /// @brief comparison: greater than + /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + // double inverse + if (compares_unordered(lhs, rhs)) + { + return false; + } + return !(lhs <= rhs); + } + + /// @brief comparison: greater than + /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>(const_reference lhs, ScalarType rhs) noexcept + { + return lhs > basic_json(rhs); + } + + /// @brief comparison: greater than + /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) > rhs; + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } + return !(lhs < rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept + { + return lhs >= basic_json(rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) >= rhs; + } +#endif + +#undef JSON_IMPLEMENT_OPERATOR + + /// @} + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ +#ifndef JSON_NO_IO + /// @brief serialize to stream + /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = o.width() > 0; + const auto indentation = pretty_print ? o.width() : 0; + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // do the actual serialization + serializer s(detail::output_adapter<char>(o), o.fill()); + s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation)); + return o; + } + + /// @brief serialize to stream + /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ + /// @deprecated This function is deprecated since 3.0.0 and will be removed in + /// version 4.0.0 of the library. Please use + /// operator<<(std::ostream&, const basic_json&) instead; that is, + /// replace calls like `j >> o;` with `o << j;`. + JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&)) + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } +#endif // JSON_NO_IO + /// @} + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /// @brief deserialize from a compatible input + /// @sa https://json.nlohmann.me/api/basic_json/parse/ + template<typename InputType> + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json parse(InputType&& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false) + { + basic_json result; + parser(detail::input_adapter(std::forward<InputType>(i)), cb, allow_exceptions, ignore_comments).parse(true, result); + return result; + } + + /// @brief deserialize from a pair of character iterators + /// @sa https://json.nlohmann.me/api/basic_json/parse/ + template<typename IteratorType> + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json parse(IteratorType first, + IteratorType last, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false) + { + basic_json result; + parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); + return result; + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) + static basic_json parse(detail::span_input_adapter&& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false) + { + basic_json result; + parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); + return result; + } + + /// @brief check if the input is valid JSON + /// @sa https://json.nlohmann.me/api/basic_json/accept/ + template<typename InputType> + static bool accept(InputType&& i, + const bool ignore_comments = false) + { + return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments).accept(true); + } + + /// @brief check if the input is valid JSON + /// @sa https://json.nlohmann.me/api/basic_json/accept/ + template<typename IteratorType> + static bool accept(IteratorType first, IteratorType last, + const bool ignore_comments = false) + { + return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) + static bool accept(detail::span_input_adapter&& i, + const bool ignore_comments = false) + { + return parser(i.get(), nullptr, false, ignore_comments).accept(true); + } + + /// @brief generate SAX events + /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ + template <typename InputType, typename SAX> + JSON_HEDLEY_NON_NULL(2) + static bool sax_parse(InputType&& i, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false) + { + auto ia = detail::input_adapter(std::forward<InputType>(i)); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict); + } + + /// @brief generate SAX events + /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ + template<class IteratorType, class SAX> + JSON_HEDLEY_NON_NULL(3) + static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false) + { + auto ia = detail::input_adapter(std::move(first), std::move(last)); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict); + } + + /// @brief generate SAX events + /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ + /// @deprecated This function is deprecated since 3.8.0 and will be removed in + /// version 4.0.0 of the library. Please use + /// sax_parse(ptr, ptr + len) instead. + template <typename SAX> + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...)) + JSON_HEDLEY_NON_NULL(2) + static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false) + { + auto ia = i.get(); + return format == input_format_t::json + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict); + } +#ifndef JSON_NO_IO + /// @brief deserialize from stream + /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/ + /// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in + /// version 4.0.0 of the library. Please use + /// operator>>(std::istream&, basic_json&) instead; that is, + /// replace calls like `j << i;` with `i >> j;`. + JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&)) + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + return operator>>(i, j); + } + + /// @brief deserialize from stream + /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + parser(detail::input_adapter(i)).parse(false, j); + return i; + } +#endif // JSON_NO_IO + /// @} + + /////////////////////////// + // convenience functions // + /////////////////////////// + + /// @brief return the type as string + /// @sa https://json.nlohmann.me/api/basic_json/type_name/ + JSON_HEDLEY_RETURNS_NON_NULL + const char* type_name() const noexcept + { + switch (m_data.m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::binary: + return "binary"; + case value_t::discarded: + return "discarded"; + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + default: + return "number"; + } + } + + JSON_PRIVATE_UNLESS_TESTED: + ////////////////////// + // member variables // + ////////////////////// + + struct data + { + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + data(const value_t v) + : m_type(v), m_value(v) + { + } + + data(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create<array_t>(cnt, val); + } + + data() noexcept = default; + data(data&&) noexcept = default; + data(const data&) noexcept = delete; + data& operator=(data&&) noexcept = delete; + data& operator=(const data&) noexcept = delete; + + ~data() noexcept + { + m_value.destroy(m_type); + } + }; + + data m_data = {}; + +#if JSON_DIAGNOSTICS + /// a pointer to a parent value (for debugging purposes) + basic_json* m_parent = nullptr; +#endif + + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + public: + /// @brief create a CBOR serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ + static std::vector<std::uint8_t> to_cbor(const basic_json& j) + { + std::vector<std::uint8_t> result; + to_cbor(j, result); + return result; + } + + /// @brief create a CBOR serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ + static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o) + { + binary_writer<std::uint8_t>(o).write_cbor(j); + } + + /// @brief create a CBOR serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ + static void to_cbor(const basic_json& j, detail::output_adapter<char> o) + { + binary_writer<char>(o).write_cbor(j); + } + + /// @brief create a MessagePack serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ + static std::vector<std::uint8_t> to_msgpack(const basic_json& j) + { + std::vector<std::uint8_t> result; + to_msgpack(j, result); + return result; + } + + /// @brief create a MessagePack serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ + static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o) + { + binary_writer<std::uint8_t>(o).write_msgpack(j); + } + + /// @brief create a MessagePack serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ + static void to_msgpack(const basic_json& j, detail::output_adapter<char> o) + { + binary_writer<char>(o).write_msgpack(j); + } + + /// @brief create a UBJSON serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ + static std::vector<std::uint8_t> to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector<std::uint8_t> result; + to_ubjson(j, result, use_size, use_type); + return result; + } + + /// @brief create a UBJSON serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ + static void to_ubjson(const basic_json& j, detail::output_adapter<std::uint8_t> o, + const bool use_size = false, const bool use_type = false) + { + binary_writer<std::uint8_t>(o).write_ubjson(j, use_size, use_type); + } + + /// @brief create a UBJSON serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ + static void to_ubjson(const basic_json& j, detail::output_adapter<char> o, + const bool use_size = false, const bool use_type = false) + { + binary_writer<char>(o).write_ubjson(j, use_size, use_type); + } + + /// @brief create a BJData serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ + static std::vector<std::uint8_t> to_bjdata(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector<std::uint8_t> result; + to_bjdata(j, result, use_size, use_type); + return result; + } + + /// @brief create a BJData serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ + static void to_bjdata(const basic_json& j, detail::output_adapter<std::uint8_t> o, + const bool use_size = false, const bool use_type = false) + { + binary_writer<std::uint8_t>(o).write_ubjson(j, use_size, use_type, true, true); + } + + /// @brief create a BJData serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ + static void to_bjdata(const basic_json& j, detail::output_adapter<char> o, + const bool use_size = false, const bool use_type = false) + { + binary_writer<char>(o).write_ubjson(j, use_size, use_type, true, true); + } + + /// @brief create a BSON serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ + static std::vector<std::uint8_t> to_bson(const basic_json& j) + { + std::vector<std::uint8_t> result; + to_bson(j, result); + return result; + } + + /// @brief create a BSON serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ + static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o) + { + binary_writer<std::uint8_t>(o).write_bson(j); + } + + /// @brief create a BSON serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ + static void to_bson(const basic_json& j, detail::output_adapter<char> o) + { + binary_writer<char>(o).write_bson(j); + } + + /// @brief create a JSON value from an input in CBOR format + /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ + template<typename InputType> + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_cbor(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward<InputType>(i)); + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in CBOR format + /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ + template<typename IteratorType> + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_cbor(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + return res ? result : basic_json(value_t::discarded); + } + + template<typename T> + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) + static basic_json from_cbor(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) + static basic_json from_cbor(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = i.get(); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in MessagePack format + /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ + template<typename InputType> + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_msgpack(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward<InputType>(i)); + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in MessagePack format + /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ + template<typename IteratorType> + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_msgpack(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + template<typename T> + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) + static basic_json from_msgpack(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_msgpack(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) + static basic_json from_msgpack(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = i.get(); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in UBJSON format + /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ + template<typename InputType> + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_ubjson(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward<InputType>(i)); + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in UBJSON format + /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ + template<typename IteratorType> + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_ubjson(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + template<typename T> + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) + static basic_json from_ubjson(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_ubjson(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) + static basic_json from_ubjson(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = i.get(); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in BJData format + /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ + template<typename InputType> + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward<InputType>(i)); + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in BJData format + /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ + template<typename IteratorType> + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in BSON format + /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ + template<typename InputType> + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bson(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward<InputType>(i)); + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in BSON format + /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ + template<typename IteratorType> + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bson(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + template<typename T> + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) + static basic_json from_bson(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_bson(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) + static basic_json from_bson(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions); + auto ia = i.get(); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + /// @} + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /// @brief access specified element via JSON Pointer + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0> + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens) + reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr) + { + return ptr.get_unchecked(this); + } + + /// @brief access specified element via JSON Pointer + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0> + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens) + const_reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr) const + { + return ptr.get_unchecked(this); + } + + /// @brief access specified element via JSON Pointer + /// @sa https://json.nlohmann.me/api/basic_json/at/ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0> + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens) + reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr) + { + return ptr.get_checked(this); + } + + /// @brief access specified element via JSON Pointer + /// @sa https://json.nlohmann.me/api/basic_json/at/ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0> + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens) + const_reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr) const + { + return ptr.get_checked(this); + } + + /// @brief return flattened JSON value + /// @sa https://json.nlohmann.me/api/basic_json/flatten/ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /// @brief unflatten a previously flattened JSON value + /// @sa https://json.nlohmann.me/api/basic_json/unflatten/ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /// @brief applies a JSON patch in-place without copying the object + /// @sa https://json.nlohmann.me/api/basic_json/patch/ + void patch_inplace(const basic_json& json_patch) + { + basic_json& result = *this; + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string & op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.empty()) + { + result = val; + return; + } + + // make sure the top element of the pointer exists + json_pointer const top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.back(); + ptr.pop_back(); + // parent must exist when performing patch add per RFC6902 specs + basic_json& parent = result.at(ptr); + + switch (parent.m_data.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = json_pointer::template array_index<basic_json_t>(last_path); + if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) + { + // avoid undefined behavior + JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &parent)); + } + + // default case: insert add offset + parent.insert(parent.begin() + static_cast<difference_type>(idx), val); + } + break; + } + + // if there exists a parent it cannot be primitive + case value_t::string: // LCOV_EXCL_LINE + case value_t::boolean: // LCOV_EXCL_LINE + case value_t::number_integer: // LCOV_EXCL_LINE + case value_t::number_unsigned: // LCOV_EXCL_LINE + case value_t::number_float: // LCOV_EXCL_LINE + case value_t::binary: // LCOV_EXCL_LINE + case value_t::discarded: // LCOV_EXCL_LINE + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [this, & result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.back(); + ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (JSON_HEDLEY_LIKELY(it != parent.end())) + { + parent.erase(it); + } + else + { + JSON_THROW(out_of_range::create(403, detail::concat("key '", last_path, "' not found"), this)); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(json_pointer::template array_index<basic_json_t>(last_path)); + } + }; + + // type check: top level value must be an array + if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &json_patch)); + } + + // iterate and apply the operations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json & + { + // find value + auto it = val.m_data.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : detail::concat("operation '", op, '\''); + + // check if desired value is present + if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end())) + { + // NOLINTNEXTLINE(performance-inefficient-string-concatenation) + JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have member '", member, "'"), &val)); + } + + // check if result is of type string + if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) + { + // NOLINTNEXTLINE(performance-inefficient-string-concatenation) + JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have string member '", member, "'"), &val)); + } + + // no error: return value + return it->second; + }; + + // type check: every element of the array must be an object + if (JSON_HEDLEY_UNLIKELY(!val.is_object())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &val)); + } + + // collect mandatory members + const auto op = get_value("op", "op", true).template get<std::string>(); + const auto path = get_value(op, "path", true).template get<std::string>(); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const auto from_path = get_value("move", "from", true).template get<std::string>(); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json const v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const auto from_path = get_value("copy", "from", true).template get<std::string>(); + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json const v = result.at(from_ptr); + + // The copy is functionally identical to an "add" + // operation at the target location using the value + // specified in the "from" member. + operation_add(ptr, v); + break; + } + + case patch_operations::test: + { + bool success = false; + JSON_TRY + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_INTERNAL_CATCH (out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (JSON_HEDLEY_UNLIKELY(!success)) + { + JSON_THROW(other_error::create(501, detail::concat("unsuccessful: ", val.dump()), &val)); + } + + break; + } + + case patch_operations::invalid: + default: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + JSON_THROW(parse_error::create(105, 0, detail::concat("operation value '", op, "' is invalid"), &val)); + } + } + } + } + + /// @brief applies a JSON patch to a copy of the current object + /// @sa https://json.nlohmann.me/api/basic_json/patch/ + basic_json patch(const basic_json& json_patch) const + { + basic_json result = *this; + result.patch_inplace(json_patch); + return result; + } + + /// @brief creates a diff as a JSON patch + /// @sa https://json.nlohmann.me/api/basic_json/diff/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json diff(const basic_json& source, const basic_json& target, + const std::string& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + return result; + } + + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + std::size_t i = 0; + while (i < source.size() && i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], detail::concat(path, '/', std::to_string(i))); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // We now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast<difference_type>(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", detail::concat(path, '/', std::to_string(i))} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", detail::concat(path, "/-")}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.cbegin(); it != source.cend(); ++it) + { + // escape the key name to be used in a JSON patch + const auto path_key = detail::concat(path, '/', detail::escape(it.key())); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path_key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, {"path", path_key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.cbegin(); it != target.cend(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto path_key = detail::concat(path, '/', detail::escape(it.key())); + result.push_back( + { + {"op", "add"}, {"path", path_key}, + {"value", it.value()} + }); + } + } + + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + break; + } + } + + return result; + } + /// @} + + //////////////////////////////// + // JSON Merge Patch functions // + //////////////////////////////// + + /// @name JSON Merge Patch functions + /// @{ + + /// @brief applies a JSON Merge Patch + /// @sa https://json.nlohmann.me/api/basic_json/merge_patch/ + void merge_patch(const basic_json& apply_patch) + { + if (apply_patch.is_object()) + { + if (!is_object()) + { + *this = object(); + } + for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it) + { + if (it.value().is_null()) + { + erase(it.key()); + } + else + { + operator[](it.key()).merge_patch(it.value()); + } + } + } + else + { + *this = apply_patch; + } + } + + /// @} +}; + +/// @brief user-defined to_string function for JSON values +/// @sa https://json.nlohmann.me/api/basic_json/to_string/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +std::string to_string(const NLOHMANN_BASIC_JSON_TPL& j) +{ + return j.dump(); +} + +inline namespace literals +{ +inline namespace json_literals +{ + +/// @brief user-defined string literal for JSON values +/// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json/ +JSON_HEDLEY_NON_NULL(1) +#if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) + inline nlohmann::json operator ""_json(const char* s, std::size_t n) +#else + inline nlohmann::json operator "" _json(const char* s, std::size_t n) +#endif +{ + return nlohmann::json::parse(s, s + n); +} + +/// @brief user-defined string literal for JSON pointer +/// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json_pointer/ +JSON_HEDLEY_NON_NULL(1) +#if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) + inline nlohmann::json::json_pointer operator ""_json_pointer(const char* s, std::size_t n) +#else + inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) +#endif +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +} // namespace json_literals +} // namespace literals +NLOHMANN_JSON_NAMESPACE_END + +/////////////////////// +// nonmember support // +/////////////////////// + +namespace std // NOLINT(cert-dcl58-cpp) +{ + +/// @brief hash value for JSON objects +/// @sa https://json.nlohmann.me/api/basic_json/std_hash/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct hash<nlohmann::NLOHMANN_BASIC_JSON_TPL> // NOLINT(cert-dcl58-cpp) +{ + std::size_t operator()(const nlohmann::NLOHMANN_BASIC_JSON_TPL& j) const + { + return nlohmann::detail::hash(j); + } +}; + +// specialization for std::less<value_t> +template<> +struct less< ::nlohmann::detail::value_t> // do not remove the space after '<', see https://github.com/nlohmann/json/pull/679 +{ + /*! + @brief compare two value_t enum values + @since version 3.0.0 + */ + bool operator()(::nlohmann::detail::value_t lhs, + ::nlohmann::detail::value_t rhs) const noexcept + { +#if JSON_HAS_THREE_WAY_COMPARISON + return std::is_lt(lhs <=> rhs); // *NOPAD* +#else + return ::nlohmann::detail::operator<(lhs, rhs); +#endif + } +}; + +// C++20 prohibit function specialization in the std namespace. +#ifndef JSON_HAS_CPP_20 + +/// @brief exchanges the values of two JSON objects +/// @sa https://json.nlohmann.me/api/basic_json/std_swap/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC_JSON_TPL& j2) noexcept( // NOLINT(readability-inconsistent-declaration-parameter-name, cert-dcl58-cpp) + is_nothrow_move_constructible<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value&& // NOLINT(misc-redundant-expression,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + is_nothrow_move_assignable<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value) +{ + j1.swap(j2); +} + +#endif + +} // namespace std + +#if JSON_USE_GLOBAL_UDLS + #if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) + using nlohmann::literals::json_literals::operator ""_json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers) + using nlohmann::literals::json_literals::operator ""_json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers) + #else + using nlohmann::literals::json_literals::operator "" _json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers) + using nlohmann::literals::json_literals::operator "" _json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers) + #endif +#endif + +// #include <nlohmann/detail/macro_unscope.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +// restore clang diagnostic settings +#if defined(__clang__) + #pragma clang diagnostic pop +#endif + +// clean up +#undef JSON_ASSERT +#undef JSON_INTERNAL_CATCH +#undef JSON_THROW +#undef JSON_PRIVATE_UNLESS_TESTED +#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION +#undef NLOHMANN_BASIC_JSON_TPL +#undef JSON_EXPLICIT +#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL +#undef JSON_INLINE_VARIABLE +#undef JSON_NO_UNIQUE_ADDRESS +#undef JSON_DISABLE_ENUM_SERIALIZATION +#undef JSON_USE_GLOBAL_UDLS + +#ifndef JSON_TEST_KEEP_MACROS + #undef JSON_CATCH + #undef JSON_TRY + #undef JSON_HAS_CPP_11 + #undef JSON_HAS_CPP_14 + #undef JSON_HAS_CPP_17 + #undef JSON_HAS_CPP_20 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #undef JSON_HAS_THREE_WAY_COMPARISON + #undef JSON_HAS_RANGES + #undef JSON_HAS_STATIC_RTTI + #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +// #include <nlohmann/thirdparty/hedley/hedley_undef.hpp> +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me> +// SPDX-License-Identifier: MIT + + + +#undef JSON_HEDLEY_ALWAYS_INLINE +#undef JSON_HEDLEY_ARM_VERSION +#undef JSON_HEDLEY_ARM_VERSION_CHECK +#undef JSON_HEDLEY_ARRAY_PARAM +#undef JSON_HEDLEY_ASSUME +#undef JSON_HEDLEY_BEGIN_C_DECLS +#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#undef JSON_HEDLEY_CLANG_HAS_FEATURE +#undef JSON_HEDLEY_CLANG_HAS_WARNING +#undef JSON_HEDLEY_COMPCERT_VERSION +#undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#undef JSON_HEDLEY_CONCAT +#undef JSON_HEDLEY_CONCAT3 +#undef JSON_HEDLEY_CONCAT3_EX +#undef JSON_HEDLEY_CONCAT_EX +#undef JSON_HEDLEY_CONST +#undef JSON_HEDLEY_CONSTEXPR +#undef JSON_HEDLEY_CONST_CAST +#undef JSON_HEDLEY_CPP_CAST +#undef JSON_HEDLEY_CRAY_VERSION +#undef JSON_HEDLEY_CRAY_VERSION_CHECK +#undef JSON_HEDLEY_C_DECL +#undef JSON_HEDLEY_DEPRECATED +#undef JSON_HEDLEY_DEPRECATED_FOR +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#undef JSON_HEDLEY_DIAGNOSTIC_POP +#undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#undef JSON_HEDLEY_DMC_VERSION +#undef JSON_HEDLEY_DMC_VERSION_CHECK +#undef JSON_HEDLEY_EMPTY_BASES +#undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#undef JSON_HEDLEY_END_C_DECLS +#undef JSON_HEDLEY_FLAGS +#undef JSON_HEDLEY_FLAGS_CAST +#undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_BUILTIN +#undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_EXTENSION +#undef JSON_HEDLEY_GCC_HAS_FEATURE +#undef JSON_HEDLEY_GCC_HAS_WARNING +#undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#undef JSON_HEDLEY_GCC_VERSION +#undef JSON_HEDLEY_GCC_VERSION_CHECK +#undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#undef JSON_HEDLEY_GNUC_HAS_FEATURE +#undef JSON_HEDLEY_GNUC_HAS_WARNING +#undef JSON_HEDLEY_GNUC_VERSION +#undef JSON_HEDLEY_GNUC_VERSION_CHECK +#undef JSON_HEDLEY_HAS_ATTRIBUTE +#undef JSON_HEDLEY_HAS_BUILTIN +#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_HAS_EXTENSION +#undef JSON_HEDLEY_HAS_FEATURE +#undef JSON_HEDLEY_HAS_WARNING +#undef JSON_HEDLEY_IAR_VERSION +#undef JSON_HEDLEY_IAR_VERSION_CHECK +#undef JSON_HEDLEY_IBM_VERSION +#undef JSON_HEDLEY_IBM_VERSION_CHECK +#undef JSON_HEDLEY_IMPORT +#undef JSON_HEDLEY_INLINE +#undef JSON_HEDLEY_INTEL_CL_VERSION +#undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#undef JSON_HEDLEY_INTEL_VERSION +#undef JSON_HEDLEY_INTEL_VERSION_CHECK +#undef JSON_HEDLEY_IS_CONSTANT +#undef JSON_HEDLEY_IS_CONSTEXPR_ +#undef JSON_HEDLEY_LIKELY +#undef JSON_HEDLEY_MALLOC +#undef JSON_HEDLEY_MCST_LCC_VERSION +#undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#undef JSON_HEDLEY_MESSAGE +#undef JSON_HEDLEY_MSVC_VERSION +#undef JSON_HEDLEY_MSVC_VERSION_CHECK +#undef JSON_HEDLEY_NEVER_INLINE +#undef JSON_HEDLEY_NON_NULL +#undef JSON_HEDLEY_NO_ESCAPE +#undef JSON_HEDLEY_NO_RETURN +#undef JSON_HEDLEY_NO_THROW +#undef JSON_HEDLEY_NULL +#undef JSON_HEDLEY_PELLES_VERSION +#undef JSON_HEDLEY_PELLES_VERSION_CHECK +#undef JSON_HEDLEY_PGI_VERSION +#undef JSON_HEDLEY_PGI_VERSION_CHECK +#undef JSON_HEDLEY_PREDICT +#undef JSON_HEDLEY_PRINTF_FORMAT +#undef JSON_HEDLEY_PRIVATE +#undef JSON_HEDLEY_PUBLIC +#undef JSON_HEDLEY_PURE +#undef JSON_HEDLEY_REINTERPRET_CAST +#undef JSON_HEDLEY_REQUIRE +#undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#undef JSON_HEDLEY_REQUIRE_MSG +#undef JSON_HEDLEY_RESTRICT +#undef JSON_HEDLEY_RETURNS_NON_NULL +#undef JSON_HEDLEY_SENTINEL +#undef JSON_HEDLEY_STATIC_ASSERT +#undef JSON_HEDLEY_STATIC_CAST +#undef JSON_HEDLEY_STRINGIFY +#undef JSON_HEDLEY_STRINGIFY_EX +#undef JSON_HEDLEY_SUNPRO_VERSION +#undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#undef JSON_HEDLEY_TINYC_VERSION +#undef JSON_HEDLEY_TINYC_VERSION_CHECK +#undef JSON_HEDLEY_TI_ARMCL_VERSION +#undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL2000_VERSION +#undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL430_VERSION +#undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL6X_VERSION +#undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL7X_VERSION +#undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#undef JSON_HEDLEY_TI_CLPRU_VERSION +#undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#undef JSON_HEDLEY_TI_VERSION +#undef JSON_HEDLEY_TI_VERSION_CHECK +#undef JSON_HEDLEY_UNAVAILABLE +#undef JSON_HEDLEY_UNLIKELY +#undef JSON_HEDLEY_UNPREDICTABLE +#undef JSON_HEDLEY_UNREACHABLE +#undef JSON_HEDLEY_UNREACHABLE_RETURN +#undef JSON_HEDLEY_VERSION +#undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#undef JSON_HEDLEY_VERSION_DECODE_MINOR +#undef JSON_HEDLEY_VERSION_DECODE_REVISION +#undef JSON_HEDLEY_VERSION_ENCODE +#undef JSON_HEDLEY_WARNING +#undef JSON_HEDLEY_WARN_UNUSED_RESULT +#undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#undef JSON_HEDLEY_FALL_THROUGH + + + +#endif // INCLUDE_NLOHMANN_JSON_HPP_ diff --git a/tools/allowlist.txt b/tools/allowlist.txt new file mode 100644 index 0000000..b2e7737 --- /dev/null +++ b/tools/allowlist.txt @@ -0,0 +1,11 @@ +# Extra domains the PS5 may reach through tools/sony_dns.py (one per line, '#' starts a comment). +# +# Everything Nubix needs is built into the script; you do NOT need to add anything to play. +# Each entry also allows all of its subdomains ("example.com" allows "www.example.com"). +# Sony / PlayStation domains stay blocked even if you list them here. +# +# You are responsible for what you allow. Only add domains you know and trust. +# +# Examples (remove the leading '#' to enable): +# youtube.com +# googlevideo.com diff --git a/tools/cli_gssv.cpp b/tools/cli_gssv.cpp new file mode 100644 index 0000000..29c9b30 --- /dev/null +++ b/tools/cli_gssv.cpp @@ -0,0 +1,252 @@ +// Nubix — xc-cli subcommands for the catalog + gssv session API (live testing). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include "cli_gssv.hpp" + +#include <algorithm> +#include <atomic> +#include <cctype> +#include <chrono> +#include <csignal> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <memory> +#include <string> +#include <thread> +#include <vector> + +#include "core/auth.hpp" +#include "core/catalog.hpp" +#include "core/config.hpp" +#include "core/gssv.hpp" +#include "core/log.hpp" +#include "platform/platform.hpp" + +using namespace xc; + +namespace { + +std::atomic<bool> g_cancel{false}; + +void onCancel(int) { g_cancel = true; } + +void sleepMs(int ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } + +int gssvUsage() { + std::fprintf(stderr, + "usage: xc-cli titles [filter]\n" + " xc-cli consoles\n" + " xc-cli play <titleId> [--no-connect] [--timeout S]\n" + " xc-cli play --home <serverId> [--timeout S]\n"); + return 2; +} + +std::string lower(std::string s) { + for (char& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); + return s; +} + +bool ensureAuth(Config& cfg, Auth& auth) { + if (!auth.isLoggedIn()) { + std::fprintf(stderr, "not logged in (run: xc-cli login)\n"); + return false; + } + std::string err; + if (!auth.ensureFresh(err)) { + std::fprintf(stderr, "auth: %s\n", err.c_str()); + return false; + } + cfg.save(); + return true; +} + +int cmdTitles(Config& cfg, Auth& auth, const std::string& filter) { + if (!ensureAuth(cfg, auth)) return 1; + Catalog cat(cfg); + std::vector<Title> titles; + std::string err; + const auto t0 = std::chrono::steady_clock::now(); + if (!cat.fetchCloudTitles(titles, err)) { + std::fprintf(stderr, "titles: %s\n", err.c_str()); + return 1; + } + const double secs = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count(); + const std::string f = lower(filter); + size_t shown = 0; + for (const auto& t : titles) { + if (!f.empty() && lower(t.name).find(f) == std::string::npos && lower(t.titleId).find(f) == std::string::npos) + continue; + std::printf("%-28s %-14s %-3s %-4s %-3s %-4s %s%s%s\n", t.titleId.c_str(), t.productId.c_str(), + t.gamePass ? "GP" : "", t.f2p ? "F2P" : "", t.hasEntitlement ? "OWN" : "", + t.f2pOnly ? "ADS" : "", t.name.c_str(), t.publisher.empty() ? "" : " / ", + t.publisher.c_str()); + if (!filter.empty() && !t.imageUrl.empty()) std::printf(" art: %s\n", t.imageUrl.c_str()); + ++shown; + } + std::printf("%zu/%zu playable titles (%.1f s)\n", shown, titles.size(), secs); + return 0; +} + +int cmdConsoles(Config& cfg, Auth& auth) { + if (!ensureAuth(cfg, auth)) return 1; + Catalog cat(cfg); + std::vector<Console> consoles; + std::string err; + if (!cat.fetchConsoles(consoles, err)) { + std::fprintf(stderr, "consoles: %s\n", err.c_str()); + return 1; + } + for (const auto& c : consoles) + std::printf("%-20s %-16s %-18s %s\n", c.serverId.c_str(), c.consoleType.c_str(), c.powerState.c_str(), + c.name.c_str()); + std::printf("%zu consoles\n", consoles.size()); + return 0; +} + +// Start a session, drive it to Provisioned (or ReadyToConnect with --no-connect), then DELETE it. +int cmdPlay(Config& cfg, Auth& auth, SessionKind kind, const std::string& id, bool doConnect, int timeoutSec) { + if (!ensureAuth(cfg, auth)) return 1; + const Tokens tk = cfg.tokens(); + // xhome: the console agent only accepts the android/720 fingerprint (see gssv::deviceTierFor). + Settings gs = cfg.settings(); + gs.resolution = gssv::deviceTierFor(kind == SessionKind::Home, gs.resolution); + + struct Offering { + const char* name; + std::string base, token; + }; + std::vector<Offering> offerings; + if (kind == SessionKind::Home) { + offerings.push_back({"xhome", tk.xhomeBase, tk.xhomeGs}); + } else { + if (!tk.xcloudGs.empty()) offerings.push_back({"xgpuweb", tk.xcloudBase, tk.xcloudGs}); + if (!tk.xcloudF2pGs.empty() && (offerings.empty() || gs.f2pFallback)) + offerings.push_back({"xgpuwebf2p", tk.xcloudF2pBase, tk.xcloudF2pGs}); + } + if (offerings.empty() || offerings.front().token.empty()) { + std::fprintf(stderr, "no %s offering token for this account\n", kind == SessionKind::Home ? "xhome" : "xCloud"); + return 1; + } + + // Our own handler so Ctrl-C still DELETEs the session. + g_cancel = false; + auto prevInt = std::signal(SIGINT, onCancel); + auto prevTerm = std::signal(SIGTERM, onCancel); + + int rc = 1; + std::string err; + SessionInfo s; + const Offering* used = nullptr; + std::unique_ptr<GssvClient> client; + for (const Offering& off : offerings) { + client.reset(new GssvClient(off.base, off.token, gs)); + std::printf("offering %s (%s), osName %s\n", off.name, off.base.c_str(), + gssv::osNameForResolution(gs.resolution)); + if (!client->cleanupActive(kind)) std::printf(" (active-session cleanup reported a problem, continuing)\n"); + if (client->play(kind, id, s, err)) { + used = &off; + break; + } + std::fprintf(stderr, "play: %s\n", err.c_str()); + if (!s.errorCode.empty()) std::fprintf(stderr, " code %s %s\n", s.errorCode.c_str(), s.errorMessage.c_str()); + if (s.errorCode != kErrOfferingDoesNotContainTitle) break; + } + if (!used) { + std::signal(SIGINT, prevInt); + std::signal(SIGTERM, prevTerm); + return 1; + } + std::printf("session %s (%s) state %s\n", s.sessionId.c_str(), s.sessionPath.c_str(), s.state.c_str()); + + bool connected = false; + bool waitShown = false; + std::string lastState; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeoutSec); + while (!g_cancel) { + if (std::chrono::steady_clock::now() > deadline) { + std::fprintf(stderr, "timeout after %d s in state %s\n", timeoutSec, s.state.c_str()); + break; + } + if (!client->pollState(s, err)) { + std::fprintf(stderr, "state: %s\n", err.c_str()); + break; + } + if (s.state != lastState) { + std::printf("state: %s\n", s.state.c_str()); + lastState = s.state; + } + if (s.state == "WaitingForResources" && kind == SessionKind::Cloud && !waitShown) { + waitShown = true; + int w = client->waitTimeSeconds(id); + if (w >= 0) std::printf(" estimated wait: %d s\n", w); + } else if (s.state == "ReadyToConnect" && !connected) { + if (!doConnect) { + std::printf("ReadyToConnect reached (--no-connect)\n"); + rc = 0; + break; + } + std::string lpt; + if (!auth.fetchLpt(lpt, err)) { + std::fprintf(stderr, "lpt: %s\n", err.c_str()); + break; + } + if (!client->connect(s, lpt, err)) { + std::fprintf(stderr, "connect: %s\n", err.c_str()); + break; + } + std::printf("connect sent\n"); + connected = true; + } else if (s.state == "Provisioned") { + const int ka = client->keepAliveSeconds(s); + std::printf("provisioned; keepalive interval %d s\n", ka); + std::printf("keepalive: %s\n", client->keepAlive(s) ? "ok" : "failed"); + rc = 0; + break; + } else if (s.state == "Failed") { + std::fprintf(stderr, "failed: %s %s\n", s.errorCode.c_str(), s.errorMessage.c_str()); + break; + } + sleepMs(1000); + } + if (g_cancel) std::fprintf(stderr, "cancelled\n"); + std::printf("stopping session: %s\n", client->stop(s) ? "ok" : "failed"); + + std::signal(SIGINT, prevInt); + std::signal(SIGTERM, prevTerm); + return rc; +} + +} // namespace + +bool cli_gssv_handles(const char* cmd) { + return cmd && (!std::strcmp(cmd, "titles") || !std::strcmp(cmd, "consoles") || !std::strcmp(cmd, "play")); +} + +int cli_gssv(int argc, char** argv) { + if (argc < 1 || !cli_gssv_handles(argv[0])) return gssvUsage(); + const std::string cmd = argv[0]; + + Config cfg(platform::dataDir() + "/config.json"); + if (!cfg.load()) XC_LOGW("config %s could not be parsed, using defaults", cfg.path().c_str()); + Auth auth(cfg); + + if (cmd == "titles") return cmdTitles(cfg, auth, argc > 1 ? argv[1] : ""); + if (cmd == "consoles") return cmdConsoles(cfg, auth); + + // play + SessionKind kind = SessionKind::Cloud; + bool doConnect = true; + int timeoutSec = 300; + std::string id; + for (int i = 1; i < argc; ++i) { + const std::string a = argv[i]; + if (a == "--home") kind = SessionKind::Home; + else if (a == "--no-connect") doConnect = false; + else if (a == "--timeout" && i + 1 < argc) timeoutSec = std::max(10, std::atoi(argv[++i])); + else if (!a.empty() && a[0] != '-' && id.empty()) id = a; + else return gssvUsage(); + } + if (id.empty()) return gssvUsage(); + return cmdPlay(cfg, auth, kind, id, doConnect, timeoutSec); +} diff --git a/tools/cli_gssv.hpp b/tools/cli_gssv.hpp new file mode 100644 index 0000000..56e1907 --- /dev/null +++ b/tools/cli_gssv.hpp @@ -0,0 +1,19 @@ +// Nubix — xc-cli subcommands for the catalog + gssv session API (live testing). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#pragma once + +// Run a gssv/catalog subcommand. argv[0] is the subcommand name: +// titles [filter] playable cloud titles (both offerings merged, hydrated) +// consoles xHome consoles +// play <titleId> [--no-connect] [--timeout S] +// start a cloud session, poll /state (queue, /connect at +// ReadyToConnect) until Provisioned, then DELETE it +// play --home <serverId> [--timeout S] same against a home console (xhome offering) +// Precondition: the caller has run platform::init(), logInit() and Http::globalInit(). +// Uses the same config.json (platform::dataDir()) as the app. Returns a process exit code +// (0 ok, 1 failure, 2 usage). +int cli_gssv(int argc, char** argv); + +// True if `cmd` is handled by cli_gssv(). +bool cli_gssv_handles(const char* cmd); diff --git a/tools/linkcheck.cpp b/tools/linkcheck.cpp new file mode 100644 index 0000000..0ef6eed --- /dev/null +++ b/tools/linkcheck.cpp @@ -0,0 +1,60 @@ +// Nubix — dependency link check (not shipped). Touches every third-party library the app +// uses so that a full static link of every dependency is exercised independently of the app. +// Build: -DXC_BUILD_LINKCHECK=ON -> target xc-linkcheck. Running it prints a local SDP offer. +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <SDL.h> +#include <SDL_ttf.h> +#include <curl/curl.h> +#include <opus.h> +#include <openssl/opensslv.h> +#include <openssl/rand.h> + +#include <chrono> +#include <cstdio> +#include <future> +#include <rtc/rtc.hpp> + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavutil/frame.h> +} + +int main(int, char**) { + rtc::InitLogger(rtc::LogLevel::Warning); + rtc::Configuration cfg; + cfg.disableAutoNegotiation = true; + auto pc = std::make_shared<rtc::PeerConnection>(cfg); + rtc::Description::Video v("video", rtc::Description::Direction::RecvOnly); + v.addH264Codec(102, std::string("level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f")); + auto vt = pc->addTrack(v); + vt->chainMediaHandler(std::make_shared<rtc::RtcpReceivingSession>()); + rtc::Description::Audio a("audio", rtc::Description::Direction::RecvOnly); + a.addOpusCodec(111); + auto at = pc->addTrack(a); + rtc::DataChannelInit init; + init.protocol = "messageV1"; + auto dc = pc->createDataChannel("message", init); + std::promise<void> done; + std::atomic<bool> fired{false}; + pc->onGatheringStateChange([&](rtc::PeerConnection::GatheringState s) { + if (s == rtc::PeerConnection::GatheringState::Complete && !fired.exchange(true)) done.set_value(); + }); + pc->setLocalDescription(rtc::Description::Type::Offer); + done.get_future().wait_for(std::chrono::seconds(5)); + if (auto ld = pc->localDescription()) std::printf("%s\n", std::string(*ld).c_str()); + + unsigned char rnd[8]; + std::printf("openssl %s rand=%d\n", OPENSSL_VERSION_TEXT, RAND_bytes(rnd, sizeof(rnd))); + std::printf("curl %s\n", curl_version()); + std::printf("opus %s\n", opus_get_version_string()); + std::printf("avcodec h264 %s\n", avcodec_find_decoder(AV_CODEC_ID_H264) ? "yes" : "no"); + AVFrame* f = av_frame_alloc(); + av_frame_free(&f); + SDL_version sv; + SDL_GetVersion(&sv); + std::printf("SDL %d.%d.%d ttf init=%d\n", sv.major, sv.minor, sv.patch, TTF_Init()); + TTF_Quit(); + pc->close(); + return 0; +} diff --git a/tools/sony_dns.py b/tools/sony_dns.py new file mode 100755 index 0000000..30329a3 --- /dev/null +++ b/tools/sony_dns.py @@ -0,0 +1,309 @@ +#!/usr/bin/env python3 +# Nubix — protective DNS server for a jailbroken PS5: allow only what Nubix needs, block the rest. +# Copyright (C) 2026 Nubix contributors +# SPDX-License-Identifier: GPL-3.0-only +""" +Run this on a PC in the same network as the PS5, then set the PS5's Primary AND Secondary DNS to +the address this script prints. + +Allowlist mode: every DNS query is BLOCKED (answered with 0.0.0.0 / ::) unless the name belongs to + 1. the domains Nubix needs for Xbox Cloud Gaming (NUBIX_DOMAINS below), or + 2. a domain you add yourself in allowlist.txt (next to this script) or with --allow. +Sony / PlayStation domains are ALWAYS blocked, even if they appear in your allowlist, so the +console cannot reach PSN, download a system update or send telemetry. +Allowed names are forwarded unchanged to an upstream DNS server (default 1.1.1.1). + +You are responsible for every domain you add to the allowlist. + +Only the Python standard library is used (Python 3.8+). + + Windows (terminal "Run as administrator"): python tools\\sony_dns.py + macOS / Linux: sudo python3 tools/sony_dns.py + +Options: + --allow example.com allow one more domain (with its subdomains; can be repeated) + --allowlist FILE allowlist file (default: allowlist.txt next to this script) + --map host=IP answer host with a fixed IPv4 address, e.g. for a jailbreak page + (works even for Sony names; can be repeated) + --upstream 9.9.9.9 upstream DNS server (default 1.1.1.1) + --log-all print every query (ALLOW / BLOCK / MAP) to see what the PS5 asks for + --quiet print nothing per query + --port 53 listening port (the PS5 always uses 53) +The PC has to stay on while the PS5 is online; give it a fixed IP in your router. +""" + +import argparse +import ipaddress +import os +import socket +import socketserver +import struct +import sys +import threading +import time + +# What Nubix itself contacts (see src/core/auth.cpp, gssv.cpp, catalog.cpp, stream/webrtc.cpp). +# Each entry also covers its subdomains. +NUBIX_DOMAINS = [ + "login.microsoftonline.com", # Microsoft sign-in (device code, tokens) + "login.live.com", # transfer token for starting cloud sessions + "xboxlive.com", # Xbox Live tokens, profile, cloud gaming API and regions + "displaycatalog.mp.microsoft.com", # game names + "store-images.s-microsoft.com", # box art + "stun.l.google.com", # WebRTC: discovers the public address for the stream +] + +# Always blocked, whatever the allowlist says (only --map can answer one of these names). +SONY_DOMAINS = [ + "playstation.com", + "playstation.net", + "playstation.org", + "sonyentertainmentnetwork.com", + "scea.com", + "sbdnpd.com", + "ribob01.net", +] + +TYPE_A, TYPE_AAAA = 1, 28 +ANSWER_TTL = 300 +UPSTREAM_TIMEOUT = 4.0 + +_print_lock = threading.Lock() + + +def log(msg): + with _print_lock: + print(time.strftime("%H:%M:%S"), msg, flush=True) + + +def normalize(domain): + return domain.strip().lower().strip(".") + + +def matches(name, domains): + return any(name == d or name.endswith("." + d) for d in domains) + + +def load_allowlist(path): + """One domain per line; '#' starts a comment. Missing file = empty list.""" + domains = [] + if not path or not os.path.isfile(path): + return domains + with open(path, encoding="utf-8") as f: + for line in f: + entry = normalize(line.split("#", 1)[0]) + if entry: + domains.append(entry) + return domains + + +def parse_question(packet): + """Return (qname, qtype, end_offset_of_question) of the first question, or None.""" + if len(packet) < 12 or struct.unpack(">H", packet[4:6])[0] < 1: + return None + labels, pos = [], 12 + while True: + if pos >= len(packet): + return None + length = packet[pos] + if length == 0: + pos += 1 + break + if length & 0xC0: # compression pointers do not occur in a query's question + return None + pos += 1 + labels.append(packet[pos:pos + length].decode("ascii", "replace")) + pos += length + if pos + 4 > len(packet): + return None + qtype = struct.unpack(">H", packet[pos:pos + 2])[0] + return ".".join(labels).lower().rstrip("."), qtype, pos + 4 + + +def local_response(query, qtype, question_end, ipv4=b"\x00\x00\x00\x00", ipv6=b"\x00" * 16): + """Answer the question locally: A -> ipv4, AAAA -> ipv6 (None = no AAAA record), + any other type -> empty NOERROR answer.""" + qid = query[0:2] + flags = struct.unpack(">H", query[2:4])[0] + resp_flags = 0x8000 | (flags & 0x7800) | (flags & 0x0100) | 0x0080 # QR, opcode, RD, RA + if qtype == TYPE_A: + answer = struct.pack(">HHHIH", 0xC00C, TYPE_A, 1, ANSWER_TTL, 4) + ipv4 + elif qtype == TYPE_AAAA and ipv6 is not None: + answer = struct.pack(">HHHIH", 0xC00C, TYPE_AAAA, 1, ANSWER_TTL, 16) + ipv6 + else: + answer = b"" + header = qid + struct.pack(">HHHHH", resp_flags, 1, 1 if answer else 0, 0, 0) + return header + query[12:question_end] + answer + + +def servfail(query): + if len(query) < 12: + return b"" + flags = struct.unpack(">H", query[2:4])[0] + resp_flags = 0x8000 | (flags & 0x7900) | 0x0080 | 2 # SERVFAIL + return query[0:2] + struct.pack(">HHHHH", resp_flags, 0, 0, 0, 0) + + +class Resolver: + def __init__(self, upstream, allowed, mapped, log_all, quiet): + self.upstream = upstream + self.allowed = allowed + self.mapped = mapped + self.log_all = log_all + self.quiet = quiet + self.counts = {"ALLOW": 0, "BLOCK": 0, "MAP": 0} + + def _note(self, verdict, name): + self.counts[verdict] += 1 + if self.quiet: + return + if self.log_all or verdict == "BLOCK": + log("%-5s %s" % (verdict, name)) + + def handle(self, query, tcp=False): + q = parse_question(query) + if not q: + return servfail(query) + name, qtype, end = q + if name in self.mapped: + self._note("MAP", name) + return local_response(query, qtype, end, ipv4=self.mapped[name], ipv6=None) + if matches(name, SONY_DOMAINS) or not matches(name, self.allowed): + self._note("BLOCK", name) + return local_response(query, qtype, end) + self._note("ALLOW", name) + try: + return self.forward_tcp(query) if tcp else self.forward_udp(query) + except OSError as e: + log("upstream error for %s: %s" % (name, e)) + return servfail(query) + + def forward_udp(self, query): + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.settimeout(UPSTREAM_TIMEOUT) + s.sendto(query, (self.upstream, 53)) + while True: + data, _ = s.recvfrom(65535) + if data[:2] == query[:2]: + return data + + def forward_tcp(self, query): + with socket.create_connection((self.upstream, 53), timeout=UPSTREAM_TIMEOUT) as s: + s.sendall(struct.pack(">H", len(query)) + query) + size = struct.unpack(">H", recv_exact(s, 2))[0] + return recv_exact(s, size) + + +def recv_exact(s, n): + buf = b"" + while len(buf) < n: + chunk = s.recv(n - len(buf)) + if not chunk: + raise OSError("connection closed") + buf += chunk + return buf + + +class UDPHandler(socketserver.BaseRequestHandler): + def handle(self): + data, sock = self.request + resp = self.server.resolver.handle(data) + if resp: + sock.sendto(resp, self.client_address) + + +class TCPHandler(socketserver.BaseRequestHandler): + def handle(self): + try: + while True: + size = struct.unpack(">H", recv_exact(self.request, 2))[0] + resp = self.server.resolver.handle(recv_exact(self.request, size), tcp=True) + self.request.sendall(struct.pack(">H", len(resp)) + resp) + except OSError: + pass + + +class ThreadingUDP(socketserver.ThreadingMixIn, socketserver.UDPServer): + daemon_threads = True + allow_reuse_address = True + + +class ThreadingTCP(socketserver.ThreadingMixIn, socketserver.TCPServer): + daemon_threads = True + allow_reuse_address = True + + +def lan_ip(): + """Address of this PC on the local network (no packet is sent).""" + try: + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.connect(("192.0.2.1", 53)) + return s.getsockname()[0] + except OSError: + return "this PC's IP address" + + +def parse_maps(values): + mapped = {} + for v in values: + host, sep, ip = v.partition("=") + try: + if not sep: + raise ValueError + mapped[normalize(host)] = ipaddress.IPv4Address(ip.strip()).packed + except ValueError: + sys.exit("--map expects host=IPv4, e.g. --map manuals.playstation.net=192.168.1.20 (got %r)" % v) + return mapped + + +def main(): + default_list = os.path.join(os.path.dirname(os.path.abspath(__file__)), "allowlist.txt") + ap = argparse.ArgumentParser(description="Protective DNS for a jailbroken PS5: allows what Nubix needs, " + "blocks everything else and always blocks Sony.") + ap.add_argument("--allow", action="append", default=[], metavar="DOMAIN", help="allow one more domain") + ap.add_argument("--allowlist", default=default_list, metavar="FILE", help="allowlist file (default: %(default)s)") + ap.add_argument("--map", action="append", default=[], metavar="HOST=IP", help="answer HOST with a fixed IPv4") + ap.add_argument("--upstream", default="1.1.1.1", help="upstream DNS server (default 1.1.1.1)") + ap.add_argument("--port", type=int, default=53, help="listening port (default 53)") + ap.add_argument("--bind", default="0.0.0.0", help="listening address (default all interfaces)") + ap.add_argument("--log-all", action="store_true", help="print every query") + ap.add_argument("--quiet", action="store_true", help="print nothing per query") + args = ap.parse_args() + + user = load_allowlist(args.allowlist) + [normalize(d) for d in args.allow if normalize(d)] + ignored = [d for d in user if matches(d, SONY_DOMAINS)] + allowed = NUBIX_DOMAINS + [d for d in user if d not in ignored] + mapped = parse_maps(args.map) + resolver = Resolver(args.upstream, allowed, mapped, args.log_all, args.quiet) + try: + udp = ThreadingUDP((args.bind, args.port), UDPHandler) + tcp = ThreadingTCP((args.bind, args.port), TCPHandler) + except PermissionError: + sys.exit("Permission denied for port %d: run as administrator (Windows) or with sudo (macOS/Linux)." % args.port) + except OSError as e: + sys.exit("Cannot listen on port %d: %s\nIs another DNS server already running on this PC?" % (args.port, e)) + udp.resolver = tcp.resolver = resolver + for srv in (udp, tcp): + threading.Thread(target=srv.serve_forever, daemon=True).start() + + print("Nubix protective DNS is running (allowlist mode: everything not listed is blocked).") + print(" On the PS5 set Primary DNS AND Secondary DNS to: %s" % lan_ip()) + print(" Allowed for Nubix: %s" % ", ".join(NUBIX_DOMAINS)) + extra = [d for d in user if d not in ignored] + print(" Allowed by you: %s" % (", ".join(extra) if extra else "(none; add domains to %s)" % args.allowlist)) + if ignored: + print(" Ignored (Sony domains are always blocked): %s" % ", ".join(ignored)) + if mapped: + print(" Fixed answers: %s" % ", ".join("%s=%s" % (h, socket.inet_ntoa(ip)) for h, ip in mapped.items())) + print(" Allowed queries go to %s. Press Ctrl+C to stop." % args.upstream) + try: + while True: + time.sleep(3600) + except KeyboardInterrupt: + c = resolver.counts + print("\nStopped. %d allowed, %d blocked, %d mapped." % (c["ALLOW"], c["BLOCK"], c["MAP"])) + print("Without this PC the PS5 has no DNS. Do not switch it back to automatic DNS while jailbroken.") + + +if __name__ == "__main__": + main() diff --git a/tools/xc-cli.cpp b/tools/xc-cli.cpp new file mode 100644 index 0000000..3542228 --- /dev/null +++ b/tools/xc-cli.cpp @@ -0,0 +1,347 @@ +// Nubix — host CLI for live API testing without the UI. +// +// xc-cli login device-code login (prints code + URL), then authorize streaming +// xc-cli devicecode only request + print a device code (live endpoint smoke test) +// xc-cli token refresh + authorize streaming, print offerings and regions +// xc-cli refresh refresh MSA token + gs tokens +// xc-cli status show stored account/token state +// xc-cli titles [filter] list playable cloud titles (tools/cli_gssv.cpp) +// xc-cli consoles list xHome consoles (tools/cli_gssv.cpp) +// xc-cli play <titleId>|--home <serverId> [--no-connect] [--timeout S] +// gssv session start/poll/connect/keepalive/stop (tools/cli_gssv.cpp) +// xc-cli session cloud|home ID gssv only: start, poll until Provisioned (or fail), stop +// xc-cli stream cloud|home ID [seconds] full headless stream via Streamer (no video output) +// xc-cli offer print a generated SDP offer (local WebRTC gathering only) +// xc-cli logout +// +// Options: --verbose. Data dir: $XC_DATA_DIR or the platform default (shared with the app). +// Copyright (C) 2026 Nubix contributors +// SPDX-License-Identifier: GPL-3.0-only +#include <SDL.h> + +#include <atomic> +#include <chrono> +#include <csignal> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <string> +#include <thread> +#include <vector> + +extern "C" { +#include <libavutil/frame.h> +} + +#include "core/auth.hpp" +#include "core/config.hpp" +#include "core/gssv.hpp" +#include "core/http.hpp" +#include "core/log.hpp" +#include "cli_gssv.hpp" +#include "core/protocol.hpp" +#include "platform/platform.hpp" +#include "stream/streamer.hpp" +#include "stream/webrtc.hpp" + +using namespace xc; + +namespace { + +std::atomic<bool> g_stop{false}; + +void onSignal(int) { g_stop = true; } + +void sleepMs(int ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } + +int usage() { + std::fprintf(stderr, + "usage: xc-cli [--verbose] <command>\n" + " login | devicecode | token | refresh | status | logout\n" + " titles [filter] | consoles\n" + " play <titleId> | play --home <serverId> [--no-connect] [--timeout S]\n" + " session <cloud|home> <titleId|serverId>\n" + " stream <cloud|home> <titleId|serverId> [seconds]\n" + " offer\n"); + return 2; +} + +bool parseKind(const std::string& s, SessionKind& k) { + if (s == "cloud") { k = SessionKind::Cloud; return true; } + if (s == "home") { k = SessionKind::Home; return true; } + return false; +} + +int cmdLogin(Config& cfg, Auth& auth) { + DeviceCode dc; + std::string err; + if (!auth.requestDeviceCode(dc, err)) { + std::fprintf(stderr, "device code request failed: %s\n", err.c_str()); + return 1; + } + std::printf("Open %s and enter code: %s\n(QR url: %s)\n", dc.verificationUri.c_str(), dc.userCode.c_str(), + dc.qrUrl.c_str()); + std::fflush(stdout); + int interval = dc.interval; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(dc.expiresIn); + while (!g_stop && std::chrono::steady_clock::now() < deadline) { + for (int i = 0; i < interval * 10 && !g_stop; ++i) sleepMs(100); + if (g_stop) break; + PollResult r = auth.pollToken(dc, err); + if (r == PollResult::Pending) { + if (err == "slow_down") interval += 5; // RFC 8628 + continue; + } + if (r != PollResult::Success) { + std::fprintf(stderr, "login failed: %s\n", err.c_str()); + return 1; + } + if (!auth.authorizeStreaming(err)) { + std::fprintf(stderr, "signed in, but authorizeStreaming failed: %s\n", err.c_str()); + return 1; + } + cfg.save(); + std::printf("Logged in as %s\n", cfg.gamertag().c_str()); + return 0; + } + std::fprintf(stderr, "login expired/cancelled\n"); + return 1; +} + +// Only request a device code and print it (live smoke test of the MSAL endpoint; no login). +int cmdDeviceCode(Auth& auth) { + DeviceCode dc; + std::string err; + if (!auth.requestDeviceCode(dc, err)) { + std::fprintf(stderr, "device code request failed: %s\n", err.c_str()); + return 1; + } + std::printf("user code : %s\n", dc.userCode.c_str()); + std::printf("verify url : %s\n", dc.verificationUri.c_str()); + std::printf("qr url : %s\n", dc.qrUrl.c_str()); + std::printf("interval : %d s\n", dc.interval); + std::printf("expires in : %d s\n", dc.expiresIn); + if (!dc.message.empty()) std::printf("message : %s\n", dc.message.c_str()); + return 0; +} + +void printRegions(const char* name, const std::string& base, const std::vector<Region>& regions) { + if (regions.empty()) { + std::printf("%-11s : not available\n", name); + return; + } + std::printf("%-11s : %s\n", name, base.c_str()); + for (const auto& r : regions) + std::printf(" %c %-22s %s%s\n", r.baseUri == base ? '*' : ' ', r.name.c_str(), r.baseUri.c_str(), + r.isDefault ? " (default)" : ""); +} + +// Refresh the MSA token + authorize streaming, then print offerings and regions. +int cmdToken(Config& cfg, Auth& auth) { + std::string err; + if (!auth.isLoggedIn()) { + std::fprintf(stderr, "not signed in - run: xc-cli login\n"); + return 1; + } + if (!auth.refresh(err) || !auth.authorizeStreaming(err)) { + std::fprintf(stderr, "token: %s\n", err.c_str()); + return 1; + } + const Tokens t = cfg.tokens(); + std::printf("gamertag : %s\n", t.gamertag.c_str()); + std::printf("market : %s\n", t.market.c_str()); + std::printf("msa valid : %lld s\n", (long long)(t.msaExpiry - platform::unixTime())); + std::printf("gs valid : %lld s\n", (long long)(t.gsExpiry - platform::unixTime())); + std::printf("xbl web : %s\n", t.xblToken.empty() ? "-" : "token"); + printRegions("xhome", t.xhomeBase, t.xhomeRegions); + printRegions("xgpuweb", t.xcloudBase, t.xcloudRegions); + printRegions("xgpuwebf2p", t.xcloudF2pBase, t.xcloudF2pRegions); + return 0; +} + +int cmdStatus(const Config& cfg, const Auth& auth) { + const Tokens t = cfg.tokens(); + std::printf("config : %s\n", cfg.path().c_str()); + std::printf("installId : %s\n", cfg.installId().c_str()); + std::printf("logged in : %s\n", auth.isLoggedIn() ? "yes" : "no"); + std::printf("gamertag : %s\n", t.gamertag.c_str()); + std::printf("msa expiry : %lld (now %lld)\n", (long long)t.msaExpiry, (long long)platform::unixTime()); + std::printf("gs expiry : %lld\n", (long long)t.gsExpiry); + std::printf("xhome : %s %s\n", t.xhomeGs.empty() ? "-" : "token", t.xhomeBase.c_str()); + std::printf("xcloud : %s %s\n", t.xcloudGs.empty() ? "-" : "token", t.xcloudBase.c_str()); + std::printf("xcloud f2p : %s %s\n", t.xcloudF2pGs.empty() ? "-" : "token", t.xcloudF2pBase.c_str()); + return 0; +} + +int cmdSession(Config& cfg, Auth& auth, SessionKind kind, const std::string& id) { + std::string err; + if (!auth.ensureFresh(err)) { + std::fprintf(stderr, "auth: %s\n", err.c_str()); + return 1; + } + const Tokens t = cfg.tokens(); + Settings gs = cfg.settings(); + gs.resolution = gssv::deviceTierFor(kind == SessionKind::Home, gs.resolution); // xhome: android only + GssvClient gssv(kind == SessionKind::Home ? t.xhomeBase : t.xcloudBase, + kind == SessionKind::Home ? t.xhomeGs : t.xcloudGs, gs); + gssv.cleanupActive(kind); + SessionInfo s; + if (!gssv.play(kind, id, s, err)) { + std::fprintf(stderr, "play: %s\n", err.c_str()); + return 1; + } + std::printf("session %s (%s)\n", s.sessionId.c_str(), s.sessionPath.c_str()); + bool connected = false; + int rc = 1; + while (!g_stop) { + if (!gssv.pollState(s, err)) { + std::fprintf(stderr, "state: %s\n", err.c_str()); + break; + } + std::printf("state: %s\n", s.state.c_str()); + if (s.state == "ReadyToConnect" && !connected) { + std::string lpt; + if (!auth.fetchLpt(lpt, err) || !gssv.connect(s, lpt, err)) { + std::fprintf(stderr, "connect: %s\n", err.c_str()); + break; + } + connected = true; + } else if (s.state == "Provisioned") { + std::printf("provisioned; keepalive interval %d s\n", gssv.keepAliveSeconds(s)); + rc = 0; + break; + } else if (s.state == "Failed") { + std::fprintf(stderr, "failed: %s %s\n", s.errorCode.c_str(), s.errorMessage.c_str()); + break; + } + sleepMs(1000); + } + gssv.stop(s); + return rc; +} + +int cmdStream(Config& cfg, Auth& auth, SessionKind kind, const std::string& id, int seconds) { + if (SDL_Init(SDL_INIT_AUDIO) != 0) std::fprintf(stderr, "SDL audio unavailable: %s\n", SDL_GetError()); + int rc = 1; + { + Streamer st(cfg, auth); + if (!st.start(kind, id)) { + std::fprintf(stderr, "stream start failed\n"); + SDL_Quit(); + return 1; + } + const auto t0 = std::chrono::steady_clock::now(); + StreamState last = StreamState::Idle; + uint64_t frames = 0; + while (!g_stop) { + StreamState s = st.state(); + if (s != last) { + std::printf("state: %s %s\n", streamStateName(s), st.statusText().c_str()); + last = s; + } + if (AVFrame* f = st.currentFrame()) { + ++frames; + av_frame_free(&f); + } + if (s == StreamState::Ended) { rc = 0; break; } + if (s == StreamState::Failed) break; + if (s == StreamState::Streaming) { + rc = 0; + const double el = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count(); + static int lastPrint = -1; + if (static_cast<int>(el) != lastPrint) { + lastPrint = static_cast<int>(el); + StreamStats ss = st.stats(); + std::printf("%dx%d %.1f fps %.0f kbps decode %.1f ms loss %.1f%% frames %llu\n", ss.width, + ss.height, ss.fps, ss.bitrateKbps, ss.decodeMs, ss.lossPercent, + (unsigned long long)frames); + } + if (seconds > 0 && el >= seconds) break; + } + sleepMs(5); + } + st.stop(); + } + SDL_Quit(); + return rc; +} + +int cmdOffer() { + WebRtc rtc; + WebRtc::Callbacks cb; + if (!rtc.init(cb)) { + std::fprintf(stderr, "webrtc init failed\n"); + return 1; + } + std::string ufrag, pwd, fp; + std::vector<std::string> cands; + if (!rtc.gatherLocal(ufrag, pwd, fp, cands, 5000)) { + std::fprintf(stderr, "gathering failed\n"); + return 1; + } + std::printf("%s\n", proto::buildOffer(ufrag, pwd, fp, false, "1080").c_str()); + for (const auto& c : cands) std::printf("%s\n", c.c_str()); + std::printf("ice body: %s\n", proto::icePostBody(cands, ufrag).c_str()); + rtc.close(); + return 0; +} + +} // namespace + +int main(int argc, char** argv) { + std::vector<std::string> args; + bool verbose = false; + for (int i = 1; i < argc; ++i) { + if (!std::strcmp(argv[i], "--verbose") || !std::strcmp(argv[i], "-v")) verbose = true; + else args.emplace_back(argv[i]); + } + if (args.empty()) return usage(); + + std::signal(SIGINT, onSignal); + std::signal(SIGTERM, onSignal); + + if (!platform::init(argc, argv)) { + std::fprintf(stderr, "cannot create data dir %s\n", platform::dataDir().c_str()); + return 1; + } + logInit("", verbose ? LogLevel::Debug : LogLevel::Info); + Http::globalInit(platform::assetPath("cacert.pem")); + + // catalog + gssv commands (titles, consoles, play) live in cli_gssv.cpp; they build their own + // Config/Auth from the same data dir. + if (cli_gssv_handles(args[0].c_str())) { + std::vector<char*> av; + for (auto& a : args) av.push_back(const_cast<char*>(a.c_str())); + const int grc = cli_gssv(static_cast<int>(av.size()), av.data()); + Http::globalCleanup(); + logShutdown(); + return grc; + } + + Config cfg(platform::dataDir() + "/config.json"); + if (!cfg.load()) XC_LOGW("config %s could not be parsed, using defaults", cfg.path().c_str()); + Auth auth(cfg); + + const std::string& cmd = args[0]; + int rc = 2; + SessionKind kind = SessionKind::Cloud; + if (cmd == "login") rc = cmdLogin(cfg, auth); + else if (cmd == "devicecode") rc = cmdDeviceCode(auth); + else if (cmd == "token") rc = cmdToken(cfg, auth); + else if (cmd == "refresh") { + std::string err; + rc = (auth.refresh(err) && auth.authorizeStreaming(err)) ? 0 : 1; + if (rc) std::fprintf(stderr, "refresh failed: %s\n", err.c_str()); + else cfg.save(); + } else if (cmd == "status") rc = cmdStatus(cfg, auth); + else if (cmd == "logout") { auth.logout(); rc = 0; } + else if (cmd == "session" && args.size() >= 3 && parseKind(args[1], kind)) rc = cmdSession(cfg, auth, kind, args[2]); + else if (cmd == "stream" && args.size() >= 3 && parseKind(args[1], kind)) + rc = cmdStream(cfg, auth, kind, args[2], args.size() > 3 ? std::atoi(args[3].c_str()) : 0); + else if (cmd == "offer") rc = cmdOffer(); + else rc = usage(); + + Http::globalCleanup(); + logShutdown(); + return rc; +}