summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.clang-format66
-rw-r--r--.gitattributes2
-rw-r--r--.github/workflows/build-and-test-deb.yml114
-rw-r--r--.github/workflows/build-and-test-msys.yml90
-rw-r--r--.github/workflows/build-and-test-rh.yml98
-rw-r--r--.github/workflows/build-and-test.yml142
-rw-r--r--.github/workflows/codeql.yml86
-rw-r--r--.github/workflows/coverage.yml71
-rw-r--r--.github/workflows/coverity.yml63
-rw-r--r--.github/workflows/lint.yml56
-rw-r--r--.github/workflows/nix.yml64
-rw-r--r--.gitignore7
-rw-r--r--CMakeLists.txt273
-rw-r--r--LICENSE.md34
-rw-r--r--README.adoc230
-rw-r--r--cmake/sexp-samples-folder.h.in24
-rw-r--r--cmake/sexpp.pc.in9
-rw-r--r--cmake/version.cmake162
-rw-r--r--codecov.yml24
-rw-r--r--default.nix49
-rw-r--r--flake.lock42
-rw-r--r--flake.nix47
-rw-r--r--include/sexpp/ext-key-format.h95
-rw-r--r--include/sexpp/sexp-error.h73
-rw-r--r--include/sexpp/sexp-public.h30
-rw-r--r--include/sexpp/sexp.h466
-rw-r--r--man/sexpp.167
-rw-r--r--samples/baseline/sexp-sample-a19
-rw-r--r--samples/baseline/sexp-sample-b1
-rw-r--r--samples/baseline/sexp-sample-cbin0 -> 430 bytes
-rw-r--r--samples/compat/g10/canonical.keybin0 -> 710 bytes
-rwxr-xr-xsamples/compat/g23/correct.key43
-rwxr-xr-xsamples/compat/g23/correct_mult_fields.key45
-rwxr-xr-xsamples/compat/g23/correct_no_eol.key43
-rwxr-xr-xsamples/compat/g23/correct_with_comment.key44
-rwxr-xr-xsamples/compat/g23/correct_with_comment_at_eof.key45
-rwxr-xr-xsamples/compat/g23/correct_with_empty_line.key44
-rwxr-xr-xsamples/compat/g23/correct_with_two_empty_lines.key45
-rw-r--r--samples/compat/g23/correct_with_windows_eol.key45
-rwxr-xr-xsamples/compat/g23/malformed_invalid_name_char.key43
-rwxr-xr-xsamples/compat/g23/malformed_invalid_name_first_char.key43
-rwxr-xr-xsamples/compat/g23/malformed_name_break.key44
-rwxr-xr-xsamples/compat/g23/malformed_name_eof.key43
-rwxr-xr-xsamples/compat/g23/malformed_no_key.key43
-rwxr-xr-xsamples/compat/g23/malformed_two_keys.key84
-rw-r--r--src/ext-key-format.cpp308
-rw-r--r--src/sexp-char-defs.cpp345
-rw-r--r--src/sexp-depth-manager.cpp50
-rw-r--r--src/sexp-error.cpp56
-rw-r--r--src/sexp-input.cpp514
-rw-r--r--src/sexp-main.cpp231
-rw-r--r--src/sexp-object.cpp188
-rw-r--r--src/sexp-output.cpp197
-rw-r--r--src/sexp-simple-string.cpp191
-rw-r--r--tests/include/sexp-tests.h42
-rwxr-xr-xtests/scripts/tests.sh191
-rw-r--r--tests/src/baseline-tests.cpp115
-rw-r--r--tests/src/compare-files.cpp126
-rw-r--r--tests/src/exception-tests.cpp331
-rw-r--r--tests/src/g10-compat-tests.cpp51
-rw-r--r--tests/src/g23-compat-tests.cpp139
-rw-r--r--tests/src/g23-exception-tests.cpp108
-rw-r--r--tests/src/primitives-tests.cpp394
-rw-r--r--version.txt1
64 files changed, 6736 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..3b17299
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,66 @@
+---
+Language: Cpp
+# BasedOnStyle: Mozilla
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: true
+AlignEscapedNewlinesLeft: true
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: true
+ AfterStruct: false
+ AfterUnion: false
+ BeforeElse: false
+ IndentBraces: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: false
+BreakStringLiterals: true
+ColumnLimit: 95
+ContinuationIndentWidth: 2
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+IncludeIsMainRegex: '$'
+IndentCaseLabels: false
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+PointerAlignment: Right
+ReflowComments: true
+SortIncludes: false
+SpaceAfterCStyleCast: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Auto
+TabWidth: 8
+UseTab: Never
+...
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..6bd52cb
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+*.giattributes text eol=lf
+samples/** -text
diff --git a/.github/workflows/build-and-test-deb.yml b/.github/workflows/build-and-test-deb.yml
new file mode 100644
index 0000000..a9c0eb4
--- /dev/null
+++ b/.github/workflows/build-and-test-deb.yml
@@ -0,0 +1,114 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+name: build-and-test-deb
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+env:
+# Looks like the last version that supports i386
+ CMAKE_VERSION: '3.20.6-2'
+ MAKEFLAGS: j4
+
+jobs:
+ build-and-test:
+ name: ${{ matrix.image }}-${{ matrix.env.CC }} shared lib ${{ matrix.shared }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ image: [ 'i386/debian:10', 'i386/debian:11', 'amd64/debian:11' ]
+ env: [ {CC: gcc, CXX: g++}, {CC: clang, CXX: clang++} ]
+ shared: [on, off]
+
+ container: ${{ matrix.image }}
+ env: ${{ matrix.env }}
+ steps:
+ - name: Install packages
+ run: |
+ apt-get update
+ apt-get -y install sudo git wget build-essential libssl-dev software-properties-common
+
+ - name: Checkout
+ # Because i386 support v1 only (no node run-time)
+ uses: actions/checkout@v1
+ with:
+ fetch-depth: 1
+
+ - name: Install cmake/i386
+ if: startsWith(matrix.image, 'i386')
+ run: |
+ wget -nv https://github.com/xpack-dev-tools/cmake-xpack/releases/download/v${{ env.CMAKE_VERSION }}/xpack-cmake-${{ env.CMAKE_VERSION }}-linux-ia32.tar.gz
+ tar -zxf xpack-cmake-${{ env.CMAKE_VERSION }}-linux-ia32.tar.gz --directory /usr/local --strip-components=1 --skip-old-files
+
+ - name: Install cmake/amd64
+ if: startsWith(matrix.image, 'amd64')
+ run: |
+ wget -nv https://github.com/xpack-dev-tools/cmake-xpack/releases/download/v${{ env.CMAKE_VERSION }}/xpack-cmake-${{ env.CMAKE_VERSION }}-linux-x64.tar.gz
+ tar -zxf xpack-cmake-${{ env.CMAKE_VERSION }}-linux-x64.tar.gz --directory /usr/local --strip-components=1 --skip-old-files
+
+ - name: Install clang
+ if: matrix.env.CC == 'clang'
+ run: |
+ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
+ apt-add-repository "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch main"
+ apt-get install -y clang
+
+ - name: Adjust environment
+ run: |
+ echo "SEXP_INSTALL_PATH=$PWD/install" >> $GITHUB_ENV
+ echo "SHARED_LIB=${{ matrix.shared }}" >> $GITHUB_ENV
+
+ - name: Configure
+ run: cmake . -Bbuild -DCMAKE_INSTALL_PREFIX=${{ env.SEXP_INSTALL_PATH }} -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=${{ matrix.shared }}
+
+ - name: Build
+ run: cmake --build build --config Release
+
+ - name: Run tests
+ run: ctest --test-dir build --output-on-failure
+
+ - name: Checkout shell test framework/amd64 only
+ # i386 supports actions/checkout@v1 only (no Node.js 32-bit binary)
+ # and and actions/checkout@v1 has some issues with nested repositories
+ if: startsWith(matrix.image, 'amd64')
+ uses: actions/checkout@v3
+ with:
+ repository: kward/shunit2
+ path: ${{github.workspace}}/tests/shunit2
+ fetch-depth: 1
+
+ - name: Install
+ run: cmake --install build
+
+ - name: Run additional tests
+ # See above, just assume that in i386 config it was installed correctly
+ if: startsWith(matrix.image, 'amd64')
+ shell: bash
+ run: tests/scripts/tests.sh
diff --git a/.github/workflows/build-and-test-msys.yml b/.github/workflows/build-and-test-msys.yml
new file mode 100644
index 0000000..99e2096
--- /dev/null
+++ b/.github/workflows/build-and-test-msys.yml
@@ -0,0 +1,90 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+name: build-and-test-msys
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+env:
+ MAKEFLAGS: j4
+
+defaults:
+ run:
+ shell: msys2 {0}
+jobs:
+ build-and-test:
+ name: MSys-${{ matrix.msystem }} shared lib ${{ matrix.shared }}
+ runs-on: windows-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ msystem: [ ucrt64, mingw64, clang64 ]
+ shared: [on, off]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+
+ - name: Install MSys
+ uses: msys2/setup-msys2@v2
+ with:
+ msystem: ${{ matrix.msystem }}
+ pacboy: >-
+ toolchain:p
+ cmake:p
+ path-type: minimal
+
+ - name: Adjust environment
+ shell: bash
+ run: |
+ echo "SEXP_INSTALL_PATH=$(cygpath -u $PWD/install)" >> $GITHUB_ENV
+ echo "SHARED_LIB=${{ matrix.shared }}" >> $GITHUB_ENV
+
+ - name: Configure
+ run: cmake -Bbuild -DCMAKE_INSTALL_PREFIX=${{ env.SEXP_INSTALL_PATH }} -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=${{ matrix.shared }}
+
+ - name: Build
+ run: cmake --build build --config Release
+
+ - name: Run tests
+ run: ctest --test-dir build --output-on-failure
+
+ - name: Checkout shell test framework
+ uses: actions/checkout@v3
+ with:
+ repository: kward/shunit2
+ path: ${{ github.workspace }}/tests/shunit2
+ fetch-depth: 1
+
+ - name: Install
+ run: cmake --install build
+
+ - name: Run additional tests
+ run: tests/scripts/tests.sh
diff --git a/.github/workflows/build-and-test-rh.yml b/.github/workflows/build-and-test-rh.yml
new file mode 100644
index 0000000..c30fc19
--- /dev/null
+++ b/.github/workflows/build-and-test-rh.yml
@@ -0,0 +1,98 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+name: build-and-test-rh
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+env:
+ MAKEFLAGS: j4
+
+jobs:
+ build-and-test:
+ name: ${{ matrix.image }}-${{ matrix.env.CC }} shared lib ${{ matrix.shared }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ image: [ 'centos:7', 'tgagor/centos:stream8', 'quay.io/centos/centos:stream9', 'fedora:35', 'fedora:36' ]
+ env: [ {CC: gcc, CXX: g++}, {CC: clang, CXX: clang++} ]
+ shared: [ on, off ]
+ container: ${{ matrix.image }}
+ env: ${{ matrix.env }}
+ steps:
+ - name: Install packages CentOS 7
+ if: matrix.image == 'centos:7'
+ run: |
+ yum -y install http://opensource.wandisco.com/centos/7/git/x86_64/wandisco-git-release-7-2.noarch.rpm
+ yum -y install sudo git make wget gcc gcc-c++ clang
+ wget -nv https://github.com/Kitware/CMake/releases/download/v3.24.3/cmake-3.24.3-linux-x86_64.sh
+ chmod a+x cmake-3.24.3-linux-x86_64.sh
+ ./cmake-3.24.3-linux-x86_64.sh --skip-license --exclude-subdir --prefix=/usr/local
+
+ - name: Install packages not CentOS 7
+ if: matrix.image != 'centos:7'
+ run: yum -y install sudo git gcc gcc-c++ make cmake clang
+
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+
+ - name: Adjust environment
+ run: |
+ echo "SEXP_INSTALL_PATH=$PWD/install" >> $GITHUB_ENV
+ echo "SHARED_LIB=${{ matrix.shared }}" >> $GITHUB_ENV
+
+ - name: Configure
+ run: cmake . -Bbuild -DCMAKE_INSTALL_PREFIX=${{ env.SEXP_INSTALL_PATH }} -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=${{ matrix.shared }}
+
+ - name: Build
+ run: cmake --build build --config Release
+
+ - name: Run tests
+ run: ctest --test-dir build --output-on-failure
+
+ - name: Install
+ run: cmake --install build
+
+ - name: Install xargs
+ if: matrix.image == 'fedora:35'
+ run: sudo yum -y install findutils
+
+ - name: Checkout shell test framework
+ uses: actions/checkout@v3
+ with:
+ repository: kward/shunit2
+ path: ${{github.workspace}}/tests/shunit2
+ fetch-depth: 1
+
+ - name: Run additional tests
+ shell: bash
+ run: tests/scripts/tests.sh
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
new file mode 100644
index 0000000..ca1ffa6
--- /dev/null
+++ b/.github/workflows/build-and-test.yml
@@ -0,0 +1,142 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+name: build-and-test
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+env:
+ MAKEFLAGS: j4
+
+jobs:
+ build-and-test:
+ runs-on: ${{ matrix.os }}
+ name: ${{ matrix.os }}-${{ matrix.env.CC }} shared lib ${{ matrix.shared }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ ubuntu-20.04, macos-11, windows-2019 ]
+ env: [ { } ]
+ shared: [on, off]
+ # on macos gcc is an alias to clang by default, so there is no real value in macos clang run
+ include:
+ - { os: ubuntu-20.04, env: { CC: clang, CXX: clang++ }, shared: on }
+ - { os: ubuntu-20.04, env: { CC: clang, CXX: clang++ }, shared: off }
+ # Builds sexp dll with MSVC is not supported
+ exclude:
+ - { os: windows-2019, env: { }, shared: on }
+
+ env: ${{ matrix.env }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+
+ - name: Adjust environment for Windows build
+ if: runner.os == 'Windows'
+ shell: bash
+ run: echo "OSTYPE=windows" >> $GITHUB_ENV
+
+ - name: Adjust environment
+ run: echo "SHARED_LIB=${{ matrix.shared }}" >> $GITHUB_ENV
+
+ - name: Configure
+ run: cmake -Bbuild -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=${{ matrix.shared }}
+
+ - name: Build
+ run: cmake --build build --config Release
+
+ - name: Run tests
+ run: ctest --test-dir build --output-on-failure
+
+ - name: Checkout shell test framework
+ uses: actions/checkout@v3
+ with:
+ repository: kward/shunit2
+ path: ${{github.workspace}}/tests/shunit2
+ fetch-depth: 1
+
+ - name: Install
+ run: cmake --install build
+
+ - name: Run additional tests
+ shell: bash
+ run: tests/scripts/tests.sh
+
+ offline-gtest:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+
+ - name: Install googletest
+ run: |
+ sudo apt-get -y install googletest
+ sudo cmake -B/usr/src/googletest/build /usr/src/googletest
+ sudo cmake --build /usr/src/googletest/build
+ sudo cmake --install /usr/src/googletest/build
+
+ - name: Configure
+ run: |
+ cmake -Bbuild \
+ -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DDOWNLOAD_GTEST=OFF
+
+ - name: Build
+ run: cmake --build build
+
+ - name: Run tests
+ run: ctest --test-dir build --output-on-failure
+
+ sanitizers:
+ runs-on: ubuntu-latest
+ env:
+ CC: clang
+ CXX: clang++
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+
+ - name: Configure
+ run: |
+ cmake -Bbuild \
+ -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install \
+ -DWITH_SANITIZERS=ON
+
+ - name: Build
+ run: cmake --build build
+
+ - name: Run tests
+ run: ctest --test-dir build --output-on-failure
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..ed38ede
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,86 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ "main" ]
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'cpp' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+
+ # - run: |
+ # echo "Run, Build Application using script"
+ # ./location_of_script_within_repo/buildscript.sh
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
new file mode 100644
index 0000000..55df2ff
--- /dev/null
+++ b/.github/workflows/coverage.yml
@@ -0,0 +1,71 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+name: coverage
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+jobs:
+ coverage:
+ runs-on: ubuntu-latest
+ env:
+ CC: gcc
+ CXX: g++
+ CODECOV_TOKEN: dbb06ac6-97ef-4d04-a0c6-24f7e3d8e1d6
+ MAKEFLAGS: j4
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+
+ - name: Configure
+ run: |
+ cmake -Bbuild \
+ -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install \
+ -DWITH_COVERAGE=ON
+
+ - name: Build
+ run: cmake --build build
+
+ - name: Run tests
+ run: ctest --test-dir build --output-on-failure
+
+ - name: Upload to codecov.io
+ run: |
+ curl -Os https://uploader.codecov.io/latest/linux/codecov
+ curl https://uploader.codecov.io/verification.gpg | gpg --no-default-keyring --keyring trustedkeys.gpg --import
+ curl -Os https://uploader.codecov.io/latest/linux/codecov
+ curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM
+ curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM.sig
+ gpgv codecov.SHA256SUM.sig codecov.SHA256SUM
+ shasum -a 256 -c codecov.SHA256SUM
+ find "build" -type f -name '*.gcno' -exec gcov -p {} +
+ chmod +x codecov
+ ./codecov -t ${{ env.CODECOV_TOKEN }}
diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml
new file mode 100644
index 0000000..5c74eeb
--- /dev/null
+++ b/.github/workflows/coverity.yml
@@ -0,0 +1,63 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+name: coverity
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+jobs:
+ coverity:
+ runs-on: ubuntu-latest
+ env:
+ CC: gcc
+ CXX: g++
+ MAKEFLAGS: j4
+ COVERITY_TOKEN: qjcM1CWLcq9PJB3yL0ZXIw
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+
+ - name: Install googletest
+ run: |
+ sudo apt-get -y install googletest
+ sudo cmake -B/usr/src/googletest/build /usr/src/googletest
+ sudo cmake --build /usr/src/googletest/build
+ sudo cmake --install /usr/src/googletest/build
+
+ - name: Configure
+ run: cmake -Bbuild -DDOWNLOAD_GTEST=OFF
+
+ - name: Coverity Scan
+ uses: vapier/coverity-scan-action@v1
+ with:
+ email: maxirmx@sw.consulting
+ token: ${{ env.COVERITY_TOKEN }}
+ command: cmake --build build
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..0a80e40
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,56 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+name: lint
+
+on:
+ workflow_dispatch:
+
+ pull_request:
+ paths-ignore:
+ - '/.*'
+ - '!.clang-format'
+ - '**.adoc'
+ - '**.md'
+
+jobs:
+ clang-format:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: DoozyX/clang-format-lint-action@v0.11
+ with:
+ source: '.'
+ extensions: 'h,cpp,c'
+ clangFormatVersion: 11
+# inplace: True
+# - uses: EndBug/add-and-commit@v9
+# with:
+# author_name: A robot on behalf of Maxim Samsonov
+# author_email: m.samsonov@computer.org
+# message: 'Committing clang-format changes'
+# env:
+# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ shellcheck:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: ludeeus/action-shellcheck@master
diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml
new file mode 100644
index 0000000..ed19bce
--- /dev/null
+++ b/.github/workflows/nix.yml
@@ -0,0 +1,64 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+name: nix
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+
+ - name: nix setup
+ uses: cachix/install-nix-action@v22
+ with:
+ nix_path: nixpkgs=channel:nixos-unstable
+
+ - name: nix build
+ run: nix build
+
+ - name: Checkout shell test framework
+ uses: actions/checkout@v3
+ with:
+ repository: kward/shunit2
+ path: ${{github.workspace}}/tests/shunit2
+ fetch-depth: 1
+
+ - name: Run tests
+ run: DIR_INSTALL=result tests/scripts/tests.sh
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..66516f9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+### CMake and build artifacts ###
+/build
+/baseline
+/tests/include/sexp-samples-folder.h
+/.vscode
+/result
+/tests/shunit2
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..9ac363c
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,273 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+# 3.14 -- FetchContent
+cmake_minimum_required(VERSION 3.14)
+
+include(cmake/version.cmake)
+determine_version("${CMAKE_CURRENT_SOURCE_DIR}" SEXPP)
+# project name, etc
+project(sexpp
+ VERSION "${SEXPP_VERSION}"
+ LANGUAGES C CXX
+ DESCRIPTION "S-expressions parser and generator C++ library, fully compliant to [https://people.csail.mit.edu/rivest/Sexp.txt]"
+)
+
+option(WITH_SEXP_TESTS "Build tests" ON)
+option(WITH_SEXP_CLI "Build sexp console application" ON)
+option(WITH_SANITIZERS "Enable ASAN and other sanitizers" OFF)
+option(WITH_COVERAGE "Enable coverage report" OFF)
+option(DOWNLOAD_GTEST "Download googletest" ON)
+option(BUILD_SHARED_LIBS "Build shared library" OFF)
+
+include(GNUInstallDirs)
+include(CheckCXXSourceCompiles)
+
+if (BUILD_SHARED_LIBS)
+ set(TYPE "SHARED")
+else (BUILD_SHARED_LIBS)
+ set(TYPE "STATIC")
+endif (BUILD_SHARED_LIBS)
+
+if (BUILD_SHARED_LIBS AND MSVC)
+ message(FATAL_ERROR "Building sexp shared library with MSVC is not supported")
+endif(BUILD_SHARED_LIBS AND MSVC)
+
+
+message(STATUS "Building ${TYPE} library")
+
+if (WITH_SANITIZERS)
+ if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ message(FATAL_ERROR "Sanitizers work with clang compiler only.")
+ endif()
+ if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+ message(STATUS "Forcing build type to Debug for sanitizers")
+ set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type. Forced to Debug" FORCE)
+ set(WITH_TESTS ON CACHE STRING "Forced to ON" FORCE)
+ endif()
+endif()
+
+if (WITH_COVERAGE)
+ if (NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+ message(FATAL_ERROR "Coverage works with GNU compiler only.")
+ endif()
+ if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+ message(STATUS "Forcing build type to Debug for coverage")
+ set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type. Forced to Debug" FORCE)
+ set(WITH_TESTS ON CACHE STRING "Forced to ON" FORCE)
+ endif()
+endif()
+
+if(NOT CMAKE_BUILD_TYPE)
+ message(STATUS "Defaulting build type to Debug")
+ set(CMAKE_BUILD_TYPE Debug)
+endif(NOT CMAKE_BUILD_TYPE)
+
+message(STATUS "Building ${CMAKE_BUILD_TYPE} configuration")
+
+if (WITH_SANITIZERS)
+ add_compile_options(-fsanitize=leak,address,undefined -fno-omit-frame-pointer -fno-common -O1)
+ link_libraries(-fsanitize=leak,address,undefined)
+endif(WITH_SANITIZERS)
+
+if (WITH_COVERAGE)
+ add_compile_options(--coverage -O0)
+ link_libraries(--coverage)
+endif(WITH_COVERAGE)
+
+# set warning flags at the top level
+if(NOT MSVC)
+ add_compile_options(
+ -Wall -Wextra
+ -Wunreachable-code -Wpointer-arith
+ -Wmissing-declarations
+ )
+# relax some warnings a bit
+ add_compile_options(
+ -Wno-pedantic
+ -Wno-ignored-qualifiers
+ -Wno-unused-parameter
+ -Wno-missing-field-initializers
+ )
+
+endif(NOT MSVC)
+
+add_library(sexpp ${TYPE}
+ "src/sexp-input.cpp"
+ "src/sexp-output.cpp"
+ "src/sexp-object.cpp"
+ "src/sexp-simple-string.cpp"
+ "src/sexp-char-defs.cpp"
+ "src/sexp-error.cpp"
+ "src/sexp-depth-manager.cpp"
+ "src/ext-key-format.cpp"
+ "include/sexpp/sexp.h"
+ "include/sexpp/sexp-error.h"
+ "include/sexpp/ext-key-format.h"
+)
+
+target_compile_features(sexpp PUBLIC cxx_std_11)
+target_include_directories(sexpp PUBLIC
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+ $<INSTALL_INTERFACE:include>
+)
+
+if (BUILD_SHARED_LIBS)
+ target_compile_definitions(sexpp PUBLIC BUILD_SHARED_LIBS)
+ set_target_properties(sexpp PROPERTIES CXX_VISIBILITY_PRESET "hidden")
+endif (BUILD_SHARED_LIBS)
+
+set_target_properties(sexpp PROPERTIES
+ POSITION_INDEPENDENT_CODE ON
+ RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
+ VERSION "${SEXPP_VERSION}"
+ SOVERSION "${SEXPP_MAJOR_VERSION}"
+ OUTPUT_NAME "sexpp"
+)
+
+if (WITH_SEXP_CLI)
+ add_executable (sexpp-cli
+ src/sexp-main.cpp
+ include/sexpp/sexp.h
+ include/sexpp/sexp-error.h
+ )
+ target_include_directories (sexpp-cli PUBLIC include)
+ target_link_libraries(sexpp-cli PRIVATE sexpp)
+ target_compile_features(sexpp-cli PUBLIC cxx_std_11)
+ set_target_properties(sexpp-cli PROPERTIES
+ RUNTIME_OUTPUT_NAME sexpp
+ RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
+ )
+endif (WITH_SEXP_CLI)
+
+
+if(WITH_SEXP_TESTS)
+ CHECK_CXX_SOURCE_COMPILES(
+ "#include <iostream>
+ #include <type_traits>
+ struct test_struct {
+ std::string str;
+ };
+ int main() {
+ bool b = std::is_copy_constructible<test_struct>::value;
+ return b ? 0 : -1 ;
+ }"
+ CAN_USE_NEW_GTEST
+ )
+
+ if(CAN_USE_NEW_GTEST)
+ set(GTEST_TAG "release-1.12.1")
+ else(CAN_USE_NEW_GTEST)
+ if(WITH_COVERAGE)
+ message(FATAL_ERROR "Coverage requires newer version of GTest that won't compile with your toolset")
+ endif(WITH_COVERAGE)
+ set(GTEST_TAG "release-1.8.1")
+ endif(CAN_USE_NEW_GTEST)
+
+ include(CTest)
+ enable_testing()
+ include(GoogleTest)
+
+ set(SEXP_SAMPLES_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/samples")
+
+ configure_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/cmake/sexp-samples-folder.h.in"
+ "${CMAKE_CURRENT_SOURCE_DIR}/tests/include/sexp-samples-folder.h"
+ @ONLY
+ )
+
+ add_executable(sexpp-tests
+ "tests/src/baseline-tests.cpp"
+ "tests/src/exception-tests.cpp"
+ "tests/src/primitives-tests.cpp"
+ "tests/src/g10-compat-tests.cpp"
+ "tests/src/g23-compat-tests.cpp"
+ "tests/src/g23-exception-tests.cpp"
+ "tests/src/compare-files.cpp"
+ "tests/include/sexp-tests.h"
+ )
+
+ if(DOWNLOAD_GTEST)
+ message(STATUS "Fetching googletest")
+ include(FetchContent)
+ FetchContent_Declare(
+ googletest
+ GIT_REPOSITORY https://github.com/google/googletest.git
+ GIT_TAG ${GTEST_TAG}
+ )
+ # maintain compiler/linker settings on Windows
+ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
+ # do not install gtest
+ set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
+ # explicitely disable unneeded gmock build
+ set(BUILD_GMOCK OFF CACHE BOOL "" FORCE)
+
+ FetchContent_MakeAvailable(googletest)
+ set(GTestMain gtest_main)
+ set(GTest gtest)
+ else(DOWNLOAD_GTEST)
+ find_package(GTest REQUIRED)
+ set(GTestMain GTest::Main)
+ set(GTest GTest::GTest)
+
+ if(NOT MSVC)
+ target_link_libraries(sexpp-tests PRIVATE pthread)
+ endif(NOT MSVC)
+ endif(DOWNLOAD_GTEST)
+
+ target_link_libraries(sexpp-tests PRIVATE
+ sexpp
+ ${GTestMain}
+ ${GTest}
+ )
+
+ target_include_directories(sexpp-tests PRIVATE
+ tests/include
+ include
+ "${GTEST_INCLUDE_DIRS}"
+ )
+
+ target_compile_features(sexpp-tests PUBLIC cxx_std_11)
+ set_target_properties(sexpp-tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+
+ gtest_discover_tests(sexpp-tests)
+endif(WITH_SEXP_TESTS)
+
+set(CONFIGURED_PC "${CMAKE_CURRENT_BINARY_DIR}/sexpp.pc")
+configure_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/cmake/sexpp.pc.in"
+ "${CONFIGURED_PC}"
+ @ONLY
+)
+
+if (WIN32 AND BUILD_SHARED_LIBS)
+ install(TARGETS sexpp RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+ install(TARGETS sexpp ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+else(WIN32 AND BUILD_SHARED_LIBS)
+ install(TARGETS sexpp DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+endif(WIN32 AND BUILD_SHARED_LIBS)
+
+install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
+install(FILES "${CONFIGURED_PC}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
+if(WITH_SEXP_CLI)
+ install(TARGETS sexpp-cli DESTINATION "${CMAKE_INSTALL_BINDIR}")
+ install(FILES man/sexpp.1 DESTINATION "${CMAKE_INSTALL_MANDIR}/man1")
+endif(WITH_SEXP_CLI)
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..2e579d2
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,34 @@
+Original MIT License
+====================
+
+Copyright 1997 Ronald L. Rivest, Butler Lampson
+Copyright 1997 MIT Laboratory for Computer Science
+
+The code is available under the "MIT License" (open source).
+
+License text available at:
+https://opensource.org/licenses/MIT
+
+
+Ribose MIT License
+====================
+
+Copyright 2021-2022 Ribose Inc.
+
+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.
+
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..765685c
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,230 @@
+= S-Expressions parser and generator library in C\++ (SEXP in C++)
+
+image:https://github.com/rnpgp/sexp/workflows/build-and-test/badge.svg["Build status Ubuntu/macOS/Windows", link="https://github.com/rnpgp/sexp/actions?workflow=build-and-test"]
+image:https://github.com/rnpgp/sexp/workflows/build-and-test-rh/badge.svg["Build status CentOS/Fedora", link="https://github.com/rnpgp/sexp/actions?workflow=build-and-test-rh"]
+image:https://github.com/rnpgp/sexp/workflows/build-and-test-deb/badge.svg["Build status Debian", link="https://github.com/rnpgp/sexp/actions?workflow=build-and-test-deb"]
+image:https://github.com/rnpgp/sexp/workflows/build-and-test-msys/badge.svg["Build status MSys", link="https://github.com/rnpgp/sexp/actions?workflow=build-and-test-msys"]
+
+
+image:https://codecov.io/gh/rnpgp/sexpp/branch/main/graph/badge.svg["Code coverage", link="https://codecov.io/gh/rnpgp/sexpp"]
+image:https://github.com/rnpgp/sexpp/workflows/CodeQL/badge.svg["CodeQL analysis", link="https://github.com/rnpgp/sexpp/actions?workflow=CodeQL"]
+image:https://scan.coverity.com/projects/28717/badge.svg["Coverity Scan Build Status", link="https://scan.coverity.com/projects/rnpgp-sexpp"]
+
+
+== Purpose
+
+This is a C++ library for working with S-Expressions.
+
+This implementation is derived from the reference SEXP C library developed by
+Prof. Ronald Rivest and Prof. Butler Lampson of MIT LCS (now CSAIL).
+
+This library differs from the original C implementation in the following ways:
+
+* It aims to be reuseable in C++ implementations and is importable via CMake.
+* It includes a test suite for correctness testing and tests against malformed
+ S-Expressions.
+* It supports, and is tested against, all major platforms, including:
+** Ubuntu, Debian, Fedora, CentOS
+** macOS
+** Windows
+** msys
+* It implements additional interface to work with S-Expressions wrapped by GnuPG
+ 2.3+ extended format
+ (https://github.com/gpg/gnupg/blob/master/agent/keyformat.txt[specification]).
+
+
+The original C library was available at (but no longer accessible):
+
+* http://people.csail.mit.edu/rivest/sexp.html
+
+
+== Background
+
+S-Expressions are a data structure for representing complex data as a variation
+on https://en.wikipedia.org/wiki/Lisp_(programming_language)[LISP] S-Expressions.
+
+S-Expressions were originally adopted for use in
+http://theory.lcs.mit.edu/~cis/sdsi.html[SDSI] and
+http://world.std.com/~cme/html/spki.html[SPKI].
+
+SDSI has been developed by Prof.
+https://people.csail.mit.edu/rivest/index.html[Ronald L. Rivest] and
+Prof. Butler Lampson of
+http://www.lcs.mit.edu/[MIT's Laboratory for Computer Science],
+members of
+http://theory.lcs.mit.edu/~cis[LCS's Cryptography and Information Security]
+research group.
+
+NOTE: SDSI research has been supported by DARPA contract DABT63-96-C-0018,
+"Security for Distributed Computer Systems".
+
+NOTE: SPKI has been developed by
+http://www.clark.net/pub/cme/home.html[Carl Ellison] and others in the IETF SPKI
+working group.
+
+
+== S-Expressions specification
+
+* https://datatracker.ietf.org/doc/draft-rivest-sexp/
+
+NOTE: The "SEXP 1.0 guide" used to be at
+https://people.csail.mit.edu/rivest/Sexp.txt.
+
+
+== Code
+
+The library is a deep rework to C++ of the original
+https://people.csail.mit.edu/rivest/sexp.html[SEXP library] that maintains full
+support of original specification.
+
+While most applications will not need anything but the simple canonical and
+transport formats; however, the code here is considerably more complex because
+it also supports the advanced format, both for input and for output.
+
+
+== Building and installation
+
+[source,sh]
+----
+$ mkdir build
+$ cd build
+$ cmake ..
+$ cmake --build .
+$ ctest
+$ cmake --install .
+----
+
+
+== CMake script options
+
+`BUILD_SHARED_LIBS:BOOL`::
+(default: `OFF`)
+build shared library
+
+`WITH_SEXP_TESTS:BOOL`::
+(default: `ON`)
+build tests
+
+`DOWNLOAD_GTEST`::
+(default: `ON`)
+if tests are built, automatically download googletest from GitHub;
+when set to `OFF`, the googletest binary package needs to be available for SEXP
+tests.
+
+`WITH_SEXP_CLI:BOOL`::
+(default: `ON`) build the `sexp` command-line utility
+
+`WITH_SANITIZERS:BOOL`::
+(default: `OFF`)
+build with address and other sanitizers (requires clang compiler)
+
+
+
+== SEXPP command-line utility
+
+The `sexpp` command-line utility is reference parser and generator of
+S-Expressions. It can read, parse and print out SEXP in all defined formats.
+
+=== Switches
+
+.`sexpp` switches
+[options="header"]
+|===
+| Switch | Description | Default
+
+3+| Input
+| `-i <filename>` | input file name | read input from console (stdin)
+| `-p` | prompt input if reading from console | disabled
+| `-s` | treat input as a single SEXP string | disabled, input is treated as an S-Expression
+
+3+| Output
+| `-o <filename>` | output file name: | write output to console (stdout)
+| `-a` | generate advanced transport format | enabled if no format is specified
+| `-b` | generate base-64 transport format | disabled
+| `-c` | generate canonical format | disabled
+| `-l` | suppress linefeeds after output | disabled
+| `-w <width>` | set output line width (0 implies no constraint)| 75
+
+3+| Miscellaneous
+| `-x` | execute repeatedly until EOF | process single S-Expression then exit
+| `-h` | print help message and exit |
+
+|===
+
+Running without switches implies: `-p -a -b -c -x`.
+
+=== Usage examples
+
+Prompt for S-Expressions input from console, parse and output it to
+`certificate.dat` in base64 transport format.
+
+[source]
+----
+$ sexpp -o certificate.dat -p -b
+
+> Input:
+> (aa bb (cc dd))
+>
+> Writing base64 (of canonical) output to 'certificate.dat'
+----
+
+Parse all S-Expressions from `certificate.dat`, output them to console in
+advanced transport format with no prompts:
+
+[source,sh]
+----
+$ sexpp -i certificate.dat -x
+
+> (2:aa2:bb(2:cc2:dd))
+----
+
+Parse S-Expressions from `certificate.dat`, output it to console in canonical,
+base64 and advanced format with prompts and no width limitation:
+
+[source,sh]
+----
+$ sexpp -i certificate.dat -a -b -c -p -w 0
+
+> Reading input from certificate.dat
+>
+> Canonical output:
+> (2:aa2:bb(2:cc2:dd))
+> Base64 (of canonical) output:
+> {KDI6YWEyOmJiKDI6Y2MyOmRkKSk=}
+> Advanced transport output:
+> (aa bb (cc dd))
+----
+
+Repeatedly prompt for S-Expressions input from console, parse and output it
+console in advanced, base64 and canonical formats:
+
+[source,sh]
+----
+$ sexpp -p -a -b -c -x
+----
+
+or just
+
+[source,sh]
+----
+$ sexpp
+
+> Input:
+> (abc def (ghi jkl))
+>
+> Canonical output:
+> (3:abc3:def(3:ghi3:jkl))
+> Base64 (of canonical) output:
+> {KDM6YWJjMzpkZWYoMzpnaGkzOmprbCkp}
+> Advanced transport output:
+> (abc def (ghi jkl))
+>
+> Input:
+> ^C
+----
+
+== License
+
+Copyright Ribose.
+
+The code is made available as open-source software under the MIT License.
diff --git a/cmake/sexp-samples-folder.h.in b/cmake/sexp-samples-folder.h.in
new file mode 100644
index 0000000..7b652e8
--- /dev/null
+++ b/cmake/sexp-samples-folder.h.in
@@ -0,0 +1,24 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ */
+
+const std::string sexp_samples_folder = "@SEXP_SAMPLES_FOLDER@";
diff --git a/cmake/sexpp.pc.in b/cmake/sexpp.pc.in
new file mode 100644
index 0000000..1ab63e1
--- /dev/null
+++ b/cmake/sexpp.pc.in
@@ -0,0 +1,9 @@
+libdir=@CMAKE_INSTALL_FULL_LIBDIR@
+includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
+
+Name: sexp
+Description: C++ library for working with S-Expressions
+Version: @PROJECT_VERSION@
+URL: https://github.com/rnpgp/sexp
+Libs: -L${libdir} -lsexpp
+Cflags: -I${includedir}
diff --git a/cmake/version.cmake b/cmake/version.cmake
new file mode 100644
index 0000000..41287e4
--- /dev/null
+++ b/cmake/version.cmake
@@ -0,0 +1,162 @@
+#
+# Copyright 2018-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+# desired length of commit hash
+set(GIT_REV_LEN 7)
+
+# call git, store output in var (can fail)
+macro(_git var)
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" ${ARGN}
+ WORKING_DIRECTORY "${source_dir}"
+ RESULT_VARIABLE _git_ec
+ OUTPUT_VARIABLE ${var}
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ )
+endmacro()
+
+function(extract_version_info version var_prefix)
+ # extract the main components
+ # v1.9.0-3-g5b92266+1546836556
+ # v1.9.0-3-g5b92266-dirty+1546836556
+ string(REGEX MATCH "^v?(([0-9]+)\\.[0-9]+\\.[0-9]+)(-([0-9]+)-g([0-9a-f]+)(-dirty)?)?(\\+([0-9]+))?$" matches "${version}")
+ if (NOT matches)
+ message(FATAL_ERROR "Failed to extract version components from ${version}.")
+ endif()
+ set(${var_prefix}_VERSION "${CMAKE_MATCH_1}" PARENT_SCOPE) # 1.9.0
+ set(${var_prefix}_MAJOR_VERSION "${CMAKE_MATCH_2}" PARENT_SCOPE) # 1
+ if (NOT CMAKE_MATCH_4)
+ set(CMAKE_MATCH_4 "0")
+ endif()
+ set(${var_prefix}_VERSION_NCOMMITS "${CMAKE_MATCH_4}" PARENT_SCOPE) # 3
+ if (NOT CMAKE_MATCH_5)
+ set(CMAKE_MATCH_5 "0")
+ endif()
+ set(${var_prefix}_VERSION_GIT_REV "${CMAKE_MATCH_5}" PARENT_SCOPE) # 5b92266
+ if (CMAKE_MATCH_6 STREQUAL "-dirty")
+ set(${var_prefix}_VERSION_IS_DIRTY TRUE PARENT_SCOPE)
+ else()
+ set(${var_prefix}_VERSION_IS_DIRTY FALSE PARENT_SCOPE)
+ endif()
+ # timestamp is optional, default to 0
+ if (NOT CMAKE_MATCH_8)
+ set(CMAKE_MATCH_8 "0")
+ endif()
+ set(${var_prefix}_VERSION_COMMIT_TIMESTAMP "${CMAKE_MATCH_8}" PARENT_SCOPE) # 1546836556
+endfunction()
+
+function(determine_version source_dir var_prefix)
+ set(has_release_tag NO)
+ set(has_version_txt NO)
+ set(local_prefix "_determine_ver")
+ # find out base version via version.txt
+ set(base_version "0.0.0")
+ if (EXISTS "${source_dir}/version.txt")
+ set(has_version_txt YES)
+ file(STRINGS "${source_dir}/version.txt" version_file)
+ extract_version_info("${version_file}" "${local_prefix}")
+ set(base_version "${${local_prefix}_VERSION}")
+ message(STATUS "Found version.txt with ${version_file}")
+ else()
+ message(STATUS "Found no version.txt.")
+ endif()
+ # for GIT_EXECUTABLE
+ find_package(Git)
+ # get a description of the version, something like:
+ # v1.9.1-0-g38ffe82 (a tagged release)
+ # v1.9.1-0-g38ffe82-dirty (a tagged release with local modifications)
+ # v1.9.0-3-g5b92266 (post-release snapshot)
+ # v1.9.0-3-g5b92266-dirty (post-release snapshot with local modifications)
+ _git(version describe --abbrev=${GIT_REV_LEN} --match "v[0-9]*" --long --dirty)
+ if (NOT _git_ec EQUAL 0)
+ # no annotated tags, fake one
+ message(STATUS "Found no annotated tags.")
+ _git(revision rev-parse --short=${GIT_REV_LEN} --verify HEAD)
+ if (_git_ec EQUAL 0)
+ set(version "v${base_version}-0-g${revision}")
+ # check if dirty (this won't detect untracked files, but should be ok)
+ _git(changes diff-index --quiet HEAD --)
+ if (NOT _git_ec EQUAL 0)
+ string(APPEND version "-dirty")
+ endif()
+ # append the commit timestamp of the most recent commit (only
+ # in non-release branches -- typically master)
+ _git(commit_timestamp show -s --format=%ct)
+ if (_git_ec EQUAL 0)
+ string(APPEND version "+${commit_timestamp}")
+ endif()
+ elseif(has_version_txt)
+ # Nothing to get from git - so use version.txt completely
+ set(version "${version_file}")
+ else()
+ # Sad case - no git, no version.txt
+ set(version "v${base_version}")
+ endif()
+ else()
+ set(has_release_tag YES)
+ message(STATUS "Found annotated tag ${version}")
+ endif()
+ extract_version_info("${version}" "${local_prefix}")
+ if ("${has_version_txt}" AND NOT ${base_version} STREQUAL ${local_prefix}_VERSION)
+ message(WARNING "Tagged version ${${local_prefix}_VERSION} doesn't match one from the version.txt: ${base_version}")
+ if (${base_version} VERSION_GREATER ${local_prefix}_VERSION)
+ set(${local_prefix}_VERSION ${base_version})
+ endif()
+ endif()
+ foreach(suffix VERSION VERSION_NCOMMITS VERSION_GIT_REV VERSION_IS_DIRTY VERSION_COMMIT_TIMESTAMP)
+ if (NOT DEFINED ${local_prefix}_${suffix})
+ message(FATAL_ERROR "Unable to determine version.")
+ endif()
+ set(${var_prefix}_${suffix} "${${local_prefix}_${suffix}}" PARENT_SCOPE)
+ message(STATUS "${var_prefix}_${suffix}: ${${local_prefix}_${suffix}}")
+ endforeach()
+ # Set VERSION_SUFFIX and VERSION_FULL. When making changes, be aware that
+ # this is used in packaging as well and will affect ordering.
+ # | state | version_full |
+ # |-----------------------------------------------------|
+ # | exact tag | 0.9.0 |
+ # | exact tag, dirty | 0.9.0+git20180604 |
+ # | after tag | 0.9.0+git20180604.1.085039f |
+ # | no tag, version.txt | 0.9.0+git20180604.2ee02af |
+ # | no tag, no version.txt| 0.0.0+git20180604.2ee02af |
+ string(TIMESTAMP date "%Y%m%d" UTC)
+ set(version_suffix "")
+ if (NOT ${local_prefix}_VERSION_NCOMMITS EQUAL 0)
+ # 0.9.0+git20150604.4.289818b
+ string(APPEND version_suffix "+git${date}.${${local_prefix}_VERSION_NCOMMITS}.${${local_prefix}_VERSION_GIT_REV}")
+ elseif ((NOT has_release_tag) AND ((NOT has_version_txt) OR ("${base_version}" STREQUAL "0.0.0") OR (NOT "${revision}" STREQUAL "")))
+ # 0.9.0+git20150604.289818b
+ string(APPEND version_suffix "+git${date}.${${local_prefix}_VERSION_GIT_REV}")
+ elseif(${local_prefix}_VERSION_IS_DIRTY)
+ # 0.9.0+git20150604
+ string(APPEND version_suffix "+git${date}")
+ endif()
+ set(version_full "${${local_prefix}_VERSION}${version_suffix}")
+ # set the results
+ set(${var_prefix}_VERSION_SUFFIX "${version_suffix}" PARENT_SCOPE)
+ set(${var_prefix}_VERSION_FULL "${version_full}" PARENT_SCOPE)
+ set(${var_prefix}_MAJOR_VERSION "${${local_prefix}_MAJOR_VERSION}" PARENT_SCOPE) # 1
+ # for informational purposes
+ message(STATUS "${var_prefix}_MAJOR_VERSION: ${${local_prefix}_MAJOR_VERSION}")
+ message(STATUS "${var_prefix}_VERSION_SUFFIX: ${version_suffix}")
+ message(STATUS "${var_prefix}_VERSION_FULL: ${version_full}")
+endfunction()
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..d5eedac
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,24 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+ignore:
+ - "tests"
+ - "src/sexp-main.cpp"
diff --git a/default.nix b/default.nix
new file mode 100644
index 0000000..618fa25
--- /dev/null
+++ b/default.nix
@@ -0,0 +1,49 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+{ pkgs ? import <nixpkgs> { }
+, lib ? pkgs.lib
+, stdenv ? pkgs.stdenv
+}:
+
+stdenv.mkDerivation rec {
+ pname = "sexp";
+ version = "unstable";
+
+ src = ./.;
+
+ cmakeFlags = [
+ "-DCMAKE_INSTALL_PREFIX=${placeholder "out"}"
+ "-DWITH_SEXP_TESTS=on"
+ "-DWITH_SEXP_CLI=on"
+ "-DDOWNLOAD_GTEST=off"
+ ];
+
+ nativeBuildInputs = with pkgs; [ cmake gtest pkg-config ];
+
+ meta = with lib; {
+ homepage = "https://github.com/rnpgp/sexp";
+ description = "S-expressions parser and generator C++ library, fully compliant to [https://people.csail.mit.edu/rivest/Sexp.txt]";
+ license = licenses.bsd2;
+ platforms = platforms.all;
+ maintainers = with maintainers; [ ribose-jeffreylau ];
+ };
+} \ No newline at end of file
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..a31de2c
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,42 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "locked": {
+ "lastModified": 1667395993,
+ "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1669023499,
+ "narHash": "sha256-iXvCW2VaH4LJD1AElk82/FaR7yepj/BqLYOJ3GAhdKQ=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "c107fb66da7c150110fe301c38932eeccf5f9824",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..c2fb794
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,47 @@
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+{
+ description = "S-expressions parser and generator C++ library, fully compliant to [https://people.csail.mit.edu/rivest/Sexp.txt]";
+
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs";
+ flake-utils.url = "github:numtide/flake-utils";
+ };
+
+ outputs = { self, nixpkgs, flake-utils }:
+ flake-utils.lib.eachDefaultSystem (system:
+ let
+ pkgs = nixpkgs.legacyPackages.${system};
+ thePackage = pkgs.callPackage ./default.nix { };
+ in
+ rec {
+ defaultApp = flake-utils.lib.mkApp {
+ drv = defaultPackage;
+ };
+ defaultPackage = thePackage;
+ devShell = pkgs.mkShell {
+ buildInputs = [
+ thePackage
+ ];
+ };
+ });
+}
diff --git a/include/sexpp/ext-key-format.h b/include/sexpp/ext-key-format.h
new file mode 100644
index 0000000..bef746f
--- /dev/null
+++ b/include/sexpp/ext-key-format.h
@@ -0,0 +1,95 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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 <map>
+
+#include "sexp-public.h"
+#include "sexp.h"
+
+namespace ext_key_format {
+
+void SEXP_PUBLIC_SYMBOL ext_key_error(
+ sexp::sexp_exception_t::severity level, const char *msg, size_t c1, size_t c2, int pos);
+
+class ext_key_input_stream_t;
+
+class SEXP_PUBLIC_SYMBOL extended_private_key_t {
+ public:
+ // Comparison of names is done case insensitively !!!
+ struct ci_less {
+ // case-independent (ci) compare_less binary function
+ bool operator()(const std::string &s1, const std::string &s2) const
+ {
+ return std::lexicographical_compare(
+ s1.begin(), s1.end(), s2.begin(), s2.end(), [](char a, char b) {
+ return std::tolower(a) < std::tolower(b);
+ });
+ }
+ };
+
+ // C++ 11 compatible version (no std::equals)
+ static bool iequals(const std::string &a, const std::string &b)
+ {
+ size_t sz = a.size();
+ if (b.size() != sz)
+ return false;
+ for (size_t i = 0; i < sz; ++i)
+ if (tolower(a[i]) != tolower(b[i]))
+ return false;
+ return true;
+ }
+
+ typedef std::multimap<std::string, std::string, ci_less> fields_map_t;
+
+ sexp::sexp_list_t key;
+ fields_map_t fields;
+
+ void parse(ext_key_input_stream_t &is);
+};
+
+class SEXP_PUBLIC_SYMBOL ext_key_input_stream_t : public sexp::sexp_input_stream_t {
+ private:
+ static const bool namechar[256]; /* true if allowed in the name field */
+
+ static bool is_newline_char(int c) { return c == '\r' || c == '\n'; };
+ static bool is_namechar(int c) { return ((c >= 0 && c <= 255) && namechar[c]); }
+
+ bool is_scanning_value;
+ bool has_key;
+
+ int skip_line(void);
+ virtual int read_char(void);
+ std::string scan_name(int c);
+ std::string scan_value(void);
+
+ public:
+ ext_key_input_stream_t(std::istream *i, size_t md = 0)
+ : sexp_input_stream_t(i, md), is_scanning_value(false), has_key(false)
+ {
+ }
+ virtual ~ext_key_input_stream_t() = default;
+ void scan(extended_private_key_t &extended_key);
+};
+} // namespace ext_key_format
diff --git a/include/sexpp/sexp-error.h b/include/sexpp/sexp-error.h
new file mode 100644
index 0000000..3ed345f
--- /dev/null
+++ b/include/sexpp/sexp-error.h
@@ -0,0 +1,73 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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 <cstdint>
+#include <exception>
+#include <iostream>
+#include <string>
+
+#include "sexp-public.h"
+
+namespace sexp {
+
+class SEXP_PUBLIC_SYMBOL sexp_exception_t : public std::exception {
+ public:
+ enum severity { error = 0, warning = 1 };
+
+ protected:
+ static severity verbosity;
+ static bool interactive;
+
+ int position; // May be EOF aka -1
+ severity level;
+ std::string message;
+
+ public:
+ sexp_exception_t(std::string error_message,
+ severity error_level,
+ int error_position,
+ const char *prefix = "SEXP")
+ : position{error_position}, level{error_level},
+ message{format(prefix, std::move(error_message), error_level, error_position)} {};
+
+ static std::string format(std::string prf,
+ std::string message,
+ severity level,
+ int position);
+
+ static bool shall_throw(severity level) { return level == error || verbosity != error; };
+ virtual const char *what(void) const throw() { return message.c_str(); };
+ severity get_level(void) const { return level; };
+ uint32_t get_position(void) const { return position; };
+ static severity get_verbosity(void) { return verbosity; };
+ static bool is_interactive(void) { return interactive; };
+ static void set_verbosity(severity new_verbosity) { verbosity = new_verbosity; };
+ static void set_interactive(bool new_interactive) { interactive = new_interactive; };
+};
+
+void SEXP_PUBLIC_SYMBOL
+sexp_error(sexp_exception_t::severity level, const char *msg, size_t c1, size_t c2, int pos);
+
+} // namespace sexp
diff --git a/include/sexpp/sexp-public.h b/include/sexpp/sexp-public.h
new file mode 100644
index 0000000..ab0674e
--- /dev/null
+++ b/include/sexpp/sexp-public.h
@@ -0,0 +1,30 @@
+/**
+ *
+ * Copyright 2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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
+
+#ifdef BUILD_SHARED_LIBS
+#define SEXP_PUBLIC_SYMBOL __attribute__((visibility("default")))
+#else
+#define SEXP_PUBLIC_SYMBOL
+#endif
diff --git a/include/sexpp/sexp.h b/include/sexpp/sexp.h
new file mode 100644
index 0000000..bb6ae4e
--- /dev/null
+++ b/include/sexpp/sexp.h
@@ -0,0 +1,466 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ * Original copyright
+ *
+ * SEXP standard header file: sexp.h
+ * Ronald L. Rivest
+ * 6/29/1997
+ */
+
+#pragma once
+
+#include <inttypes.h>
+#include <climits>
+#include <limits>
+#include <cctype>
+#include <locale>
+#include <cstring>
+#include <memory>
+#include <algorithm>
+#include <iostream>
+#include <string>
+#include <vector>
+#include <cassert>
+
+#include "sexp-public.h"
+#include "sexp-error.h"
+
+namespace sexp {
+
+/*
+ * SEXP octet_t definitions
+ * We maintain some presumable redundancy with ctype
+ * However, we do enforce 'C' locale this way
+ */
+
+class SEXP_PUBLIC_SYMBOL sexp_char_defs_t {
+ protected:
+ static const bool base64digit[256]; /* true if c is base64 digit */
+ static const bool tokenchar[256]; /* true if c can be in a token */
+ static const unsigned char values[256][3]; /* values of c as { dec. hex, base64 } digit */
+ static std::locale c_locale;
+
+ static bool is_white_space(int c)
+ {
+ return c >= 0 && c <= 255 && std::isspace((char) c, c_locale);
+ };
+ static bool is_dec_digit(int c)
+ {
+ return c >= 0 && c <= 255 && std::isdigit((char) c, c_locale);
+ };
+ static bool is_hex_digit(int c)
+ {
+ return c >= 0 && c <= 255 && std::isxdigit((char) c, c_locale);
+ };
+ static bool is_base64_digit(int c) { return c >= 0 && c <= 255 && base64digit[c]; };
+ static bool is_token_char(int c) { return c >= 0 && c <= 255 && tokenchar[c]; };
+ static bool is_alpha(int c)
+ {
+ return c >= 0 && c <= 255 && std::isalpha((char) c, c_locale);
+ };
+
+ /* decvalue(c) is value of c as dec digit */
+ static unsigned char decvalue(int c) { return (c >= 0 && c <= 255) ? values[c][0] : 0; };
+ /* hexvalue(c) is value of c as a hex digit */
+ static unsigned char hexvalue(int c) { return (c >= 0 && c <= 255) ? values[c][1] : 0; };
+ /* base64value(c) is value of c as base64 digit */
+ static unsigned char base64value(int c)
+ {
+ return (c >= 0 && c <= 255) ? values[c][2] : 0;
+ };
+};
+
+class sexp_string_t;
+class sexp_list_t;
+
+class sexp_output_stream_t;
+class sexp_input_stream_t;
+
+/*
+ * SEXP simple string
+ */
+
+typedef uint8_t octet_t;
+
+class SEXP_PUBLIC_SYMBOL sexp_simple_string_t : public std::basic_string<octet_t>,
+ private sexp_char_defs_t {
+ public:
+ sexp_simple_string_t(void) = default;
+ sexp_simple_string_t(const octet_t *dt) : std::basic_string<octet_t>{dt} {}
+ sexp_simple_string_t(const octet_t *bt, size_t ln) : std::basic_string<octet_t>{bt, ln} {}
+ sexp_simple_string_t &append(int c)
+ {
+ (*this) += (octet_t)(c & 0xFF);
+ return *this;
+ }
+ // Returns length for printing simple string as a token
+ size_t advanced_length_token(void) const { return length(); }
+ // Returns length for printing simple string as a base64 string
+ size_t advanced_length_base64(void) const { return (2 + 4 * ((length() + 2) / 3)); }
+ // Returns length for printing simple string ss in quoted-string mode
+ size_t advanced_length_quoted(void) const { return (1 + length() + 1); }
+ // Returns length for printing simple string ss in hexadecimal mode
+ size_t advanced_length_hexadecimal(void) const { return (1 + 2 * length() + 1); }
+ size_t advanced_length(sexp_output_stream_t *os) const;
+
+ sexp_output_stream_t *print_canonical_verbatim(sexp_output_stream_t *os) const;
+ sexp_output_stream_t *print_advanced(sexp_output_stream_t *os) const;
+ sexp_output_stream_t *print_token(sexp_output_stream_t *os) const;
+ sexp_output_stream_t *print_quoted(sexp_output_stream_t *os) const;
+ sexp_output_stream_t *print_hexadecimal(sexp_output_stream_t *os) const;
+ sexp_output_stream_t *print_base64(sexp_output_stream_t *os) const;
+
+ bool can_print_as_quoted_string(void) const;
+ bool can_print_as_token(const sexp_output_stream_t *os) const;
+
+ bool operator==(const char *right) const noexcept
+ {
+ return length() == std::strlen(right) && std::memcmp(data(), right, length()) == 0;
+ }
+
+ bool operator!=(const char *right) const noexcept
+ {
+ return length() != std::strlen(right) || std::memcmp(data(), right, length()) != 0;
+ }
+
+ unsigned as_unsigned() const noexcept
+ {
+ return empty() ? std::numeric_limits<uint32_t>::max() :
+ (unsigned) atoi(reinterpret_cast<const char *>(c_str()));
+ }
+};
+
+inline bool operator==(const sexp_simple_string_t *left, const std::string &right) noexcept
+{
+ return *left == right.c_str();
+}
+
+inline bool operator!=(const sexp_simple_string_t *left, const std::string &right) noexcept
+{
+ return *left != right.c_str();
+}
+
+/*
+ * SEXP object
+ */
+
+class SEXP_PUBLIC_SYMBOL sexp_object_t {
+ public:
+ virtual ~sexp_object_t(){};
+
+ virtual sexp_output_stream_t *print_canonical(sexp_output_stream_t *os) const = 0;
+ virtual sexp_output_stream_t *print_advanced(sexp_output_stream_t *os) const;
+ virtual size_t advanced_length(sexp_output_stream_t *os) const = 0;
+
+ virtual sexp_list_t * sexp_list_view(void) noexcept { return nullptr; }
+ virtual sexp_string_t *sexp_string_view(void) noexcept { return nullptr; }
+ virtual bool is_sexp_list(void) const noexcept { return false; }
+ virtual bool is_sexp_string(void) const noexcept { return false; }
+
+ virtual const sexp_list_t *sexp_list_at(
+ std::vector<std::shared_ptr<sexp_object_t>>::size_type pos) const noexcept
+ {
+ return nullptr;
+ }
+ virtual const sexp_string_t *sexp_string_at(
+ std::vector<std::shared_ptr<sexp_object_t>>::size_type pos) const noexcept
+ {
+ return nullptr;
+ }
+ virtual const sexp_simple_string_t *sexp_simple_string_at(
+ std::vector<std::shared_ptr<sexp_object_t>>::size_type pos) const noexcept
+ {
+ return nullptr;
+ }
+ virtual bool operator==(const char *right) const noexcept { return false; }
+ virtual bool operator!=(const char *right) const noexcept { return true; }
+ virtual unsigned as_unsigned() const noexcept
+ {
+ return std::numeric_limits<uint32_t>::max();
+ }
+};
+
+/*
+ * SEXP string
+ */
+
+class SEXP_PUBLIC_SYMBOL sexp_string_t : public sexp_object_t {
+ protected:
+ bool with_presentation_hint;
+ sexp_simple_string_t presentation_hint;
+ sexp_simple_string_t data_string;
+
+ public:
+ sexp_string_t(const octet_t *dt) : with_presentation_hint(false), data_string(dt) {}
+ sexp_string_t(const octet_t *bt, size_t ln)
+ : with_presentation_hint(false), data_string(bt, ln)
+ {
+ }
+ sexp_string_t(const std::string &str)
+ : with_presentation_hint(false),
+ data_string(reinterpret_cast<const octet_t *>(str.data()))
+ {
+ }
+ sexp_string_t(void) : with_presentation_hint(false) {}
+ sexp_string_t(sexp_input_stream_t *sis) { parse(sis); };
+
+ const bool has_presentation_hint(void) const noexcept { return with_presentation_hint; }
+ const sexp_simple_string_t &get_string(void) const noexcept { return data_string; }
+ const sexp_simple_string_t &set_string(const sexp_simple_string_t &ss)
+ {
+ return data_string = ss;
+ }
+ const sexp_simple_string_t &get_presentation_hint(void) const noexcept
+ {
+ return presentation_hint;
+ }
+ const sexp_simple_string_t &set_presentation_hint(const sexp_simple_string_t &ph)
+ {
+ with_presentation_hint = true;
+ return presentation_hint = ph;
+ }
+
+ virtual sexp_output_stream_t *print_canonical(sexp_output_stream_t *os) const;
+ virtual sexp_output_stream_t *print_advanced(sexp_output_stream_t *os) const;
+ virtual size_t advanced_length(sexp_output_stream_t *os) const;
+
+ virtual sexp_string_t *sexp_string_view(void) noexcept { return this; }
+ virtual bool is_sexp_string(void) const noexcept { return true; }
+
+ virtual bool operator==(const char *right) const noexcept { return data_string == right; }
+ virtual bool operator!=(const char *right) const noexcept { return data_string != right; }
+
+ void parse(sexp_input_stream_t *sis);
+ virtual unsigned as_unsigned() const noexcept { return data_string.as_unsigned(); }
+};
+
+inline bool operator==(const sexp_string_t *left, const std::string &right) noexcept
+{
+ return *left == right.c_str();
+}
+
+inline bool operator!=(const sexp_string_t *left, const std::string &right) noexcept
+{
+ return *left != right.c_str();
+}
+
+/*
+ * SEXP list
+ */
+
+class SEXP_PUBLIC_SYMBOL sexp_list_t : public sexp_object_t,
+ public std::vector<std::shared_ptr<sexp_object_t>> {
+ public:
+ virtual ~sexp_list_t() {}
+
+ virtual sexp_output_stream_t *print_canonical(sexp_output_stream_t *os) const;
+ virtual sexp_output_stream_t *print_advanced(sexp_output_stream_t *os) const;
+ virtual size_t advanced_length(sexp_output_stream_t *os) const;
+
+ virtual sexp_list_t *sexp_list_view(void) noexcept { return this; }
+ virtual bool is_sexp_list(void) const noexcept { return true; }
+
+ virtual const sexp_list_t *sexp_list_at(size_type pos) const noexcept
+ {
+ return pos < size() ? (*at(pos)).sexp_list_view() : nullptr;
+ }
+ virtual const sexp_string_t *sexp_string_at(size_type pos) const noexcept
+ {
+ return pos < size() ? (*at(pos)).sexp_string_view() : nullptr;
+ }
+ const sexp_simple_string_t *sexp_simple_string_at(size_type pos) const noexcept
+ {
+ auto s = sexp_string_at(pos);
+ return s != nullptr ? &s->get_string() : nullptr;
+ }
+
+ void parse(sexp_input_stream_t *sis);
+};
+
+/*
+ sexp_depth_manager controls maximum allowed nesting of sexp lists
+ for sexp_input_stream, sexp_output_stream processing
+ One still can create an object with deeper nesting manually
+*/
+
+class SEXP_PUBLIC_SYMBOL sexp_depth_manager {
+ public:
+ static const size_t DEFAULT_MAX_DEPTH = 1024;
+
+ private:
+ size_t depth; /* current depth of nested SEXP lists */
+ size_t max_depth; /* maximum allowed depth of nested SEXP lists, 0 if no limit */
+ protected:
+ sexp_depth_manager(size_t m_depth = DEFAULT_MAX_DEPTH);
+ void reset_depth(size_t m_depth);
+ void increase_depth(int count = -1);
+ void decrease_depth(void);
+};
+
+/*
+ * SEXP input stream
+ */
+
+class SEXP_PUBLIC_SYMBOL sexp_input_stream_t : public sexp_char_defs_t, sexp_depth_manager {
+ protected:
+ std::istream *input_file;
+ uint32_t byte_size; /* 4 or 6 or 8 == currently scanning mode */
+ int next_char; /* character currently being scanned */
+ uint32_t bits; /* Bits waiting to be used */
+ uint32_t n_bits; /* number of such bits waiting to be used */
+ int count; /* number of 8-bit characters output by get_char */
+
+ virtual int read_char(void);
+
+ public:
+ sexp_input_stream_t(std::istream *i,
+ size_t max_depth = sexp_depth_manager::DEFAULT_MAX_DEPTH);
+ virtual ~sexp_input_stream_t() = default;
+ sexp_input_stream_t * set_input(std::istream *i,
+ size_t max_depth = sexp_depth_manager::DEFAULT_MAX_DEPTH);
+ sexp_input_stream_t * set_byte_size(uint32_t new_byte_size);
+ uint32_t get_byte_size(void) { return byte_size; }
+ sexp_input_stream_t * get_char(void);
+ sexp_input_stream_t * skip_white_space(void);
+ sexp_input_stream_t * skip_char(int c);
+ std::shared_ptr<sexp_object_t> scan_to_eof();
+ std::shared_ptr<sexp_object_t> scan_object(void);
+ std::shared_ptr<sexp_string_t> scan_string(void);
+ std::shared_ptr<sexp_list_t> scan_list(void);
+ sexp_simple_string_t scan_simple_string(void);
+ void scan_token(sexp_simple_string_t &ss);
+ void scan_verbatim_string(sexp_simple_string_t &ss, uint32_t length);
+ void scan_quoted_string(sexp_simple_string_t &ss, uint32_t length);
+ void scan_hexadecimal_string(sexp_simple_string_t &ss, uint32_t length);
+ void scan_base64_string(sexp_simple_string_t &ss, uint32_t length);
+ uint32_t scan_decimal_string(void);
+
+ int get_next_char(void) const { return next_char; }
+ int set_next_char(int c) { return next_char = c; }
+
+ sexp_input_stream_t *open_list(void);
+ sexp_input_stream_t *close_list(void);
+};
+
+/*
+ * SEXP output stream
+ */
+
+class SEXP_PUBLIC_SYMBOL sexp_output_stream_t : sexp_depth_manager {
+ public:
+ const uint32_t default_line_length = 75;
+ enum sexp_print_mode { /* PRINTING MODES */
+ canonical = 1, /* standard for hashing and tranmission */
+ base64 = 2, /* base64 version of canonical */
+ advanced = 3 /* pretty-printed */
+ };
+
+ protected:
+ std::ostream * output_file;
+ uint32_t base64_count; /* number of hex or base64 chars printed this region */
+ uint32_t byte_size; /* 4 or 6 or 8 depending on output mode */
+ uint32_t bits; /* bits waiting to go out */
+ uint32_t n_bits; /* number of bits waiting to go out */
+ sexp_print_mode mode; /* base64, advanced, or canonical */
+ uint32_t column; /* column where next character will go */
+ uint32_t max_column; /* max usable column, or 0 if no maximum */
+ uint32_t indent; /* current indentation level (starts at 0) */
+ public:
+ sexp_output_stream_t(std::ostream *o,
+ size_t max_depth = sexp_depth_manager::DEFAULT_MAX_DEPTH);
+ sexp_output_stream_t *set_output(std::ostream *o,
+ size_t max_depth = sexp_depth_manager::DEFAULT_MAX_DEPTH);
+ sexp_output_stream_t *put_char(int c); /* output a character */
+ sexp_output_stream_t *new_line(sexp_print_mode mode); /* go to next line (and indent) */
+ sexp_output_stream_t *var_put_char(int c);
+ sexp_output_stream_t *flush(void);
+ sexp_output_stream_t *print_decimal(uint64_t n);
+
+ sexp_output_stream_t *change_output_byte_size(int newByteSize, sexp_print_mode mode);
+
+ sexp_output_stream_t *print_canonical(const std::shared_ptr<sexp_object_t> &obj)
+ {
+ return obj->print_canonical(this);
+ }
+ sexp_output_stream_t *print_advanced(const std::shared_ptr<sexp_object_t> &obj)
+ {
+ return obj->print_advanced(this);
+ };
+ sexp_output_stream_t *print_base64(const std::shared_ptr<sexp_object_t> &obj);
+ sexp_output_stream_t *print_canonical(const sexp_simple_string_t *ss)
+ {
+ return ss->print_canonical_verbatim(this);
+ }
+ sexp_output_stream_t *print_advanced(const sexp_simple_string_t *ss)
+ {
+ return ss->print_advanced(this);
+ };
+
+ uint32_t get_byte_size(void) const { return byte_size; }
+ uint32_t get_column(void) const { return column; }
+ sexp_output_stream_t *reset_column(void)
+ {
+ column = 0;
+ return this;
+ }
+ uint32_t get_max_column(void) const { return max_column; }
+ sexp_output_stream_t *set_max_column(uint32_t mc)
+ {
+ max_column = mc;
+ return this;
+ }
+ sexp_output_stream_t *inc_indent(void)
+ {
+ ++indent;
+ return this;
+ }
+ sexp_output_stream_t *dec_indent(void)
+ {
+ --indent;
+ return this;
+ }
+
+ sexp_output_stream_t *open_list(void)
+ {
+ put_char('(');
+ increase_depth();
+
+ return this;
+ }
+ sexp_output_stream_t *close_list(void)
+ {
+ put_char(')')->decrease_depth();
+ return this;
+ }
+ sexp_output_stream_t *var_open_list(void)
+ {
+ var_put_char('(')->increase_depth();
+ return this;
+ }
+ sexp_output_stream_t *var_close_list(void)
+ {
+ var_put_char(')')->decrease_depth();
+ return this;
+ }
+};
+
+} // namespace sexp
diff --git a/man/sexpp.1 b/man/sexpp.1
new file mode 100644
index 0000000..5a6965c
--- /dev/null
+++ b/man/sexpp.1
@@ -0,0 +1,67 @@
+.TH SEXPP "1" "June 2023" "sexpp" "User Commands"
+
+.SH NAME
+
+sexpp - Read, parse, and print out S-expressions
+
+.SH SYNOPSIS
+
+.B cat certificate-file | sexpp -a -x
+
+.SH DESCRIPTION
+
+\fBsexpp\fP typically reads an S-expression from standard input and rewrites it to standard output.
+
+Running without switches implies: -p -a -b -c -x
+
+.SH INPUT OPTIONS
+
+.B -i filename
+Takes input from file instead of stdin.
+.TP
+.B -p
+Prompts user for console input.
+.TP
+.B -s
+Treat input up to EOF as a single string, instead of parsing.
+
+.SH CONTROL LOOP
+
+The main routine typically reads one S-expression, prints it out again, and stops. This may be modified:
+.TP
+.B -x
+Execute main loop repeatedly until end of file.
+
+.SH OUTPUT OPTIONS
+
+The output format is normally canonical, but this can be changed.
+More than one output format can be requested at once.
+
+.TP
+.B -o filename
+Write output to file instead of stdout.
+.TP
+.B -a
+Write output in advanced transport format.
+.TP
+.B -b
+Write output in base-64 output format.
+.TP
+.B -c
+Write output in canonical format.
+.TP
+.B -l
+Suppress linefeeds after output.
+.TP
+.B -w width
+Changes line width to specified width (0 implies no line-width constraint). Default: 75
+
+.SH AUTHOR
+
+The \fBsexpp\fP project is maintained by Maxim Samsonov on behalf of Ribose, Inc.
+It is based on code from Ron Rivest and Butler Lampson.
+This manual page was written by Daniel Kahn Gillmor for the Debian project, but may be used by others.
+
+.SH SEE ALSO
+
+https://datatracker.ietf.org/doc/draft-rivest-sexp/
diff --git a/samples/baseline/sexp-sample-a b/samples/baseline/sexp-sample-a
new file mode 100644
index 0000000..63d8742
--- /dev/null
+++ b/samples/baseline/sexp-sample-a
@@ -0,0 +1,19 @@
+(certificate
+ (issuer
+ (name
+ (public-key
+ rsa-with-md5
+ (e |NFGq/E3wh9f4rJIQVXhS|)
+ (n |d738/4ghP9rFZ0gAIYZ5q9y6iskDJwASi5rEQpEQq8ZyMZeIZzIAR2I5iGE=|))
+ aid-committee))
+ (subject
+ (ref
+ (public-key
+ rsa-with-md5
+ (e |NFGq/E3wh9f4rJIQVXhS|)
+ (n |d738/4ghP9rFZ0gAIYZ5q9y6iskDJwASi5rEQpEQq8ZyMZeIZzIAR2I5iGE=|))
+ tom
+ mother))
+ (not-before "1997-01-01_09:00:00")
+ (not-after "1998-01-01_09:00:00")
+ (tag (spend (account "12345678") (* numeric range "1" "1000")))) \ No newline at end of file
diff --git a/samples/baseline/sexp-sample-b b/samples/baseline/sexp-sample-b
new file mode 100644
index 0000000..6311e61
--- /dev/null
+++ b/samples/baseline/sexp-sample-b
@@ -0,0 +1 @@
+{KDExOmNlcnRpZmljYXRlKDY6aXNzdWVyKDQ6bmFtZSgxMDpwdWJsaWMta2V5MTI6cnNhLXdpdGgtbWQ1KDE6ZTE1OjRRqvxN8IfX+KySEFV4UikoMTpuNDQ6d738/4ghP9rFZ0gAIYZ5q9y6iskDJwASi5rEQpEQq8ZyMZeIZzIAR2I5iGEpKTEzOmFpZC1jb21taXR0ZWUpKSg3OnN1YmplY3QoMzpyZWYoMTA6cHVibGljLWtleTEyOnJzYS13aXRoLW1kNSgxOmUxNTo0Uar8TfCH1/iskhBVeFIpKDE6bjQ0One9/P+IIT/axWdIACGGeavcuorJAycAEouaxEKREKvGcjGXiGcyAEdiOYhhKSkzOnRvbTY6bW90aGVyKSkoMTA6bm90LWJlZm9yZTE5OjE5OTctMDEtMDFfMDk6MDA6MDApKDk6bm90LWFmdGVyMTk6MTk5OC0wMS0wMV8wOTowMDowMCkoMzp0YWcoNTpzcGVuZCg3OmFjY291bnQ4OjEyMzQ1Njc4KSgxOio3Om51bWVyaWM1OnJhbmdlMToxNDoxMDAwKSkpKQ==}
diff --git a/samples/baseline/sexp-sample-c b/samples/baseline/sexp-sample-c
new file mode 100644
index 0000000..a02692a
--- /dev/null
+++ b/samples/baseline/sexp-sample-c
Binary files differ
diff --git a/samples/compat/g10/canonical.key b/samples/compat/g10/canonical.key
new file mode 100644
index 0000000..423784e
--- /dev/null
+++ b/samples/compat/g10/canonical.key
Binary files differ
diff --git a/samples/compat/g23/correct.key b/samples/compat/g23/correct.key
new file mode 100755
index 0000000..1e9ee7b
--- /dev/null
+++ b/samples/compat/g23/correct.key
@@ -0,0 +1,43 @@
+Description: RSA/RSA
+Created: 20221130T160847
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
diff --git a/samples/compat/g23/correct_mult_fields.key b/samples/compat/g23/correct_mult_fields.key
new file mode 100755
index 0000000..1a6ddd5
--- /dev/null
+++ b/samples/compat/g23/correct_mult_fields.key
@@ -0,0 +1,45 @@
+Description: RSA/RSA
+Created: 20221130T160847
+Description: RSA/RSA
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
+Description: RSA/RSA
diff --git a/samples/compat/g23/correct_no_eol.key b/samples/compat/g23/correct_no_eol.key
new file mode 100755
index 0000000..641d58e
--- /dev/null
+++ b/samples/compat/g23/correct_no_eol.key
@@ -0,0 +1,43 @@
+Description: RSA/RSA with no terminating end-of-line
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
+Created: 20221130T160847 \ No newline at end of file
diff --git a/samples/compat/g23/correct_with_comment.key b/samples/compat/g23/correct_with_comment.key
new file mode 100755
index 0000000..74a7db9
--- /dev/null
+++ b/samples/compat/g23/correct_with_comment.key
@@ -0,0 +1,44 @@
+Description: RSA/RSA with a comment
+Created: 20221130T160847
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+ # *** this is a comment, dude ***
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
diff --git a/samples/compat/g23/correct_with_comment_at_eof.key b/samples/compat/g23/correct_with_comment_at_eof.key
new file mode 100755
index 0000000..fbc800b
--- /dev/null
+++ b/samples/compat/g23/correct_with_comment_at_eof.key
@@ -0,0 +1,45 @@
+Description: RSA/RSA with a comment
+Created: 20221130T160847
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+ # *** this is a comment, dude ***
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
+ # another comment \ No newline at end of file
diff --git a/samples/compat/g23/correct_with_empty_line.key b/samples/compat/g23/correct_with_empty_line.key
new file mode 100755
index 0000000..72e5485
--- /dev/null
+++ b/samples/compat/g23/correct_with_empty_line.key
@@ -0,0 +1,44 @@
+Description: RSA/RSA with empty line (aka comment)
+Created: 20221130T160847
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
diff --git a/samples/compat/g23/correct_with_two_empty_lines.key b/samples/compat/g23/correct_with_two_empty_lines.key
new file mode 100755
index 0000000..0074a5d
--- /dev/null
+++ b/samples/compat/g23/correct_with_two_empty_lines.key
@@ -0,0 +1,45 @@
+Description: RSA/RSA with empty line (aka comment)
+Created: 20221130T160847
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+
+
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
diff --git a/samples/compat/g23/correct_with_windows_eol.key b/samples/compat/g23/correct_with_windows_eol.key
new file mode 100644
index 0000000..10e3e1d
--- /dev/null
+++ b/samples/compat/g23/correct_with_windows_eol.key
@@ -0,0 +1,45 @@
+Description: RSA/RSA with empty line (aka comment)
+Created: 20221130T160847
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+
+
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
diff --git a/samples/compat/g23/malformed_invalid_name_char.key b/samples/compat/g23/malformed_invalid_name_char.key
new file mode 100755
index 0000000..f4394a1
--- /dev/null
+++ b/samples/compat/g23/malformed_invalid_name_char.key
@@ -0,0 +1,43 @@
+Description: RSA/RSA
+Created@: 20221130T160847
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
diff --git a/samples/compat/g23/malformed_invalid_name_first_char.key b/samples/compat/g23/malformed_invalid_name_first_char.key
new file mode 100755
index 0000000..f872c80
--- /dev/null
+++ b/samples/compat/g23/malformed_invalid_name_first_char.key
@@ -0,0 +1,43 @@
+Description: RSA/RSA
+1Created: 20221130T160847
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
diff --git a/samples/compat/g23/malformed_name_break.key b/samples/compat/g23/malformed_name_break.key
new file mode 100755
index 0000000..24cd7ba
--- /dev/null
+++ b/samples/compat/g23/malformed_name_break.key
@@ -0,0 +1,44 @@
+Descr
+ iption: RSA/RSA
+Created: 20221130T160847
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
diff --git a/samples/compat/g23/malformed_name_eof.key b/samples/compat/g23/malformed_name_eof.key
new file mode 100755
index 0000000..4f96ea3
--- /dev/null
+++ b/samples/compat/g23/malformed_name_eof.key
@@ -0,0 +1,43 @@
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
+Description: RSA/RSA
+Create \ No newline at end of file
diff --git a/samples/compat/g23/malformed_no_key.key b/samples/compat/g23/malformed_no_key.key
new file mode 100755
index 0000000..5d36252
--- /dev/null
+++ b/samples/compat/g23/malformed_no_key.key
@@ -0,0 +1,43 @@
+Description: RSA/RSA
+Created: 20221130T160847
+May: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
diff --git a/samples/compat/g23/malformed_two_keys.key b/samples/compat/g23/malformed_two_keys.key
new file mode 100755
index 0000000..a7de9e5
--- /dev/null
+++ b/samples/compat/g23/malformed_two_keys.key
@@ -0,0 +1,84 @@
+Description: RSA/RSA
+Created: 20221130T160847
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
+Key: (private-key (rsa (n #00CC38FA3F9A00F746AA87912071E7AD8EB2CD59BD5F
+ 895E275680DD63090E95B73275598554EC67BB1E27506F3CB5E324AD3B830365B96B04
+ FA3BF70EB362F01C8B3B6057AABAC4525171E22C6C9BF7CF540EBA2F9C58F82F9612D0
+ 7ED4924FF9AC13AAC68566BA1391862A6FA0BD51A7BAE1B2457C1EED1E5112C69254DB
+ 808ACC405FA0CEB14B7569EC8A7AB2A741F5AB94CBFF500843D992A4A8478C7C72547D
+ 579CEA5219F69CD396516D1CD769250E33346666319D0D795B769751C00A5E2EF075B3
+ 383D582208618DBA5DBC4130285885607D3E07BF208255F2B8180234B278224B982731
+ D77E1DD65FA205CE8497829BDF1631BCF3B23B2121AF73B4E2081721A8978A3182C726
+ 5E168DB0B19D4C085D048AC0735E4D5F48D1B3A86E135C09F38C193DA86153CB071DD9
+ C882D2861AFA1CF3C78CC5A9C64FFA7358E995F9CFAEB986ACF84B384BAAC114A5379F
+ 1EB30205255EBD93E2BA9A5FB23B5AC5DD0AE4D30A906AA7340386902D2B3C3F53C7A9
+ 072F3C49387C9E9E9C61A161D5#)(e #010001#)(d
+ #2CF7ACCE9D5BF216CACCCFFD5B675E9569B482BA8A93463A963708C85A41AE60A8C9
+ 231D932DFCC73C63FEB787A02B3433BAC365D79BC4BF535429D9C0511C3AC4C39DBA9F
+ FAC0E8B94B40B8746FC15893132D16F7AFB57250F2B29B02B676CAAC83A61A515C65C3
+ 924AFF04165A29652BE6AF68D26442E4E68822562D3E29E7260354AB2567EF8C1A773D
+ A4B7BAD170C5479DC5897B5069537B531C720822EA8CB0E279CBFB308CA6A0562C96E9
+ AD9ABDAE33AF0A788932E2D26B61F55BBA4C4EA0073C86CA9C778FBBB47D2BD1C1EEDE
+ 68C8E196C0E706EE5EBC3187DE7420847E790D87D7DCAA240F344E524AC9C874102F9D
+ 8A2E80DCF724E67AEE37C75F530A6F8DD9C4F62C4835C3499ED94CDF2613F2E98534E2
+ C9ED205EF3C395F448F8E9C23F828411527E7726D15CD0643F8136ADDDCE82239C936F
+ 5C41E0587D7BC408E7184E0A3B31A95F2A8B5582BEA158033F117824C39BD017BADDEC
+ F3A6A9AB4E5936028D5560E77477E30129014BC5A7FF7568C0A3A8AD360245F8F5EA71
+ #)(p #00D0BFD60AC41E78E7286EED32B815137FFC405BDD689FFC2A5A62C6D03FA283
+ 06B1136E2E749541B6C52F1C7C1CA4966655BD81865D48F67FCD1458B0050F089F5357
+ 94854D0084E0A755AAA8BC28103F501C4E3316BF6DE2C433D55F8A1C2D2080B05DB6AA
+ 2578D86F1AD9E682834602C858FCABF87FD32499024818967E638A02F55604E9FA19A7
+ 7274402F657160D1067943E5C7EE6F712542A45E38C9442038D8B1CFDABE61A9EBC70D
+ F599F4F90ECFF1E26C6DE690F1E47E629BAED394C9#)(q
+ #00FA72D522884561A299245282BA4271F3427649595CAFE5518810AA43799F0919F7
+ 3A78E114D22CD5B1F214D7A2227ACA5B7B5CD2F02B53A80AD58593479EF7317FE543EB
+ 40C42A31B2A427C0E9433C51E0952CE7F69E2E94C1491C769D4A3A1A2CAABC281B84DD
+ 73213DE5E8BFDCB85459959ECE74F855E85CCF6CEADAEC1B70FA06822922E047CBF405
+ B3B3FBED6F4101173A4849B2D5DB133286CA27A06126741C67B654E1EC35E72A01A12E
+ B465658607B1D75C6223B8F9FC8852481026AD#)(u
+ #00C7613519C7353FC3629EEB21E0AE0188354F8BCFB6FC2B6B1B15EA87C3AAD39B16
+ BF989D090D16BB1AFF69DFC4266C1E3EA9C3FF4B7ACF6E6E24CF8E309FF6C791FC37D1
+ A896D2F7AC6ADF887EBD416669ECE6366B013A786C0626D087D4EE2503512A1EB8E415
+ 9D63B5D45BF921828FB40CCD9409062725D7974A5D0CBBF0BAB9E055868C0794A2696E
+ FA77CB2C027326B54B502010A7853DE5305C86B10473DD779BB04795D6DA7C308B3DFC
+ A4D0F449D8D0AFCDD3E4B0A0E1F36F90CED736#)))
diff --git a/src/ext-key-format.cpp b/src/ext-key-format.cpp
new file mode 100644
index 0000000..7a402cb
--- /dev/null
+++ b/src/ext-key-format.cpp
@@ -0,0 +1,308 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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 "sexpp/ext-key-format.h"
+
+using namespace sexp;
+
+namespace ext_key_format {
+
+void ext_key_error(
+ sexp_exception_t::severity level, const char *msg, size_t c1, size_t c2, int pos)
+{
+ char tmp[256];
+ sexp_exception_t::severity l = (sexp_exception_t::severity) level;
+ snprintf(tmp, sizeof(tmp) / sizeof(tmp[0]), msg, c1, c2);
+ if (sexp_exception_t::shall_throw(l))
+ throw sexp_exception_t(tmp, l, pos, "EXTENDED KEY FORMAT");
+ if (sexp_exception_t::is_interactive()) {
+ std::cout.flush() << std::endl
+ << "*** "
+ << sexp_exception_t::format("EXTENDED KEY FORMAT", tmp, l, pos)
+ << " ***" << std::endl;
+ }
+}
+
+// Valid characters are all ASCII letters, numbers and the hyphen.
+// true if allowed in the name field
+const bool ext_key_input_stream_t::namechar[256] = {
+ /* 0x00 */ false, /* 0x01 */ false, /* 0x02 */ false,
+ /* 0x03 */ false, /* 0x04 */ false, /* 0x05 */ false,
+ /* 0x06 */ false, /* 0x07 */ false, /* 0x08 */ false,
+ /* 0x09 */ false, /* 0x0a */ false, /* 0x0b */ false,
+ /* 0x0c */ false, /* 0x0d */ false, /* 0x0e */ false,
+ /* 0x0f */ false, /* 0x10 */ false, /* 0x11 */ false,
+ /* 0x12 */ false, /* 0x13 */ false, /* 0x14 */ false,
+ /* 0x15 */ false, /* 0x16 */ false, /* 0x17 */ false,
+ /* 0x18 */ false, /* 0x19 */ false, /* 0x1a */ false,
+ /* 0x1b */ false, /* 0x1c */ false, /* 0x1d */ false,
+ /* 0x1e */ false, /* 0x1f */ false, /* 0x20 */ false,
+ /* 0x21 ! */ false, /* 0x22 " */ false, /* 0x23 # */ false,
+ /* 0x24 $ */ false, /* 0x25 % */ false, /* 0x26 & */ false,
+ /* 0x27 ' */ false, /* 0x28 ( */ false, /* 0x29 ) */ false,
+ /* 0x2a * */ false, /* 0x2b + */ false, /* 0x2c , */ false,
+ /* 0x2d - */ true, /* 0x2e . */ false, /* 0x2f / */ false,
+ /* 0x30 0 */ true, /* 0x31 1 */ true, /* 0x32 2 */ true,
+ /* 0x33 3 */ true, /* 0x34 4 */ true, /* 0x35 5 */ true,
+ /* 0x36 6 */ true, /* 0x37 7 */ true, /* 0x38 8 */ true,
+ /* 0x39 9 */ true, /* 0x3a : */ false, /* 0x3b ; */ false,
+ /* 0x3c < */ false, /* 0x3d = */ false, /* 0x3e > */ false,
+ /* 0x3f ? */ false, /* 0x40 @ */ false, /* 0x41 A */ true,
+ /* 0x42 B */ true, /* 0x43 C */ true, /* 0x44 D */ true,
+ /* 0x45 E */ true, /* 0x46 F */ true, /* 0x47 G */ true,
+ /* 0x48 H */ true, /* 0x49 I */ true, /* 0x4a J */ true,
+ /* 0x4b K */ true, /* 0x4c L */ true, /* 0x4d M */ true,
+ /* 0x4e N */ true, /* 0x4f O */ true, /* 0x50 P */ true,
+ /* 0x51 Q */ true, /* 0x52 R */ true, /* 0x53 S */ true,
+ /* 0x54 T */ true, /* 0x55 U */ true, /* 0x56 V */ true,
+ /* 0x57 W */ true, /* 0x58 X */ true, /* 0x59 Y */ true,
+ /* 0x5a Z */ true, /* 0x5b [ */ false, /* 0x5c \ */ false,
+ /* 0x5d ] */ false, /* 0x5e ^ */ false, /* 0x5f _ */ false,
+ /* 0x60 ` */ false, /* 0x61 a */ true, /* 0x62 b */ true,
+ /* 0x63 c */ true, /* 0x64 d */ true, /* 0x65 e */ true,
+ /* 0x66 f */ true, /* 0x67 g */ true, /* 0x68 h */ true,
+ /* 0x69 i */ true, /* 0x6a j */ true, /* 0x6b k */ true,
+ /* 0x6c l */ true, /* 0x6d m */ true, /* 0x6e n */ true,
+ /* 0x6f o */ true, /* 0x70 p */ true, /* 0x71 q */ true,
+ /* 0x72 r */ true, /* 0x73 s */ true, /* 0x74 t */ true,
+ /* 0x75 u */ true, /* 0x76 v */ true, /* 0x77 w */ true,
+ /* 0x78 x */ true, /* 0x79 y */ true, /* 0x7a z */ true,
+ /* 0x7b { */ false, /* 0x7c | */ false, /* 0x7d } */ false,
+ /* 0x7e ~ */ false, /* 0x7f */ false, /* 0x80 */ false,
+ /* 0x81 */ false, /* 0x82 */ false, /* 0x83 */ false,
+ /* 0x84 */ false, /* 0x85 */ false, /* 0x86 */ false,
+ /* 0x87 */ false, /* 0x88 */ false, /* 0x89 */ false,
+ /* 0x8a */ false, /* 0x8b */ false, /* 0x8c */ false,
+ /* 0x8d */ false, /* 0x8e */ false, /* 0x8f */ false,
+ /* 0x90 */ false, /* 0x91 */ false, /* 0x92 */ false,
+ /* 0x93 */ false, /* 0x94 */ false, /* 0x95 */ false,
+ /* 0x96 */ false, /* 0x97 */ false, /* 0x98 */ false,
+ /* 0x99 */ false, /* 0x9a */ false, /* 0x9b */ false,
+ /* 0x9c */ false, /* 0x9d */ false, /* 0x9e */ false,
+ /* 0x9f */ false, /* 0xa0 */ false, /* 0xa1 */ false,
+ /* 0xa2 */ false, /* 0xa3 */ false, /* 0xa4 */ false,
+ /* 0xa5 */ false, /* 0xa6 */ false, /* 0xa7 */ false,
+ /* 0xa8 */ false, /* 0xa9 */ false, /* 0xaa */ false,
+ /* 0xab */ false, /* 0xac */ false, /* 0xad */ false,
+ /* 0xae */ false, /* 0xaf */ false, /* 0xb0 */ false,
+ /* 0xb1 */ false, /* 0xb2 */ false, /* 0xb3 */ false,
+ /* 0xb4 */ false, /* 0xb5 */ false, /* 0xb6 */ false,
+ /* 0xb7 */ false, /* 0xb8 */ false, /* 0xb9 */ false,
+ /* 0xba */ false, /* 0xbb */ false, /* 0xbc */ false,
+ /* 0xbd */ false, /* 0xbe */ false, /* 0xbf */ false,
+ /* 0xc0 */ false, /* 0xc1 */ false, /* 0xc2 */ false,
+ /* 0xc3 */ false, /* 0xc4 */ false, /* 0xc5 */ false,
+ /* 0xc6 */ false, /* 0xc7 */ false, /* 0xc8 */ false,
+ /* 0xc9 */ false, /* 0xca */ false, /* 0xcb */ false,
+ /* 0xcc */ false, /* 0xcd */ false, /* 0xce */ false,
+ /* 0xcf */ false, /* 0xd0 */ false, /* 0xd1 */ false,
+ /* 0xd2 */ false, /* 0xd3 */ false, /* 0xd4 */ false,
+ /* 0xd5 */ false, /* 0xd6 */ false, /* 0xd7 */ false,
+ /* 0xd8 */ false, /* 0xd9 */ false, /* 0xda */ false,
+ /* 0xdb */ false, /* 0xdc */ false, /* 0xdd */ false,
+ /* 0xde */ false, /* 0xdf */ false, /* 0xe0 */ false,
+ /* 0xe1 */ false, /* 0xe2 */ false, /* 0xe3 */ false,
+ /* 0xe4 */ false, /* 0xe5 */ false, /* 0xe6 */ false,
+ /* 0xe7 */ false, /* 0xe8 */ false, /* 0xe9 */ false,
+ /* 0xea */ false, /* 0xeb */ false, /* 0xec */ false,
+ /* 0xed */ false, /* 0xee */ false, /* 0xef */ false,
+ /* 0xf0 */ false, /* 0xf1 */ false, /* 0xf2 */ false,
+ /* 0xf3 */ false, /* 0xf4 */ false, /* 0xf5 */ false,
+ /* 0xf6 */ false, /* 0xf7 */ false, /* 0xf8 */ false,
+ /* 0xf9 */ false, /* 0xfa */ false, /* 0xfb */ false,
+ /* 0xfc */ false, /* 0xfd */ false, /* 0xfe */ false};
+
+/*
+ * ext_key_input_stream_t::skip_line
+ */
+int ext_key_input_stream_t::skip_line(void)
+{
+ int c;
+ do {
+ c = input_file->get();
+ } while (!is_newline_char(c) && c != EOF);
+ return c;
+}
+
+/*
+ * ext_key_input_stream_t::read_char
+ */
+int ext_key_input_stream_t::read_char(void)
+{
+ int lookahead_1 = input_file->get();
+ count++;
+ if (is_scanning_value && is_newline_char(lookahead_1)) {
+ while (true) {
+ int lookahead_2 = input_file->peek();
+ if (lookahead_1 == '\r' && lookahead_2 == '\n') {
+ lookahead_1 = input_file->get();
+ count++;
+ lookahead_2 = input_file->peek();
+ }
+ if (lookahead_2 == ' ') {
+ input_file->get();
+ count++;
+ lookahead_2 = input_file->peek();
+ if (lookahead_2 == '#') {
+ lookahead_1 = skip_line();
+ continue;
+ }
+ if (is_newline_char(lookahead_2)) {
+ lookahead_1 = lookahead_2;
+ continue;
+ }
+ lookahead_1 = input_file->get();
+ count++;
+ }
+ return lookahead_1;
+ }
+ }
+ return lookahead_1;
+}
+
+/*
+ * ext_key_input_stream_t::scan_name
+ * A name must start with a letter and end with a colon. Valid characters are all ASCII
+ * letters, numbers and the hyphen. Comparison of names is done case insensitively. Names may
+ * be used several times to represent an array of values. Note that the name “Key” is special
+ * in that it is madandory must occur only once.
+ */
+
+std::string ext_key_input_stream_t::scan_name(int c)
+{
+ std::string name;
+ if (!is_alpha(c)) {
+ ext_key_error(sexp_exception_t::error,
+ isprint(next_char) ?
+ "unexpected character '%c' (0x%x) found starting a name field" :
+ "unexpected character '0x%x' found starting a name field",
+ c,
+ c,
+ count);
+ } else {
+ name += (char) c;
+ c = read_char();
+ while (c != ':') {
+ if (c == EOF) {
+ ext_key_error(sexp_exception_t::error, "unexpected end of file", 0, 0, count);
+ }
+ if (is_newline_char(c)) {
+ ext_key_error(sexp_exception_t::error, "unexpected end of line", 0, 0, count);
+ }
+ if (!is_namechar(c)) {
+ ext_key_error(sexp_exception_t::error,
+ isprint(next_char) ?
+ "unexpected character '%c' (0x%x) found in a name field" :
+ "unexpected character '0x%x' found in a name field",
+ c,
+ c,
+ count);
+ }
+ name += (int) c;
+ c = read_char();
+ }
+ }
+ return name;
+}
+
+/*
+ * ext_key_input_stream_t::scan_value
+ * Values are UTF-8 encoded strings. Values can be wrapped at any point, and continued in
+ * the next line indicated by leading whitespace. A continuation line with one leading space
+ * does not introduce a blank so that the lines can be effectively concatenated. A blank
+ * line as part of a continuation line encodes a newline.
+ */
+std::string ext_key_input_stream_t::scan_value(void)
+{
+ std::string value;
+ int c;
+ do {
+ c = read_char();
+ } while (is_white_space(c));
+ while (c != EOF && !is_newline_char(c)) {
+ value += c;
+ c = read_char();
+ }
+ return value;
+}
+
+/*
+ * ext_key_input_stream_t::scan
+ * GnuPG 2.3+ uses a new format to store private keys that is both more flexible and easier to
+ * read and edit by human beings. The new format stores name, value-pairs using the common mail
+ * and http header convention.
+ */
+void ext_key_input_stream_t::scan(extended_private_key_t &res)
+{
+ set_byte_size(8);
+ int c = read_char();
+ if (c == '(') {
+ set_next_char(c);
+ res.key.parse(this);
+ has_key = true;
+ } else {
+ while (c != EOF) {
+ // Comparison of names is done case insensitively
+ std::string name = scan_name(c);
+ // The name “Key” is special in that it is mandatory and must occur only once.
+ // The associated value holds the actual S-expression with the cryptographic key.
+ // The S-expression is formatted using the ‘Advanced Format’
+ // (GCRYSEXP_FMT_ADVANCED) that avoids non-printable characters so that the file
+ // can be easily inspected and edited.
+ is_scanning_value = true;
+ if (extended_private_key_t::iequals(name, "key")) {
+ if (has_key) {
+ ext_key_error(sexp_exception_t::error,
+ "'key' field must occur only once",
+ 0,
+ 0,
+ count);
+ }
+ do {
+ c = read_char();
+ } while (is_white_space(c));
+ set_next_char(c);
+ res.key.parse(this);
+ has_key = true;
+ } else {
+ std::string value = scan_value();
+ res.fields.insert(std::pair<std::string, std::string>{name, value});
+ }
+ c = read_char();
+ is_scanning_value = false;
+ }
+ }
+ if (!has_key) {
+ ext_key_error(sexp_exception_t::error, "missing mandatory 'key' field", 0, 0, count);
+ }
+}
+
+/*
+ * extended_private_key_t::parse
+ */
+void extended_private_key_t::parse(ext_key_input_stream_t &is)
+{
+ is.scan(*this);
+}
+
+} // namespace ext_key_format \ No newline at end of file
diff --git a/src/sexp-char-defs.cpp b/src/sexp-char-defs.cpp
new file mode 100644
index 0000000..f31b62d
--- /dev/null
+++ b/src/sexp-char-defs.cpp
@@ -0,0 +1,345 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ * Original copyright
+ *
+ * SEXP implementation code sexp-input.c
+ * Ron Rivest
+ * 7/21/1997
+ */
+
+#include "sexpp/sexp.h"
+
+namespace sexp {
+
+/**************************************/
+/* CHARACTER ROUTINES AND DEFINITIONS */
+/**************************************/
+std::locale sexp_char_defs_t::c_locale{"C"};
+
+const unsigned char sexp_char_defs_t::values[256][3] =
+ {/* values of c as { dec. hex, base64 } digit */
+ {/* 0x00 */ 0x00, 0x00, 0x00}, {/* 0x01 */ 0x00, 0x00, 0x00},
+ {/* 0x02 */ 0x00, 0x00, 0x00}, {/* 0x03 */ 0x00, 0x00, 0x00},
+ {/* 0x04 */ 0x00, 0x00, 0x00}, {/* 0x05 */ 0x00, 0x00, 0x00},
+ {/* 0x06 */ 0x00, 0x00, 0x00}, {/* 0x07 */ 0x00, 0x00, 0x00},
+ {/* 0x08 */ 0x00, 0x00, 0x00}, {/* 0x09 */ 0x00, 0x00, 0x00},
+ {/* 0x0a */ 0x00, 0x00, 0x00}, {/* 0x0b */ 0x00, 0x00, 0x00},
+ {/* 0x0c */ 0x00, 0x00, 0x00}, {/* 0x0d */ 0x00, 0x00, 0x00},
+ {/* 0x0e */ 0x00, 0x00, 0x00}, {/* 0x0f */ 0x00, 0x00, 0x00},
+ {/* 0x10 */ 0x00, 0x00, 0x00}, {/* 0x11 */ 0x00, 0x00, 0x00},
+ {/* 0x12 */ 0x00, 0x00, 0x00}, {/* 0x13 */ 0x00, 0x00, 0x00},
+ {/* 0x14 */ 0x00, 0x00, 0x00}, {/* 0x15 */ 0x00, 0x00, 0x00},
+ {/* 0x16 */ 0x00, 0x00, 0x00}, {/* 0x17 */ 0x00, 0x00, 0x00},
+ {/* 0x18 */ 0x00, 0x00, 0x00}, {/* 0x19 */ 0x00, 0x00, 0x00},
+ {/* 0x1a */ 0x00, 0x00, 0x00}, {/* 0x1b */ 0x00, 0x00, 0x00},
+ {/* 0x1c */ 0x00, 0x00, 0x00}, {/* 0x1d */ 0x00, 0x00, 0x00},
+ {/* 0x1e */ 0x00, 0x00, 0x00}, {/* 0x1f */ 0x00, 0x00, 0x00},
+ {/* 0x20 */ 0x00, 0x00, 0x00}, {/* 0x21 ! */ 0x00, 0x00, 0x00},
+ {/* 0x22 " */ 0x00, 0x00, 0x00}, {/* 0x23 # */ 0x00, 0x00, 0x00},
+ {/* 0x24 $ */ 0x00, 0x00, 0x00}, {/* 0x25 % */ 0x00, 0x00, 0x00},
+ {/* 0x26 & */ 0x00, 0x00, 0x00}, {/* 0x27 ' */ 0x00, 0x00, 0x00},
+ {/* 0x28 ( */ 0x00, 0x00, 0x00}, {/* 0x29 ) */ 0x00, 0x00, 0x00},
+ {/* 0x2a * */ 0x00, 0x00, 0x00}, {/* 0x2b + */ 0x00, 0x00, 0x3e},
+ {/* 0x2c , */ 0x00, 0x00, 0x00}, {/* 0x2d - */ 0x00, 0x00, 0x00},
+ {/* 0x2e . */ 0x00, 0x00, 0x00}, {/* 0x2f / */ 0x00, 0x00, 0x3f},
+ {/* 0x30 0 */ 0x00, 0x00, 0x34}, {/* 0x31 1 */ 0x01, 0x01, 0x35},
+ {/* 0x32 2 */ 0x02, 0x02, 0x36}, {/* 0x33 3 */ 0x03, 0x03, 0x37},
+ {/* 0x34 4 */ 0x04, 0x04, 0x38}, {/* 0x35 5 */ 0x05, 0x05, 0x39},
+ {/* 0x36 6 */ 0x06, 0x06, 0x3a}, {/* 0x37 7 */ 0x07, 0x07, 0x3b},
+ {/* 0x38 8 */ 0x08, 0x08, 0x3c}, {/* 0x39 9 */ 0x09, 0x09, 0x3d},
+ {/* 0x3a : */ 0x00, 0x00, 0x00}, {/* 0x3b ; */ 0x00, 0x00, 0x00},
+ {/* 0x3c < */ 0x00, 0x00, 0x00}, {/* 0x3d = */ 0x00, 0x00, 0x00},
+ {/* 0x3e > */ 0x00, 0x00, 0x00}, {/* 0x3f ? */ 0x00, 0x00, 0x00},
+ {/* 0x40 @ */ 0x00, 0x00, 0x00}, {/* 0x41 A */ 0x00, 0x0a, 0x00},
+ {/* 0x42 B */ 0x00, 0x0b, 0x01}, {/* 0x43 C */ 0x00, 0x0c, 0x02},
+ {/* 0x44 D */ 0x00, 0x0d, 0x03}, {/* 0x45 E */ 0x00, 0x0e, 0x04},
+ {/* 0x46 F */ 0x00, 0x0f, 0x05}, {/* 0x47 G */ 0x00, 0x00, 0x06},
+ {/* 0x48 H */ 0x00, 0x00, 0x07}, {/* 0x49 I */ 0x00, 0x00, 0x08},
+ {/* 0x4a J */ 0x00, 0x00, 0x09}, {/* 0x4b K */ 0x00, 0x00, 0x0a},
+ {/* 0x4c L */ 0x00, 0x00, 0x0b}, {/* 0x4d M */ 0x00, 0x00, 0x0c},
+ {/* 0x4e N */ 0x00, 0x00, 0x0d}, {/* 0x4f O */ 0x00, 0x00, 0x0e},
+ {/* 0x50 P */ 0x00, 0x00, 0x0f}, {/* 0x51 Q */ 0x00, 0x00, 0x10},
+ {/* 0x52 R */ 0x00, 0x00, 0x11}, {/* 0x53 S */ 0x00, 0x00, 0x12},
+ {/* 0x54 T */ 0x00, 0x00, 0x13}, {/* 0x55 U */ 0x00, 0x00, 0x14},
+ {/* 0x56 V */ 0x00, 0x00, 0x15}, {/* 0x57 W */ 0x00, 0x00, 0x16},
+ {/* 0x58 X */ 0x00, 0x00, 0x17}, {/* 0x59 Y */ 0x00, 0x00, 0x18},
+ {/* 0x5a Z */ 0x00, 0x00, 0x19}, {/* 0x5b [ */ 0x00, 0x00, 0x00},
+ {/* 0x5c \ */ 0x00, 0x00, 0x00}, {/* 0x5d ] */ 0x00, 0x00, 0x00},
+ {/* 0x5e ^ */ 0x00, 0x00, 0x00}, {/* 0x5f _ */ 0x00, 0x00, 0x00},
+ {/* 0x60 ` */ 0x00, 0x00, 0x00}, {/* 0x61 a */ 0x00, 0x0a, 0x1a},
+ {/* 0x62 b */ 0x00, 0x0b, 0x1b}, {/* 0x63 c */ 0x00, 0x0c, 0x1c},
+ {/* 0x64 d */ 0x00, 0x0d, 0x1d}, {/* 0x65 e */ 0x00, 0x0e, 0x1e},
+ {/* 0x66 f */ 0x00, 0x0f, 0x1f}, {/* 0x67 g */ 0x00, 0x00, 0x20},
+ {/* 0x68 h */ 0x00, 0x00, 0x21}, {/* 0x69 i */ 0x00, 0x00, 0x22},
+ {/* 0x6a j */ 0x00, 0x00, 0x23}, {/* 0x6b k */ 0x00, 0x00, 0x24},
+ {/* 0x6c l */ 0x00, 0x00, 0x25}, {/* 0x6d m */ 0x00, 0x00, 0x26},
+ {/* 0x6e n */ 0x00, 0x00, 0x27}, {/* 0x6f o */ 0x00, 0x00, 0x28},
+ {/* 0x70 p */ 0x00, 0x00, 0x29}, {/* 0x71 q */ 0x00, 0x00, 0x2a},
+ {/* 0x72 r */ 0x00, 0x00, 0x2b}, {/* 0x73 s */ 0x00, 0x00, 0x2c},
+ {/* 0x74 t */ 0x00, 0x00, 0x2d}, {/* 0x75 u */ 0x00, 0x00, 0x2e},
+ {/* 0x76 v */ 0x00, 0x00, 0x2f}, {/* 0x77 w */ 0x00, 0x00, 0x30},
+ {/* 0x78 x */ 0x00, 0x00, 0x31}, {/* 0x79 y */ 0x00, 0x00, 0x32},
+ {/* 0x7a z */ 0x00, 0x00, 0x33}, {/* 0x7b { */ 0x00, 0x00, 0x00},
+ {/* 0x7c | */ 0x00, 0x00, 0x00}, {/* 0x7d } */ 0x00, 0x00, 0x00},
+ {/* 0x7e ~ */ 0x00, 0x00, 0x00}, {/* 0x7f */ 0x00, 0x00, 0x00},
+ {/* 0x80 */ 0x00, 0x00, 0x00}, {/* 0x81 */ 0x00, 0x00, 0x00},
+ {/* 0x82 */ 0x00, 0x00, 0x00}, {/* 0x83 */ 0x00, 0x00, 0x00},
+ {/* 0x84 */ 0x00, 0x00, 0x00}, {/* 0x85 */ 0x00, 0x00, 0x00},
+ {/* 0x86 */ 0x00, 0x00, 0x00}, {/* 0x87 */ 0x00, 0x00, 0x00},
+ {/* 0x88 */ 0x00, 0x00, 0x00}, {/* 0x89 */ 0x00, 0x00, 0x00},
+ {/* 0x8a */ 0x00, 0x00, 0x00}, {/* 0x8b */ 0x00, 0x00, 0x00},
+ {/* 0x8c */ 0x00, 0x00, 0x00}, {/* 0x8d */ 0x00, 0x00, 0x00},
+ {/* 0x8e */ 0x00, 0x00, 0x00}, {/* 0x8f */ 0x00, 0x00, 0x00},
+ {/* 0x90 */ 0x00, 0x00, 0x00}, {/* 0x91 */ 0x00, 0x00, 0x00},
+ {/* 0x92 */ 0x00, 0x00, 0x00}, {/* 0x93 */ 0x00, 0x00, 0x00},
+ {/* 0x94 */ 0x00, 0x00, 0x00}, {/* 0x95 */ 0x00, 0x00, 0x00},
+ {/* 0x96 */ 0x00, 0x00, 0x00}, {/* 0x97 */ 0x00, 0x00, 0x00},
+ {/* 0x98 */ 0x00, 0x00, 0x00}, {/* 0x99 */ 0x00, 0x00, 0x00},
+ {/* 0x9a */ 0x00, 0x00, 0x00}, {/* 0x9b */ 0x00, 0x00, 0x00},
+ {/* 0x9c */ 0x00, 0x00, 0x00}, {/* 0x9d */ 0x00, 0x00, 0x00},
+ {/* 0x9e */ 0x00, 0x00, 0x00}, {/* 0x9f */ 0x00, 0x00, 0x00},
+ {/* 0xa0 */ 0x00, 0x00, 0x00}, {/* 0xa1 */ 0x00, 0x00, 0x00},
+ {/* 0xa2 */ 0x00, 0x00, 0x00}, {/* 0xa3 */ 0x00, 0x00, 0x00},
+ {/* 0xa4 */ 0x00, 0x00, 0x00}, {/* 0xa5 */ 0x00, 0x00, 0x00},
+ {/* 0xa6 */ 0x00, 0x00, 0x00}, {/* 0xa7 */ 0x00, 0x00, 0x00},
+ {/* 0xa8 */ 0x00, 0x00, 0x00}, {/* 0xa9 */ 0x00, 0x00, 0x00},
+ {/* 0xaa */ 0x00, 0x00, 0x00}, {/* 0xab */ 0x00, 0x00, 0x00},
+ {/* 0xac */ 0x00, 0x00, 0x00}, {/* 0xad */ 0x00, 0x00, 0x00},
+ {/* 0xae */ 0x00, 0x00, 0x00}, {/* 0xaf */ 0x00, 0x00, 0x00},
+ {/* 0xb0 */ 0x00, 0x00, 0x00}, {/* 0xb1 */ 0x00, 0x00, 0x00},
+ {/* 0xb2 */ 0x00, 0x00, 0x00}, {/* 0xb3 */ 0x00, 0x00, 0x00},
+ {/* 0xb4 */ 0x00, 0x00, 0x00}, {/* 0xb5 */ 0x00, 0x00, 0x00},
+ {/* 0xb6 */ 0x00, 0x00, 0x00}, {/* 0xb7 */ 0x00, 0x00, 0x00},
+ {/* 0xb8 */ 0x00, 0x00, 0x00}, {/* 0xb9 */ 0x00, 0x00, 0x00},
+ {/* 0xba */ 0x00, 0x00, 0x00}, {/* 0xbb */ 0x00, 0x00, 0x00},
+ {/* 0xbc */ 0x00, 0x00, 0x00}, {/* 0xbd */ 0x00, 0x00, 0x00},
+ {/* 0xbe */ 0x00, 0x00, 0x00}, {/* 0xbf */ 0x00, 0x00, 0x00},
+ {/* 0xc0 */ 0x00, 0x00, 0x00}, {/* 0xc1 */ 0x00, 0x00, 0x00},
+ {/* 0xc2 */ 0x00, 0x00, 0x00}, {/* 0xc3 */ 0x00, 0x00, 0x00},
+ {/* 0xc4 */ 0x00, 0x00, 0x00}, {/* 0xc5 */ 0x00, 0x00, 0x00},
+ {/* 0xc6 */ 0x00, 0x00, 0x00}, {/* 0xc7 */ 0x00, 0x00, 0x00},
+ {/* 0xc8 */ 0x00, 0x00, 0x00}, {/* 0xc9 */ 0x00, 0x00, 0x00},
+ {/* 0xca */ 0x00, 0x00, 0x00}, {/* 0xcb */ 0x00, 0x00, 0x00},
+ {/* 0xcc */ 0x00, 0x00, 0x00}, {/* 0xcd */ 0x00, 0x00, 0x00},
+ {/* 0xce */ 0x00, 0x00, 0x00}, {/* 0xcf */ 0x00, 0x00, 0x00},
+ {/* 0xd0 */ 0x00, 0x00, 0x00}, {/* 0xd1 */ 0x00, 0x00, 0x00},
+ {/* 0xd2 */ 0x00, 0x00, 0x00}, {/* 0xd3 */ 0x00, 0x00, 0x00},
+ {/* 0xd4 */ 0x00, 0x00, 0x00}, {/* 0xd5 */ 0x00, 0x00, 0x00},
+ {/* 0xd6 */ 0x00, 0x00, 0x00}, {/* 0xd7 */ 0x00, 0x00, 0x00},
+ {/* 0xd8 */ 0x00, 0x00, 0x00}, {/* 0xd9 */ 0x00, 0x00, 0x00},
+ {/* 0xda */ 0x00, 0x00, 0x00}, {/* 0xdb */ 0x00, 0x00, 0x00},
+ {/* 0xdc */ 0x00, 0x00, 0x00}, {/* 0xdd */ 0x00, 0x00, 0x00},
+ {/* 0xde */ 0x00, 0x00, 0x00}, {/* 0xdf */ 0x00, 0x00, 0x00},
+ {/* 0xe0 */ 0x00, 0x00, 0x00}, {/* 0xe1 */ 0x00, 0x00, 0x00},
+ {/* 0xe2 */ 0x00, 0x00, 0x00}, {/* 0xe3 */ 0x00, 0x00, 0x00},
+ {/* 0xe4 */ 0x00, 0x00, 0x00}, {/* 0xe5 */ 0x00, 0x00, 0x00},
+ {/* 0xe6 */ 0x00, 0x00, 0x00}, {/* 0xe7 */ 0x00, 0x00, 0x00},
+ {/* 0xe8 */ 0x00, 0x00, 0x00}, {/* 0xe9 */ 0x00, 0x00, 0x00},
+ {/* 0xea */ 0x00, 0x00, 0x00}, {/* 0xeb */ 0x00, 0x00, 0x00},
+ {/* 0xec */ 0x00, 0x00, 0x00}, {/* 0xed */ 0x00, 0x00, 0x00},
+ {/* 0xee */ 0x00, 0x00, 0x00}, {/* 0xef */ 0x00, 0x00, 0x00},
+ {/* 0xf0 */ 0x00, 0x00, 0x00}, {/* 0xf1 */ 0x00, 0x00, 0x00},
+ {/* 0xf2 */ 0x00, 0x00, 0x00}, {/* 0xf3 */ 0x00, 0x00, 0x00},
+ {/* 0xf4 */ 0x00, 0x00, 0x00}, {/* 0xf5 */ 0x00, 0x00, 0x00},
+ {/* 0xf6 */ 0x00, 0x00, 0x00}, {/* 0xf7 */ 0x00, 0x00, 0x00},
+ {/* 0xf8 */ 0x00, 0x00, 0x00}, {/* 0xf9 */ 0x00, 0x00, 0x00},
+ {/* 0xfa */ 0x00, 0x00, 0x00}, {/* 0xfb */ 0x00, 0x00, 0x00},
+ {/* 0xfc */ 0x00, 0x00, 0x00}, {/* 0xfd */ 0x00, 0x00, 0x00},
+ {/* 0xfe */ 0x00, 0x00, 0x00}, {/* 0xff */ 0x00, 0x00, 0x00}};
+
+const bool sexp_char_defs_t::base64digit[256] =
+ {/* c is base64 digit */
+ /* 0x00 */ false, /* 0x01 */ false, /* 0x02 */ false,
+ /* 0x03 */ false, /* 0x04 */ false, /* 0x05 */ false,
+ /* 0x06 */ false, /* 0x07 */ false, /* 0x08 */ false,
+ /* 0x09 */ false, /* 0x0a */ false, /* 0x0b */ false,
+ /* 0x0c */ false, /* 0x0d */ false, /* 0x0e */ false,
+ /* 0x0f */ false, /* 0x10 */ false, /* 0x11 */ false,
+ /* 0x12 */ false, /* 0x13 */ false, /* 0x14 */ false,
+ /* 0x15 */ false, /* 0x16 */ false, /* 0x17 */ false,
+ /* 0x18 */ false, /* 0x19 */ false, /* 0x1a */ false,
+ /* 0x1b */ false, /* 0x1c */ false, /* 0x1d */ false,
+ /* 0x1e */ false, /* 0x1f */ false, /* 0x20 */ false,
+ /* 0x21 ! */ false, /* 0x22 " */ false, /* 0x23 # */ false,
+ /* 0x24 $ */ false, /* 0x25 % */ false, /* 0x26 & */ false,
+ /* 0x27 ' */ false, /* 0x28 ( */ false, /* 0x29 ) */ false,
+ /* 0x2a * */ false, /* 0x2b + */ true, /* 0x2c , */ false,
+ /* 0x2d - */ false, /* 0x2e . */ false, /* 0x2f / */ true,
+ /* 0x30 0 */ true, /* 0x31 1 */ true, /* 0x32 2 */ true,
+ /* 0x33 3 */ true, /* 0x34 4 */ true, /* 0x35 5 */ true,
+ /* 0x36 6 */ true, /* 0x37 7 */ true, /* 0x38 8 */ true,
+ /* 0x39 9 */ true, /* 0x3a : */ false, /* 0x3b ; */ false,
+ /* 0x3c < */ false, /* 0x3d = */ false, /* 0x3e > */ false,
+ /* 0x3f ? */ false, /* 0x40 @ */ false, /* 0x41 A */ true,
+ /* 0x42 B */ true, /* 0x43 C */ true, /* 0x44 D */ true,
+ /* 0x45 E */ true, /* 0x46 F */ true, /* 0x47 G */ true,
+ /* 0x48 H */ true, /* 0x49 I */ true, /* 0x4a J */ true,
+ /* 0x4b K */ true, /* 0x4c L */ true, /* 0x4d M */ true,
+ /* 0x4e N */ true, /* 0x4f O */ true, /* 0x50 P */ true,
+ /* 0x51 Q */ true, /* 0x52 R */ true, /* 0x53 S */ true,
+ /* 0x54 T */ true, /* 0x55 U */ true, /* 0x56 V */ true,
+ /* 0x57 W */ true, /* 0x58 X */ true, /* 0x59 Y */ true,
+ /* 0x5a Z */ true, /* 0x5b [ */ false, /* 0x5c \ */ false,
+ /* 0x5d ] */ false, /* 0x5e ^ */ false, /* 0x5f _ */ false,
+ /* 0x60 ` */ false, /* 0x61 a */ true, /* 0x62 b */ true,
+ /* 0x63 c */ true, /* 0x64 d */ true, /* 0x65 e */ true,
+ /* 0x66 f */ true, /* 0x67 g */ true, /* 0x68 h */ true,
+ /* 0x69 i */ true, /* 0x6a j */ true, /* 0x6b k */ true,
+ /* 0x6c l */ true, /* 0x6d m */ true, /* 0x6e n */ true,
+ /* 0x6f o */ true, /* 0x70 p */ true, /* 0x71 q */ true,
+ /* 0x72 r */ true, /* 0x73 s */ true, /* 0x74 t */ true,
+ /* 0x75 u */ true, /* 0x76 v */ true, /* 0x77 w */ true,
+ /* 0x78 x */ true, /* 0x79 y */ true, /* 0x7a z */ true,
+ /* 0x7b { */ false, /* 0x7c | */ false, /* 0x7d } */ false,
+ /* 0x7e ~ */ false, /* 0x7f */ false, /* 0x80 */ false,
+ /* 0x81 */ false, /* 0x82 */ false, /* 0x83 */ false,
+ /* 0x84 */ false, /* 0x85 */ false, /* 0x86 */ false,
+ /* 0x87 */ false, /* 0x88 */ false, /* 0x89 */ false,
+ /* 0x8a */ false, /* 0x8b */ false, /* 0x8c */ false,
+ /* 0x8d */ false, /* 0x8e */ false, /* 0x8f */ false,
+ /* 0x90 */ false, /* 0x91 */ false, /* 0x92 */ false,
+ /* 0x93 */ false, /* 0x94 */ false, /* 0x95 */ false,
+ /* 0x96 */ false, /* 0x97 */ false, /* 0x98 */ false,
+ /* 0x99 */ false, /* 0x9a */ false, /* 0x9b */ false,
+ /* 0x9c */ false, /* 0x9d */ false, /* 0x9e */ false,
+ /* 0x9f */ false, /* 0xa0 */ false, /* 0xa1 */ false,
+ /* 0xa2 */ false, /* 0xa3 */ false, /* 0xa4 */ false,
+ /* 0xa5 */ false, /* 0xa6 */ false, /* 0xa7 */ false,
+ /* 0xa8 */ false, /* 0xa9 */ false, /* 0xaa */ false,
+ /* 0xab */ false, /* 0xac */ false, /* 0xad */ false,
+ /* 0xae */ false, /* 0xaf */ false, /* 0xb0 */ false,
+ /* 0xb1 */ false, /* 0xb2 */ false, /* 0xb3 */ false,
+ /* 0xb4 */ false, /* 0xb5 */ false, /* 0xb6 */ false,
+ /* 0xb7 */ false, /* 0xb8 */ false, /* 0xb9 */ false,
+ /* 0xba */ false, /* 0xbb */ false, /* 0xbc */ false,
+ /* 0xbd */ false, /* 0xbe */ false, /* 0xbf */ false,
+ /* 0xc0 */ false, /* 0xc1 */ false, /* 0xc2 */ false,
+ /* 0xc3 */ false, /* 0xc4 */ false, /* 0xc5 */ false,
+ /* 0xc6 */ false, /* 0xc7 */ false, /* 0xc8 */ false,
+ /* 0xc9 */ false, /* 0xca */ false, /* 0xcb */ false,
+ /* 0xcc */ false, /* 0xcd */ false, /* 0xce */ false,
+ /* 0xcf */ false, /* 0xd0 */ false, /* 0xd1 */ false,
+ /* 0xd2 */ false, /* 0xd3 */ false, /* 0xd4 */ false,
+ /* 0xd5 */ false, /* 0xd6 */ false, /* 0xd7 */ false,
+ /* 0xd8 */ false, /* 0xd9 */ false, /* 0xda */ false,
+ /* 0xdb */ false, /* 0xdc */ false, /* 0xdd */ false,
+ /* 0xde */ false, /* 0xdf */ false, /* 0xe0 */ false,
+ /* 0xe1 */ false, /* 0xe2 */ false, /* 0xe3 */ false,
+ /* 0xe4 */ false, /* 0xe5 */ false, /* 0xe6 */ false,
+ /* 0xe7 */ false, /* 0xe8 */ false, /* 0xe9 */ false,
+ /* 0xea */ false, /* 0xeb */ false, /* 0xec */ false,
+ /* 0xed */ false, /* 0xee */ false, /* 0xef */ false,
+ /* 0xf0 */ false, /* 0xf1 */ false, /* 0xf2 */ false,
+ /* 0xf3 */ false, /* 0xf4 */ false, /* 0xf5 */ false,
+ /* 0xf6 */ false, /* 0xf7 */ false, /* 0xf8 */ false,
+ /* 0xf9 */ false, /* 0xfa */ false, /* 0xfb */ false,
+ /* 0xfc */ false, /* 0xfd */ false, /* 0xfe */ false};
+
+const bool sexp_char_defs_t::tokenchar[256] =
+ {/* c can be in a token */
+ /* 0x00 */ false, /* 0x01 */ false, /* 0x02 */ false,
+ /* 0x03 */ false, /* 0x04 */ false, /* 0x05 */ false,
+ /* 0x06 */ false, /* 0x07 */ false, /* 0x08 */ false,
+ /* 0x09 */ false, /* 0x0a */ false, /* 0x0b */ false,
+ /* 0x0c */ false, /* 0x0d */ false, /* 0x0e */ false,
+ /* 0x0f */ false, /* 0x10 */ false, /* 0x11 */ false,
+ /* 0x12 */ false, /* 0x13 */ false, /* 0x14 */ false,
+ /* 0x15 */ false, /* 0x16 */ false, /* 0x17 */ false,
+ /* 0x18 */ false, /* 0x19 */ false, /* 0x1a */ false,
+ /* 0x1b */ false, /* 0x1c */ false, /* 0x1d */ false,
+ /* 0x1e */ false, /* 0x1f */ false, /* 0x20 */ false,
+ /* 0x21 ! */ false, /* 0x22 " */ false, /* 0x23 # */ false,
+ /* 0x24 $ */ false, /* 0x25 % */ false, /* 0x26 & */ false,
+ /* 0x27 ' */ false, /* 0x28 ( */ false, /* 0x29 ) */ false,
+ /* 0x2a * */ true, /* 0x2b + */ true, /* 0x2c , */ false,
+ /* 0x2d - */ true, /* 0x2e . */ true, /* 0x2f / */ true,
+ /* 0x30 0 */ true, /* 0x31 1 */ true, /* 0x32 2 */ true,
+ /* 0x33 3 */ true, /* 0x34 4 */ true, /* 0x35 5 */ true,
+ /* 0x36 6 */ true, /* 0x37 7 */ true, /* 0x38 8 */ true,
+ /* 0x39 9 */ true, /* 0x3a : */ true, /* 0x3b ; */ false,
+ /* 0x3c < */ false, /* 0x3d = */ true, /* 0x3e > */ false,
+ /* 0x3f ? */ false, /* 0x40 @ */ false, /* 0x41 A */ true,
+ /* 0x42 B */ true, /* 0x43 C */ true, /* 0x44 D */ true,
+ /* 0x45 E */ true, /* 0x46 F */ true, /* 0x47 G */ true,
+ /* 0x48 H */ true, /* 0x49 I */ true, /* 0x4a J */ true,
+ /* 0x4b K */ true, /* 0x4c L */ true, /* 0x4d M */ true,
+ /* 0x4e N */ true, /* 0x4f O */ true, /* 0x50 P */ true,
+ /* 0x51 Q */ true, /* 0x52 R */ true, /* 0x53 S */ true,
+ /* 0x54 T */ true, /* 0x55 U */ true, /* 0x56 V */ true,
+ /* 0x57 W */ true, /* 0x58 X */ true, /* 0x59 Y */ true,
+ /* 0x5a Z */ true, /* 0x5b [ */ false, /* 0x5c \ */ false,
+ /* 0x5d ] */ false, /* 0x5e ^ */ false, /* 0x5f _ */ true,
+ /* 0x60 ` */ false, /* 0x61 a */ true, /* 0x62 b */ true,
+ /* 0x63 c */ true, /* 0x64 d */ true, /* 0x65 e */ true,
+ /* 0x66 f */ true, /* 0x67 g */ true, /* 0x68 h */ true,
+ /* 0x69 i */ true, /* 0x6a j */ true, /* 0x6b k */ true,
+ /* 0x6c l */ true, /* 0x6d m */ true, /* 0x6e n */ true,
+ /* 0x6f o */ true, /* 0x70 p */ true, /* 0x71 q */ true,
+ /* 0x72 r */ true, /* 0x73 s */ true, /* 0x74 t */ true,
+ /* 0x75 u */ true, /* 0x76 v */ true, /* 0x77 w */ true,
+ /* 0x78 x */ true, /* 0x79 y */ true, /* 0x7a z */ true,
+ /* 0x7b { */ false, /* 0x7c | */ false, /* 0x7d } */ false,
+ /* 0x7e ~ */ false, /* 0x7f */ false, /* 0x80 */ false,
+ /* 0x81 */ false, /* 0x82 */ false, /* 0x83 */ false,
+ /* 0x84 */ false, /* 0x85 */ false, /* 0x86 */ false,
+ /* 0x87 */ false, /* 0x88 */ false, /* 0x89 */ false,
+ /* 0x8a */ false, /* 0x8b */ false, /* 0x8c */ false,
+ /* 0x8d */ false, /* 0x8e */ false, /* 0x8f */ false,
+ /* 0x90 */ false, /* 0x91 */ false, /* 0x92 */ false,
+ /* 0x93 */ false, /* 0x94 */ false, /* 0x95 */ false,
+ /* 0x96 */ false, /* 0x97 */ false, /* 0x98 */ false,
+ /* 0x99 */ false, /* 0x9a */ false, /* 0x9b */ false,
+ /* 0x9c */ false, /* 0x9d */ false, /* 0x9e */ false,
+ /* 0x9f */ false, /* 0xa0 */ false, /* 0xa1 */ false,
+ /* 0xa2 */ false, /* 0xa3 */ false, /* 0xa4 */ false,
+ /* 0xa5 */ false, /* 0xa6 */ false, /* 0xa7 */ false,
+ /* 0xa8 */ false, /* 0xa9 */ false, /* 0xaa */ false,
+ /* 0xab */ false, /* 0xac */ false, /* 0xad */ false,
+ /* 0xae */ false, /* 0xaf */ false, /* 0xb0 */ false,
+ /* 0xb1 */ false, /* 0xb2 */ false, /* 0xb3 */ false,
+ /* 0xb4 */ false, /* 0xb5 */ false, /* 0xb6 */ false,
+ /* 0xb7 */ false, /* 0xb8 */ false, /* 0xb9 */ false,
+ /* 0xba */ false, /* 0xbb */ false, /* 0xbc */ false,
+ /* 0xbd */ false, /* 0xbe */ false, /* 0xbf */ false,
+ /* 0xc0 */ false, /* 0xc1 */ false, /* 0xc2 */ false,
+ /* 0xc3 */ false, /* 0xc4 */ false, /* 0xc5 */ false,
+ /* 0xc6 */ false, /* 0xc7 */ false, /* 0xc8 */ false,
+ /* 0xc9 */ false, /* 0xca */ false, /* 0xcb */ false,
+ /* 0xcc */ false, /* 0xcd */ false, /* 0xce */ false,
+ /* 0xcf */ false, /* 0xd0 */ false, /* 0xd1 */ false,
+ /* 0xd2 */ false, /* 0xd3 */ false, /* 0xd4 */ false,
+ /* 0xd5 */ false, /* 0xd6 */ false, /* 0xd7 */ false,
+ /* 0xd8 */ false, /* 0xd9 */ false, /* 0xda */ false,
+ /* 0xdb */ false, /* 0xdc */ false, /* 0xdd */ false,
+ /* 0xde */ false, /* 0xdf */ false, /* 0xe0 */ false,
+ /* 0xe1 */ false, /* 0xe2 */ false, /* 0xe3 */ false,
+ /* 0xe4 */ false, /* 0xe5 */ false, /* 0xe6 */ false,
+ /* 0xe7 */ false, /* 0xe8 */ false, /* 0xe9 */ false,
+ /* 0xea */ false, /* 0xeb */ false, /* 0xec */ false,
+ /* 0xed */ false, /* 0xee */ false, /* 0xef */ false,
+ /* 0xf0 */ false, /* 0xf1 */ false, /* 0xf2 */ false,
+ /* 0xf3 */ false, /* 0xf4 */ false, /* 0xf5 */ false,
+ /* 0xf6 */ false, /* 0xf7 */ false, /* 0xf8 */ false,
+ /* 0xf9 */ false, /* 0xfa */ false, /* 0xfb */ false,
+ /* 0xfc */ false, /* 0xfd */ false, /* 0xfe */ false};
+
+} // namespace sexp
diff --git a/src/sexp-depth-manager.cpp b/src/sexp-depth-manager.cpp
new file mode 100644
index 0000000..2f26e27
--- /dev/null
+++ b/src/sexp-depth-manager.cpp
@@ -0,0 +1,50 @@
+/**
+ *
+ * Copyright 2023 Ribose Inc.
+ *
+ * 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 "sexpp/sexp.h"
+
+namespace sexp {
+
+sexp_depth_manager::sexp_depth_manager(size_t m_depth)
+{
+ reset_depth(m_depth);
+}
+void sexp_depth_manager::reset_depth(size_t m_depth)
+{
+ depth = 0;
+ max_depth = m_depth;
+}
+void sexp_depth_manager::increase_depth(int count)
+{
+ if (max_depth != 0 && ++depth > max_depth)
+ sexp_error(sexp_exception_t::error,
+ "Maximum allowed SEXP list depth (%u) is exceeded",
+ max_depth,
+ 0,
+ count);
+}
+void sexp_depth_manager::decrease_depth(void)
+{
+ depth--;
+}
+} // namespace sexp
diff --git a/src/sexp-error.cpp b/src/sexp-error.cpp
new file mode 100644
index 0000000..3558f02
--- /dev/null
+++ b/src/sexp-error.cpp
@@ -0,0 +1,56 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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 "sexpp/sexp-error.h"
+
+namespace sexp {
+
+sexp_exception_t::severity sexp_exception_t::verbosity = sexp_exception_t::error;
+bool sexp_exception_t::interactive = false;
+
+std::string sexp_exception_t::format(std::string prf,
+ std::string message,
+ severity level,
+ int position)
+{
+ std::string r = prf + (level == error ? " ERROR: " : " WARNING: ") + message;
+ if (position >= 0)
+ r += " at position " + std::to_string(position);
+ return r;
+};
+
+void sexp_error(
+ sexp_exception_t::severity level, const char *msg, size_t c1, size_t c2, int pos)
+{
+ char tmp[256];
+ sexp_exception_t::severity l = (sexp_exception_t::severity) level;
+ snprintf(tmp, sizeof(tmp) / sizeof(tmp[0]), msg, c1, c2);
+ if (sexp_exception_t::shall_throw(l))
+ throw sexp_exception_t(tmp, l, pos);
+ if (sexp_exception_t::is_interactive()) {
+ std::cout.flush() << std::endl
+ << "*** " << sexp_exception_t::format("SEXP", tmp, l, pos) << " ***"
+ << std::endl;
+ }
+}
+} // namespace sexp \ No newline at end of file
diff --git a/src/sexp-input.cpp b/src/sexp-input.cpp
new file mode 100644
index 0000000..cb21686
--- /dev/null
+++ b/src/sexp-input.cpp
@@ -0,0 +1,514 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ * Original copyright
+ *
+ * SEXP implementation code sexp-input.c
+ * Ron Rivest
+ * 7/21/1997
+ */
+
+#include "sexpp/sexp.h"
+
+namespace sexp {
+
+/*
+ * sexp_input_stream_t::sexp_input_stream_t
+ * Creates and initializes new sexp_input_stream_t object.
+ */
+
+sexp_input_stream_t::sexp_input_stream_t(std::istream *i, size_t m_depth)
+{
+ set_input(i, m_depth);
+}
+
+/*
+ * sexp_input_stream_t::set_input(std::istream *i, size_t m_depth)
+ */
+
+sexp_input_stream_t *sexp_input_stream_t::set_input(std::istream *i, size_t m_depth)
+{
+ input_file = i;
+ byte_size = 8;
+ next_char = ' ';
+ bits = 0;
+ n_bits = 0;
+ count = -1;
+ reset_depth(m_depth);
+ return this;
+}
+
+/*
+ * sexp_input_stream_t::set_byte_size(newByteSize)
+ */
+sexp_input_stream_t *sexp_input_stream_t::set_byte_size(uint32_t newByteSize)
+{
+ byte_size = newByteSize;
+ n_bits = 0;
+ bits = 0;
+ return this;
+}
+
+int sexp_input_stream_t::read_char(void)
+{
+ count++;
+ return input_file->get();
+}
+
+/*
+ * sexp_input_stream_t::get_char()
+ * This is one possible character input routine for an input stream.
+ * (This version uses the standard input stream.)
+ * get_char places next 8-bit character into is->next_char.
+ * It also updates the count of number of 8-bit characters read.
+ * The value EOF is obtained when no more input is available.
+ * This code handles 4-bit/6-bit/8-bit channels.
+ */
+sexp_input_stream_t *sexp_input_stream_t::get_char(void)
+{
+ int c;
+ if (next_char == EOF) {
+ byte_size = 8;
+ return this;
+ }
+
+ while (true) {
+ c = next_char = read_char();
+ if (c == EOF)
+ return this;
+ if ((byte_size == 6 && (c == '|' || c == '}')) || (byte_size == 4 && (c == '#'))) {
+ // end of region reached; return terminating character, after checking for
+ // unused bits
+ if (n_bits > 0 && (((1 << n_bits) - 1) & bits) != 0) {
+ sexp_error(sexp_exception_t::warning,
+ "%d-bit region ended with %d unused bits left-over",
+ byte_size,
+ n_bits,
+ count);
+ }
+ return set_byte_size(8);
+ } else if (byte_size != 8 && is_white_space(c))
+ ; /* ignore white space in hex and base64 regions */
+ else if (byte_size == 6 && c == '=')
+ ; /* ignore equals signs in base64 regions */
+ else if (byte_size == 8) {
+ return this;
+ } else if (byte_size < 8) {
+ bits = bits << byte_size;
+ n_bits += byte_size;
+ if (byte_size == 6 && is_base64_digit(c))
+ bits = bits | base64value(c);
+ else if (byte_size == 4 && is_hex_digit(c))
+ bits = bits | hexvalue(c);
+ else {
+ sexp_error(sexp_exception_t::error,
+ "character '%c' found in %u-bit coding region",
+ next_char,
+ byte_size,
+ count);
+ }
+ if (n_bits >= 8) {
+ next_char = (bits >> (n_bits - 8)) & 0xFF;
+ n_bits -= 8;
+ return this;
+ }
+ }
+ }
+}
+
+/*
+ * sexp_input_stream_t::skip_white_space
+ * Skip over any white space on the given sexp_input_stream_t.
+ */
+sexp_input_stream_t *sexp_input_stream_t::skip_white_space(void)
+{
+ while (is_white_space(next_char))
+ get_char();
+ return this;
+}
+
+/*
+ * sexp_input_stream_t::skip_char(c)
+ * Skip the following input character on input stream is, if it is
+ * equal to the character c. If it is not equal, then an error occurs.
+ */
+sexp_input_stream_t *sexp_input_stream_t::skip_char(int c)
+{
+ if (next_char != c)
+ sexp_error(sexp_exception_t::error,
+ "character '%c' found where '%c' was expected",
+ next_char,
+ c,
+ count);
+ return get_char();
+}
+
+/*
+ * sexp_input_stream_t::scan_token(ss)
+ * scan one or more characters into simple string ss as a token.
+ */
+void sexp_input_stream_t::scan_token(sexp_simple_string_t &ss)
+{
+ skip_white_space();
+ while (is_token_char(next_char)) {
+ ss.append(next_char);
+ get_char();
+ }
+}
+
+/*
+ * sexp_input_stream_t::scan_to_eof(void)
+ * scan one or more characters (until EOF reached)
+ * return an object that is just that string
+ */
+std::shared_ptr<sexp_object_t> sexp_input_stream_t::scan_to_eof(void)
+{
+ sexp_simple_string_t ss;
+ skip_white_space();
+ while (next_char != EOF) {
+ ss.append(next_char);
+ get_char();
+ }
+ auto s = std::make_shared<sexp_string_t>();
+ s->set_string(ss);
+ return s;
+}
+
+/*
+ * scan_decimal_string(is)
+ * returns long integer that is value of decimal number
+ */
+uint32_t sexp_input_stream_t::scan_decimal_string(void)
+{
+ uint32_t value = 0;
+ uint32_t i = 0;
+ while (is_dec_digit(next_char)) {
+ value = value * 10 + decvalue(next_char);
+ get_char();
+ if (i++ > 8)
+ sexp_error(sexp_exception_t::error, "Decimal number is too long", 0, 0, count);
+ }
+ return value;
+}
+
+/*
+ * sexp_input_stream_t::scan_verbatim_string(is,ss,length)
+ * Reads verbatim string of given length into simple string ss.
+ */
+void sexp_input_stream_t::scan_verbatim_string(sexp_simple_string_t &ss, uint32_t length)
+{
+ skip_white_space()->skip_char(':');
+
+ // Some length is specified always, this is ensured by the caller's logic
+ assert(length != std::numeric_limits<uint32_t>::max());
+ for (uint32_t i = 0; i < length; i++) {
+ ss.append(next_char);
+ get_char();
+ }
+}
+
+/*
+ * sexp_input_stream_t::scan_quoted_string(ss,length)
+ * Reads quoted string of given length into simple string ss.
+ * Handles ordinary C escapes.
+ * If of indefinite length, length is std::numeric_limits<uint32_t>::max().
+ */
+void sexp_input_stream_t::scan_quoted_string(sexp_simple_string_t &ss, uint32_t length)
+{
+ skip_char('"');
+ while (ss.length() <= length) {
+ if (next_char == '\"') {
+ if (length == std::numeric_limits<uint32_t>::max() || (ss.length() == length)) {
+ skip_char('\"');
+ return;
+ } else
+ sexp_error(sexp_exception_t::error,
+ "Declared length was %d, but quoted string ended too early",
+ (int) length,
+ 0,
+ count);
+ } else if (next_char == '\\') /* handle escape sequence */
+ {
+ get_char();
+ switch (next_char) {
+ case 'b':
+ ss.append('\b');
+ break;
+ case 't':
+ ss.append('\t');
+ break;
+ case 'v':
+ ss.append('\v');
+ break;
+ case 'n':
+ ss.append('\n');
+ break;
+ case 'f':
+ ss.append('\f');
+ break;
+ case 'r':
+ ss.append('\r');
+ break;
+ case '\"':
+ ss.append('\"');
+ break;
+ case '\'':
+ ss.append('\'');
+ break;
+ case '\\':
+ ss.append('\\');
+ break;
+ case 'x': /* hexadecimal number */
+ {
+ int j, val;
+ val = 0;
+ get_char();
+ for (j = 0; j < 2; j++) {
+ if (is_hex_digit(next_char)) {
+ val = ((val << 4) | hexvalue(next_char));
+ if (j < 1) {
+ get_char();
+ }
+ } else
+ sexp_error(sexp_exception_t::error,
+ "Hex character \x5cx%x... too short",
+ val,
+ 0,
+ count);
+ }
+ ss.append(val);
+ } break;
+ case '\n': /* ignore backslash line feed */
+ get_char(); /* also ignore following carriage-return if present */
+ if (next_char != '\r')
+ continue;
+ break;
+ case '\r': /* ignore backslash carriage-return */
+ get_char(); /* also ignore following linefeed if present */
+ if (next_char != '\n')
+ continue;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7': { /* octal number */
+ int j, val;
+ val = 0;
+ for (j = 0; j < 3; j++) {
+ if (next_char >= '0' && next_char <= '7') {
+ val = ((val << 3) | (next_char - '0'));
+ if (j < 2)
+ get_char();
+ } else
+ sexp_error(sexp_exception_t::error,
+ "Octal character \\%o... too short",
+ val,
+ 0,
+ count);
+ }
+ if (val > 255)
+ sexp_error(sexp_exception_t::error,
+ "Octal character \\%o... too big",
+ val,
+ 0,
+ count);
+ ss.append(val);
+ } break;
+ default:
+ sexp_error(sexp_exception_t::error,
+ "Unknown escape sequence \\%c",
+ next_char,
+ 0,
+ count);
+ }
+ } /* end of handling escape sequence */
+ else if (next_char == EOF) {
+ sexp_error(sexp_exception_t::error, "unexpected end of file", 0, 0, count);
+ } else {
+ ss.append(next_char);
+ }
+ get_char();
+ } /* end of main while loop */
+}
+
+/*
+ * scan_hexadecimal_string(ss,length)
+ * Reads hexadecimal string into simple string ss.
+ * String is of given length result, or length = std::numeric_limits<uint32_t>::max()
+ * if indefinite length.
+ */
+void sexp_input_stream_t::scan_hexadecimal_string(sexp_simple_string_t &ss, uint32_t length)
+{
+ set_byte_size(4)->skip_char('#');
+ while (next_char != EOF && (next_char != '#' || get_byte_size() == 4)) {
+ ss.append(next_char);
+ get_char();
+ }
+ skip_char('#');
+ if (ss.length() != length && length != std::numeric_limits<uint32_t>::max())
+ sexp_error(sexp_exception_t::warning,
+ "Hex string has length %d different than declared length %d",
+ ss.length(),
+ length,
+ count);
+}
+
+/*
+ * sexp_input_stream_t::scan_base64_string(ss,length)
+ * Reads base64 string into simple string ss.
+ * String is of given length result, or length = std::numeric_limits<uint32_t>::max()
+ * if indefinite length.
+ */
+void sexp_input_stream_t::scan_base64_string(sexp_simple_string_t &ss, uint32_t length)
+{
+ set_byte_size(6)->skip_char('|');
+ while (next_char != EOF && (next_char != '|' || get_byte_size() == 6)) {
+ ss.append(next_char);
+ get_char();
+ }
+ skip_char('|');
+ if (ss.length() != length && length != std::numeric_limits<uint32_t>::max())
+ sexp_error(sexp_exception_t::warning,
+ "Base64 string has length %d different than declared length %d",
+ ss.length(),
+ length,
+ count);
+}
+
+/*
+ * sexp_input_stream_t::scan_simple_string(void)
+ * Reads and returns a simple string from the input stream.
+ * Determines type of simple string from the initial character, and
+ * dispatches to appropriate routine based on that.
+ */
+sexp_simple_string_t sexp_input_stream_t::scan_simple_string(void)
+{
+ int length;
+ sexp_simple_string_t ss;
+ skip_white_space();
+ /* Note that it is important in the following code to test for token-ness
+ * before checking the other cases, so that a token may begin with ":",
+ * which would otherwise be treated as a verbatim string missing a length.
+ */
+ if (is_token_char(next_char) && !is_dec_digit(next_char)) {
+ scan_token(ss);
+ } else {
+ length = is_dec_digit(next_char) ? scan_decimal_string() :
+ std::numeric_limits<uint32_t>::max();
+
+ switch (next_char) {
+ case '\"':
+ scan_quoted_string(ss, length);
+ break;
+ case '#':
+ scan_hexadecimal_string(ss, length);
+ break;
+ case '|':
+ scan_base64_string(ss, length);
+ break;
+ case ':':
+ // ':' is 'tokenchar', so some length shall be defined
+ scan_verbatim_string(ss, length);
+ break;
+ default: {
+ const char *const msg = (next_char == EOF) ? "unexpected end of file" :
+ isprint(next_char) ? "illegal character '%c' (0x%x)" :
+ "illegal character 0x%x";
+ sexp_error(sexp_exception_t::error, msg, next_char, next_char, count);
+ }
+ }
+ }
+
+ if (ss.length() == 0)
+ sexp_error(sexp_exception_t::warning, "Simple string has zero length", 0, 0, count);
+ return ss;
+}
+
+/*
+ * sexp_input_stream_t::scan_string(void)
+ * Reads and returns a string [presentationhint]string from input stream.
+ */
+std::shared_ptr<sexp_string_t> sexp_input_stream_t::scan_string(void)
+{
+ auto s = std::make_shared<sexp_string_t>();
+ ;
+ s->parse(this);
+ return s;
+}
+
+/*
+ * sexp_input_stream_t::scan_list(void)
+ * Read and return a sexp_list_t from the input stream.
+ */
+std::shared_ptr<sexp_list_t> sexp_input_stream_t::scan_list(void)
+{
+ auto list = std::make_shared<sexp_list_t>();
+ list->parse(this);
+ return list;
+}
+
+/*
+ * sexp_input_stream_t::scan_object(void)
+ * Reads and returns a sexp_object_t from the given input stream.
+ */
+std::shared_ptr<sexp_object_t> sexp_input_stream_t::scan_object(void)
+{
+ std::shared_ptr<sexp_object_t> object;
+ skip_white_space();
+ if (next_char == '{' && byte_size != 6) {
+ set_byte_size(6)->skip_char('{');
+ object = scan_object();
+ skip_char('}');
+ } else {
+ if (next_char == '(')
+ object = scan_list();
+ else
+ object = scan_string();
+ }
+ return object;
+}
+
+/*
+ * sexp_input_stream_t::open_list(void)
+ */
+sexp_input_stream_t *sexp_input_stream_t::open_list(void)
+{
+ skip_char('(');
+ // gcc 4.8.5 generates wrong code in case of chaining like
+ // skip_char('(')->increase_depth(count)
+ increase_depth(count);
+ return this;
+}
+/*
+ * sexp_input_stream_t::close_list(void)
+ */
+sexp_input_stream_t *sexp_input_stream_t::close_list(void)
+{
+ skip_char(')');
+ decrease_depth();
+ return this;
+}
+
+} // namespace sexp
diff --git a/src/sexp-main.cpp b/src/sexp-main.cpp
new file mode 100644
index 0000000..46fae63
--- /dev/null
+++ b/src/sexp-main.cpp
@@ -0,0 +1,231 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ * Original copyright
+ *
+ * SEXP implementation code sexp-main.c
+ * Ron Rivest
+ * 6/29/1997
+ **/
+
+#include <fstream>
+
+#include "sexpp/sexp.h"
+
+using namespace sexp;
+
+const char *help = "The program 'sexp' reads, parses, and prints out S-expressions.\n"
+ " INPUT:\n"
+ " Input is normally taken from stdin, but this can be changed:\n"
+ " -i filename -- takes input from file instead.\n"
+ " -p -- prompts user for console input\n"
+ " Input is normally parsed, but this can be changed:\n"
+ " -s -- treat input up to EOF as a single string\n"
+ " CONTROL LOOP:\n"
+ " The main routine typically reads one S-expression, prints it out "
+ "again, \n"
+ " and stops. This may be modified:\n"
+ " -x -- execute main loop repeatedly until EOF\n"
+ " OUTPUT:\n"
+ " Output is normally written to stdout, but this can be changed:\n"
+ " -o filename -- write output to file instead\n"
+ " The output format is normally canonical, but this can be changed:\n"
+ " -a -- write output in advanced transport format\n"
+ " -b -- write output in base-64 output format\n"
+ " -c -- write output in canonical format\n"
+ " -l -- suppress linefeeds after output\n"
+ " More than one output format can be requested at once.\n"
+ " There is normally a line-width of 75 on output, but:\n"
+ " -w width -- changes line width to specified width.\n"
+ " (0 implies no line-width constraint)\n"
+ " Running without switches implies: -p -a -b -c -x\n"
+ " Typical usage: cat certificate-file | sexp -a -x \n";
+
+/*************************************************************************/
+/* main(argc,argv)
+ */
+int main(int argc, char **argv)
+{
+ char *c;
+ bool swa = true, swb = true, swc = true, swp = true, sws = false, swx = true, swl = false;
+ int i;
+ int ret = -1;
+ sexp_exception_t::set_interactive(true);
+ std::ifstream * ifs = nullptr;
+ sexp_input_stream_t * is = nullptr;
+ std::ofstream * ofs = nullptr;
+ sexp_output_stream_t *os = nullptr;
+ std::string ofname;
+ std::string ifname;
+ try {
+ std::shared_ptr<sexp_object_t> object;
+
+ is = new sexp_input_stream_t(&std::cin);
+ os = new sexp_output_stream_t(&std::cout);
+
+ if (argc > 1)
+ swa = swb = swc = swp = sws = swx = swl = false;
+ for (i = 1; i < argc; i++) {
+ c = argv[i];
+ if (*c != '-')
+ throw sexp_exception_t(
+ std::string("Unrecognized switch ") + c, sexp_exception_t::error, EOF);
+ c++;
+ if (*c == 'a')
+ swa = true; /* advanced output */
+ else if (*c == 'b')
+ swb = true; /* base-64 output */
+ else if (*c == 'c')
+ swc = true; /* canonical output */
+ else if (*c == 'h') { /* help */
+ std::cout << help;
+ exit(0);
+ } else if (*c == 'i') { /* input file */
+ if (i + 1 < argc)
+ i++;
+ ifs = new std::ifstream(argv[i], std::ifstream::binary);
+ if (ifs->fail())
+ sexp_error(sexp_exception_t::error, "Can't open input file.", 0, 0, EOF);
+ is->set_input(ifs);
+ ifname = argv[i];
+ } else if (*c == 'l')
+ swl = true; /* suppress linefeeds after output */
+ else if (*c == 'o') { /* output file */
+ if (i + 1 < argc)
+ i++;
+ ofs = new std::ofstream(argv[i], std::ifstream::binary);
+ if (ofs->fail())
+ sexp_error(sexp_exception_t::error, "Can't open output file.", 0, 0, EOF);
+ os->set_output(ofs);
+ ofname = argv[i];
+ } else if (*c == 'p')
+ swp = true; /* prompt for input */
+ else if (*c == 's')
+ sws = true; /* treat input as one big string */
+ else if (*c == 'w') { /* set output width */
+ if (i + 1 < argc)
+ i++;
+ os->set_max_column(atoi(argv[i]));
+ } else if (*c == 'x')
+ swx = true; /* execute repeatedly */
+ else
+ throw sexp_exception_t(
+ std::string("Unrecognized switch ") + argv[i], sexp_exception_t::error, EOF);
+ }
+
+ if (swa == false && swb == false && swc == false)
+ swc = true; /* must have some output format! */
+
+ /* main loop */
+ if (swp == 0)
+ is->get_char();
+ else
+ is->set_next_char(-2); /* this is not EOF */
+ while (is->get_next_char() != EOF) {
+ if (swp) {
+ if (ifname.empty())
+ std::cout << "Input:";
+ else
+ std::cout << "Reading input from " << ifname;
+ std::cout << std::endl;
+ std::cout.flush();
+ }
+
+ is->set_byte_size(8);
+ if (is->get_next_char() == -2)
+ is->get_char();
+
+ is->skip_white_space();
+ if (is->get_next_char() == EOF)
+ break;
+
+ object = sws ? is->scan_to_eof() : is->scan_object();
+
+ if (swp)
+ std::cout << std::endl;
+
+ if (swc) {
+ if (swp) {
+ if (ofname.empty())
+ std::cout << "Canonical output:" << std::endl;
+ else
+ std::cout << "Writing canonical output to '" << ofname << "'";
+ }
+ object->print_canonical(os);
+ if (!swl) {
+ std::cout << std::endl;
+ }
+ }
+
+ if (swb) {
+ if (swp) {
+ if (ofname.empty())
+ std::cout << "Base64 (of canonical) output:" << std::endl;
+ else
+ std::cout << "Writing base64 (of canonical) output to '" << ofname
+ << "'";
+ }
+ os->set_output(ofs ? ofs : &std::cout)->print_base64(object);
+ if (!swl) {
+ std::cout << std::endl;
+ std::cout.flush();
+ }
+ }
+
+ if (swa) {
+ if (swp) {
+ if (ofname.empty())
+ std::cout << "Advanced transport output:" << std::endl;
+ else
+ std::cout << "Writing advanced transport output to '" << ofname << "'";
+ }
+ os->set_output(ofs ? ofs : &std::cout)->print_advanced(object);
+ if (!swl) {
+ std::cout << std::endl;
+ std::cout.flush();
+ }
+ }
+
+ if (!swx)
+ break;
+ if (!swp)
+ is->skip_white_space();
+ else if (!swl) {
+ std::cout << std::endl;
+ std::cout.flush();
+ }
+ }
+ ret = 0;
+ } catch (sexp_exception_t &e) {
+ std::cout << e.what() << std::endl;
+ } catch (...) {
+ std::cout << "UNEXPECTED ERROR" << std::endl;
+ }
+ if (is)
+ delete is;
+ if (ifs)
+ delete ifs;
+ if (os)
+ delete os;
+ if (ofs)
+ delete ofs;
+ return ret;
+} \ No newline at end of file
diff --git a/src/sexp-object.cpp b/src/sexp-object.cpp
new file mode 100644
index 0000000..d01ae11
--- /dev/null
+++ b/src/sexp-object.cpp
@@ -0,0 +1,188 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ * Original copyright
+ *
+ * SEXP implementation code sexp-output.c
+ * Ron Rivest
+ * 5/5/1997
+ */
+
+#include "sexpp/sexp.h"
+
+namespace sexp {
+
+/*
+ * sexp_string_t::parse(sis)
+ * Parses the strin from input stream
+ */
+
+void sexp_string_t::parse(sexp_input_stream_t *sis)
+{
+ if (sis->get_next_char() == '[') { /* scan presentation hint */
+ sis->skip_char('[');
+ set_presentation_hint(sis->scan_simple_string());
+ sis->skip_white_space()->skip_char(']')->skip_white_space();
+ }
+ set_string(sis->scan_simple_string());
+}
+
+/*
+ * sexp_string_t::print_canonical(os)
+ * Prints out sexp string onto output stream os
+ */
+sexp_output_stream_t *sexp_string_t::print_canonical(sexp_output_stream_t *os) const
+{
+ if (with_presentation_hint) {
+ os->var_put_char('[');
+ presentation_hint.print_canonical_verbatim(os);
+ os->var_put_char(']');
+ }
+ data_string.print_canonical_verbatim(os);
+ return os;
+}
+
+/*
+ * sexp_string_t::print_advanced(os)
+ * Prints out sexp string onto output stream os
+ */
+sexp_output_stream_t *sexp_string_t::print_advanced(sexp_output_stream_t *os) const
+{
+ sexp_object_t::print_advanced(os);
+ if (with_presentation_hint) {
+ os->put_char('[');
+ presentation_hint.print_advanced(os);
+ os->put_char(']');
+ }
+ data_string.print_advanced(os);
+ return os;
+}
+
+/*
+ * sexp_string_t::advanced_length(os)
+ * Returns length of printed image of string
+ */
+size_t sexp_string_t::advanced_length(sexp_output_stream_t *os) const
+{
+ size_t len = 0;
+ if (with_presentation_hint)
+ len += 2 + presentation_hint.advanced_length(os);
+ len += data_string.advanced_length(os);
+ return len;
+}
+
+/*
+ * sexp_list_t::parse(sis)
+ * Parses the list from input stream
+ */
+
+void sexp_list_t::parse(sexp_input_stream_t *sis)
+{
+ sis->open_list()->skip_white_space();
+ if (sis->get_next_char() == ')') {
+ ;
+ } else {
+ push_back(sis->scan_object());
+ }
+
+ while (true) {
+ sis->skip_white_space();
+ if (sis->get_next_char() == ')') { /* we just grabbed last element of list */
+ sis->close_list();
+ return;
+
+ } else {
+ push_back(sis->scan_object());
+ }
+ }
+}
+
+/*
+ * sexp_list_t::print_canonical(os)
+ * Prints out the list "list" onto output stream os
+ */
+sexp_output_stream_t *sexp_list_t::print_canonical(sexp_output_stream_t *os) const
+{
+ os->var_open_list();
+ std::for_each(begin(), end(), [os](const std::shared_ptr<sexp_object_t> &obj) {
+ obj->print_canonical(os);
+ });
+ os->var_close_list();
+ return os;
+}
+
+/*
+ * sexp_list_t::print_advanced(os)
+ * Prints out the list onto output stream os.
+ * Uses print-length to determine length of the image. If it all fits
+ * on the current line, then it is printed that way. Otherwise, it is
+ * written out in "vertical" mode, with items of the list starting in
+ * the same column on successive lines.
+ */
+sexp_output_stream_t *sexp_list_t::print_advanced(sexp_output_stream_t *os) const
+{
+ sexp_object_t::print_advanced(os);
+ int vertical = false;
+ int firstelement = true;
+ os->open_list()->inc_indent();
+ vertical = (advanced_length(os) > os->get_max_column() - os->get_column());
+
+ std::for_each(begin(), end(), [&](const std::shared_ptr<sexp_object_t> &obj) {
+ if (!firstelement) {
+ if (vertical)
+ os->new_line(sexp_output_stream_t::advanced);
+ else
+ os->put_char(' ');
+ }
+ obj->print_advanced(os);
+ firstelement = false;
+ });
+
+ if (os->get_max_column() > 0 && os->get_column() > os->get_max_column() - 2)
+ os->new_line(sexp_output_stream_t::advanced);
+ return os->dec_indent()->put_char(')');
+}
+
+/*
+ * sexp_list_t::advanced_length(os)
+ * Returns length of printed image of list given as iterator
+ */
+size_t sexp_list_t::advanced_length(sexp_output_stream_t *os) const
+{
+ size_t len = 1; /* for left paren */
+ std::for_each(begin(), end(), [&](const std::shared_ptr<sexp_object_t> &obj) {
+ len += obj->advanced_length(os);
+ });
+ return (len + 1); /* for final paren */
+}
+
+/*
+ * sexp_object_t::print_advanced(os)
+ * Prints out object on output stream os
+ */
+sexp_output_stream_t *sexp_object_t::print_advanced(sexp_output_stream_t *os) const
+{
+ if (os->get_max_column() > 0 && os->get_column() > os->get_max_column() - 4)
+ os->new_line(sexp_output_stream_t::advanced);
+ return os;
+}
+
+} // namespace sexp \ No newline at end of file
diff --git a/src/sexp-output.cpp b/src/sexp-output.cpp
new file mode 100644
index 0000000..07b476c
--- /dev/null
+++ b/src/sexp-output.cpp
@@ -0,0 +1,197 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ * Original copyright
+ *
+ * SEXP implementation code sexp-output.c
+ * Ron Rivest
+ * 5/5/1997
+ */
+
+#include "sexpp/sexp.h"
+
+namespace sexp {
+
+static const char *hexDigits = "0123456789ABCDEF";
+static const char *base64Digits =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/*
+ * sexp_output_stream_t::sexp_output_stream_t
+ * Creates and initializes new sexp_output_stream_t object.
+ */
+sexp_output_stream_t::sexp_output_stream_t(std::ostream *o, size_t m_depth)
+{
+ set_output(o, m_depth);
+}
+
+/*
+ * sexp_output_stream_t::set_output
+ * Re-initializes new sexp_output_stream_t object.
+ */
+sexp_output_stream_t *sexp_output_stream_t::set_output(std::ostream *o, size_t m_depth)
+{
+ output_file = o;
+ byte_size = 8;
+ bits = 0;
+ n_bits = 0;
+ mode = canonical;
+ column = 0;
+ max_column = default_line_length;
+ indent = 0;
+ base64_count = 0;
+ reset_depth(m_depth);
+ return this;
+}
+
+/*
+ * sexp_output_stream_t::put_char(c)
+ * Puts the character c out on the output stream os.
+ * Keeps track of the "column" the next output char will go to.
+ */
+sexp_output_stream_t *sexp_output_stream_t::put_char(int c)
+{
+ output_file->put(c);
+ column++;
+ return this;
+}
+
+/*
+ * sexp_output_stream_t::var_put_char(c)
+ * put_char with variable sized output bytes considered.
+ * int c; -- this is always an eight-bit byte being output
+ */
+sexp_output_stream_t *sexp_output_stream_t::var_put_char(int c)
+{
+ c &= 0xFF;
+ bits = (bits << 8) | c;
+ n_bits += 8;
+ while (n_bits >= byte_size) {
+ if ((byte_size == 6 || byte_size == 4 || c == '}' || c == '{' || c == '#' ||
+ c == '|') &&
+ max_column > 0 && column >= max_column)
+ new_line(mode);
+ if (byte_size == 4)
+ put_char(hexDigits[(bits >> (n_bits - 4)) & 0x0F]);
+ else if (byte_size == 6)
+ put_char(base64Digits[(bits >> (n_bits - 6)) & 0x3F]);
+ else if (byte_size == 8)
+ put_char(bits & 0xFF);
+ n_bits -= byte_size;
+ base64_count++;
+ }
+ return this;
+}
+
+/*
+ * sexp_output_stream_t::change_output_byte_size(newByteSize,newMode)
+ * Change os->byte_size to newByteSize
+ * record mode in output stream for automatic line breaks
+ */
+sexp_output_stream_t *sexp_output_stream_t::change_output_byte_size(int newByteSize,
+ sexp_print_mode newMode)
+{
+ if (newByteSize != 4 && newByteSize != 6 && newByteSize != 8)
+ sexp_error(sexp_exception_t::error, "Illegal output base %d", newByteSize, 0, EOF);
+ if (newByteSize != 8 && byte_size != 8)
+ sexp_error(sexp_exception_t::error,
+ "Illegal change of output byte size from %d to %d",
+ byte_size,
+ newByteSize,
+ EOF);
+ byte_size = newByteSize;
+ n_bits = 0;
+ bits = 0;
+ base64_count = 0;
+ mode = newMode;
+ return this;
+}
+
+/*
+ * sexp_output_stream_t::flush()
+ * flush out any remaining bits
+ */
+sexp_output_stream_t *sexp_output_stream_t::flush(void)
+{
+ if (n_bits > 0) {
+ assert(byte_size == 6);
+ put_char(base64Digits[(bits << (6 - n_bits)) & 0x3F]);
+ n_bits = 0;
+ base64_count++;
+ }
+ if (byte_size == 6) { /* and add switch here */
+ while ((base64_count & 3) != 0) {
+ if (max_column > 0 && column >= max_column)
+ new_line(mode);
+ put_char('=');
+ base64_count++;
+ }
+ }
+ return this;
+}
+
+/*
+ * sexp_output_stream_t::new_line(mode)
+ * Outputs a newline symbol to the output stream os.
+ * For advanced mode, also outputs indentation as one blank per
+ * indentation level (but never indents more than half of max_column).
+ * Resets column for next output character.
+ */
+sexp_output_stream_t *sexp_output_stream_t::new_line(sexp_print_mode mode)
+{
+ if (mode == advanced || mode == base64) {
+ put_char('\n');
+ column = 0;
+ }
+ if (mode == advanced) {
+ for (uint32_t i = 0; i < indent && (4 * i) < max_column; i++)
+ put_char(' ');
+ }
+ return this;
+}
+
+/*
+ * sexp_output_stream_t::print_decimal(n)
+ * print out n in decimal to output stream os
+ */
+sexp_output_stream_t *sexp_output_stream_t::print_decimal(uint64_t n)
+{
+ char buffer[20]; // 64*ln(2)/ln(10)
+ // since itoa is not a part of any standard
+ snprintf(buffer, sizeof(buffer) / sizeof(buffer[0]), "%" PRIu64, n);
+ for (uint32_t i = 0; buffer[i] != 0; i++)
+ var_put_char(buffer[i]);
+ return this;
+}
+
+/*
+ * base64 MODE
+ * Same as canonical, except all characters get put out as base 64 ones
+ */
+
+sexp_output_stream_t *sexp_output_stream_t::print_base64(
+ const std::shared_ptr<sexp_object_t> &object)
+{
+ change_output_byte_size(8, base64)->var_put_char('{')->change_output_byte_size(6, base64);
+ print_canonical(object);
+ return flush()->change_output_byte_size(8, base64)->var_put_char('}');
+}
+} // namespace sexp
diff --git a/src/sexp-simple-string.cpp b/src/sexp-simple-string.cpp
new file mode 100644
index 0000000..0e4c869
--- /dev/null
+++ b/src/sexp-simple-string.cpp
@@ -0,0 +1,191 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ * Original copyright
+ *
+ * SEXP implementation code sexp-output.c
+ * Ron Rivest
+ * 5/5/1997
+ */
+
+#include "sexpp/sexp.h"
+
+namespace sexp {
+/*
+ * sexp_simple_string_t::print_canonical_verbatim(os)
+ * Print out simple string on output stream os as verbatim string.
+ */
+sexp_output_stream_t *sexp_simple_string_t::print_canonical_verbatim(
+ sexp_output_stream_t *os) const
+{
+ const octet_t *c = c_str();
+ /* print out len: */
+ os->print_decimal(length())->var_put_char(':');
+ /* print characters in fragment */
+ for (uint32_t i = 0; i < length(); i++)
+ os->var_put_char((int) *c++);
+ return os;
+}
+
+/*
+ * sexp_simple_string_t::advanced_length(os)
+ * Returns length of printed image of s
+ */
+size_t sexp_simple_string_t::advanced_length(sexp_output_stream_t *os) const
+{
+ if (can_print_as_token(os))
+ return advanced_length_token();
+ else if (can_print_as_quoted_string())
+ return advanced_length_quoted();
+ else if (length() <= 4 && os->get_byte_size() == 8)
+ return advanced_length_hexadecimal();
+ else if (os->get_byte_size() == 8)
+ return advanced_length_base64();
+ else
+ return 0; /* an error condition */
+}
+
+/*
+ * sexp_simple_string_t::print_token(os)
+ * Prints out simple string ss as a token (assumes that this is OK).
+ * May run over max-column, but there is no fragmentation allowed...
+ */
+sexp_output_stream_t *sexp_simple_string_t::print_token(sexp_output_stream_t *os) const
+{
+ const octet_t *c = c_str();
+ if (os->get_max_column() > 0 && os->get_column() > (os->get_max_column() - length()))
+ os->new_line(sexp_output_stream_t::advanced);
+ for (uint32_t i = 0; i < length(); i++)
+ os->put_char((int) (*c++));
+ return os;
+}
+
+/*
+ * sexp_simple_string_t::print_base64(os)
+ * Prints out simple string ss as a base64 value.
+ */
+sexp_output_stream_t *sexp_simple_string_t::print_base64(sexp_output_stream_t *os) const
+{
+ const octet_t *c = c_str();
+ os->var_put_char('|')->change_output_byte_size(6, sexp_output_stream_t::advanced);
+ for (uint32_t i = 0; i < length(); i++)
+ os->var_put_char((int) (*c++));
+ return os->flush()
+ ->change_output_byte_size(8, sexp_output_stream_t::advanced)
+ ->var_put_char('|');
+}
+
+/*
+ * sexp_simple_string_t::print_hexadecimal(os)
+ * Prints out simple string as a hexadecimal value.
+ */
+sexp_output_stream_t *sexp_simple_string_t::print_hexadecimal(sexp_output_stream_t *os) const
+{
+ const octet_t *c = c_str();
+ os->put_char('#')->change_output_byte_size(4, sexp_output_stream_t::advanced);
+ for (uint32_t i = 0; i < length(); i++)
+ os->var_put_char((int) (*c++));
+ return os->flush()
+ ->change_output_byte_size(8, sexp_output_stream_t::advanced)
+ ->put_char('#');
+}
+
+/*
+ * sexp_simple_string_t::print_quoted(os)
+ * Prints out simple string ss as a quoted string
+ * This code assumes that all characters are tokenchars and blanks,
+ * so no escape sequences need to be generated.
+ * May run over max-column, but there is no fragmentation allowed...
+ */
+sexp_output_stream_t *sexp_simple_string_t::print_quoted(sexp_output_stream_t *os) const
+{
+ const octet_t *c = c_str();
+ os->put_char('\"');
+ for (uint32_t i = 0; i < length(); i++) {
+ if (os->get_max_column() > 0 && os->get_column() >= os->get_max_column() - 2) {
+ os->put_char('\\')->put_char('\n');
+ os->reset_column();
+ }
+ os->put_char(*c++);
+ }
+ return os->put_char('\"');
+}
+
+/*
+ * sexp_simple_string_t::print_advanced(os)
+ * Prints out simple string onto output stream ss
+ */
+sexp_output_stream_t *sexp_simple_string_t::print_advanced(sexp_output_stream_t *os) const
+{
+ if (can_print_as_token(os))
+ print_token(os);
+ else if (can_print_as_quoted_string())
+ print_quoted(os);
+ else if (length() <= 4 && os->get_byte_size() == 8)
+ print_hexadecimal(os);
+ else if (os->get_byte_size() == 8)
+ print_base64(os);
+ else
+ sexp_error(sexp_exception_t::error,
+ "Can't print in advanced mode with restricted output character set",
+ 0,
+ 0,
+ EOF);
+ return os;
+}
+
+/*
+ * sexp_simple_string_t::can_print_as_quoted_string(void)
+ * Returns true if simple string can be printed as a quoted string.
+ * Must have only tokenchars and blanks.
+ */
+bool sexp_simple_string_t::can_print_as_quoted_string(void) const
+{
+ const octet_t *c = c_str();
+ for (uint32_t i = 0; i < length(); i++, c++) {
+ if (!is_token_char((int) (*c)) && *c != ' ')
+ return false;
+ }
+ return true;
+}
+
+/*
+ * sexp_simple_string_t::can_print_as_token(os)
+ * Returns true if simple string can be printed as a token.
+ * Doesn't begin with a digit, and all characters are tokenchars.
+ */
+bool sexp_simple_string_t::can_print_as_token(const sexp_output_stream_t *os) const
+{
+ const octet_t *c = c_str();
+ if (length() <= 0)
+ return false;
+ if (is_dec_digit((int) *c))
+ return false;
+ if (os->get_max_column() > 0 && os->get_column() + length() >= os->get_max_column())
+ return false;
+ for (uint32_t i = 0; i < length(); i++) {
+ if (!is_token_char((int) (*c++)))
+ return false;
+ }
+ return true;
+}
+
+} // namespace sexp
diff --git a/tests/include/sexp-tests.h b/tests/include/sexp-tests.h
new file mode 100644
index 0000000..497fc91
--- /dev/null
+++ b/tests/include/sexp-tests.h
@@ -0,0 +1,42 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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 <cstdio>
+#include <iostream>
+#include <fstream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "sexpp/sexp.h"
+#include "sexp-samples-folder.h"
+
+bool compare_binary_files(const std::string &filename1, const std::string &filename2);
+bool compare_text_files(const std::string &filename1, const std::string &filename2);
+
+bool compare_binary_files(const std::string &filename1, std::istream &file2);
+bool compare_text_files(const std::string &filename1, std::istream &file2);
+
+std::istream &safe_get_line(std::istream &is, std::string &t);
diff --git a/tests/scripts/tests.sh b/tests/scripts/tests.sh
new file mode 100755
index 0000000..4c37c78
--- /dev/null
+++ b/tests/scripts/tests.sh
@@ -0,0 +1,191 @@
+#! /bin/bash
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+# More safety, by turning some bugs into errors.
+# Without `errexit` you don’t need ! and can replace
+# PIPESTATUS with a simple $?
+set -o errexit -o pipefail -o noclobber -o nounset
+
+assert_installed() {
+ assertTrue "$1 was not installed" "[ -f $1 ]"
+}
+
+assert_installed_var() {
+ assertTrue "{$1,$2}/$3 was not installed" "[ -f $1/$3 ] || [ -f $2/$3 ]"
+}
+
+assert_installed_var2() {
+ assertTrue "$1/{$2,$3} was not installed" "[ -f $1/$2 ] || [ -f $1/$3 ]"
+}
+
+# ......................................................................
+# Check that sexp is installed as expected
+test_install_script() {
+ echo "==> Install script test"
+
+ if [[ "${SHARED_LIB:-}" == "on" ]]; then
+ version=$(cat "$DIR_ROOT"/version.txt)
+ version_major="${version:0:1}"
+ case "$OSTYPE" in
+ darwin*)
+ assert_installed "$DIR_INS_B/sexpp"
+ assert_installed "$DIR_INS_L/libsexpp.dylib"
+ assert_installed "$DIR_INS_L/libsexpp.$version_major.dylib"
+ assert_installed "$DIR_INS_L/libsexpp.$version.dylib"
+ ;;
+ windows )
+ assert_installed "$DIR_INS_B/sexpp.exe"
+ assert_installed "$DIR_INS_B/sexpp.dll"
+ assert_installed "$DIR_INS_L/sexpp.lib"
+ ;;
+ msys)
+
+ assert_installed "$DIR_INS_B/sexpp.exe"
+ assert_installed "$DIR_INS_B/libsexpp.dll"
+ assert_installed "$DIR_INS_L/libsexpp.dll.a"
+ ;;
+ *)
+ assert_installed "$DIR_INS_B/sexpp"
+ assert_installed_var "$DIR_INS_L" "$DIR_INS_L64" "libsexpp.so"
+ assert_installed_var "$DIR_INS_L" "$DIR_INS_L64" "libsexpp.so.$version_major"
+ assert_installed_var "$DIR_INS_L" "$DIR_INS_L64" "libsexpp.so.$version"
+ ;;
+ esac
+ else
+ case "$OSTYPE" in
+ windows)
+ assert_installed "$DIR_INS_B/sexpp.exe"
+ assert_installed "$DIR_INS_L/sexpp.lib"
+ ;;
+ msys)
+ assert_installed "$DIR_INS_B/sexpp.exe"
+ assert_installed "$DIR_INS_L/libsexpp.a"
+ ;;
+ *)
+ assert_installed "$DIR_INS_B/sexpp"
+ assert_installed_var "$DIR_INS_L" "$DIR_INS_L64" "libsexpp.a"
+ ;;
+ esac
+ fi
+
+ assert_installed_var "$DIR_INS_P" "$DIR_INS_P64" "sexpp.pc"
+ assert_installed_var2 "$DIR_INS_M/man1" "sexpp.1" "sexpp.1.gz"
+
+ assert_installed "$DIR_INS_I/sexp.h"
+ assert_installed "$DIR_INS_I/sexp-error.h"
+}
+
+# ......................................................................
+# Check sexp client application
+# THese are the examples from README.adoc
+test_sexp_cli() {
+ echo "==> SEXP client application test"
+
+# On Windows there will be CRLF vs LS mismatch
+# We would rather skip these tests
+ if [[ "$OSTYPE" == "windows" || "$OSTYPE" == "msys" ]]; then
+ startSkipping
+ fi
+
+ app="$DIR_INS_B/sexpp"
+# shellcheck disable=SC2251
+! IFS= read -r -d '' expected << EOM
+Input:
+
+Writing base64 (of canonical) output to certificate.dat
+EOM
+ export LD_LIBRARY_PATH="$DIR_INS_L":"$DIR_INS_L64"
+ rm -f input1.dat
+ echo "(aa bb (cc dd))" > input1.dat
+ output=$("$app" -o certificate.dat -p -b < input1.dat)
+# $expected possibly includes extra EOL at the end -- it depends on OS
+ assertContains "$expected" "$output"
+ output=$(cat certificate.dat)
+ assertEquals "{KDI6YWEyOmJiKDI6Y2MyOmRkKSk=}" "$output"
+
+ output=$("$app" -i certificate.dat -x)
+ assertEquals "(2:aa2:bb(2:cc2:dd))" "$output"
+
+# shellcheck disable=SC2251
+! IFS= read -r -d '' expected << EOM
+Reading input from certificate.dat
+
+Canonical output:
+(2:aa2:bb(2:cc2:dd))
+Base64 (of canonical) output:
+{KDI6YWEyOmJiKDI6Y2MyOmRkKSk=}
+Advanced transport output:
+(aa bb (cc dd))
+EOM
+
+ output=$("$app" -i certificate.dat -a -b -c -p -w 0)
+ assertContains "$expected" "$output"
+
+# shellcheck disable=SC2251
+! IFS= read -r -d '' expected << EOM
+Input:
+
+Canonical output:
+(3:abc3:def(3:ghi3:jkl))
+Base64 (of canonical) output:
+{KDM6YWJjMzpkZWYoMzpnaGkzOmprbCkp}
+Advanced transport output:
+(abc def (ghi jkl))
+
+Input:
+EOM
+ rm -f input2.dat
+ echo "(abc def (ghi jkl))" > input2.dat
+ output=$("$app" < input2.dat)
+ assertContains "$expected" "$output"
+
+ if [[ "$OSTYPE" == "windows" || "$OSTYPE" == "msys" ]]; then
+ endSkipping
+ fi
+
+}
+
+# ......................................................................
+# main
+
+DIR00=$( dirname "$0" )
+DIR0=$( cd "$DIR00" && pwd )
+DIR1="${DIR_ROOT:=$DIR0/../..}"
+DIR_ROOT=$( cd "$DIR1" && pwd )
+
+if [[ -z "${DIR_INSTALL:-}" ]]; then
+ DIR_INSTALL="$DIR_ROOT/install"
+fi
+
+DIR_INS_B="$DIR_INSTALL/bin"
+DIR_INS_L="$DIR_INSTALL/lib"
+DIR_INS_L64="$DIR_INSTALL/lib64"
+DIR_INS_M="$DIR_INSTALL/share/man"
+DIR_INS_P="$DIR_INS_L/pkgconfig"
+DIR_INS_P64="$DIR_INS_L64/pkgconfig"
+DIR_INS_I="$DIR_INSTALL/include/sexpp"
+
+DIR_TESTS=$( cd "$DIR0/.." && pwd)
+
+echo "Running sexp additional tests"
+# shellcheck source=/dev/null
+. "$DIR_TESTS"/shunit2/shunit2
diff --git a/tests/src/baseline-tests.cpp b/tests/src/baseline-tests.cpp
new file mode 100644
index 0000000..bbd59a4
--- /dev/null
+++ b/tests/src/baseline-tests.cpp
@@ -0,0 +1,115 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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 "sexp-tests.h"
+
+using namespace sexp;
+using ::testing::UnitTest;
+
+namespace {
+class BaselineTests : public testing::Test {
+ protected:
+ static const uint32_t base_sample_advanced = 0;
+ static const uint32_t base_sample_base64 = 1;
+ static const uint32_t base_sample_canonical = 2;
+ static const uint32_t n_base_samples = base_sample_canonical + 1;
+
+ static std::string base_samples[n_base_samples];
+
+ BaselineTests()
+ {
+ base_samples[base_sample_advanced] = sexp_samples_folder + "/baseline/sexp-sample-a";
+ base_samples[base_sample_base64] = sexp_samples_folder + "/baseline/sexp-sample-b";
+ base_samples[base_sample_canonical] = sexp_samples_folder + "/baseline/sexp-sample-c";
+ };
+};
+
+const uint32_t BaselineTests::n_base_samples;
+const uint32_t BaselineTests::base_sample_advanced;
+const uint32_t BaselineTests::base_sample_base64;
+const uint32_t BaselineTests::base_sample_canonical;
+std::string BaselineTests::base_samples[n_base_samples];
+
+TEST_F(BaselineTests, Scan2Canonical)
+{
+ for (uint32_t i = 0; i < n_base_samples; i++) {
+ std::ifstream ifs(base_samples[i], std::ifstream::binary);
+ bool r = ifs.fail();
+ EXPECT_FALSE(r);
+
+ if (!ifs.fail()) {
+ sexp_input_stream_t is(&ifs);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.print_canonical(obj);
+
+ std::istringstream iss(oss.str(), std::ios_base::binary);
+ EXPECT_TRUE(compare_binary_files(base_samples[base_sample_canonical], iss));
+ }
+ }
+}
+
+TEST_F(BaselineTests, Scan2Base64)
+{
+ for (uint32_t i = 0; i < n_base_samples; i++) {
+ std::ifstream ifs(base_samples[i], std::ifstream::binary);
+ EXPECT_FALSE(ifs.fail());
+
+ if (!ifs.fail()) {
+ sexp_input_stream_t is(&ifs);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+
+ os.set_max_column(0)->print_base64(obj);
+ oss << std::endl;
+
+ std::istringstream iss(oss.str(), std::ios_base::binary);
+ EXPECT_TRUE(compare_text_files(base_samples[base_sample_base64], iss));
+ }
+ }
+}
+
+TEST_F(BaselineTests, Scan2Advanced)
+{
+ for (uint32_t i = 0; i < n_base_samples; i++) {
+ std::ifstream ifs(base_samples[i], std::ifstream::binary);
+ EXPECT_FALSE(ifs.fail());
+
+ if (!ifs.fail()) {
+ sexp_input_stream_t is(&ifs);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.print_advanced(obj);
+
+ std::istringstream iss(oss.str(), std::ios_base::binary);
+ EXPECT_TRUE(compare_text_files(base_samples[base_sample_advanced], iss));
+ }
+ }
+}
+} // namespace
diff --git a/tests/src/compare-files.cpp b/tests/src/compare-files.cpp
new file mode 100644
index 0000000..49caec5
--- /dev/null
+++ b/tests/src/compare-files.cpp
@@ -0,0 +1,126 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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 <fstream>
+
+#include "sexp-tests.h"
+
+bool compare_binary_files(const std::string &filename1, const std::string &filename2)
+{
+ bool res = false;
+ std::ifstream file1(filename1, std::ifstream::ate | std::ifstream::binary);
+ std::ifstream file2(filename2, std::ifstream::ate | std::ifstream::binary);
+
+ if (file1.tellg() == file2.tellg()) { // otherwise different file size
+ file1.seekg(0);
+ file2.seekg(0);
+
+ std::istreambuf_iterator<char> begin1(file1);
+ std::istreambuf_iterator<char> begin2(file2);
+
+ res = std::equal(begin1, std::istreambuf_iterator<char>(), begin2);
+ }
+
+ return res;
+}
+
+bool compare_binary_files(const std::string &filename1, std::istream &file2)
+{
+ std::ifstream file1(filename1, std::ifstream::binary);
+
+ std::istreambuf_iterator<char> begin1(file1);
+ std::istreambuf_iterator<char> begin2(file2);
+ return std::equal(begin1, std::istreambuf_iterator<char>(), begin2);
+}
+
+std::istream &safe_get_line(std::istream &is, std::string &t)
+{
+ t.clear();
+
+ // The characters in the stream are read one-by-one using a std::streambuf.
+ // That is faster than reading them one-by-one using the std::istream.
+ // Code that uses streambuf this way must be guarded by a sentry object.
+ // The sentry object performs various tasks,
+ // such as thread synchronization and updating the stream state.
+
+ std::istream::sentry se(is, true);
+ std::streambuf * sb = is.rdbuf();
+
+ for (;;) {
+ int c = sb->sbumpc();
+ switch (c) {
+ case '\n':
+ return is;
+ case '\r':
+ if (sb->sgetc() == '\n')
+ sb->sbumpc();
+ return is;
+ case std::streambuf::traits_type::eof():
+ // Also handle the case when the last line has no line ending
+ if (t.empty())
+ is.setstate(std::ios::eofbit);
+ return is;
+ default:
+ t += (char) c;
+ }
+ }
+}
+
+bool compare_text_files(const std::string &filename1, const std::string &filename2)
+{
+ bool res = true;
+ std::ifstream file1(filename1, std::ifstream::binary);
+ std::ifstream file2(filename2, std::ifstream::binary);
+ std::string s1, s2;
+
+ while (res) {
+ if (file1.eof() && file2.eof())
+ break;
+ safe_get_line(file1, s1);
+ safe_get_line(file2, s2);
+ if (s1 != s2)
+ res = false;
+ }
+
+ return res;
+}
+
+bool compare_text_files(const std::string &filename1, std::istream &file2)
+{
+ bool res = true;
+ std::ifstream file1(filename1, std::ifstream::binary);
+ std::string s1, s2;
+ file2.seekg(0);
+
+ while (res) {
+ if (file1.eof() && file2.eof())
+ break;
+ safe_get_line(file1, s1);
+ safe_get_line(file2, s2);
+ if (s1 != s2)
+ res = false;
+ }
+
+ return res;
+}
diff --git a/tests/src/exception-tests.cpp b/tests/src/exception-tests.cpp
new file mode 100644
index 0000000..318b2b8
--- /dev/null
+++ b/tests/src/exception-tests.cpp
@@ -0,0 +1,331 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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 "sexp-tests.h"
+
+using namespace sexp;
+
+namespace {
+class ExceptionTests : public testing::Test {
+ protected:
+ static void do_scan_with_exception(const char *str_in, const char *msg)
+ {
+ try {
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+ is.set_byte_size(8)->get_char()->scan_object();
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), msg);
+ }
+ }
+};
+
+TEST_F(ExceptionTests, UnexpectedEof)
+{
+ do_scan_with_exception("(4:This2:is1:a4:test",
+ "SEXP ERROR: unexpected end of file at position 20");
+}
+
+TEST_F(ExceptionTests, UnexpectedCharacter4bit)
+{
+ do_scan_with_exception(
+ "(4:This2:is1:a4:test #)",
+ "SEXP ERROR: character ')' found in 4-bit coding region at position 22");
+}
+
+TEST_F(ExceptionTests, IllegalCharacter)
+{
+ do_scan_with_exception("(This is a test ?)",
+ "SEXP ERROR: illegal character '?' (0x3f) at position 16");
+}
+
+TEST_F(ExceptionTests, UnexpectedEofAfterQoute)
+{
+ do_scan_with_exception("(\")\n", "SEXP ERROR: unexpected end of file at position 4");
+}
+
+TEST_F(ExceptionTests, IllegalCharacterBase64)
+{
+ do_scan_with_exception("(Test {KDQ6VGhpczI6aXMxOmE0OnRlc3Qq})",
+ "SEXP ERROR: illegal character '}' (0x7d) at position 35");
+}
+
+TEST_F(ExceptionTests, InvalidHex)
+{
+ do_scan_with_exception("(\"\\x1U\")",
+ "SEXP ERROR: Hex character \\x1... too short at position 5");
+}
+
+TEST_F(ExceptionTests, InvalidOctal)
+{
+ do_scan_with_exception("(\"\\12U\")",
+ "SEXP ERROR: Octal character \\12... too short at position 5");
+}
+
+TEST_F(ExceptionTests, TooBigOctal)
+{
+ do_scan_with_exception("(\"\\666U\")",
+ "SEXP ERROR: Octal character \\666... too big at position 5");
+}
+
+TEST_F(ExceptionTests, InvalidEscape)
+{
+ do_scan_with_exception("(\"\\?\")",
+ "SEXP ERROR: Unknown escape sequence \\? at position 3");
+}
+
+TEST_F(ExceptionTests, StringTooShortQuoted)
+{
+ do_scan_with_exception(
+ "(4\"ABC\")",
+ "SEXP ERROR: Declared length was 4, but quoted string ended too early at position 6");
+}
+
+TEST_F(ExceptionTests, StringTooShortBase64)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::warning);
+ do_scan_with_exception("(8|NDpBQkNE|)",
+ "SEXP WARNING: Base64 string has length 6 different than declared "
+ "length 8 at position 12");
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+}
+
+TEST_F(ExceptionTests, StringTooShortHex)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::warning);
+ do_scan_with_exception(
+ "(8#AAABFCAD#)",
+ "SEXP WARNING: Hex string has length 4 different than declared length 8 at position 12");
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+}
+
+TEST_F(ExceptionTests, StringBadLength)
+{
+ do_scan_with_exception("(1A:AAABFCAD)",
+ "SEXP ERROR: illegal character 'A' (0x41) at position 2");
+}
+
+TEST_F(ExceptionTests, DecimalTooLong)
+{
+ do_scan_with_exception("(1234567890:AAABFCAD)",
+ "SEXP ERROR: Decimal number is too long at position 11");
+}
+
+TEST_F(ExceptionTests, Base64CurlyBracket)
+{
+ // "ey..." in base64 encoding translates to "{..."
+ do_scan_with_exception("({ey})", "SEXP ERROR: illegal character '{' (0x7b) at position 3");
+}
+
+TEST_F(ExceptionTests, UnusedBits)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::warning);
+ do_scan_with_exception(
+ "(Test |AABBCCDD11|)",
+ "SEXP WARNING: 6-bit region ended with 4 unused bits left-over at position 17");
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+}
+
+TEST_F(ExceptionTests, NotAListWhenExpected)
+{
+ try {
+ std::istringstream iss(
+ "|d738/4ghP9rFZ0gAIYZ5q9y6iskDJwASi5rEQpEQq8ZyMZeIZzIAR2I5iGE=|");
+ sexp_input_stream_t is(&iss);
+
+ sexp_list_t a_list;
+ a_list.parse(is.set_byte_size(8)->get_char());
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(),
+ "SEXP ERROR: character '|' found where '(' was expected at position 0");
+ }
+}
+
+TEST_F(ExceptionTests, InvalidByteSizeAndMode)
+{
+ try {
+ std::istringstream iss("(3:a\011c)");
+ sexp_input_stream_t is(&iss);
+ std::shared_ptr<sexp_object_t> obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.change_output_byte_size(4, sexp_output_stream_t::advanced)->print_advanced(obj);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(
+ e.what(),
+ "SEXP ERROR: Can't print in advanced mode with restricted output character set");
+ }
+}
+
+TEST_F(ExceptionTests, SexpWarning)
+{
+ testing::internal::CaptureStdout();
+ sexp::sexp_exception_t::set_interactive(true);
+ sexp_error(sexp_exception_t::warning, "Test warning", 0, 0, 200);
+ std::string output = testing::internal::GetCapturedStdout();
+ EXPECT_EQ(output, "\n*** SEXP WARNING: Test warning at position 200 ***\n");
+ sexp::sexp_exception_t::set_interactive(false);
+}
+
+static void do_parse_list_from_string(const char *str)
+{
+ std::istringstream iss(str);
+ sexp_input_stream_t is(&iss);
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+}
+
+static void do_parse_list_from_string_with_limit(const char *str, size_t m_depth)
+{
+ std::istringstream iss(str);
+ sexp_input_stream_t is(&iss, m_depth);
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+}
+
+TEST_F(ExceptionTests, MaxDepthParse)
+{
+ const char *depth_1 = "(sexp_list_1)";
+ const char *depth_4 = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4))))";
+ const char *depth_4e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 ())))";
+ const char *depth_5 =
+ "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 (sexp_list_5)))))";
+ const char *depth_5e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 ()))))";
+
+ do_parse_list_from_string(depth_1);
+ do_parse_list_from_string(depth_4);
+ do_parse_list_from_string(depth_4e);
+ do_parse_list_from_string(depth_5);
+ do_parse_list_from_string(depth_5e);
+
+ do_parse_list_from_string_with_limit(depth_1, 4);
+ do_parse_list_from_string_with_limit(depth_4, 4);
+ do_parse_list_from_string_with_limit(depth_4e, 4);
+
+ try {
+ do_parse_list_from_string_with_limit(depth_5, 4);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(
+ e.what(),
+ "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded at position 53");
+ }
+
+ try {
+ do_parse_list_from_string_with_limit(depth_5e, 4);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(
+ e.what(),
+ "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded at position 53");
+ }
+}
+
+static void do_print_list_from_string(const char *str, bool advanced, size_t m_depth = 0)
+{
+ std::istringstream iss(str);
+ sexp_input_stream_t is(&iss);
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+
+ std::ostringstream oss(str);
+ sexp_output_stream_t os(&oss, m_depth);
+ if (advanced)
+ lst.print_advanced(&os);
+ else
+ lst.print_canonical(&os);
+}
+
+TEST_F(ExceptionTests, MaxDepthPrintAdvanced)
+{
+ const char *depth_1 = "(sexp_list_1)";
+ const char *depth_4 = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4))))";
+ const char *depth_4e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 ())))";
+ const char *depth_5 =
+ "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 (sexp_list_5)))))";
+ const char *depth_5e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 ()))))";
+
+ do_print_list_from_string(depth_1, true);
+ do_print_list_from_string(depth_4, true);
+ do_print_list_from_string(depth_4e, true);
+ do_print_list_from_string(depth_5, true);
+ do_print_list_from_string(depth_5e, true);
+
+ do_print_list_from_string(depth_1, true, 4);
+ do_print_list_from_string(depth_4, true, 4);
+ do_print_list_from_string(depth_4e, true, 4);
+
+ try {
+ do_print_list_from_string(depth_5, true, 4);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded");
+ }
+
+ try {
+ do_print_list_from_string(depth_5e, true, 4);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded");
+ }
+}
+
+TEST_F(ExceptionTests, MaxDepthPrintCanonical)
+{
+ const char *depth_1 = "(sexp_list_1)";
+ const char *depth_4 = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4))))";
+ const char *depth_4e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 ())))";
+ const char *depth_5 =
+ "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 (sexp_list_5)))))";
+ const char *depth_5e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 ()))))";
+
+ do_print_list_from_string(depth_1, false);
+ do_print_list_from_string(depth_4, false);
+ do_print_list_from_string(depth_4e, false);
+ do_print_list_from_string(depth_5, false);
+ do_print_list_from_string(depth_5e, false);
+
+ do_print_list_from_string(depth_1, false, 4);
+ do_print_list_from_string(depth_4, false, 4);
+ do_print_list_from_string(depth_4e, false, 4);
+
+ try {
+ do_print_list_from_string(depth_5, false, 4);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded");
+ }
+
+ try {
+ do_print_list_from_string(depth_5e, false, 4);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded");
+ }
+}
+
+} // namespace
diff --git a/tests/src/g10-compat-tests.cpp b/tests/src/g10-compat-tests.cpp
new file mode 100644
index 0000000..62f1180
--- /dev/null
+++ b/tests/src/g10-compat-tests.cpp
@@ -0,0 +1,51 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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 "sexp-tests.h"
+
+using namespace sexp;
+
+namespace {
+class G10CompatTests : public testing::Test {
+};
+
+TEST_F(G10CompatTests, Canonical)
+{
+ std::string keyfile(sexp_samples_folder + "/compat/g10/canonical.key");
+ std::ifstream ifs(keyfile, std::ifstream::binary);
+ EXPECT_FALSE(ifs.fail());
+
+ if (!ifs.fail()) {
+ sexp_input_stream_t is(&ifs);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.print_canonical(obj);
+
+ std::istringstream iss(oss.str(), std::ios_base::binary);
+ EXPECT_TRUE(compare_binary_files(keyfile, iss));
+ }
+}
+
+} // namespace
diff --git a/tests/src/g23-compat-tests.cpp b/tests/src/g23-compat-tests.cpp
new file mode 100644
index 0000000..3a2e756
--- /dev/null
+++ b/tests/src/g23-compat-tests.cpp
@@ -0,0 +1,139 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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 "sexp-tests.h"
+#include "sexpp/ext-key-format.h"
+
+using namespace sexp;
+using namespace ext_key_format;
+
+using ::testing::UnitTest;
+
+namespace {
+class G23CompatTests : public testing::Test {
+ protected:
+ static void scan_and_check_correct(const char *fn)
+ {
+ std::ifstream ifs(sexp_samples_folder + "/compat/g23/" + fn, std::ifstream::binary);
+ bool r = ifs.fail();
+ EXPECT_FALSE(r);
+
+ if (!ifs.fail()) {
+ ext_key_input_stream_t is(&ifs);
+ extended_private_key_t extended_key;
+ is.scan(extended_key);
+ EXPECT_EQ(extended_key.fields.size(), 2);
+ EXPECT_EQ(extended_key.fields.count("Created"), 1);
+ EXPECT_EQ(extended_key.fields.count("creaTed"), 1);
+ EXPECT_EQ(extended_key.fields.count("something"), 0);
+
+ auto search = extended_key.fields.find("Created");
+ if (search != extended_key.fields.end()) {
+ EXPECT_EQ(search->second, "20221130T160847");
+ }
+ }
+ }
+};
+
+TEST_F(G23CompatTests, G10Test)
+{
+ std::ifstream ifs(sexp_samples_folder + "/compat/g10/canonical.key",
+ std::ifstream::binary);
+ bool r = ifs.fail();
+ EXPECT_FALSE(r);
+
+ if (!ifs.fail()) {
+ ext_key_input_stream_t is(&ifs);
+ extended_private_key_t extended_key;
+ is.scan(extended_key);
+ EXPECT_EQ(extended_key.fields.size(), 0);
+ }
+}
+
+// Correct extended key format
+TEST_F(G23CompatTests, G23Correct)
+{
+ scan_and_check_correct("correct.key");
+}
+
+// Correct extended key format, no terminating end of line
+TEST_F(G23CompatTests, G23CorrectNoEol)
+{
+ scan_and_check_correct("correct_no_eol.key");
+}
+
+// Correct extended key format, with a comment
+TEST_F(G23CompatTests, G23CorrectWithComment)
+{
+ scan_and_check_correct("correct_with_comment.key");
+}
+
+// Correct extended key format, with an empty line (which is comment a well)
+TEST_F(G23CompatTests, G23CorrectWithTwoEmptyLines)
+{
+ scan_and_check_correct("correct_with_two_empty_lines.key");
+}
+
+// Correct extended key format, with two empty linea
+TEST_F(G23CompatTests, G23CorrectWithEmptyLine)
+{
+ scan_and_check_correct("correct_with_empty_line.key");
+}
+
+// Correct extended key format, witg windows line endings
+TEST_F(G23CompatTests, G23CorrectWithWindowsEol)
+{
+ scan_and_check_correct("correct_with_windows_eol.key");
+}
+
+// Correct extended key format, with a comment at the end of file
+TEST_F(G23CompatTests, G23CorrectWithCommentAtEof)
+{
+ scan_and_check_correct("correct_with_comment_at_eof.key");
+}
+
+// Correct extended key format, with multiple fields of the same name
+TEST_F(G23CompatTests, G23CorrectWithMultFields)
+{
+ std::ifstream ifs(sexp_samples_folder + "/compat/g23/correct_mult_fields.key",
+ std::ifstream::binary);
+ bool r = ifs.fail();
+ EXPECT_FALSE(r);
+
+ if (!ifs.fail()) {
+ ext_key_input_stream_t is(&ifs);
+ extended_private_key_t extended_key;
+ extended_key.parse(is);
+ EXPECT_EQ(extended_key.fields.size(), 4);
+ EXPECT_EQ(extended_key.fields.count("Created"), 1);
+ EXPECT_EQ(extended_key.fields.count("Description"), 3);
+ EXPECT_EQ(extended_key.fields.count("something"), 0);
+
+ auto search = extended_key.fields.find("Description");
+ if (search != extended_key.fields.end()) {
+ EXPECT_EQ(search->second, "RSA/RSA");
+ }
+ }
+}
+
+} // namespace
diff --git a/tests/src/g23-exception-tests.cpp b/tests/src/g23-exception-tests.cpp
new file mode 100644
index 0000000..4f4413d
--- /dev/null
+++ b/tests/src/g23-exception-tests.cpp
@@ -0,0 +1,108 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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 "sexp-tests.h"
+#include "sexpp/ext-key-format.h"
+
+using namespace sexp;
+using namespace ext_key_format;
+
+using ::testing::UnitTest;
+
+namespace {
+class G23ExceptionTests : public testing::Test {
+ protected:
+ static void do_scan_ex(const char *fn, const char *msg)
+ {
+ std::ifstream ifs(sexp_samples_folder + "/compat/g23/" + fn, std::ifstream::binary);
+ EXPECT_FALSE(ifs.fail());
+ if (!ifs.fail()) {
+ try {
+ ext_key_input_stream_t is(&ifs);
+ extended_private_key_t extended_key;
+ is.scan(extended_key);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), msg);
+ }
+ }
+ }
+};
+
+// Malformed extended key format, line break inside name
+TEST_F(G23ExceptionTests, G23MalformedNameBreak)
+{
+ do_scan_ex("malformed_name_break.key",
+ "EXTENDED KEY FORMAT ERROR: unexpected end of line at position 5");
+}
+
+// Malformed extended key format, eof break inside name
+TEST_F(G23ExceptionTests, G23MalformedNameEof)
+{
+ do_scan_ex("malformed_name_eof.key",
+ "EXTENDED KEY FORMAT ERROR: unexpected end of file at position 2800");
+}
+
+// Malformed extended key format, invalid character name
+TEST_F(G23ExceptionTests, G23MalformedInvalidNameChar)
+{
+ do_scan_ex(
+ "malformed_invalid_name_char.key",
+ "EXTENDED KEY FORMAT ERROR: unexpected character '@' (0x40) found in a name field "
+ "at position 28");
+}
+
+// Malformed extended key format, invalid character name
+TEST_F(G23ExceptionTests, G23MalformedInvalidNameFirstChar)
+{
+ do_scan_ex(
+ "malformed_invalid_name_first_char.key",
+ "EXTENDED KEY FORMAT ERROR: unexpected character '1' (0x31) found starting a name field "
+ "at position 21");
+}
+
+// Malformed extended key format, no key field
+TEST_F(G23ExceptionTests, G23MalformedNoKey)
+{
+ do_scan_ex("malformed_no_key.key",
+ "EXTENDED KEY FORMAT ERROR: missing mandatory 'key' field at position 2819");
+}
+
+// Malformed extended key format, two key fields
+TEST_F(G23ExceptionTests, G23MalformedTwoKeys)
+{
+ do_scan_ex("malformed_two_keys.key",
+ "EXTENDED KEY FORMAT ERROR: 'key' field must occur only once at position 2822");
+}
+
+TEST_F(G23ExceptionTests, G23Warning)
+{
+ testing::internal::CaptureStdout();
+ sexp::sexp_exception_t::set_interactive(true);
+ ext_key_error(sexp_exception_t::warning, "Test warning", 0, 0, 200);
+ std::string output = testing::internal::GetCapturedStdout();
+ EXPECT_EQ(output, "\n*** EXTENDED KEY FORMAT WARNING: Test warning at position 200 ***\n");
+ sexp::sexp_exception_t::set_interactive(false);
+}
+
+} // namespace
diff --git a/tests/src/primitives-tests.cpp b/tests/src/primitives-tests.cpp
new file mode 100644
index 0000000..0689d27
--- /dev/null
+++ b/tests/src/primitives-tests.cpp
@@ -0,0 +1,394 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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 "sexp-tests.h"
+
+using namespace sexp;
+
+namespace {
+class PrimitivesTests : public testing::Test {
+ protected:
+ static void do_test_advanced(const char *str_in, const char *str_out = nullptr)
+ {
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.print_advanced(obj);
+ const char *sample = str_out == nullptr ? str_in : str_out;
+ EXPECT_EQ(oss.str(), sample);
+ }
+
+ static void do_test_canonical(const char *str_in, const char *str_out = nullptr)
+ {
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.print_canonical(obj);
+ const char *sample = str_out == nullptr ? str_in : str_out;
+ EXPECT_EQ(oss.str(), sample);
+ }
+};
+
+TEST_F(PrimitivesTests, EmptyList)
+{
+ do_test_canonical("( )", "()");
+ do_test_advanced("( )", "()");
+}
+
+TEST_F(PrimitivesTests, EmptyString)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+ do_test_canonical("(\"\")", "(0:)");
+ do_test_advanced("(\"\")", "(\"\")");
+}
+
+TEST_F(PrimitivesTests, String)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+ do_test_canonical("(ab)", "(2:ab)");
+ do_test_advanced("(ab)", "(ab)");
+}
+
+TEST_F(PrimitivesTests, QuotedStringWithOctal)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+ do_test_canonical("\"ab\\015\"", "3:ab\r");
+ do_test_advanced("\"ab\\015\"", "#61620D#");
+}
+
+TEST_F(PrimitivesTests, QuotedStringWithEscape)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+ do_test_canonical("\"ab\\tc\"", "4:ab\tc");
+ do_test_advanced("4:ab\tc", "#61620963#");
+}
+
+TEST_F(PrimitivesTests, HexString)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+ do_test_canonical("#616263#", "3:abc");
+ do_test_advanced("#616263#", "abc");
+}
+
+TEST_F(PrimitivesTests, ListList)
+{
+ do_test_canonical("(string-level-1 (string-level-2) )",
+ "(14:string-level-1(14:string-level-2))");
+ do_test_advanced("(string-level-1 (string-level-2) )",
+ "(string-level-1 (string-level-2))");
+}
+
+TEST_F(PrimitivesTests, Base64Ofoctet_t)
+{
+ do_test_canonical("|YWJj|", "3:abc");
+ do_test_advanced("|YWJj|", "abc");
+}
+
+TEST_F(PrimitivesTests, Base64OfVerbatium)
+{
+ do_test_canonical("{MzphYmM=}", "3:abc");
+ do_test_advanced("{MzphYmM=}", "abc");
+}
+
+TEST_F(PrimitivesTests, MultilineLinux)
+{
+ do_test_canonical("\"abcd\\\nef\"", "6:abcdef");
+ do_test_advanced("\"abcd\\\nef\"", "abcdef");
+}
+
+TEST_F(PrimitivesTests, MultilineMac)
+{
+ do_test_canonical("\"abcd\\\ref\"", "6:abcdef");
+ do_test_advanced("\"abcd\\\ref\"", "abcdef");
+}
+
+TEST_F(PrimitivesTests, MultilineWin)
+{
+ do_test_canonical("\"abcd\\\r\nef\"", "6:abcdef");
+ do_test_advanced("\"abcd\\\r\nef\"", "abcdef");
+}
+
+TEST_F(PrimitivesTests, MultilineBsd)
+{
+ do_test_canonical("\"abcd\\\n\ref\"", "6:abcdef");
+ do_test_advanced("\"abcd\\\n\ref\"", "abcdef");
+}
+
+TEST_F(PrimitivesTests, Wrap)
+{
+ const char *reallyLong = "(a (b (c ddddddddddddddd"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "ddddddd)))";
+ const char *stillLong = "(1:a(1:b(1:c169:ddddddddd"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "ddddddddddddd)))";
+ const char *broken = "(a\n"
+ " (b\n"
+ " (c\n"
+ " "
+ "\"ddddddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "dddddddddddddddd\\\n"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "dddddddddddddddddd\\\n"
+ "ddddddddddddddddddddddddddd\")))";
+ do_test_canonical(reallyLong, stillLong);
+ do_test_advanced(reallyLong, broken);
+}
+
+TEST_F(PrimitivesTests, Escapes)
+{
+ do_test_canonical("(\"\\b\\t\\v\\n\\f\\r\\\"\\'\\\\\")", "(9:\b\t\v\n\f\r\"'\\)");
+ do_test_advanced("(\"\\b\\t\\v\\n\\f\\r\\\"\\'\\\\\")", "(|CAkLCgwNIidc|)");
+
+ do_test_canonical("(\"\\040\\041\\042\\043\\044\")", "(5: !\"#$)");
+ do_test_advanced("(\"\\065\\061\\062\\063\\064\")", "(\"51234\")");
+
+ do_test_canonical("(\"\\x40\\x41\\x42\\x43\\x44\")", "(5:@ABCD)");
+ do_test_advanced("(\"\\x65\\x61\\x62\\x63\\x64\")", "(eabcd)");
+}
+
+TEST_F(PrimitivesTests, at4rnp)
+{
+ const char *str_in = "(rnp_block (rnp_list1 rnp_list2))";
+
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+
+ EXPECT_EQ(lst.sexp_list_at(0), nullptr);
+ EXPECT_NE(lst.sexp_list_at(1), nullptr);
+
+ EXPECT_NE(lst.sexp_string_at(0), nullptr);
+ EXPECT_EQ(lst.sexp_string_at(1), nullptr);
+
+ const sexp_object_t *obj = lst.sexp_list_at(1);
+
+ if (obj != nullptr) {
+ EXPECT_EQ(obj->sexp_list_at(0), nullptr);
+ EXPECT_EQ(obj->sexp_list_at(1), nullptr);
+ }
+
+ const sexp_string_t *sstr = lst.sexp_string_at(0);
+ EXPECT_STREQ(reinterpret_cast<const char *>(sstr->get_string().c_str()), "rnp_block");
+}
+
+TEST_F(PrimitivesTests, eq4rnp)
+{
+ const char *str_in = "(rnp_block (rnp_list1 rnp_list2))";
+
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+
+ EXPECT_TRUE(*lst.at(0) == "rnp_block");
+ EXPECT_FALSE(*lst.at(0) == "not_rnp_block");
+ EXPECT_FALSE(*lst.at(1) == "rnp_block");
+ EXPECT_FALSE(*lst.at(1) == "not_rnp_block");
+
+ EXPECT_TRUE(*lst.sexp_string_at(0) == "rnp_block");
+ EXPECT_FALSE(*lst.sexp_string_at(0) == "not_rnp_block");
+ EXPECT_TRUE(*lst.sexp_simple_string_at(0) == "rnp_block");
+ EXPECT_FALSE(*lst.sexp_simple_string_at(0) == "not_rnp_block");
+
+ EXPECT_TRUE(*lst.sexp_list_at(1)->at(0) == "rnp_list1");
+ EXPECT_TRUE(*lst.sexp_list_at(1)->sexp_string_at(1) == "rnp_list2");
+
+ EXPECT_TRUE(lst.sexp_string_at(0) == std::string("rnp_block"));
+ EXPECT_FALSE(lst.sexp_string_at(0) == std::string("not_rnp_block"));
+ EXPECT_TRUE(lst.sexp_simple_string_at(0) == std::string("rnp_block"));
+ EXPECT_FALSE(lst.sexp_simple_string_at(0) == std::string("not_rnp_block"));
+}
+
+TEST_F(PrimitivesTests, ne4rnp)
+{
+ const char *str_in = "(rnp_block (rnp_list1 rnp_list2))";
+
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+
+ EXPECT_FALSE(*lst.at(0) != "rnp_block");
+ EXPECT_TRUE(*lst.at(0) != "not_rnp_block");
+ EXPECT_TRUE(*lst.at(1) != "rnp_block");
+ EXPECT_TRUE(*lst.at(1) != "not_rnp_block");
+
+ EXPECT_FALSE(*lst.sexp_string_at(0) != "rnp_block");
+ EXPECT_TRUE(*lst.sexp_string_at(0) != "not_rnp_block");
+ EXPECT_FALSE(*lst.sexp_simple_string_at(0) != "rnp_block");
+ EXPECT_TRUE(*lst.sexp_simple_string_at(0) != "not_rnp_block");
+
+ EXPECT_FALSE(*lst.sexp_list_at(1)->at(0) != "rnp_list1");
+ EXPECT_FALSE(*lst.sexp_list_at(1)->sexp_string_at(1) != "rnp_list2");
+
+ EXPECT_FALSE(lst.sexp_string_at(0) != std::string("rnp_block"));
+ EXPECT_TRUE(lst.sexp_string_at(0) != std::string("not_rnp_block"));
+ EXPECT_FALSE(lst.sexp_simple_string_at(0) != std::string("rnp_block"));
+ EXPECT_TRUE(lst.sexp_simple_string_at(0) != std::string("not_rnp_block"));
+}
+
+TEST_F(PrimitivesTests, u4rnp)
+{
+ const char *str_in1 = "(unsigned_value \"12345\")";
+ const char *str_in2 = "(14:unsigned_value5:54321)";
+
+ std::istringstream iss1(str_in1);
+ std::istringstream iss2(str_in2);
+
+ sexp_input_stream_t is(&iss1);
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+ EXPECT_EQ(lst.sexp_string_at(1)->as_unsigned(), 12345);
+
+ lst.clear();
+ lst.parse(is.set_input(&iss2)->set_byte_size(8)->get_char());
+ EXPECT_EQ(lst.sexp_string_at(1)->as_unsigned(), 54321);
+}
+
+TEST_F(PrimitivesTests, proInheritance)
+{
+ sexp_list_t lst;
+ EXPECT_FALSE(lst.is_sexp_string());
+ EXPECT_TRUE(lst.is_sexp_list());
+ EXPECT_EQ(lst.sexp_string_view(), nullptr);
+ EXPECT_EQ(lst.sexp_list_view(), &lst);
+ EXPECT_EQ(lst.as_unsigned(), std::numeric_limits<uint32_t>::max());
+ EXPECT_EQ(lst.sexp_list_at(0), nullptr);
+ EXPECT_EQ(lst.sexp_string_at(0), nullptr);
+ EXPECT_EQ(lst.sexp_simple_string_at(0), nullptr);
+
+ sexp_string_t str;
+ EXPECT_FALSE(str.is_sexp_list());
+ EXPECT_TRUE(str.is_sexp_string());
+ EXPECT_EQ(str.sexp_string_view(), &str);
+ EXPECT_EQ(str.sexp_list_view(), nullptr);
+ EXPECT_EQ(str.sexp_list_at(0), nullptr);
+ EXPECT_EQ(str.sexp_string_at(0), nullptr);
+ EXPECT_EQ(str.sexp_simple_string_at(0), nullptr);
+}
+
+TEST_F(PrimitivesTests, DisplayHint)
+{
+ do_test_canonical("(URL [URI]www.ribose.com)", "(3:URL[3:URI]14:www.ribose.com)");
+ do_test_advanced("(3:URL[3:URI]14:www.ribose.com)", "(URL [URI]www.ribose.com)");
+}
+
+TEST_F(PrimitivesTests, scanToEof)
+{
+ const char *str_in = "ABCD";
+
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+
+ auto object = is.scan_to_eof();
+ EXPECT_TRUE(object->is_sexp_string());
+
+ is.set_byte_size(4);
+ EXPECT_EQ(is.get_byte_size(), 4);
+
+ EXPECT_EQ(is.get_char(), &is);
+ EXPECT_EQ(is.get_byte_size(), 8);
+}
+
+TEST_F(PrimitivesTests, ChangeOutputByteSizeTest)
+{
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+
+ EXPECT_EQ(os.change_output_byte_size(8, sexp_output_stream_t::advanced), &os);
+
+ try {
+ os.change_output_byte_size(7, sexp_output_stream_t::advanced);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), "SEXP ERROR: Illegal output base 7");
+ }
+
+ EXPECT_EQ(os.change_output_byte_size(4, sexp_output_stream_t::advanced), &os);
+
+ try {
+ os.change_output_byte_size(6, sexp_output_stream_t::advanced);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), "SEXP ERROR: Illegal change of output byte size from 4 to 6");
+ }
+}
+
+TEST_F(PrimitivesTests, FlushTest)
+{
+ std::ostringstream oss1(std::ios_base::binary);
+ std::ostringstream oss2(std::ios_base::binary);
+ sexp_output_stream_t os(&oss1);
+
+ EXPECT_EQ(
+ os.change_output_byte_size(6, sexp_output_stream_t::advanced)->print_decimal(1)->flush(),
+ &os);
+ EXPECT_EQ(oss1.str(), "MQ==");
+ os.set_output(&oss2)
+ ->change_output_byte_size(6, sexp_output_stream_t::advanced)
+ ->set_max_column(2)
+ ->print_decimal(2)
+ ->flush();
+ EXPECT_EQ(oss2.str(), "Mg\n==");
+}
+
+TEST_F(PrimitivesTests, ListWrapTest)
+{
+ std::istringstream iss("(abc)");
+ sexp_input_stream_t is(&iss);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.set_max_column(5)->print_advanced(obj);
+ EXPECT_EQ(oss.str(), "(abc\n )");
+}
+
+TEST_F(PrimitivesTests, EnsureHexTest)
+{
+ std::istringstream iss("(3:a\011c)");
+ sexp_input_stream_t is(&iss);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.print_advanced(obj);
+ EXPECT_EQ(oss.str(), "(#610963#)");
+}
+
+} // namespace
diff --git a/version.txt b/version.txt
new file mode 100644
index 0000000..1e9b46b
--- /dev/null
+++ b/version.txt
@@ -0,0 +1 @@
+0.8.7