This is an automated email from the ASF dual-hosted git repository.

HTHou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 280c0189aa5 Support standard TLS and mTLS in the C++ client (#18601)
280c0189aa5 is described below

commit 280c0189aa505e0d78967aa4c2b0da6ef4566a30
Author: Hongzhi Gao <[email protected]>
AuthorDate: Tue Sep 15 09:43:32 2026 +0800

    Support standard TLS and mTLS in the C++ client (#18601)
---
 .github/workflows/client-cpp-package.yml           |  38 ++--
 .github/workflows/multi-language-client.yml        |   7 +-
 iotdb-client/client-cpp/CMakeLists.txt             |  14 ++
 iotdb-client/client-cpp/README.md                  |  53 +++---
 iotdb-client/client-cpp/README_zh.md               |  12 +-
 iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake   | 201 ++++++++++++++++-----
 .../client-cpp/cmake/InstallOpenSSLRuntime.cmake   |   4 +-
 iotdb-client/client-cpp/pom.xml                    |  62 ++-----
 .../package-metadata/third_party/DEPENDENCIES.md   |   2 +-
 .../src/include/AbstractSessionBuilder.h           |  15 +-
 iotdb-client/client-cpp/src/include/Session.h      |   3 +
 .../client-cpp/src/include/SessionBuilder.h        |  14 +-
 iotdb-client/client-cpp/src/include/SessionC.h     |  15 ++
 iotdb-client/client-cpp/src/include/SessionPool.h  |  21 ++-
 .../src/include/{TableSession.h => SslConfig.h}    |  32 +---
 iotdb-client/client-cpp/src/include/TableSession.h |   3 +-
 .../client-cpp/src/include/TableSessionBuilder.h   |  14 +-
 iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp  |  33 ++--
 iotdb-client/client-cpp/src/rpc/NodesSupplier.h    |  13 +-
 .../TableSession.h => rpc/RpcSslUtils.cpp}         |  36 ++--
 .../{include/TableSession.h => rpc/RpcSslUtils.h}  |  33 ++--
 .../client-cpp/src/rpc/SessionConnection.cpp       |  13 +-
 .../client-cpp/src/rpc/SessionConnection.h         |   3 +-
 iotdb-client/client-cpp/src/rpc/SessionImpl.h      |   3 +-
 .../client-cpp/src/rpc/ThriftConnection.cpp        |  11 +-
 iotdb-client/client-cpp/src/rpc/ThriftConnection.h |   6 +-
 iotdb-client/client-cpp/src/session/Session.cpp    |  27 +--
 iotdb-client/client-cpp/src/session/SessionC.cpp   |  69 +++++++
 .../client-cpp/src/session/SessionPool.cpp         |  17 +-
 .../TableSession.h => session/SslConfig.cpp}       |  37 ++--
 .../client-cpp/src/session/TableSession.cpp        |   5 +-
 iotdb-client/client-cpp/test/CMakeLists.txt        |  36 +++-
 .../client-cpp/test/cpp/RpcSslIotdbE2eTest.cpp     | 194 ++++++++++++++++++++
 iotdb-client/client-cpp/test/cpp/sessionIT.cpp     |   3 +-
 .../client-cpp/test/cpp/sessionUtilsTest.cpp       |  18 +-
 iotdb-client/client-cpp/test/fixtures/tls/ca.crt   |  19 ++
 .../client-cpp/test/fixtures/tls/client.crt        |  17 ++
 .../client-cpp/test/fixtures/tls/client.key        |  28 +++
 .../client-cpp/test/fixtures/tls/tls-server.p12    | Bin 0 -> 2608 bytes
 .../TableSession.h => test/main_rpc_ssl.cpp}       |  29 +--
 .../test/scripts/configure_iotdb_ssl_it.py         | 117 ++++++++++++
 .../client-cpp/test/scripts/run_cpp_it_phases.py   | 130 +++++++++++++
 iotdb-client/client-cpp/third-party/README.md      |   6 +-
 pom.xml                                            |   4 +
 scripts/sbin/windows/start-confignode.bat          |   4 +-
 scripts/sbin/windows/start-datanode.bat            |   4 +-
 scripts/sbin/windows/start-standalone.bat          |   7 +-
 47 files changed, 1089 insertions(+), 343 deletions(-)

diff --git a/.github/workflows/client-cpp-package.yml 
b/.github/workflows/client-cpp-package.yml
index 38eac3fbcbc..70f2bffdddd 100644
--- a/.github/workflows/client-cpp-package.yml
+++ b/.github/workflows/client-cpp-package.yml
@@ -309,14 +309,10 @@ jobs:
         shell: bash
         run: |
           set -euxo pipefail
-          # Pin openssl@3 (Apache-2.0): the default 'openssl' formula will 
move to
-          # OpenSSL 4.0, which drops the legacy TLS-method APIs Thrift still 
uses.
-          brew install boost openssl@3 llvm@17 bison
+          brew install boost llvm@17 bison
           ln -sf "$(brew --prefix llvm@17)/bin/clang-format" "$(brew 
--prefix)/bin/clang-format"
           echo "$(brew --prefix bison)/bin" >> "$GITHUB_PATH"
           echo "$(brew --prefix llvm@17)/bin" >> "$GITHUB_PATH"
-          # Homebrew OpenSSL is keg-only, so point find_package(OpenSSL) at it.
-          echo "OPENSSL_ROOT_DIR=$(brew --prefix openssl@3)" >> "$GITHUB_ENV"
           clang-format --version
           bison --version
       - name: Cache Maven packages
@@ -373,6 +369,22 @@ jobs:
           unzip -q -o "${PKG_TARBALL}" -d "${PKG_UNPACK}"
           PKG_ROOT=$(find "${PKG_UNPACK}" -mindepth 1 -maxdepth 1 -type d 
-name 'iotdb-session-cpp-*' -print -quit)
           test -n "${PKG_ROOT}"
+          
OPENSSL_BUILD_INSTALL="${GITHUB_WORKSPACE}/iotdb-client/client-cpp/target/build/_deps/openssl/install"
+          test -d "${OPENSSL_BUILD_INSTALL}"
+          rm -rf "${RUNNER_TEMP}/openssl-build-install-hidden"
+          mv "${OPENSSL_BUILD_INSTALL}" 
"${RUNNER_TEMP}/openssl-build-install-hidden"
+          SSL_DYLIB=$(find "${PKG_ROOT}/lib" -type f -name 'libssl*.dylib' 
-print -quit)
+          CRYPTO_DYLIB=$(find "${PKG_ROOT}/lib" -type f -name 
'libcrypto*.dylib' -print -quit)
+          test -n "${SSL_DYLIB}"
+          test -n "${CRYPTO_DYLIB}"
+          otool -D "${SSL_DYLIB}" | grep -F "@rpath/$(basename "${SSL_DYLIB}")"
+          otool -D "${CRYPTO_DYLIB}" | grep -F "@rpath/$(basename 
"${CRYPTO_DYLIB}")"
+          otool -L "${SSL_DYLIB}" | grep -F "@rpath/$(basename 
"${CRYPTO_DYLIB}")"
+          if otool -L "${PKG_ROOT}/lib/libiotdb_session.dylib" "${SSL_DYLIB}" 
"${CRYPTO_DYLIB}" \
+              | grep -F "${GITHUB_WORKSPACE}"; then
+            echo "Packaged dylibs still reference the build workspace"
+            exit 1
+          fi
           EXAMPLE_BUILD="${RUNNER_TEMP}/client-cpp-example-smoke"
           cmake -S "${PKG_ROOT}/examples" -B "${EXAMPLE_BUILD}" \
             -DCMAKE_BUILD_TYPE=Release \
@@ -419,7 +431,8 @@ jobs:
       - name: Install C++ dependencies (Windows)
         shell: pwsh
         run: |
-          choco install winflexbison3 -y --no-progress
+          choco install winflexbison3 strawberryperl -y --no-progress
+          echo 'C:\Strawberry\perl\bin' >> $env:GITHUB_PATH
           $boostArgs = @('install', '${{ matrix.boost_choco }}', '-y', 
'--no-progress')
           if ('${{ matrix.boost_choco_version }}' -ne '') {
             $boostArgs += @("--version=${{ matrix.boost_choco_version }}")
@@ -433,18 +446,6 @@ jobs:
             throw "Boost not found under C:\local after installing ${{ 
matrix.boost_choco }}"
           }
           echo $boostDir.FullName >> $env:GITHUB_PATH
-          # Use a pinned OpenSSL 3.x (Apache-2.0). 'choco install openssl' now
-          # installs OpenSSL 4.0, which removed the legacy TLS-method APIs that
-          # Apache Thrift's TSSLSocket still calls. The FireDaemon zip is a 
clean
-          # prebuilt OpenSSL 3.5.x that keeps them.
-          $sslZip = "$env:RUNNER_TEMP\openssl-3.5.3.zip"
-          $sslDir = "$env:RUNNER_TEMP\openssl-3"
-          curl.exe -L --fail --retry 3 -o $sslZip 
'https://download.firedaemon.com/FireDaemon-OpenSSL/openssl-3.5.3.zip'
-          Expand-Archive -Path $sslZip -DestinationPath $sslDir -Force
-          $sslPath = (Get-ChildItem $sslDir -Recurse -Directory -Filter 'x64' 
| Select-Object -First 1).FullName
-          if (-not $sslPath) { throw "OpenSSL x64 dir not found under $sslDir" 
}
-          echo "$sslPath\bin" >> $env:GITHUB_PATH
-          echo "OPENSSL_ROOT_DIR=$sslPath" >> $env:GITHUB_ENV
       - name: Cache Maven packages
         uses: actions/cache@v5
         with:
@@ -536,6 +537,7 @@ jobs:
           fi
           cmake "${CMAKE_ARGS[@]}"
           cmake --build "${EXAMPLE_BUILD}" --config Release
+          export PATH="${PKG_ROOT}/lib:${PATH}"
           ./mvnw -pl distribution -am -DskipTests -Dspotless.skip=true package
           if [ "${RUNNER_OS}" = "Windows" ]; then
             WORKSPACE_BASE="$(cygpath -u "${GITHUB_WORKSPACE}")"
diff --git a/.github/workflows/multi-language-client.yml 
b/.github/workflows/multi-language-client.yml
index 5437a654985..61a947c3e08 100644
--- a/.github/workflows/multi-language-client.yml
+++ b/.github/workflows/multi-language-client.yml
@@ -198,9 +198,12 @@ jobs:
         # (was causing problems on windows, but could cause problem on linux, 
when updating the thrift module)
         run: |
           if [[ "${{ matrix.os }}" == "windows-2025-vs2026" ]]; then
-            ./mvnw clean verify -P with-cpp -pl iotdb-client/client-cpp -am 
-Dcmake.generator="Visual Studio 18 2026"
-          else
+            ./mvnw clean verify -P with-cpp -pl iotdb-client/client-cpp -am 
-Diotdb.openssl.from.source=OFF -Dcmake.generator="Visual Studio 18 2026"
+          elif [[ "${{ runner.os }}" == "macOS" ]]; then
+            # Exercise the default source build, including bundled dylib 
relocation, on pull requests.
             ./mvnw clean verify -P with-cpp -pl iotdb-client/client-cpp -am
+          else
+            ./mvnw clean verify -P with-cpp -pl iotdb-client/client-cpp -am 
-Diotdb.openssl.from.source=OFF
           fi
       - name: Upload Artifact
         if: failure()
diff --git a/iotdb-client/client-cpp/CMakeLists.txt 
b/iotdb-client/client-cpp/CMakeLists.txt
index 084396bc9b5..f6178d183d8 100644
--- a/iotdb-client/client-cpp/CMakeLists.txt
+++ b/iotdb-client/client-cpp/CMakeLists.txt
@@ -217,6 +217,7 @@ include(GNUInstallDirs)
 set(IOTDB_PUBLIC_HEADERS
         Export.h
         SessionConfig.h
+        SslConfig.h
         Session.h
         Common.h
         Optional.h
@@ -284,6 +285,17 @@ set(IOTDB_SESSION_CI_BUILD_ID "$ENV{GITHUB_RUN_ID}")
 if(NOT IOTDB_SESSION_CI_BUILD_ID)
     set(IOTDB_SESSION_CI_BUILD_ID "local")
 endif()
+if(WITH_SSL)
+    set(IOTDB_SESSION_OPENSSL_VERSION "${OPENSSL_VERSION}")
+    if(IOTDB_OPENSSL_FROM_SOURCE)
+        set(IOTDB_SESSION_OPENSSL_PROVIDER "pinned-source")
+    else()
+        set(IOTDB_SESSION_OPENSSL_PROVIDER "system")
+    endif()
+else()
+    set(IOTDB_SESSION_OPENSSL_VERSION "disabled")
+    set(IOTDB_SESSION_OPENSSL_PROVIDER "disabled")
+endif()
 file(WRITE "${CMAKE_BINARY_DIR}/package-metadata/VERSION"
         "${IOTDB_SESSION_VERSION}\n")
 file(WRITE "${CMAKE_BINARY_DIR}/package-metadata/BUILD-INFO.txt"
@@ -296,6 +308,8 @@ file(WRITE 
"${CMAKE_BINARY_DIR}/package-metadata/BUILD-INFO.txt"
         "cmake.generator=${CMAKE_GENERATOR}\n"
         "cmake.build.type=${CMAKE_BUILD_TYPE}\n"
         "with.ssl=${WITH_SSL}\n"
+        "openssl.version=${IOTDB_SESSION_OPENSSL_VERSION}\n"
+        "openssl.provider=${IOTDB_SESSION_OPENSSL_PROVIDER}\n"
         "iotdb.offline=${IOTDB_OFFLINE}\n"
         "iotdb.use.cxx11.abi=${IOTDB_USE_CXX11_ABI}\n"
         "iotdb.extra.cxx.flags=${IOTDB_EXTRA_CXX_FLAGS}\n")
diff --git a/iotdb-client/client-cpp/README.md 
b/iotdb-client/client-cpp/README.md
index 852c38d447a..d4fdb8de2ef 100644
--- a/iotdb-client/client-cpp/README.md
+++ b/iotdb-client/client-cpp/README.md
@@ -386,7 +386,8 @@ etc. directly.
 | `BOOST_VERSION`       | `1.60.0` (`1.84.0` on macOS)     | Boost version 
that CMake will look for / download.                                            
           |
 | `THRIFT_VERSION`      | `0.24.0`                         | Apache Thrift 
version to build from source.                                                   
           |
 | `BOOST_ROOT`          | (unset)                          | Existing Boost 
install to reuse, equivalent to `-Dboost.include.dir=...` from the legacy 
build.          |
-| `OPENSSL_ROOT_DIR`    | (unset)                          | Existing OpenSSL 
install when `WITH_SSL=ON`.                                                     
        |
+| `IOTDB_OPENSSL_FROM_SOURCE` | `ON`                       | Build the 
checksum-pinned OpenSSL source release; set `OFF` to opt into system OpenSSL.   
               |
+| `OPENSSL_ROOT_DIR`    | (unset)                          | Existing OpenSSL 
install used only when `IOTDB_OPENSSL_FROM_SOURCE=OFF`.                         
        |
 | `CMAKE_INSTALL_PREFIX`| `<build>/install`                | Install location. 
                                                                                
       |
 | `CMAKE_BUILD_TYPE`    | `Release`                        | Single-config 
generator build type. Use `Debug` to produce a debug library.                   
           |
 
@@ -427,9 +428,9 @@ cmake --build build --config Release --target install
 
    | Platform   | Required files                                               
                                                                                
                        |
    
|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-   | `linux/`   | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, 
`m4-1.4.19.tar.gz`, `flex-2.6.4.tar.gz`, `bison-3.8.tar.gz` (and 
`openssl-3.5.0.tar.gz` only when `WITH_SSL=ON` and no system OpenSSL is 
present) |
-   | `mac/`     | `thrift-0.24.0.tar.gz`, `boost_1_84_0.tar.gz` (newer Boost 
for Xcode/Clang; Apple ships m4/flex/bison; `openssl-3.5.0.tar.gz` optional)    
                           |
-   | `windows/` | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz` (Boost headers 
only - no `b2` build required for `iotdb_session`)                              
                        |
+   | `linux/`   | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, 
`m4-1.4.19.tar.gz`, `flex-2.6.4.tar.gz`, `bison-3.8.tar.gz`, 
`openssl-3.5.8.tar.gz` |
+   | `mac/`     | `thrift-0.24.0.tar.gz`, `boost_1_84_0.tar.gz`, 
`openssl-3.5.8.tar.gz` |
+   | `windows/` | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, 
`openssl-3.5.8.tar.gz` |
 
    Reference URLs (the configure step uses the same):
    - Apache Thrift 0.24.0: 
<https://archive.apache.org/dist/thrift/0.24.0/thrift-0.24.0.tar.gz>
@@ -437,7 +438,7 @@ cmake --build build --config Release --target install
    - GNU m4 1.4.19:       <https://ftp.gnu.org/gnu/m4/m4-1.4.19.tar.gz>
    - GNU flex 2.6.4:      
<https://github.com/westes/flex/releases/download/v2.6.4/flex-2.6.4.tar.gz>
    - GNU bison 3.8:       <https://ftp.gnu.org/gnu/bison/bison-3.8.tar.gz>
-   - OpenSSL 3.5.0:       <https://www.openssl.org/source/openssl-3.5.0.tar.gz>
+   - OpenSSL 3.5.8:       
<https://github.com/openssl/openssl/releases/download/openssl-3.5.8/openssl-3.5.8.tar.gz>
 
 2. Run the build with offline mode enabled:
 
@@ -492,11 +493,9 @@ Prerequisites:
 2. **flex / bison.** Install <https://sourceforge.net/projects/winflexbison/>
    and rename `win_flex.exe`→`flex.exe`, `win_bison.exe`→`bison.exe` on
    `PATH`.
-3. **OpenSSL** *(`WITH_SSL=ON` is the default)*: install OpenSSL — e.g.
-   `choco install openssl`, or a Win64 OpenSSL installer from
-   <https://slproweb.com/products/Win32OpenSSL.html> — then pass
-   `-DOPENSSL_ROOT_DIR=...` to CMake if it is not auto-detected. Pass
-   `-DWITH_SSL=OFF` to build without SSL.
+3. **Perl** *(`WITH_SSL=ON` is the default)*: install Strawberry Perl so the
+   pinned OpenSSL source release can be built (`choco install strawberryperl`).
+   Pass `-DWITH_SSL=OFF` to build without SSL.
 
 On Windows the SDK ships as **`iotdb_session.dll`** plus an import library
 **`iotdb_session.lib`**, built with **`/MD`** (dynamic CRT, same as a
@@ -516,20 +515,30 @@ OpenSSL **3.x** is used (Apache-2.0 licensed). Note that 
**OpenSSL 4.0 removed**
 the legacy TLS-method APIs (`TLSv1_method`, `SSLv3_method`, …) that Apache
 Thrift's `TSSLSocket` still calls, so install/point at a 3.x build, not 4.0.
 
-CMake calls `find_package(OpenSSL)` and uses the system OpenSSL it finds. Its
-shared libraries are **bundled into the package `lib/` directory** (next to
-`iotdb_session`, which records an `$ORIGIN`/`@loader_path` runtime path) so the
-published SDK is self-contained.
+By default CMake downloads OpenSSL 3.5.8, verifies its SHA-256 checksum, and
+builds shared libraries from source on Linux, macOS, and Windows. The runtime
+libraries are bundled into the package `lib/` directory so the published SDK is
+self-contained. Set `-DIOTDB_OPENSSL_FROM_SOURCE=OFF` to opt into a compatible
+system OpenSSL 3.x instead.
 
-Fallbacks:
+Enable authenticated TLS by configuring a PEM CA certificate. Add a PEM client
+certificate chain and an unencrypted PEM private key for mutual TLS:
 
-- **Linux / macOS** – when no system OpenSSL is found (or
-  `-DIOTDB_OPENSSL_FROM_SOURCE=ON`, which the Linux packaging build uses so the
-  AlmaLinux 8 baseline's OpenSSL 1.1.1 is never redistributed), build
-  `openssl-3.5.0.tar.gz` from source as **shared** libraries and bundle them.
-- **Windows** – fail with a friendly message; install a prebuilt OpenSSL 3.x
-  (e.g. the FireDaemon or slproweb 3.5.x zip) and set `-DOPENSSL_ROOT_DIR=...`.
-  Building OpenSSL from source via MSVC is out of scope.
+```cpp
+auto session = SessionBuilder()
+                   .host("127.0.0.1")
+                   ->rpcPort(6667)
+                   ->useSSL(true)
+                   ->trustCertFilePath("ca.crt")
+                   ->clientCertificateFilePath("client.crt")
+                   ->clientPrivateKeyFilePath("client.key")
+                   ->build();
+```
+
+The client certificate and private key must either both be configured or both
+be omitted. The same methods are available on `TableSessionBuilder` and
+`SessionPoolBuilder`; the C API provides `ts_session_set_ssl_config()` and
+`ts_table_session_new_with_ssl()`.
 
 ## Tests
 
diff --git a/iotdb-client/client-cpp/README_zh.md 
b/iotdb-client/client-cpp/README_zh.md
index 7cd060c7d7c..f2c591c2b2c 100644
--- a/iotdb-client/client-cpp/README_zh.md
+++ b/iotdb-client/client-cpp/README_zh.md
@@ -243,11 +243,13 @@ Maven 构建会把 SDK 安装到 `target/install/`,并生成
 | `BOOST_INCLUDEDIR` | `boost.include.dir` |
 | `CMAKE_BUILD_TYPE` | `cmake.build.type`,例如 `-Dcmake.build.type=Debug` |
 
-SSL 默认开启(`WITH_SSL=ON`)。所捆绑的 Apache Thrift 0.24 同时支持 OpenSSL 1.x
-与 3.x,因此直接使用系统的 OpenSSL(任意版本)。CMake 通过 `find_package(OpenSSL)`
-解析系统 OpenSSL,找不到时回退到从源码构建 OpenSSL 3.5.0;并会把所用的 OpenSSL
-动态库一并复制到产物 `lib/` 目录。Windows 可用 `choco install openssl` 安装。
-直接使用 CMake 时传入 `-DWITH_SSL=OFF`、`-DIOTDB_OFFLINE=ON` 等即可。
+SSL 默认开启(`WITH_SSL=ON`)。默认构建会下载固定版本的 OpenSSL 3.5.8 源码,
+校验 SHA-256 后在 Linux、macOS 和 Windows 上编译,并将动态库复制到 SDK 的
+`lib/` 目录。设置 `-DIOTDB_OPENSSL_FROM_SOURCE=OFF` 可改用系统 OpenSSL 3.x。
+
+标准 TLS 使用 `useSSL(true)` 和 `trustCertFilePath("ca.crt")`;mTLS 再同时设置
+`clientCertificateFilePath("client.crt")` 与
+`clientPrivateKeyFilePath("client.key")`。客户端证书和未加密 PEM 私钥必须成对配置。
 Debug 构建请在配置阶段传入 `-DCMAKE_BUILD_TYPE=Debug`。Windows 使用 Visual
 Studio 生成器时也需要传入该选项,以便内置 Thrift 静态库使用 Debug MSVC 运行时;
 随后用 `cmake --build build --config Debug --target install` 构建安装。
diff --git a/iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake 
b/iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake
index 26c24ba6c2f..165736dd160 100644
--- a/iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake
+++ b/iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake
@@ -18,33 +18,29 @@
 # =============================================================================
 # FetchOpenSSL.cmake  (only included when WITH_SSL=ON)
 #
-# Apache Thrift 0.24 (bundled by this client) builds against OpenSSL 1.x and 
3.x,
-# so any system OpenSSL is used as-is, whatever its version.
+# Apache Thrift 0.24 (bundled by this client) builds against OpenSSL 3.x.
 #
-# Resolution order:
-#   1. find_package(OpenSSL) - any system / vendor install is taken as-is.
-#   2. On Linux/macOS, when no system OpenSSL is present:
-#         use tarball 
${IOTDB_OS_DEPS_DIR}/openssl-${OPENSSL_FALLBACK_VERSION}.tar.gz
-#         or download from openssl.org when not in offline mode, then
-#         ./config && make && make install_sw into 
${CMAKE_BINARY_DIR}/_deps/openssl.
-#   3. On Windows: emit a FATAL_ERROR asking for a prebuilt OpenSSL; building
-#      OpenSSL from source on MSVC is out of scope.
+# By default, a fixed OpenSSL source release is downloaded, checksum-verified,
+# built as shared libraries, and installed under ${CMAKE_BINARY_DIR}/_deps.
+# Set IOTDB_OPENSSL_FROM_SOURCE=OFF to opt into a compatible system OpenSSL.
 #
 # Side effects:
 #   Defines imported targets OpenSSL::SSL / OpenSSL::Crypto via find_package
 #   so callers can just link against them.
 # =============================================================================
 
-# Version built from source when no system OpenSSL is found. Named distinctly
+# Version built from source by default. Named distinctly
 # from find_package's OPENSSL_VERSION output variable to avoid collisions.
-set(OPENSSL_FALLBACK_VERSION "3.5.0"
+set(OPENSSL_FALLBACK_VERSION "3.5.8"
     CACHE STRING "OpenSSL version built from source when no system OpenSSL is 
found")
+set(OPENSSL_FALLBACK_SHA256
+    "a8f84a39918ec6415ce765d9b429d313ba97b8143169c172e734b9514464f5b2"
+    CACHE STRING "SHA-256 checksum of the pinned OpenSSL source archive")
 
-# Build OpenSSL from source even if a system one exists. Used by the Linux
-# packaging build, whose AlmaLinux 8 baseline ships OpenSSL 1.1.1 (EOL, not
-# Apache-2.0, must not be redistributed) - we build 3.x there instead.
+# Build OpenSSL from source even if a system one exists, making release
+# packages independent of the build host's OpenSSL installation.
 option(IOTDB_OPENSSL_FROM_SOURCE
-        "Ignore any system OpenSSL and build OpenSSL 
${OPENSSL_FALLBACK_VERSION} from source" OFF)
+        "Ignore any system OpenSSL and build OpenSSL 
${OPENSSL_FALLBACK_VERSION} from source" ON)
 
 if(NOT IOTDB_OPENSSL_FROM_SOURCE)
     find_package(OpenSSL QUIET)
@@ -54,27 +50,36 @@ if(NOT IOTDB_OPENSSL_FROM_SOURCE)
     endif()
 endif()
 
-if(WIN32)
-    message(FATAL_ERROR
-            "[OpenSSL] WITH_SSL=ON but no OpenSSL was found on Windows. "
-            "Please install a prebuilt OpenSSL (e.g. 'choco install openssl'), 
"
-            "then re-run the configure step with 
-DOPENSSL_ROOT_DIR=<install_path>. "
-            "Pass -DWITH_SSL=OFF to build without SSL.")
-endif()
-
-# --- Linux / macOS: build OpenSSL ${OPENSSL_FALLBACK_VERSION} from source -
+# --- Build the pinned OpenSSL source release ---------------------------------
 set(_ossl_tarname "openssl-${OPENSSL_FALLBACK_VERSION}.tar.gz")
 set(_ossl_tarball "${IOTDB_OS_DEPS_DIR}/${_ossl_tarname}")
 
-if(NOT EXISTS "${_ossl_tarball}")
+set(_ossl_download_required ON)
+if(EXISTS "${_ossl_tarball}")
+    file(SHA256 "${_ossl_tarball}" _ossl_existing_sha256)
+    if(_ossl_existing_sha256 STREQUAL "${OPENSSL_FALLBACK_SHA256}")
+        set(_ossl_download_required OFF)
+    elseif(IOTDB_OFFLINE)
+        message(FATAL_ERROR
+                "[OpenSSL] checksum mismatch for offline archive 
${_ossl_tarball}: "
+                "expected ${OPENSSL_FALLBACK_SHA256}, got 
${_ossl_existing_sha256}")
+    else()
+        message(STATUS "[OpenSSL] replacing archive with an invalid checksum")
+        file(REMOVE "${_ossl_tarball}")
+    endif()
+endif()
+
+if(_ossl_download_required)
     if(IOTDB_OFFLINE)
         message(FATAL_ERROR
                 "[OpenSSL] IOTDB_OFFLINE=ON but ${_ossl_tarname} is missing in 
${IOTDB_OS_DEPS_DIR}.")
     endif()
-    set(_ossl_url "https://www.openssl.org/source/${_ossl_tarname}";)
+    set(_ossl_url
+        
"https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_FALLBACK_VERSION}/${_ossl_tarname}";)
     message(STATUS "[OpenSSL] downloading ${_ossl_url}")
     file(DOWNLOAD "${_ossl_url}" "${_ossl_tarball}"
-            SHOW_PROGRESS TLS_VERIFY ON STATUS _st)
+            SHOW_PROGRESS TLS_VERIFY ON
+            EXPECTED_HASH "SHA256=${OPENSSL_FALLBACK_SHA256}" STATUS _st)
     list(GET _st 0 _code)
     if(NOT _code EQUAL 0)
         list(GET _st 1 _msg)
@@ -94,41 +99,137 @@ if(NOT EXISTS "${_ossl_stamp}")
     message(STATUS "[OpenSSL] extracting ${_ossl_tarball}")
     file(ARCHIVE_EXTRACT INPUT "${_ossl_tarball}" DESTINATION 
"${_ossl_root}/src")
 
-    include(ProcessorCount)
-    ProcessorCount(_jobs)
-    if(_jobs LESS 1)
-        set(_jobs 1)
+    message(STATUS "[OpenSSL] configuring -> ${_ossl_inst}")
+    if(WIN32)
+        # Git for Windows also ships a minimal Perl, but it lacks modules 
required by OpenSSL.
+        find_program(_ossl_perl NAMES perl.exe perl
+                PATHS "C:/Strawberry/perl/bin" NO_DEFAULT_PATH)
+        if(NOT _ossl_perl)
+            find_program(_ossl_perl NAMES perl.exe perl REQUIRED)
+        endif()
+        find_program(_vswhere NAMES vswhere.exe
+                PATHS "$ENV{ProgramFiles}/Microsoft Visual Studio/Installer"
+                      "C:/Program Files (x86)/Microsoft Visual 
Studio/Installer")
+        if(NOT _vswhere)
+            message(FATAL_ERROR "[OpenSSL] vswhere.exe was not found")
+        endif()
+        if(CMAKE_GENERATOR MATCHES "Visual Studio ([0-9]+)")
+            set(_vs_major "${CMAKE_MATCH_1}")
+            math(EXPR _vs_next_major "${_vs_major} + 1")
+            set(_vs_range "[${_vs_major}.0,${_vs_next_major}.0)")
+        else()
+            set(_vs_range "[15.0,19.0)")
+        endif()
+        execute_process(
+                COMMAND "${_vswhere}" -latest -products * -version 
"${_vs_range}"
+                        -requires 
Microsoft.VisualStudio.Component.VC.Tools.x86.x64
+                        -property installationPath
+                OUTPUT_VARIABLE _vs_install OUTPUT_STRIP_TRAILING_WHITESPACE
+                RESULT_VARIABLE _rc)
+        if(NOT _rc EQUAL 0 OR NOT _vs_install)
+            message(FATAL_ERROR "[OpenSSL] a matching Visual Studio C++ 
toolchain was not found")
+        endif()
+        file(TO_NATIVE_PATH "${_vs_install}/VC/Auxiliary/Build/vcvars64.bat" 
_vcvars)
+        file(TO_NATIVE_PATH "${_ossl_inst}" _ossl_inst_native)
+        file(TO_NATIVE_PATH "${_ossl_src}" _ossl_src_native)
+        file(TO_NATIVE_PATH "${_ossl_perl}" _ossl_perl_native)
+        set(_ossl_build_script "${_ossl_root}/build-openssl.cmd")
+        file(WRITE "${_ossl_build_script}"
+                "@echo on\r\n"
+                "call \"${_vcvars}\"\r\n"
+                "if errorlevel 1 exit /b %errorlevel%\r\n"
+                "cd /d \"${_ossl_src_native}\"\r\n"
+                "\"${_ossl_perl_native}\" Configure VC-WIN64A 
--prefix=\"${_ossl_inst_native}\" --openssldir=\"${_ossl_inst_native}\\ssl\" 
shared no-tests no-asm\r\n"
+                "if errorlevel 1 exit /b %errorlevel%\r\n"
+                "nmake\r\n"
+                "if errorlevel 1 exit /b %errorlevel%\r\n"
+                "nmake install_sw\r\n")
+        execute_process(COMMAND cmd /d /c "${_ossl_build_script}" 
RESULT_VARIABLE _rc)
+        if(NOT _rc EQUAL 0)
+            message(FATAL_ERROR "[OpenSSL] Windows source build failed 
(rc=${_rc})")
+        endif()
+    else()
+        include(ProcessorCount)
+        ProcessorCount(_jobs)
+        if(_jobs LESS 1)
+            set(_jobs 1)
+        endif()
+        execute_process(
+                COMMAND ./config --prefix=${_ossl_inst} 
--openssldir=${_ossl_inst}/ssl shared no-tests
+                WORKING_DIRECTORY "${_ossl_src}"
+                RESULT_VARIABLE _rc)
+        if(NOT _rc EQUAL 0)
+            message(FATAL_ERROR "[OpenSSL] config failed (rc=${_rc})")
+        endif()
+        message(STATUS "[OpenSSL] building (-j${_jobs})")
+        execute_process(
+                COMMAND make -j${_jobs}
+                WORKING_DIRECTORY "${_ossl_src}"
+                RESULT_VARIABLE _rc)
+        if(NOT _rc EQUAL 0)
+            message(FATAL_ERROR "[OpenSSL] make failed (rc=${_rc})")
+        endif()
+        execute_process(
+                COMMAND make install_sw
+                WORKING_DIRECTORY "${_ossl_src}"
+                RESULT_VARIABLE _rc)
+        if(NOT _rc EQUAL 0)
+            message(FATAL_ERROR "[OpenSSL] make install_sw failed (rc=${_rc})")
+        endif()
     endif()
+    file(TOUCH "${_ossl_stamp}")
+endif()
+
+if(APPLE)
+    # OpenSSL's Darwin build records its absolute installation prefix in each 
dylib. Rewrite the
+    # IDs before iotdb_session links against them so both the client and 
libssl resolve the bundled
+    # libraries relative to the package's lib/ directory.
+    find_program(_install_name_tool NAMES install_name_tool REQUIRED)
+    find_program(_otool NAMES otool REQUIRED)
+    find_library(_ossl_ssl_dylib NAMES ssl
+            PATHS "${_ossl_inst}/lib" "${_ossl_inst}/lib64" NO_DEFAULT_PATH)
+    find_library(_ossl_crypto_dylib NAMES crypto
+            PATHS "${_ossl_inst}/lib" "${_ossl_inst}/lib64" NO_DEFAULT_PATH)
+    if(NOT _ossl_ssl_dylib OR NOT _ossl_crypto_dylib)
+        message(FATAL_ERROR "[OpenSSL] built Darwin dylibs were not found")
+    endif()
+    get_filename_component(_ossl_ssl_real "${_ossl_ssl_dylib}" REALPATH)
+    get_filename_component(_ossl_crypto_real "${_ossl_crypto_dylib}" REALPATH)
+    get_filename_component(_ossl_ssl_name "${_ossl_ssl_real}" NAME)
+    get_filename_component(_ossl_crypto_name "${_ossl_crypto_real}" NAME)
 
-    message(STATUS "[OpenSSL] configuring -> ${_ossl_inst}")
-    # ./config auto-detects the platform target. Build SHARED libraries
-    # (libssl.so.3 / libcrypto.so.3) so they can be bundled next to
-    # libiotdb_session and shipped as the SDK's OpenSSL runtime.
     execute_process(
-            COMMAND ./config --prefix=${_ossl_inst} 
--openssldir=${_ossl_inst}/ssl shared
-            WORKING_DIRECTORY "${_ossl_src}"
+            COMMAND "${_install_name_tool}" -id "@rpath/${_ossl_ssl_name}" 
"${_ossl_ssl_real}"
             RESULT_VARIABLE _rc)
     if(NOT _rc EQUAL 0)
-        message(FATAL_ERROR "[OpenSSL] config failed (rc=${_rc})")
+        message(FATAL_ERROR "[OpenSSL] failed to make the libssl install name 
relocatable")
     endif()
-
-    message(STATUS "[OpenSSL] building (-j${_jobs})")
     execute_process(
-            COMMAND make -j${_jobs}
-            WORKING_DIRECTORY "${_ossl_src}"
+            COMMAND "${_install_name_tool}" -id "@rpath/${_ossl_crypto_name}" 
"${_ossl_crypto_real}"
             RESULT_VARIABLE _rc)
     if(NOT _rc EQUAL 0)
-        message(FATAL_ERROR "[OpenSSL] make failed (rc=${_rc})")
+        message(FATAL_ERROR "[OpenSSL] failed to make the libcrypto install 
name relocatable")
     endif()
 
-    execute_process(
-            COMMAND make install_sw
-            WORKING_DIRECTORY "${_ossl_src}"
-            RESULT_VARIABLE _rc)
+    execute_process(COMMAND "${_otool}" -L "${_ossl_ssl_real}"
+            OUTPUT_VARIABLE _ossl_ssl_dependencies RESULT_VARIABLE _rc)
     if(NOT _rc EQUAL 0)
-        message(FATAL_ERROR "[OpenSSL] make install_sw failed (rc=${_rc})")
+        message(FATAL_ERROR "[OpenSSL] failed to inspect libssl dependencies")
+    endif()
+    string(REGEX MATCH "[^\n\t ]*libcrypto[^\n\t ]*\\.dylib"
+            _ossl_crypto_dependency "${_ossl_ssl_dependencies}")
+    if(NOT _ossl_crypto_dependency)
+        message(FATAL_ERROR "[OpenSSL] libssl does not reference the expected 
libcrypto dylib")
+    endif()
+    if(NOT _ossl_crypto_dependency STREQUAL "@rpath/${_ossl_crypto_name}")
+        execute_process(
+                COMMAND "${_install_name_tool}" -change 
"${_ossl_crypto_dependency}"
+                        "@rpath/${_ossl_crypto_name}" "${_ossl_ssl_real}"
+                RESULT_VARIABLE _rc)
+        if(NOT _rc EQUAL 0)
+            message(FATAL_ERROR "[OpenSSL] failed to make the libssl 
dependency relocatable")
+        endif()
     endif()
-    file(TOUCH "${_ossl_stamp}")
 endif()
 
 set(OPENSSL_ROOT_DIR "${_ossl_inst}" CACHE PATH "OpenSSL root" FORCE)
diff --git a/iotdb-client/client-cpp/cmake/InstallOpenSSLRuntime.cmake 
b/iotdb-client/client-cpp/cmake/InstallOpenSSLRuntime.cmake
index f3e181b8e8f..42e99d5a9bd 100644
--- a/iotdb-client/client-cpp/cmake/InstallOpenSSLRuntime.cmake
+++ b/iotdb-client/client-cpp/cmake/InstallOpenSSLRuntime.cmake
@@ -26,8 +26,8 @@
 # OPENSSL_SSL_LIBRARY / OPENSSL_CRYPTO_LIBRARY / OPENSSL_ROOT_DIR /
 # OPENSSL_VERSION_MAJOR.
 #
-# When OpenSSL was linked statically (the from-source fallback uses no-shared),
-# there is nothing to bundle: those objects are already inside 
libiotdb_session.
+# When a user opts into a statically linked system OpenSSL, there is nothing to
+# bundle because those objects are already inside libiotdb_session.
 # =============================================================================
 
 # Windows: find_package resolves the import .lib; the runtime DLLs live in
diff --git a/iotdb-client/client-cpp/pom.xml b/iotdb-client/client-cpp/pom.xml
index 288acfcb558..656f0349974 100644
--- a/iotdb-client/client-cpp/pom.xml
+++ b/iotdb-client/client-cpp/pom.xml
@@ -34,7 +34,7 @@
         The C++ client build is now driven entirely by the top-level
         CMakeLists.txt in this directory. Maven only:
           1. Runs cmake configure + build (cmake-maven-plugin)
-          2. Optionally starts/stops an IoTDB server 
(process-exec-maven-plugin)
+          2. Runs plain/TLS/mTLS integration-test phases (exec-maven-plugin)
           3. Packages the produced install tree (maven-assembly-plugin)
 
         Everything else - thrift download, code generation, Boost/m4/flex/bison
@@ -50,7 +50,7 @@
         <iotdb.deps.dir>${project.basedir}/third-party</iotdb.deps.dir>
         <iotdb.offline>OFF</iotdb.offline>
         <with.ssl>ON</with.ssl>
-        <iotdb.openssl.from.source>OFF</iotdb.openssl.from.source>
+        <iotdb.openssl.from.source>ON</iotdb.openssl.from.source>
         <iotdb.cxx11.abi/>
         <!-- Switched to OFF by the .skipTests profile below. -->
         <build.tests>ON</build.tests>
@@ -63,6 +63,7 @@
         
<client.cpp.package.classifier>${os.classifier}</client.cpp.package.classifier>
         
<client.cpp.package.name>iotdb-session-cpp-${project.version}-${client.cpp.package.classifier}</client.cpp.package.name>
         <client.cpp.ci.build.id>${env.GITHUB_RUN_ID}</client.cpp.ci.build.id>
+        
<iotdb.dist.root>${project.basedir}/../../distribution/target/apache-iotdb-${project.version}-all-bin/apache-iotdb-${project.version}-all-bin</iotdb.dist.root>
     </properties>
     <dependencies>
         <dependency>
@@ -144,51 +145,30 @@
                         <goals>
                             <goal>test</goal>
                         </goals>
-                        <phase>integration-test</phase>
-                        <configuration>
-                            <config>${cmake.build.type}</config>
-                            
<projectDirectory>${cmake.project.dir}</projectDirectory>
-                            <skipTests>${maven.test.skip}</skipTests>
-                        </configuration>
+                        <phase>none</phase>
                     </execution>
                 </executions>
             </plugin>
-            <!-- Start a local IoTDB server before integration-test, stop it 
after. -->
             <plugin>
-                <groupId>com.bazaarvoice.maven.plugins</groupId>
-                <artifactId>process-exec-maven-plugin</artifactId>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
                 <executions>
                     <execution>
-                        <id>start-iotdb</id>
+                        <id>run-cpp-it-phases</id>
                         <goals>
-                            <goal>start</goal>
+                            <goal>exec</goal>
                         </goals>
-                        <phase>pre-integration-test</phase>
-                        <configuration>
-                            <skip>${ctest.skip.tests}</skip>
-                            <name>iotdb-server</name>
-                            <waitForInterrupt>false</waitForInterrupt>
-                            <waitAfterLaunch>45</waitAfterLaunch>
-                            
<processLogFile>${cmake.project.dir}/test.log</processLogFile>
-                            <arguments>
-                                
<argument>${project.basedir}/../../distribution/target/apache-iotdb-${project.version}-all-bin/apache-iotdb-${project.version}-all-bin/sbin/${iotdb.start.script}</argument>
-                            </arguments>
-                        </configuration>
-                    </execution>
-                    <execution>
-                        <id>stop-iotdb</id>
-                        <goals>
-                            <goal>stop-all</goal>
-                        </goals>
-                        <phase>post-integration-test</phase>
+                        <phase>integration-test</phase>
                         <configuration>
                             <skip>${ctest.skip.tests}</skip>
-                            <name>iotdb-server</name>
-                            <waitForInterrupt>false</waitForInterrupt>
-                            <waitAfterLaunch>5</waitAfterLaunch>
-                            
<processLogFile>${cmake.project.dir}/stop.log</processLogFile>
+                            <executable>python</executable>
                             <arguments>
-                                
<argument>${project.basedir}/../../distribution/target/apache-iotdb-${project.version}-all-bin/apache-iotdb-${project.version}-all-bin/sbin/${iotdb.stop.script}</argument>
+                                
<argument>${project.basedir}/test/scripts/run_cpp_it_phases.py</argument>
+                                <argument>${cmake.project.dir}</argument>
+                                <argument>${iotdb.dist.root}</argument>
+                                
<argument>${project.basedir}/test/fixtures</argument>
+                                <argument>--config</argument>
+                                <argument>${cmake.build.type}</argument>
                             </arguments>
                         </configuration>
                     </execution>
@@ -275,8 +255,6 @@
                 </os>
             </activation>
             <properties>
-                <iotdb.start.script>start-standalone.sh</iotdb.start.script>
-                <iotdb.stop.script>stop-standalone.sh</iotdb.stop.script>
                 <os.suffix>linux</os.suffix>
                 
<client.cpp.package.classifier>linux-x86_64-glibc2.28</client.cpp.package.classifier>
             </properties>
@@ -291,8 +269,6 @@
                 </os>
             </activation>
             <properties>
-                <iotdb.start.script>start-standalone.sh</iotdb.start.script>
-                <iotdb.stop.script>stop-standalone.sh</iotdb.stop.script>
                 <os.suffix>linux</os.suffix>
                 
<client.cpp.package.classifier>linux-aarch64-glibc2.28</client.cpp.package.classifier>
             </properties>
@@ -306,8 +282,6 @@
                 </os>
             </activation>
             <properties>
-                <iotdb.start.script>start-standalone.sh</iotdb.start.script>
-                <iotdb.stop.script>stop-standalone.sh</iotdb.stop.script>
                 <os.suffix>mac</os.suffix>
                 
<client.cpp.package.classifier>macos-x86_64</client.cpp.package.classifier>
             </properties>
@@ -321,8 +295,6 @@
                 </os>
             </activation>
             <properties>
-                <iotdb.start.script>start-standalone.sh</iotdb.start.script>
-                <iotdb.stop.script>stop-standalone.sh</iotdb.stop.script>
                 <os.suffix>mac</os.suffix>
                 
<client.cpp.package.classifier>macos-aarch64</client.cpp.package.classifier>
             </properties>
@@ -335,8 +307,6 @@
                 </os>
             </activation>
             <properties>
-                
<iotdb.start.script>windows/start-standalone.bat</iotdb.start.script>
-                
<iotdb.stop.script>windows/stop-standalone.bat</iotdb.stop.script>
                 <os.suffix>win</os.suffix>
                 
<client.cpp.package.classifier>windows-x86_64-msvc14.3</client.cpp.package.classifier>
             </properties>
diff --git 
a/iotdb-client/client-cpp/src/assembly/package-metadata/third_party/DEPENDENCIES.md
 
b/iotdb-client/client-cpp/src/assembly/package-metadata/third_party/DEPENDENCIES.md
index 696ce154d16..da8b7698a70 100644
--- 
a/iotdb-client/client-cpp/src/assembly/package-metadata/third_party/DEPENDENCIES.md
+++ 
b/iotdb-client/client-cpp/src/assembly/package-metadata/third_party/DEPENDENCIES.md
@@ -33,7 +33,7 @@ the [`NOTICE`](NOTICE) file in this directory; non-Apache 
license texts are unde
 | --- | --- | --- | --- |
 | Apache Thrift | 0.24.0 | statically linked | Apache License 2.0 |
 | Boost | 1.60.0 on Linux/Windows, 1.84.0 on macOS by default | statically 
linked (header-only) | Boost Software License 1.0 |
-| OpenSSL | 3.x: system OpenSSL 3.x when present, else 3.5.0 built from source 
(`WITH_SSL=ON`, default) | bundled shared libs in `lib/` | Apache License 2.0 |
+| OpenSSL | 3.5.8 built from checksum-pinned source (`WITH_SSL=ON`, default) | 
bundled shared libs in `lib/` | Apache License 2.0 |
 
 ## Build-time only (not redistributed)
 
diff --git a/iotdb-client/client-cpp/src/include/AbstractSessionBuilder.h 
b/iotdb-client/client-cpp/src/include/AbstractSessionBuilder.h
index 3735dfa227d..9e865da78e8 100644
--- a/iotdb-client/client-cpp/src/include/AbstractSessionBuilder.h
+++ b/iotdb-client/client-cpp/src/include/AbstractSessionBuilder.h
@@ -21,8 +21,10 @@
 #define IOTDB_ABSTRACTSESSIONBUILDER_H
 
 #include <string>
+#include <vector>
 
 #include "SessionConfig.h"
+#include "SslConfig.h"
 
 class AbstractSessionBuilder {
 public:
@@ -54,8 +56,19 @@ public:
   bool enableRedirections = DEFAULT_ENABLE_REDIRECTIONS;
   bool enableRPCCompression = DEFAULT_ENABLE_RPC_COMPRESSION;
   std::vector<std::string> nodeUrls;
+  // Kept for source compatibility with callers that configure builder fields 
directly.
   bool useSSL = false;
   std::string trustCertFilePath;
+  SslConfig sslConfig;
+
+  SslConfig getSslConfig() const {
+    SslConfig result = sslConfig;
+    result.useSsl = useSSL || result.useSsl;
+    if (result.trustCertFilePath.empty()) {
+      result.trustCertFilePath = trustCertFilePath;
+    }
+    return result;
+  }
 };
 
-#endif // IOTDB_ABSTRACTSESSIONBUILDER_H
\ No newline at end of file
+#endif // IOTDB_ABSTRACTSESSIONBUILDER_H
diff --git a/iotdb-client/client-cpp/src/include/Session.h 
b/iotdb-client/client-cpp/src/include/Session.h
index 76895a30291..054c8f6f773 100644
--- a/iotdb-client/client-cpp/src/include/Session.h
+++ b/iotdb-client/client-cpp/src/include/Session.h
@@ -582,12 +582,14 @@ private:
 
 class SessionConnection;
 class TableSession;
+class SessionTestAccessor;
 
 class Session {
   struct Impl;
   std::unique_ptr<Impl> impl_;
   friend class SessionConnection;
   friend class TableSession;
+  friend class SessionTestAccessor;
 
 public:
   Session(const std::string& host, int rpcPort);
@@ -606,6 +608,7 @@ public:
 
   void setSqlDialect(const std::string& dialect);
   void setDatabase(const std::string& database);
+  void setSslConfig(const SslConfig& sslConfig);
   std::string getDatabase();
   void changeDatabase(const std::string& database);
 
diff --git a/iotdb-client/client-cpp/src/include/SessionBuilder.h 
b/iotdb-client/client-cpp/src/include/SessionBuilder.h
index 14342697eb5..3167bd1afe5 100644
--- a/iotdb-client/client-cpp/src/include/SessionBuilder.h
+++ b/iotdb-client/client-cpp/src/include/SessionBuilder.h
@@ -36,11 +36,23 @@ public:
 
   SessionBuilder* useSSL(bool useSSL) {
     AbstractSessionBuilder::useSSL = useSSL;
+    AbstractSessionBuilder::sslConfig.useSsl = useSSL;
     return this;
   }
 
   SessionBuilder* trustCertFilePath(const std::string& trustCertFilePath) {
     AbstractSessionBuilder::trustCertFilePath = trustCertFilePath;
+    AbstractSessionBuilder::sslConfig.trustCertFilePath = trustCertFilePath;
+    return this;
+  }
+
+  SessionBuilder* clientCertificateFilePath(const std::string& 
clientCertificateFilePath) {
+    AbstractSessionBuilder::sslConfig.clientCertificateFilePath = 
clientCertificateFilePath;
+    return this;
+  }
+
+  SessionBuilder* clientPrivateKeyFilePath(const std::string& 
clientPrivateKeyFilePath) {
+    AbstractSessionBuilder::sslConfig.clientPrivateKeyFilePath = 
clientPrivateKeyFilePath;
     return this;
   }
 
@@ -103,4 +115,4 @@ public:
   }
 };
 
-#endif // IOTDB_SESSION_BUILDER_H
\ No newline at end of file
+#endif // IOTDB_SESSION_BUILDER_H
diff --git a/iotdb-client/client-cpp/src/include/SessionC.h 
b/iotdb-client/client-cpp/src/include/SessionC.h
index fdce5801a9d..91117936eef 100644
--- a/iotdb-client/client-cpp/src/include/SessionC.h
+++ b/iotdb-client/client-cpp/src/include/SessionC.h
@@ -131,6 +131,14 @@ TsStatus ts_session_open_with_compression(CSession* 
session, bool enableRPCCompr
 
 TsStatus ts_session_close(CSession* session);
 
+/**
+ * Enables TLS for a tree-model session. Pass NULL for both client certificate 
and private key
+ * to use one-way TLS; pass PEM paths for both to use mutual TLS.
+ */
+TsStatus ts_session_set_ssl_config(CSession* session, const char* 
trustCertFilePath,
+                                   const char* clientCertificateFilePath,
+                                   const char* clientPrivateKeyFilePath);
+
 /* ============================================================
  *  Session Lifecycle  —  Table Model
  * ============================================================ */
@@ -148,6 +156,13 @@ TsStatus ts_table_session_open(CTableSession* session);
 
 TsStatus ts_table_session_close(CTableSession* session);
 
+/** Creates and opens a TLS or mutual-TLS table-model session. */
+CTableSession* ts_table_session_new_with_ssl(const char* host, int rpcPort, 
const char* username,
+                                             const char* password, const char* 
database,
+                                             const char* trustCertFilePath,
+                                             const char* 
clientCertificateFilePath,
+                                             const char* 
clientPrivateKeyFilePath);
+
 /* ============================================================
  *  Timezone
  * ============================================================ */
diff --git a/iotdb-client/client-cpp/src/include/SessionPool.h 
b/iotdb-client/client-cpp/src/include/SessionPool.h
index 4483dab0c51..0b578294a35 100644
--- a/iotdb-client/client-cpp/src/include/SessionPool.h
+++ b/iotdb-client/client-cpp/src/include/SessionPool.h
@@ -188,6 +188,8 @@ public:
   SessionPool& setWaitToGetSessionTimeoutMs(int64_t timeoutMs);
   SessionPool& setUseSSL(bool useSSL);
   SessionPool& setTrustCertFilePath(std::string path);
+  SessionPool& setClientCertificateFilePath(std::string path);
+  SessionPool& setClientPrivateKeyFilePath(std::string path);
 
   // Borrow a Session. Blocks until one is free or a new one can be created,
   // up to timeoutMs (<= 0 means use the pool default). Throws IoTDBException 
on
@@ -247,8 +249,7 @@ private:
   bool enableAutoFetch_ = AbstractSessionBuilder::DEFAULT_ENABLE_AUTO_FETCH;
   bool enableRPCCompression_ = 
AbstractSessionBuilder::DEFAULT_ENABLE_RPC_COMPRESSION;
   int connectTimeoutMs_ = AbstractSessionBuilder::DEFAULT_CONNECT_TIMEOUT_MS;
-  bool useSSL_ = false;
-  std::string trustCertFilePath_;
+  SslConfig sslConfig_;
 
   // pool sizing / waiting policy
   size_t maxSize_;
@@ -333,10 +334,20 @@ public:
   }
   SessionPoolBuilder* useSSL(bool v) {
     AbstractSessionBuilder::useSSL = v;
+    AbstractSessionBuilder::sslConfig.useSsl = v;
     return this;
   }
   SessionPoolBuilder* trustCertFilePath(const std::string& v) {
     AbstractSessionBuilder::trustCertFilePath = v;
+    AbstractSessionBuilder::sslConfig.trustCertFilePath = v;
+    return this;
+  }
+  SessionPoolBuilder* clientCertificateFilePath(const std::string& v) {
+    AbstractSessionBuilder::sslConfig.clientCertificateFilePath = v;
+    return this;
+  }
+  SessionPoolBuilder* clientPrivateKeyFilePath(const std::string& v) {
+    AbstractSessionBuilder::sslConfig.clientPrivateKeyFilePath = v;
     return this;
   }
   SessionPoolBuilder* maxSize(size_t v) {
@@ -379,8 +390,10 @@ public:
         .setEnableRPCCompression(AbstractSessionBuilder::enableRPCCompression)
         .setConnectTimeoutMs(AbstractSessionBuilder::connectTimeoutMs)
         .setWaitToGetSessionTimeoutMs(waitTimeoutMs_)
-        .setUseSSL(AbstractSessionBuilder::useSSL)
-        .setTrustCertFilePath(AbstractSessionBuilder::trustCertFilePath);
+        .setUseSSL(AbstractSessionBuilder::getSslConfig().useSsl)
+        
.setTrustCertFilePath(AbstractSessionBuilder::getSslConfig().trustCertFilePath)
+        
.setClientCertificateFilePath(AbstractSessionBuilder::sslConfig.clientCertificateFilePath)
+        
.setClientPrivateKeyFilePath(AbstractSessionBuilder::sslConfig.clientPrivateKeyFilePath);
     return pool;
   }
 
diff --git a/iotdb-client/client-cpp/src/include/TableSession.h 
b/iotdb-client/client-cpp/src/include/SslConfig.h
similarity index 50%
copy from iotdb-client/client-cpp/src/include/TableSession.h
copy to iotdb-client/client-cpp/src/include/SslConfig.h
index d1eecfeeaba..2a0a44dad44 100644
--- a/iotdb-client/client-cpp/src/include/TableSession.h
+++ b/iotdb-client/client-cpp/src/include/SslConfig.h
@@ -17,30 +17,18 @@
  * under the License.
  */
 
-// This file is a translation of the Java file 
iotdb-client/session/src/main/java/org/apache/iotdb/session/TableSession.java
+#ifndef IOTDB_SSL_CONFIG_H
+#define IOTDB_SSL_CONFIG_H
 
-#ifndef IOTDB_TABLESESSION_H
-#define IOTDB_TABLESESSION_H
+#include <string>
 
-#include "Session.h"
+struct SslConfig {
+  bool useSsl = false;
+  std::string trustCertFilePath;
+  std::string clientCertificateFilePath;
+  std::string clientPrivateKeyFilePath;
 
-class TableSession {
-private:
-  std::shared_ptr<Session> session_;
-  string getDatabase();
-
-public:
-  TableSession(std::shared_ptr<Session> session) {
-    this->session_ = session;
-  }
-  ~TableSession() {}
-
-  void insert(Tablet& tablet, bool sorted = false);
-  void executeNonQueryStatement(const std::string& sql);
-  unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql);
-  unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql, 
int64_t timeoutInMs);
-  void open(bool enableRPCCompression = false);
-  void close();
+  void validate() const;
 };
 
-#endif // IOTDB_TABLESESSION_H
\ No newline at end of file
+#endif // IOTDB_SSL_CONFIG_H
diff --git a/iotdb-client/client-cpp/src/include/TableSession.h 
b/iotdb-client/client-cpp/src/include/TableSession.h
index d1eecfeeaba..2944b2355e8 100644
--- a/iotdb-client/client-cpp/src/include/TableSession.h
+++ b/iotdb-client/client-cpp/src/include/TableSession.h
@@ -41,6 +41,7 @@ public:
   unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql, 
int64_t timeoutInMs);
   void open(bool enableRPCCompression = false);
   void close();
+  void setSslConfig(const SslConfig& sslConfig);
 };
 
-#endif // IOTDB_TABLESESSION_H
\ No newline at end of file
+#endif // IOTDB_TABLESESSION_H
diff --git a/iotdb-client/client-cpp/src/include/TableSessionBuilder.h 
b/iotdb-client/client-cpp/src/include/TableSessionBuilder.h
index 3c9739ecc8e..3e762ad77c7 100644
--- a/iotdb-client/client-cpp/src/include/TableSessionBuilder.h
+++ b/iotdb-client/client-cpp/src/include/TableSessionBuilder.h
@@ -47,11 +47,23 @@ public:
   }
   TableSessionBuilder* useSSL(bool useSSL) {
     AbstractSessionBuilder::useSSL = useSSL;
+    AbstractSessionBuilder::sslConfig.useSsl = useSSL;
     return this;
   }
 
   TableSessionBuilder* trustCertFilePath(const std::string& trustCertFilePath) 
{
     AbstractSessionBuilder::trustCertFilePath = trustCertFilePath;
+    AbstractSessionBuilder::sslConfig.trustCertFilePath = trustCertFilePath;
+    return this;
+  }
+
+  TableSessionBuilder* clientCertificateFilePath(const std::string& 
clientCertificateFilePath) {
+    AbstractSessionBuilder::sslConfig.clientCertificateFilePath = 
clientCertificateFilePath;
+    return this;
+  }
+
+  TableSessionBuilder* clientPrivateKeyFilePath(const std::string& 
clientPrivateKeyFilePath) {
+    AbstractSessionBuilder::sslConfig.clientPrivateKeyFilePath = 
clientPrivateKeyFilePath;
     return this;
   }
 
@@ -93,4 +105,4 @@ public:
   }
 };
 
-#endif // IOTDB_TABLESESSIONBUILDER_H
\ No newline at end of file
+#endif // IOTDB_TABLESESSIONBUILDER_H
diff --git a/iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp 
b/iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp
index 55fd13a08f4..f2872d476e5 100644
--- a/iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp
+++ b/iotdb-client/client-cpp/src/rpc/NodesSupplier.cpp
@@ -67,33 +67,33 @@ std::vector<TEndPoint> 
StaticNodesSupplier::getEndPointList() {
 
 StaticNodesSupplier::~StaticNodesSupplier() = default;
 
-std::shared_ptr<NodesSupplier> NodesSupplier::create(
-    const std::vector<TEndPoint>& endpoints, const std::string& userName,
-    const std::string& password, bool useSSL, const std::string& 
trustCertFilePath,
-    const std::string& zoneId, int32_t thriftDefaultBufferSize, int32_t 
thriftMaxFrameSize,
-    int32_t connectionTimeoutInMs, bool enableRPCCompression, const 
std::string& version,
-    std::chrono::milliseconds refreshInterval, NodeSelectionPolicy policy) {
+std::shared_ptr<NodesSupplier>
+NodesSupplier::create(const std::vector<TEndPoint>& endpoints, const 
std::string& userName,
+                      const std::string& password, const SslConfig& sslConfig,
+                      const std::string& zoneId, int32_t 
thriftDefaultBufferSize,
+                      int32_t thriftMaxFrameSize, int32_t 
connectionTimeoutInMs,
+                      bool enableRPCCompression, const std::string& version,
+                      std::chrono::milliseconds refreshInterval, 
NodeSelectionPolicy policy) {
   if (endpoints.empty()) {
     return nullptr;
   }
   auto supplier = std::make_shared<NodesSupplier>(
-      userName, password, useSSL, trustCertFilePath, zoneId, 
thriftDefaultBufferSize,
-      thriftMaxFrameSize, connectionTimeoutInMs, enableRPCCompression, 
version, endpoints, policy);
+      userName, password, sslConfig, zoneId, thriftDefaultBufferSize, 
thriftMaxFrameSize,
+      connectionTimeoutInMs, enableRPCCompression, version, endpoints, policy);
   supplier->startBackgroundRefresh(refreshInterval);
   return supplier;
 }
 
-NodesSupplier::NodesSupplier(const std::string& userName, const std::string& 
password, bool useSSL,
-                             const std::string& trustCertFilePath, const 
std::string& zoneId,
+NodesSupplier::NodesSupplier(const std::string& userName, const std::string& 
password,
+                             const SslConfig& sslConfig, const std::string& 
zoneId,
                              int32_t thriftDefaultBufferSize, int32_t 
thriftMaxFrameSize,
                              int32_t connectionTimeoutInMs, bool 
enableRPCCompression,
                              const std::string& version, const 
std::vector<TEndPoint>& endpoints,
                              NodeSelectionPolicy policy)
-    : userName_(userName), password_(password), zoneId_(zoneId),
-      thriftDefaultBufferSize_(thriftDefaultBufferSize), 
thriftMaxFrameSize_(thriftMaxFrameSize),
-      connectionTimeoutInMs_(connectionTimeoutInMs), useSSL_(useSSL),
-      trustCertFilePath_(trustCertFilePath), 
enableRPCCompression_(enableRPCCompression),
-      version_(version), endpoints_(endpoints), selectionPolicy_(policy) {
+    : userName_(userName), password_(password), 
thriftDefaultBufferSize_(thriftDefaultBufferSize),
+      thriftMaxFrameSize_(thriftMaxFrameSize), 
connectionTimeoutInMs_(connectionTimeoutInMs),
+      sslConfig_(sslConfig), enableRPCCompression_(enableRPCCompression), 
version_(version),
+      zoneId_(zoneId), endpoints_(endpoints), selectionPolicy_(policy) {
   deduplicateEndpoints();
 }
 
@@ -156,8 +156,7 @@ std::vector<TEndPoint> 
NodesSupplier::fetchLatestEndpoints() {
     try {
       if (client_ == nullptr) {
         client_ = std::make_shared<ThriftConnection>(endpoint);
-        client_->init(userName_, password_, enableRPCCompression_, useSSL_, 
trustCertFilePath_,
-                      zoneId_, version_);
+        client_->init(userName_, password_, enableRPCCompression_, sslConfig_, 
zoneId_, version_);
       }
 
       auto sessionDataSet = 
client_->executeQueryStatement(SHOW_AVAILABLE_URLS_COMMAND);
diff --git a/iotdb-client/client-cpp/src/rpc/NodesSupplier.h 
b/iotdb-client/client-cpp/src/rpc/NodesSupplier.h
index c067bbb6d72..a5fe04ef0f0 100644
--- a/iotdb-client/client-cpp/src/rpc/NodesSupplier.h
+++ b/iotdb-client/client-cpp/src/rpc/NodesSupplier.h
@@ -78,8 +78,8 @@ public:
 
   static std::shared_ptr<NodesSupplier>
   create(const std::vector<TEndPoint>& endpoints, const std::string& userName,
-         const std::string& password, bool useSSL = false,
-         const std::string& trustCertFilePath = "", const std::string& zoneId 
= "",
+         const std::string& password, const SslConfig& sslConfig = SslConfig(),
+         const std::string& zoneId = "",
          int32_t thriftDefaultBufferSize = 
ThriftConnection::THRIFT_DEFAULT_BUFFER_SIZE,
          int32_t thriftMaxFrameSize = ThriftConnection::THRIFT_MAX_FRAME_SIZE,
          int32_t connectionTimeoutInMs = 
ThriftConnection::CONNECTION_TIMEOUT_IN_MS,
@@ -87,8 +87,8 @@ public:
          std::chrono::milliseconds refreshInterval = 
std::chrono::milliseconds(TIMEOUT_IN_MS),
          NodeSelectionPolicy policy = RoundRobinPolicy::select);
 
-  NodesSupplier(const std::string& userName, const std::string& password, bool 
useSSL,
-                const std::string& trustCertFilePath, const std::string& 
zoneId,
+  NodesSupplier(const std::string& userName, const std::string& password,
+                const SslConfig& sslConfig, const std::string& zoneId,
                 int32_t thriftDefaultBufferSize, int32_t thriftMaxFrameSize,
                 int32_t connectionTimeoutInMs, bool enableRPCCompression,
                 const std::string& version, const std::vector<TEndPoint>& 
endpoints,
@@ -106,8 +106,7 @@ private:
   int32_t thriftDefaultBufferSize_;
   int32_t thriftMaxFrameSize_;
   int32_t connectionTimeoutInMs_;
-  bool useSSL_;
-  std::string trustCertFilePath_;
+  SslConfig sslConfig_;
   bool enableRPCCompression_;
   std::string version_;
   std::string zoneId_;
@@ -135,4 +134,4 @@ private:
   void stopBackgroundRefresh() noexcept;
 };
 
-#endif
\ No newline at end of file
+#endif
diff --git a/iotdb-client/client-cpp/src/include/TableSession.h 
b/iotdb-client/client-cpp/src/rpc/RpcSslUtils.cpp
similarity index 50%
copy from iotdb-client/client-cpp/src/include/TableSession.h
copy to iotdb-client/client-cpp/src/rpc/RpcSslUtils.cpp
index d1eecfeeaba..076e2de9732 100644
--- a/iotdb-client/client-cpp/src/include/TableSession.h
+++ b/iotdb-client/client-cpp/src/rpc/RpcSslUtils.cpp
@@ -17,30 +17,20 @@
  * under the License.
  */
 
-// This file is a translation of the Java file 
iotdb-client/session/src/main/java/org/apache/iotdb/session/TableSession.java
+#include "RpcSslUtils.h"
 
-#ifndef IOTDB_TABLESESSION_H
-#define IOTDB_TABLESESSION_H
+#if WITH_SSL
 
-#include "Session.h"
-
-class TableSession {
-private:
-  std::shared_ptr<Session> session_;
-  string getDatabase();
-
-public:
-  TableSession(std::shared_ptr<Session> session) {
-    this->session_ = session;
+void configureSslSocketFactory(
+    const std::shared_ptr<apache::thrift::transport::TSSLSocketFactory>& 
socketFactory,
+    const SslConfig& sslConfig) {
+  sslConfig.validate();
+  socketFactory->loadTrustedCertificates(sslConfig.trustCertFilePath.c_str());
+  if (!sslConfig.clientCertificateFilePath.empty()) {
+    
socketFactory->loadCertificate(sslConfig.clientCertificateFilePath.c_str());
+    socketFactory->loadPrivateKey(sslConfig.clientPrivateKeyFilePath.c_str());
   }
-  ~TableSession() {}
-
-  void insert(Tablet& tablet, bool sorted = false);
-  void executeNonQueryStatement(const std::string& sql);
-  unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql);
-  unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql, 
int64_t timeoutInMs);
-  void open(bool enableRPCCompression = false);
-  void close();
-};
+  socketFactory->authenticate(true);
+}
 
-#endif // IOTDB_TABLESESSION_H
\ No newline at end of file
+#endif // WITH_SSL
diff --git a/iotdb-client/client-cpp/src/include/TableSession.h 
b/iotdb-client/client-cpp/src/rpc/RpcSslUtils.h
similarity index 50%
copy from iotdb-client/client-cpp/src/include/TableSession.h
copy to iotdb-client/client-cpp/src/rpc/RpcSslUtils.h
index d1eecfeeaba..ab78ebec3f8 100644
--- a/iotdb-client/client-cpp/src/include/TableSession.h
+++ b/iotdb-client/client-cpp/src/rpc/RpcSslUtils.h
@@ -17,30 +17,21 @@
  * under the License.
  */
 
-// This file is a translation of the Java file 
iotdb-client/session/src/main/java/org/apache/iotdb/session/TableSession.java
+#ifndef IOTDB_RPC_SSL_UTILS_H
+#define IOTDB_RPC_SSL_UTILS_H
 
-#ifndef IOTDB_TABLESESSION_H
-#define IOTDB_TABLESESSION_H
+#if WITH_SSL
 
-#include "Session.h"
+#include <memory>
 
-class TableSession {
-private:
-  std::shared_ptr<Session> session_;
-  string getDatabase();
+#include <thrift/transport/TSSLSocket.h>
 
-public:
-  TableSession(std::shared_ptr<Session> session) {
-    this->session_ = session;
-  }
-  ~TableSession() {}
+#include "SslConfig.h"
 
-  void insert(Tablet& tablet, bool sorted = false);
-  void executeNonQueryStatement(const std::string& sql);
-  unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql);
-  unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql, 
int64_t timeoutInMs);
-  void open(bool enableRPCCompression = false);
-  void close();
-};
+void configureSslSocketFactory(
+    const std::shared_ptr<apache::thrift::transport::TSSLSocketFactory>& 
socketFactory,
+    const SslConfig& sslConfig);
 
-#endif // IOTDB_TABLESESSION_H
\ No newline at end of file
+#endif // WITH_SSL
+
+#endif // IOTDB_RPC_SSL_UTILS_H
diff --git a/iotdb-client/client-cpp/src/rpc/SessionConnection.cpp 
b/iotdb-client/client-cpp/src/rpc/SessionConnection.cpp
index dfdb0198e38..3eea30cb989 100644
--- a/iotdb-client/client-cpp/src/rpc/SessionConnection.cpp
+++ b/iotdb-client/client-cpp/src/rpc/SessionConnection.cpp
@@ -19,6 +19,7 @@
 #include "SessionConnection.h"
 #include "SessionImpl.h"
 #include "RpcCommon.h"
+#include "RpcSslUtils.h"
 #include "common_types.h"
 #include <thrift/protocol/TBinaryProtocol.h>
 #include <thrift/protocol/TCompactProtocol.h>
@@ -46,7 +47,7 @@ SessionConnection::SessionConnection(Session::Impl* 
session_ptr, const TEndPoint
       sqlDialect(std::move(dialect)), database(std::move(db)) {
   this->zoneId = zoneId.empty() ? getSystemDefaultZoneId() : zoneId;
   endPointList.push_back(endpoint);
-  init(endPoint, session->useSSL_, session->trustCertFilePath_);
+  init(endPoint, session->sslConfig_);
 }
 
 void SessionConnection::close() {
@@ -92,12 +93,10 @@ SessionConnection::~SessionConnection() {
   }
 }
 
-void SessionConnection::init(const TEndPoint& endpoint, bool useSSL,
-                             const std::string& trustCertFilePath) {
-  if (useSSL) {
+void SessionConnection::init(const TEndPoint& endpoint, const SslConfig& 
sslConfig) {
+  if (sslConfig.useSsl) {
 #if WITH_SSL
-    socketFactory_->loadTrustedCertificates(trustCertFilePath.c_str());
-    socketFactory_->authenticate(false);
+    configureSslSocketFactory(socketFactory_, sslConfig);
     auto sslSocket = socketFactory_->createSocket(endPoint.ip, endPoint.port);
     sslSocket->setConnTimeout(connectionTimeoutInMs);
     transport = std::make_shared<TFramedTransport>(sslSocket);
@@ -332,7 +331,7 @@ bool SessionConnection::reconnect() {
         }
         tryHostNum++;
         try {
-          init(this->endPoint, this->session->useSSL_, 
this->session->trustCertFilePath_);
+          init(this->endPoint, this->session->sslConfig_);
           reconnect = true;
         } catch (const IoTDBConnectionException& e) {
           log_warn("The current node may have been down, connection exception: 
%s", e.what());
diff --git a/iotdb-client/client-cpp/src/rpc/SessionConnection.h 
b/iotdb-client/client-cpp/src/rpc/SessionConnection.h
index 472e29fd665..c3c6f1a5735 100644
--- a/iotdb-client/client-cpp/src/rpc/SessionConnection.h
+++ b/iotdb-client/client-cpp/src/rpc/SessionConnection.h
@@ -53,7 +53,7 @@ public:
 
   const TEndPoint& getEndPoint();
 
-  void init(const TEndPoint& endpoint, bool useSSL, const std::string& 
trustCertFilePath);
+  void init(const TEndPoint& endpoint, const SslConfig& sslConfig);
 
   void insertStringRecord(const TSInsertStringRecordReq& request);
 
@@ -123,6 +123,7 @@ public:
   }
 
   friend class Session;
+  friend class SessionTestAccessor;
 
 private:
   void close();
diff --git a/iotdb-client/client-cpp/src/rpc/SessionImpl.h 
b/iotdb-client/client-cpp/src/rpc/SessionImpl.h
index 406537486c0..23c6870cce8 100644
--- a/iotdb-client/client-cpp/src/rpc/SessionImpl.h
+++ b/iotdb-client/client-cpp/src/rpc/SessionImpl.h
@@ -41,8 +41,7 @@ class Session::Impl {
 public:
   std::string host_;
   int rpcPort_ = 6667;
-  bool useSSL_ = false;
-  std::string trustCertFilePath_;
+  SslConfig sslConfig_;
   std::vector<std::string> nodeUrls_;
   std::string username_ = "root";
   std::string password_ = "root";
diff --git a/iotdb-client/client-cpp/src/rpc/ThriftConnection.cpp 
b/iotdb-client/client-cpp/src/rpc/ThriftConnection.cpp
index 1cc6c5417b2..6b8d0289a16 100644
--- a/iotdb-client/client-cpp/src/rpc/ThriftConnection.cpp
+++ b/iotdb-client/client-cpp/src/rpc/ThriftConnection.cpp
@@ -26,6 +26,7 @@
 #include <thrift/transport/TTransportException.h>
 
 #include "RpcCommon.h"
+#include "RpcSslUtils.h"
 #include "SessionDataSet.h"
 #include "SessionDataSetFactory.h"
 
@@ -64,13 +65,11 @@ void ThriftConnection::initZoneId() {
 }
 
 void ThriftConnection::init(const std::string& username, const std::string& 
password,
-                            bool enableRPCCompression, bool useSSL,
-                            const std::string& trustCertFilePath, const 
std::string& zoneId,
-                            const std::string& version) {
-  if (useSSL) {
+                            bool enableRPCCompression, const SslConfig& 
sslConfig,
+                            const std::string& zoneId, const std::string& 
version) {
+  if (sslConfig.useSsl) {
 #if WITH_SSL
-    socketFactory_->loadTrustedCertificates(trustCertFilePath.c_str());
-    socketFactory_->authenticate(false);
+    configureSslSocketFactory(socketFactory_, sslConfig);
     auto sslSocket = socketFactory_->createSocket(endPoint_.ip, 
endPoint_.port);
     sslSocket->setConnTimeout(connectionTimeoutInMs_);
     transport_ = std::make_shared<TFramedTransport>(sslSocket);
diff --git a/iotdb-client/client-cpp/src/rpc/ThriftConnection.h 
b/iotdb-client/client-cpp/src/rpc/ThriftConnection.h
index 28691174031..16bb01b7599 100644
--- a/iotdb-client/client-cpp/src/rpc/ThriftConnection.h
+++ b/iotdb-client/client-cpp/src/rpc/ThriftConnection.h
@@ -25,6 +25,7 @@
 #endif
 #include "IClientRPCService.h"
 #include "SessionConfig.h"
+#include "SslConfig.h"
 
 class SessionDataSet;
 
@@ -43,9 +44,8 @@ public:
   ~ThriftConnection();
 
   void init(const std::string& username, const std::string& password,
-            bool enableRPCCompression = false, bool useSSL = false,
-            const std::string& trustCertFilePath = "", const std::string& 
zoneId = std::string(),
-            const std::string& version = "V_1_0");
+            bool enableRPCCompression = false, const SslConfig& sslConfig = 
SslConfig(),
+            const std::string& zoneId = std::string(), const std::string& 
version = "V_1_0");
 
   std::unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql,
                                                         int64_t timeoutInMs = 
-1);
diff --git a/iotdb-client/client-cpp/src/session/Session.cpp 
b/iotdb-client/client-cpp/src/session/Session.cpp
index ab71732ebe1..e8cbc55d23c 100644
--- a/iotdb-client/client-cpp/src/session/Session.cpp
+++ b/iotdb-client/client-cpp/src/session/Session.cpp
@@ -563,7 +563,6 @@ Session::Session(const std::string& host, int rpcPort) : 
impl_(new Impl()) {
   impl_->host_ = host;
   impl_->rpcPort_ = rpcPort;
   impl_->initZoneId();
-  impl_->initNodesSupplier();
 }
 
 Session::Session(const std::vector<std::string>& nodeUrls, const std::string& 
username,
@@ -574,7 +573,6 @@ Session::Session(const std::vector<std::string>& nodeUrls, 
const std::string& us
   impl_->password_ = password;
   impl_->version = Version::V_1_0;
   impl_->initZoneId();
-  impl_->initNodesSupplier(impl_->nodeUrls_);
 }
 
 Session::Session(const std::string& host, int rpcPort, const std::string& 
username,
@@ -587,7 +585,6 @@ Session::Session(const std::string& host, int rpcPort, 
const std::string& userna
   impl_->fetchSize_ = iotdb::session::DEFAULT_FETCH_SIZE;
   impl_->version = Version::V_1_0;
   impl_->initZoneId();
-  impl_->initNodesSupplier();
 }
 
 Session::Session(const std::string& host, int rpcPort, const std::string& 
username,
@@ -601,7 +598,6 @@ Session::Session(const std::string& host, int rpcPort, 
const std::string& userna
   impl_->fetchSize_ = fetchSize;
   impl_->version = Version::V_1_0;
   impl_->initZoneId();
-  impl_->initNodesSupplier();
 }
 
 Session::Session(const std::string& host, const std::string& rpcPort, const 
std::string& username,
@@ -615,7 +611,6 @@ Session::Session(const std::string& host, const 
std::string& rpcPort, const std:
   impl_->fetchSize_ = fetchSize;
   impl_->version = Version::V_1_0;
   impl_->initZoneId();
-  impl_->initNodesSupplier();
 }
 
 Session::Session(AbstractSessionBuilder* builder) : impl_(new Impl()) {
@@ -632,10 +627,8 @@ Session::Session(AbstractSessionBuilder* builder) : 
impl_(new Impl()) {
   impl_->enableRedirection_ = builder->enableRedirections;
   impl_->connectTimeoutMs_ = builder->connectTimeoutMs;
   impl_->nodeUrls_ = builder->nodeUrls;
-  impl_->useSSL_ = builder->useSSL;
-  impl_->trustCertFilePath_ = builder->trustCertFilePath;
+  impl_->sslConfig_ = builder->getSslConfig();
   impl_->initZoneId();
-  impl_->initNodesSupplier(impl_->nodeUrls_);
 }
 
 void Session::setSqlDialect(const std::string& dialect) {
@@ -646,6 +639,14 @@ void Session::setDatabase(const std::string& database) {
   impl_->database_ = database;
 }
 
+void Session::setSslConfig(const SslConfig& sslConfig) {
+  if (!impl_->isClosed_) {
+    throw IoTDBException("SSL configuration cannot be changed after the 
Session is opened");
+  }
+  sslConfig.validate();
+  impl_->sslConfig_ = sslConfig;
+}
+
 std::string Session::getDatabase() {
   return impl_->database_;
 }
@@ -961,8 +962,7 @@ void Session::Impl::initNodesSupplier(const 
std::vector<std::string>& nodeUrls)
   }
 
   if (enableAutoFetch_) {
-    nodesSupplier_ =
-        NodesSupplier::create(endPoints, username_, password_, useSSL_, 
trustCertFilePath_);
+    nodesSupplier_ = NodesSupplier::create(endPoints, username_, password_, 
sslConfig_);
   } else {
     nodesSupplier_ = make_shared<StaticNodesSupplier>(endPoints);
   }
@@ -1147,8 +1147,10 @@ void Session::open(bool enableRPCCompression, int 
connectionTimeoutInMs) {
   }
 
   try {
+    impl_->initNodesSupplier(impl_->nodeUrls_);
     impl_->initDefaultSessionConnection();
   } catch (const exception& e) {
+    impl_->nodesSupplier_.reset();
     log_debug(e.what());
     throw IoTDBException(e.what());
   }
@@ -1188,9 +1190,11 @@ void Session::close() {
       impl_->defaultSessionConnection_.reset();
     }
   } catch (...) {
+    impl_->nodesSupplier_.reset();
     impl_->isClosed_ = true;
     throw;
   }
+  impl_->nodesSupplier_.reset();
   impl_->isClosed_ = true;
 }
 
@@ -2122,9 +2126,10 @@ bool Session::checkTimeseriesExists(const string& path) {
 }
 
 shared_ptr<SessionConnection> Session::Impl::getQuerySessionConnection() {
+  auto defaultSessionConnection = getDefaultSessionConnection();
   auto endPoint = nodesSupplier_->getQueryEndPoint();
   if (!endPoint.is_initialized() || endPointToSessionConnection.empty()) {
-    return getDefaultSessionConnection();
+    return defaultSessionConnection;
   }
 
   auto it = endPointToSessionConnection.find(endPoint.value());
diff --git a/iotdb-client/client-cpp/src/session/SessionC.cpp 
b/iotdb-client/client-cpp/src/session/SessionC.cpp
index 7365d6ff645..e5f8e67c979 100644
--- a/iotdb-client/client-cpp/src/session/SessionC.cpp
+++ b/iotdb-client/client-cpp/src/session/SessionC.cpp
@@ -154,6 +154,26 @@ static std::map<std::string, std::string> toStringMap(int 
count, const char* con
   return m;
 }
 
+static TsStatus setSslConfig(SslConfig& config, const char* trustCertFilePath,
+                             const char* clientCertificateFilePath,
+                             const char* clientPrivateKeyFilePath) {
+  if (trustCertFilePath == nullptr) {
+    return setError(TS_ERR_INVALID_PARAM, "trustCertFilePath is null");
+  }
+  if ((clientCertificateFilePath == nullptr) != (clientPrivateKeyFilePath == 
nullptr)) {
+    return setError(TS_ERR_INVALID_PARAM,
+                    "clientCertificateFilePath and clientPrivateKeyFilePath 
must both be null or "
+                    "both be set");
+  }
+  config.useSsl = true;
+  config.trustCertFilePath = trustCertFilePath;
+  config.clientCertificateFilePath =
+      clientCertificateFilePath == nullptr ? "" : clientCertificateFilePath;
+  config.clientPrivateKeyFilePath =
+      clientPrivateKeyFilePath == nullptr ? "" : clientPrivateKeyFilePath;
+  return TS_OK;
+}
+
 /**
  * Convert C typed values (void* const* values, TSDataType_C* types, int count)
  * to C++ vector<char*> that Session expects.
@@ -332,6 +352,26 @@ TsStatus ts_session_close(CSession* session) {
   }
 }
 
+TsStatus ts_session_set_ssl_config(CSession* session, const char* 
trustCertFilePath,
+                                   const char* clientCertificateFilePath,
+                                   const char* clientPrivateKeyFilePath) {
+  clearError();
+  if (!session)
+    return setError(TS_ERR_NULL_PTR, "session is null");
+  try {
+    SslConfig config;
+    TsStatus status = setSslConfig(config, trustCertFilePath, 
clientCertificateFilePath,
+                                   clientPrivateKeyFilePath);
+    if (status != TS_OK) {
+      return status;
+    }
+    session->cpp->setSslConfig(config);
+    return TS_OK;
+  } catch (const std::exception& e) {
+    return handleException(e);
+  }
+}
+
 /* ============================================================
  *  Session Lifecycle  —  Table Model
  * ============================================================ */
@@ -409,6 +449,35 @@ TsStatus ts_table_session_close(CTableSession* session) {
   }
 }
 
+CTableSession* ts_table_session_new_with_ssl(const char* host, int rpcPort, 
const char* username,
+                                             const char* password, const char* 
database,
+                                             const char* trustCertFilePath,
+                                             const char* 
clientCertificateFilePath,
+                                             const char* 
clientPrivateKeyFilePath) {
+  clearError();
+  try {
+    SslConfig config;
+    if (setSslConfig(config, trustCertFilePath, clientCertificateFilePath,
+                     clientPrivateKeyFilePath) != TS_OK) {
+      return nullptr;
+    }
+    TableSessionBuilder builder;
+    builder.host(std::string(host))
+        ->rpcPort(rpcPort)
+        ->username(std::string(username))
+        ->password(std::string(password))
+        ->database(std::string(database ? database : ""));
+    builder.sslConfig = config;
+    auto tableSession = builder.build();
+    auto* session = new CTableSession_();
+    session->cpp = std::move(tableSession);
+    return session;
+  } catch (const std::exception& e) {
+    handleException(e);
+    return nullptr;
+  }
+}
+
 /* ============================================================
  *  Timezone
  * ============================================================ */
diff --git a/iotdb-client/client-cpp/src/session/SessionPool.cpp 
b/iotdb-client/client-cpp/src/session/SessionPool.cpp
index a828f0ac2c6..c98a0fac365 100644
--- a/iotdb-client/client-cpp/src/session/SessionPool.cpp
+++ b/iotdb-client/client-cpp/src/session/SessionPool.cpp
@@ -100,12 +100,22 @@ SessionPool& 
SessionPool::setWaitToGetSessionTimeoutMs(int64_t timeoutMs) {
 }
 
 SessionPool& SessionPool::setUseSSL(bool useSSL) {
-  useSSL_ = useSSL;
+  sslConfig_.useSsl = useSSL;
   return *this;
 }
 
 SessionPool& SessionPool::setTrustCertFilePath(std::string path) {
-  trustCertFilePath_ = std::move(path);
+  sslConfig_.trustCertFilePath = std::move(path);
+  return *this;
+}
+
+SessionPool& SessionPool::setClientCertificateFilePath(std::string path) {
+  sslConfig_.clientCertificateFilePath = std::move(path);
+  return *this;
+}
+
+SessionPool& SessionPool::setClientPrivateKeyFilePath(std::string path) {
+  sslConfig_.clientPrivateKeyFilePath = std::move(path);
   return *this;
 }
 
@@ -124,8 +134,7 @@ std::shared_ptr<Session> SessionPool::constructNewSession() 
{
   builder.enableRedirections = enableRedirection_;
   builder.enableRPCCompression = enableRPCCompression_;
   builder.connectTimeoutMs = connectTimeoutMs_;
-  builder.useSSL = useSSL_;
-  builder.trustCertFilePath = trustCertFilePath_;
+  builder.sslConfig = sslConfig_;
 
   auto session = std::make_shared<Session>(&builder);
   session->open(enableRPCCompression_, connectTimeoutMs_);
diff --git a/iotdb-client/client-cpp/src/include/TableSession.h 
b/iotdb-client/client-cpp/src/session/SslConfig.cpp
similarity index 50%
copy from iotdb-client/client-cpp/src/include/TableSession.h
copy to iotdb-client/client-cpp/src/session/SslConfig.cpp
index d1eecfeeaba..e72bcb089b5 100644
--- a/iotdb-client/client-cpp/src/include/TableSession.h
+++ b/iotdb-client/client-cpp/src/session/SslConfig.cpp
@@ -17,30 +17,19 @@
  * under the License.
  */
 
-// This file is a translation of the Java file 
iotdb-client/session/src/main/java/org/apache/iotdb/session/TableSession.java
+#include "SslConfig.h"
 
-#ifndef IOTDB_TABLESESSION_H
-#define IOTDB_TABLESESSION_H
+#include <stdexcept>
 
-#include "Session.h"
-
-class TableSession {
-private:
-  std::shared_ptr<Session> session_;
-  string getDatabase();
-
-public:
-  TableSession(std::shared_ptr<Session> session) {
-    this->session_ = session;
+void SslConfig::validate() const {
+  if (!useSsl) {
+    return;
   }
-  ~TableSession() {}
-
-  void insert(Tablet& tablet, bool sorted = false);
-  void executeNonQueryStatement(const std::string& sql);
-  unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql);
-  unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql, 
int64_t timeoutInMs);
-  void open(bool enableRPCCompression = false);
-  void close();
-};
-
-#endif // IOTDB_TABLESESSION_H
\ No newline at end of file
+  if (trustCertFilePath.empty()) {
+    throw std::invalid_argument("trustCertFilePath is required when SSL is 
enabled");
+  }
+  if (clientCertificateFilePath.empty() != clientPrivateKeyFilePath.empty()) {
+    throw std::invalid_argument(
+        "clientCertificateFilePath and clientPrivateKeyFilePath must be 
configured together");
+  }
+}
diff --git a/iotdb-client/client-cpp/src/session/TableSession.cpp 
b/iotdb-client/client-cpp/src/session/TableSession.cpp
index 9cd80b7dd78..04283fc857f 100644
--- a/iotdb-client/client-cpp/src/session/TableSession.cpp
+++ b/iotdb-client/client-cpp/src/session/TableSession.cpp
@@ -43,4 +43,7 @@ void TableSession::open(bool enableRPCCompression) {
 }
 void TableSession::close() {
   session_->close();
-}
\ No newline at end of file
+}
+void TableSession::setSslConfig(const SslConfig& sslConfig) {
+  session_->setSslConfig(sslConfig);
+}
diff --git a/iotdb-client/client-cpp/test/CMakeLists.txt 
b/iotdb-client/client-cpp/test/CMakeLists.txt
index b4a7cf1f767..41deaf513d8 100644
--- a/iotdb-client/client-cpp/test/CMakeLists.txt
+++ b/iotdb-client/client-cpp/test/CMakeLists.txt
@@ -45,11 +45,22 @@ set(_test_targets
         session_c_relational_tests
         session_utils_tests)
 
+if(WITH_SSL)
+    list(APPEND _test_targets rpc_ssl_iotdb_tests)
+endif()
+
 add_executable(session_tests              main.cpp              
cpp/sessionIT.cpp)
 add_executable(session_relational_tests   main_Relational.cpp   
cpp/sessionRelationalIT.cpp)
 add_executable(session_c_tests            main_c.cpp            
cpp/sessionCIT.cpp)
 add_executable(session_c_relational_tests main_c_Relational.cpp 
cpp/sessionCRelationalIT.cpp)
 add_executable(session_utils_tests        main_utils.cpp        
cpp/sessionUtilsTest.cpp)
+if(WITH_SSL)
+    add_executable(rpc_ssl_iotdb_tests main_rpc_ssl.cpp 
cpp/RpcSslIotdbE2eTest.cpp)
+    file(TO_CMAKE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/fixtures" 
_iotdb_test_fixtures_dir)
+    target_compile_definitions(rpc_ssl_iotdb_tests PRIVATE
+            CATCH_CONFIG_NO_POSIX_SIGNALS=1
+            IOTDB_TEST_FIXTURES_DIR="${_iotdb_test_fixtures_dir}")
+endif()
 
 foreach(_t IN LISTS _test_targets)
     target_include_directories(${_t} PRIVATE
@@ -89,10 +100,23 @@ if(MSVC)
     add_test(NAME sessionCIT             CONFIGURATIONS Release COMMAND 
session_c_tests)
     add_test(NAME sessionCRelationalIT   CONFIGURATIONS Release COMMAND 
session_c_relational_tests)
     add_test(NAME sessionUtilsTest       CONFIGURATIONS Release COMMAND 
session_utils_tests)
+    if(WITH_SSL)
+        add_test(NAME rpcTlsIotdbTest CONFIGURATIONS Release COMMAND 
rpc_ssl_iotdb_tests "[tls]")
+        add_test(NAME rpcMutualTlsIotdbTest CONFIGURATIONS Release
+                COMMAND rpc_ssl_iotdb_tests "[tls],[mtls]")
+    endif()
     foreach(_t IN LISTS _test_targets)
         add_custom_command(TARGET ${_t} POST_BUILD
                 COMMAND ${CMAKE_COMMAND} -E copy_if_different
                         $<TARGET_FILE:iotdb_session> $<TARGET_FILE_DIR:${_t}>)
+        if(WITH_SSL)
+            _iotdb_collect_openssl_windows_dlls(_iotdb_ssl_runtime_dlls)
+            foreach(_ssl_dll IN LISTS _iotdb_ssl_runtime_dlls)
+                add_custom_command(TARGET ${_t} POST_BUILD
+                        COMMAND ${CMAKE_COMMAND} -E copy_if_different
+                                "${_ssl_dll}" $<TARGET_FILE_DIR:${_t}>)
+            endforeach()
+        endif()
     endforeach()
 else()
     add_test(NAME sessionIT              COMMAND session_tests)
@@ -100,10 +124,20 @@ else()
     add_test(NAME sessionCIT             COMMAND session_c_tests)
     add_test(NAME sessionCRelationalIT   COMMAND session_c_relational_tests)
     add_test(NAME sessionUtilsTest       COMMAND session_utils_tests)
+    if(WITH_SSL)
+        add_test(NAME rpcTlsIotdbTest COMMAND rpc_ssl_iotdb_tests "[tls]")
+        add_test(NAME rpcMutualTlsIotdbTest COMMAND rpc_ssl_iotdb_tests 
"[tls],[mtls]")
+    endif()
 endif()
 
 # Run sequentially: parallel ctest overloads the single local IoTDB instance.
 # sessionUtilsTest is a pure unit test and can run anytime.
 set_tests_properties(
         sessionIT sessionRelationalIT sessionCIT sessionCRelationalIT
-        PROPERTIES RUN_SERIAL TRUE)
+        PROPERTIES LABELS "plain" RUN_SERIAL TRUE)
+set_tests_properties(sessionUtilsTest PROPERTIES LABELS "plain")
+if(WITH_SSL)
+    set_tests_properties(rpcTlsIotdbTest PROPERTIES LABELS "tls-only" 
RUN_SERIAL TRUE)
+    set_tests_properties(rpcMutualTlsIotdbTest PROPERTIES
+            LABELS "mutual-auth" RUN_SERIAL TRUE ENVIRONMENT 
"IOTDB_CPP_SSL_MUTUAL_AUTH=1")
+endif()
diff --git a/iotdb-client/client-cpp/test/cpp/RpcSslIotdbE2eTest.cpp 
b/iotdb-client/client-cpp/test/cpp/RpcSslIotdbE2eTest.cpp
new file mode 100644
index 00000000000..88e97746397
--- /dev/null
+++ b/iotdb-client/client-cpp/test/cpp/RpcSslIotdbE2eTest.cpp
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <catch.hpp>
+
+#include <algorithm>
+#include <cstdlib>
+#include <memory>
+#include <string>
+
+#include "Session.h"
+#include "SessionBuilder.h"
+#include "SessionC.h"
+#include "SessionDataSet.h"
+#include "SessionImpl.h"
+#include "SessionPool.h"
+#include "TableSessionBuilder.h"
+
+class SessionTestAccessor {
+public:
+  static std::vector<TEndPoint> availableNodes(Session& session) {
+    return session.impl_->nodesSupplier_->getEndPointList();
+  }
+
+  static bool hasConnectionTo(Session& session, const TEndPoint& endpoint) {
+    return session.impl_->endPointToSessionConnection.find(endpoint) !=
+           session.impl_->endPointToSessionConnection.end();
+  }
+
+  static void makeInitialConnectionUnavailable(Session& session,
+                                               const TEndPoint& 
bootstrapEndpoint) {
+    auto initialConnection = session.impl_->defaultSessionConnection_;
+    initialConnection->close();
+    session.impl_->endPointToSessionConnection.clear();
+    session.impl_->endPointToSessionConnection.emplace(bootstrapEndpoint, 
initialConnection);
+  }
+};
+
+namespace {
+
+std::string fixture(const std::string& name) {
+  return std::string(IOTDB_TEST_FIXTURES_DIR) + "/tls/" + name;
+}
+
+bool mutualTlsEnabled() {
+  const char* value = std::getenv("IOTDB_CPP_SSL_MUTUAL_AUTH");
+  return value != nullptr && std::string(value) == "1";
+}
+
+template <typename Builder> void configureTls(Builder& builder) {
+  builder.useSSL(true)->trustCertFilePath(fixture("ca.crt"));
+  if (mutualTlsEnabled()) {
+    builder.clientCertificateFilePath(fixture("client.crt"))
+        ->clientPrivateKeyFilePath(fixture("client.key"));
+  }
+}
+
+SslConfig sslConfig() {
+  SslConfig config;
+  config.useSsl = true;
+  config.trustCertFilePath = fixture("ca.crt");
+  if (mutualTlsEnabled()) {
+    config.clientCertificateFilePath = fixture("client.crt");
+    config.clientPrivateKeyFilePath = fixture("client.key");
+  }
+  return config;
+}
+
+void requireDataSet(std::unique_ptr<SessionDataSet> dataSet) {
+  REQUIRE(dataSet != nullptr);
+  REQUIRE(dataSet->hasNext());
+  REQUIRE(dataSet->next() != nullptr);
+  dataSet->closeOperationHandle();
+}
+
+void requireCDataSet(CSessionDataSet* dataSet) {
+  REQUIRE(dataSet != nullptr);
+  REQUIRE(ts_dataset_has_next(dataSet));
+  CRowRecord* row = ts_dataset_next(dataSet);
+  REQUIRE(row != nullptr);
+  ts_row_record_destroy(row);
+  ts_dataset_destroy(dataSet);
+}
+
+} // namespace
+
+TEST_CASE("C++ APIs communicate with a TLS-enabled IoTDB", "[tls]") {
+  SessionBuilder treeBuilder;
+  
treeBuilder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root");
+  configureTls(treeBuilder);
+  auto treeSession = treeBuilder.build();
+  requireDataSet(treeSession->executeQueryStatement("SHOW VERSION"));
+  treeSession->close();
+
+  TableSessionBuilder tableBuilder;
+  
tableBuilder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root");
+  configureTls(tableBuilder);
+  auto tableSession = tableBuilder.build();
+  requireDataSet(tableSession->executeQueryStatement("SHOW VERSION"));
+  tableSession->close();
+
+  SessionPoolBuilder poolBuilder;
+  
poolBuilder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root");
+  configureTls(poolBuilder);
+  auto pool = poolBuilder.build();
+  auto pooledDataSet = pool->executeQueryStatement("SHOW VERSION");
+  REQUIRE(pooledDataSet->hasNext());
+  REQUIRE(pooledDataSet->next() != nullptr);
+  pool->close();
+}
+
+TEST_CASE("C APIs communicate with a TLS-enabled IoTDB", "[tls]") {
+  const std::string certificatePath = fixture("client.crt");
+  const std::string privateKeyPath = fixture("client.key");
+  const char* cert = mutualTlsEnabled() ? certificatePath.c_str() : nullptr;
+  const char* key = mutualTlsEnabled() ? privateKeyPath.c_str() : nullptr;
+
+  CSession* treeSession = ts_session_new("127.0.0.1", 6667, "root", "root");
+  REQUIRE(treeSession != nullptr);
+  REQUIRE(ts_session_set_ssl_config(treeSession, fixture("ca.crt").c_str(), 
cert, key) == TS_OK);
+  REQUIRE(ts_session_open(treeSession) == TS_OK);
+  CSessionDataSet* treeDataSet = nullptr;
+  REQUIRE(ts_session_execute_query(treeSession, "SHOW VERSION", &treeDataSet) 
== TS_OK);
+  requireCDataSet(treeDataSet);
+  REQUIRE(ts_session_close(treeSession) == TS_OK);
+  ts_session_destroy(treeSession);
+
+  CTableSession* tableSession = ts_table_session_new_with_ssl("127.0.0.1", 
6667, "root", "root", "",
+                                                              
fixture("ca.crt").c_str(), cert, key);
+  REQUIRE(tableSession != nullptr);
+  CSessionDataSet* tableDataSet = nullptr;
+  REQUIRE(ts_table_session_execute_query(tableSession, "SHOW VERSION", 
&tableDataSet) == TS_OK);
+  requireCDataSet(tableDataSet);
+  REQUIRE(ts_table_session_close(tableSession) == TS_OK);
+  ts_table_session_destroy(tableSession);
+}
+
+TEST_CASE("TLS node discovery uses the final SSL configuration and supports 
failover", "[tls]") {
+  std::vector<std::string> bootstrapNodes = {"127.0.0.1:6667"};
+  Session session(bootstrapNodes, "root", "root");
+  session.setSslConfig(sslConfig());
+  session.open();
+
+  auto discoveredNodes = SessionTestAccessor::availableNodes(session);
+  auto discovered =
+      std::find_if(discoveredNodes.begin(), discoveredNodes.end(), [](const 
TEndPoint& node) {
+        return node.ip == "localhost" && node.port == 6667;
+      });
+  REQUIRE(discovered != discoveredNodes.end());
+
+  TEndPoint bootstrapEndpoint;
+  bootstrapEndpoint.ip = "127.0.0.1";
+  bootstrapEndpoint.port = 6667;
+  REQUIRE(std::none_of(discoveredNodes.begin(), discoveredNodes.end(),
+                       [&bootstrapEndpoint](const TEndPoint& node) {
+                         return node.ip == bootstrapEndpoint.ip &&
+                                node.port == bootstrapEndpoint.port;
+                       }));
+
+  // Model an unavailable initial bootstrap after discovery. The query must
+  // establish a new TLS connection to the endpoint learned from the server.
+  SessionTestAccessor::makeInitialConnectionUnavailable(session, 
bootstrapEndpoint);
+  REQUIRE_FALSE(SessionTestAccessor::hasConnectionTo(session, *discovered));
+  requireDataSet(session.executeQueryStatement("SHOW VERSION"));
+  REQUIRE(SessionTestAccessor::hasConnectionTo(session, *discovered));
+  session.close();
+}
+
+TEST_CASE("mTLS server rejects a client without a certificate", "[mtls]") {
+  SessionBuilder builder;
+  builder.host("127.0.0.1")
+      ->rpcPort(6667)
+      ->username("root")
+      ->password("root")
+      ->useSSL(true)
+      ->trustCertFilePath(fixture("ca.crt"));
+  REQUIRE_THROWS(builder.build());
+}
diff --git a/iotdb-client/client-cpp/test/cpp/sessionIT.cpp 
b/iotdb-client/client-cpp/test/cpp/sessionIT.cpp
index 45624f0300d..0c04d6bab68 100644
--- a/iotdb-client/client-cpp/test/cpp/sessionIT.cpp
+++ b/iotdb-client/client-cpp/test/cpp/sessionIT.cpp
@@ -390,8 +390,7 @@ TEST_CASE("Session rejects SQL after close", 
"[sessionClose]") {
       
builder.host("127.0.0.1")->rpcPort(6667)->username("root")->password("root")->build();
   localSession->open();
   localSession->close();
-  REQUIRE_THROWS_AS(localSession->executeNonQueryStatement("show databases"),
-                    IoTDBConnectionException);
+  REQUIRE_THROWS_AS(localSession->executeQueryStatement("SHOW VERSION"), 
IoTDBConnectionException);
 }
 
 TEST_CASE("Test insertTablet ", "[testInsertTablet]") {
diff --git a/iotdb-client/client-cpp/test/cpp/sessionUtilsTest.cpp 
b/iotdb-client/client-cpp/test/cpp/sessionUtilsTest.cpp
index 18047a38868..e8d593c80d9 100644
--- a/iotdb-client/client-cpp/test/cpp/sessionUtilsTest.cpp
+++ b/iotdb-client/client-cpp/test/cpp/sessionUtilsTest.cpp
@@ -25,6 +25,19 @@
 
 using namespace std;
 
+TEST_CASE("Session query rejects states without an open connection", 
"[utils]") {
+  SECTION("before open") {
+    Session session("127.0.0.1", 6667);
+    REQUIRE_THROWS_AS(session.executeQueryStatement("SHOW VERSION"), 
IoTDBConnectionException);
+  }
+
+  SECTION("after failed open") {
+    Session session("127.0.0.1", 1);
+    REQUIRE_THROWS_AS(session.open(), IoTDBException);
+    REQUIRE_THROWS_AS(session.executeQueryStatement("SHOW VERSION"), 
IoTDBConnectionException);
+  }
+}
+
 TEST_CASE("SessionUtils filterNullColumns keeps only non-null FIELD columns", 
"[utils]") {
   vector<pair<string, TSDataType::TSDataType>> schemas = {{"s1", 
TSDataType::INT32},
                                                           {"s2", 
TSDataType::INT64},
@@ -62,8 +75,9 @@ TEST_CASE("SessionUtils filterNullColumns returns original 
when nothing to drop"
   REQUIRE(filtered.get() == &tablet);
 }
 
-TEST_CASE("SessionUtils filterNullColumns returns nullptr when tree-model 
FIELD columns are all null",
-          "[utils]") {
+TEST_CASE(
+    "SessionUtils filterNullColumns returns nullptr when tree-model FIELD 
columns are all null",
+    "[utils]") {
   vector<pair<string, TSDataType::TSDataType>> schemas = {{"s1", 
TSDataType::INT32},
                                                           {"s2", 
TSDataType::INT64}};
   Tablet tablet("root.sg.d1", schemas, 1);
diff --git a/iotdb-client/client-cpp/test/fixtures/tls/ca.crt 
b/iotdb-client/client-cpp/test/fixtures/tls/ca.crt
new file mode 100644
index 00000000000..b356ec36632
--- /dev/null
+++ b/iotdb-client/client-cpp/test/fixtures/tls/ca.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDETCCAfmgAwIBAgIUFNsUYuwzkISBNzd0RdZEMLRjhu8wDQYJKoZIhvcNAQEL
+BQAwGDEWMBQGA1UEAwwNSW9UREIgVGVzdCBDQTAeFw0yNjA3MDMwMzM0MzFaFw0z
+NjA2MzAwMzM0MzFaMBgxFjAUBgNVBAMMDUlvVERCIFRlc3QgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCutbmN5+3qN8hGPzIys3XH5sSTnBmXbGNO
+MViLiE8kysCfRMlc4ckHri/EdTsgH+V6mjf0rxuyH2+TkE7kATiYSU+a6EB/N1Fv
+hpkEi7pL8lProdtcyriTTE8PahjdbWnpTe8lNjQFbkhRnQaJr0R8DGEXpVdsAVez
+gcG5lruj0lYzZRIWhVxvSEzKTUnqaO83NcEqaRobTLj2uCmfLo4jLd4OQGf3J94w
+6ayhNfP7U4iQeReheI9YhDjNIgkClVKgmmiyQb0VfE+O/nL1OVOazybkEHXNA8So
+mN8MRafKFCSm+T1t9MBHHbcXp1tZRUHN0x4RjmAU8MPZiGQ55OsBAgMBAAGjUzBR
+MB0GA1UdDgQWBBQWSSihr7kDyU9GS6cQaPz5+vtwhTAfBgNVHSMEGDAWgBQWSSih
+r7kDyU9GS6cQaPz5+vtwhTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
+A4IBAQCT2TqltOY8slGpF+wB+pBELa1vcnZqajju5OO00uOYvkbKBEZ87wSO4Rag
+idRnKKeKPe3KcvT7DdR2z21hl36pt8neDC1b8OohL4quBcO21t7gUbmxvfcQLijd
+V9wd9sm8TriDIO/mx8ZsDwhu/dupGobboqy0r+C9t5/GsjdL17Kp30st0KffKsSs
+UxCaeL0sMD2tVQvx1a5BRrQl8IrLEzpVWDhVFiWY7iwimMX0c+bI2CcvWb8Hd/Km
+0l8Wt84XAYecWTVMEBLc/T5kj3DGv8S6TkAP4AgMn5Zjv59va+Pt7CoXbxmdjO8T
+agd3Xt8jfmKDNmnQkPmKTo5uuErR
+-----END CERTIFICATE-----
diff --git a/iotdb-client/client-cpp/test/fixtures/tls/client.crt 
b/iotdb-client/client-cpp/test/fixtures/tls/client.crt
new file mode 100644
index 00000000000..2d3dbc14fda
--- /dev/null
+++ b/iotdb-client/client-cpp/test/fixtures/tls/client.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICuzCCAaMCFBWG7ViMzmyrCBoJYpbg0zkC6USVMA0GCSqGSIb3DQEBCwUAMBgx
+FjAUBgNVBAMMDUlvVERCIFRlc3QgQ0EwHhcNMjYwNzAzMDMzNDMxWhcNMzYwNjMw
+MDMzNDMxWjAcMRowGAYDVQQDDBFJb1REQiBUZXN0IENsaWVudDCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANsjPpYWA5e0HKyUxbdoVtdYjtnJegHbHdz0
+I4hbvBDe5ySMdBIEUtNTb/zGmxb0nhkTjIxV9wh3Wb3JVhNJ4oaIclnIjfMWNc0/
+o+j8E+lce1VIV5CfZwiYUI6cilP7H4vkaGrTW14x0LcJgU8BhoQbzk5GzRdVcayc
+h+nDIsTfbMoT6Ag7dq2mS32Iq0F58IFP9ELT8cJ9Ue1mfWE74d+O5P/NtPU2CWdZ
+JXu4yka1Li8Ug3Jq+6I2LmDlBbiq+IjF5kj3iyDIBU34b2WdiOChuhaB4EyhiXf6
+j+nQM7D+N1CCf55AtfJKsiLtA3Dp73uL3OE7yr/e1scHxOG6SOECAwEAATANBgkq
+hkiG9w0BAQsFAAOCAQEAnCt5Ffs8FkKRq8SkFnqLgZX2M0mlfXe8SzQk+dFPX1s+
+/2A+6JkiZ9JniR22uryUt40B3Cq2U5zhsINVlR3voye1F8MjJxEtaIfPTTh8MI2L
+vyAQaIKtBj/VJX+tCiaYyO0tSCrAyBvdzArGcwcr3V0SdPxLzT7q4DrDM9F0uf1x
+dDUgn9inGDBpWXHNgnOLzqM7Xjzs4+vbZSCQBbYY9HTmyvp+NDFmTT8dKC2lvMZH
+Cugw0tTHv2N+wXwx33LUtAPxO5WRCZQ8PhWxJ0lGtV9MMJK2YvNyf9qCHGMiOgX0
+w4q1Gwh6ZTF9Nhsk7gNtDit+bDLb06gtA6oeNI/d8A==
+-----END CERTIFICATE-----
diff --git a/iotdb-client/client-cpp/test/fixtures/tls/client.key 
b/iotdb-client/client-cpp/test/fixtures/tls/client.key
new file mode 100644
index 00000000000..1686d990843
--- /dev/null
+++ b/iotdb-client/client-cpp/test/fixtures/tls/client.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDbIz6WFgOXtBys
+lMW3aFbXWI7ZyXoB2x3c9COIW7wQ3uckjHQSBFLTU2/8xpsW9J4ZE4yMVfcId1m9
+yVYTSeKGiHJZyI3zFjXNP6Po/BPpXHtVSFeQn2cImFCOnIpT+x+L5Ghq01teMdC3
+CYFPAYaEG85ORs0XVXGsnIfpwyLE32zKE+gIO3atpkt9iKtBefCBT/RC0/HCfVHt
+Zn1hO+HfjuT/zbT1NglnWSV7uMpGtS4vFINyavuiNi5g5QW4qviIxeZI94sgyAVN
++G9lnYjgoboWgeBMoYl3+o/p0DOw/jdQgn+eQLXySrIi7QNw6e97i9zhO8q/3tbH
+B8ThukjhAgMBAAECggEAH3DoGuqfq1V5Q724vH7o7s7S+CZzLe79UuVob7kRu63v
+pgvM34TlSVLQX4kzWVDRmjF22e+/mORe6N8JTY0tRjYvifg/faAzKfa2ksgQJ0xQ
+mcTeY26rfs0zybJmGnSOayjjXmhi1Jn7Izfm6KoEXdILgKmh5XYp8CUpTv3jcDGG
+MgdlqxG8rakJ4NHtO6qjgbaEAEsI7JbJj0T+7YPPD42KWvy9f9LYUSN0eCO/7TWw
+Cvvl6NX55tz+WwpWdDtIKVjWZRnx4ZA3cZizWGZfSDaoRJWfDTcUKJ4zXHDdxXRL
+6ha0cD7N6HJtQFvrxnyH/Uqpnm9rTKhfHvj5uTmaLQKBgQDdufi+60dIjVzQxYhy
+4w+BUaI5PQCN0naX+uvlUBzlctrImWQyGsLj2yDbo9IuDBU6qaGPC0Sl29ywBf7W
+QxsWbxE/rb9MKO2SEdLRq45W/H/Llr1IV494upWnDpWgvanBeanITveU9HA0/Fm4
+U0PrfExeBXca0dTfAD07Jr3gfwKBgQD9AtTKku/+jXfpQe4IPtZ8rjG6Ezg6KCw9
+JQVwHQaTked82Fj/1F6BiutVQwbQ6UI8FfZ7uF239Cw2O/PI28zpaCtOUPCP5TOI
+A1LdwhJAtogfXK1vSX4qxog4sNwmlboxAMixdSZGuBfO/vUxL6nQb+OH3g3gTqS5
+CjnKAQcmnwKBgQCAkASbLvD2MIFQzDiB5QZohVz6s1RO52m8VdHR9NHMePxCtC5U
+nw/B7pzuvd5wtLDaguEaf/4d7Y3YwqEwu1hJeb0Wnzf8gP6/Y3ZJ/J9b8Kxo785w
+09RsvENpyhsYSODVPiYj7yW/SLyG/ItJRX5sXHYrTh/xfRlg9FKMqboPIQKBgQDz
+0K2kxTKXOFbspocu1Pc20VrEOM8/ZAU1qx5xatcykDDmo0ooxsuHxIqB8IR5/76/
+Tl7n3MQbiCau4NlNn1r5NlQ9NUyNLk+Za7KIVwPl7sCAkHvluYnmyMju8KhGWpVB
+scK1F/KZxb/TzugTzR206o32GWt/0+lzE8KawqDUewKBgCIc9mkWBtyA5Z7qKDmZ
+6yaKs5210GzXGHBccVn6ABzV9BsWh+9r8guT2WxH6q+i4KBmFkRAk1u/AK7o/WOi
+2HdOTMgQe5j9Jxnzr6sOQcSHJLYblKbkzGonJ0eEiZH2qFtjjPIz6lqSiuXK6PPr
+fBH/Y8bZLE6KjrsBiqlzUiqV
+-----END PRIVATE KEY-----
diff --git a/iotdb-client/client-cpp/test/fixtures/tls/tls-server.p12 
b/iotdb-client/client-cpp/test/fixtures/tls/tls-server.p12
new file mode 100644
index 00000000000..53ac363590c
Binary files /dev/null and 
b/iotdb-client/client-cpp/test/fixtures/tls/tls-server.p12 differ
diff --git a/iotdb-client/client-cpp/src/include/TableSession.h 
b/iotdb-client/client-cpp/test/main_rpc_ssl.cpp
similarity index 50%
copy from iotdb-client/client-cpp/src/include/TableSession.h
copy to iotdb-client/client-cpp/test/main_rpc_ssl.cpp
index d1eecfeeaba..ebf4ea96fcd 100644
--- a/iotdb-client/client-cpp/src/include/TableSession.h
+++ b/iotdb-client/client-cpp/test/main_rpc_ssl.cpp
@@ -17,30 +17,5 @@
  * under the License.
  */
 
-// This file is a translation of the Java file 
iotdb-client/session/src/main/java/org/apache/iotdb/session/TableSession.java
-
-#ifndef IOTDB_TABLESESSION_H
-#define IOTDB_TABLESESSION_H
-
-#include "Session.h"
-
-class TableSession {
-private:
-  std::shared_ptr<Session> session_;
-  string getDatabase();
-
-public:
-  TableSession(std::shared_ptr<Session> session) {
-    this->session_ = session;
-  }
-  ~TableSession() {}
-
-  void insert(Tablet& tablet, bool sorted = false);
-  void executeNonQueryStatement(const std::string& sql);
-  unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql);
-  unique_ptr<SessionDataSet> executeQueryStatement(const std::string& sql, 
int64_t timeoutInMs);
-  void open(bool enableRPCCompression = false);
-  void close();
-};
-
-#endif // IOTDB_TABLESESSION_H
\ No newline at end of file
+#define CATCH_CONFIG_MAIN
+#include <catch.hpp>
diff --git a/iotdb-client/client-cpp/test/scripts/configure_iotdb_ssl_it.py 
b/iotdb-client/client-cpp/test/scripts/configure_iotdb_ssl_it.py
new file mode 100644
index 00000000000..fcd8d193a4f
--- /dev/null
+++ b/iotdb-client/client-cpp/test/scripts/configure_iotdb_ssl_it.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Configure an IoTDB distribution for C++ TLS integration tests."""
+
+from __future__ import annotations
+
+import re
+import shutil
+import subprocess
+import sys
+from pathlib import Path
+
+STORE_PASSWORD = "thrift"
+
+
+def replace_property(text: str, key: str, value: str) -> str:
+    pattern = re.compile(rf"^{re.escape(key)}=.*$", re.MULTILINE)
+    replacement = f"{key}={value}"
+    if pattern.search(text):
+        return pattern.sub(replacement, text, count=1)
+    return text.rstrip() + "\n" + replacement + "\n"
+
+
+def configure(dist_root: Path, fixtures_root: Path, mode: str) -> None:
+    properties = dist_root / "conf" / "iotdb-system.properties"
+    if mode == "plain":
+        text = properties.read_text(encoding="utf-8")
+        for key, value in {
+            "dn_rpc_address": "127.0.0.1",
+            "enable_thrift_ssl": "false",
+            "thrift_ssl_client_auth": "false",
+            "key_store_path": "",
+            "key_store_pwd": "",
+            "trust_store_path": "",
+            "trust_store_pwd": "",
+            "ssl_protocol": "TLS",
+        }.items():
+            text = replace_property(text, key, value)
+        properties.write_text(text, encoding="utf-8", newline="\n")
+        return
+
+    mutual_tls = mode == "mtls"
+    ssl_dir = dist_root / "conf" / "cpp-ssl-it"
+    ssl_dir.mkdir(parents=True, exist_ok=True)
+
+    server_store = ssl_dir / "tls-server.p12"
+    shutil.copy2(fixtures_root / "tls" / "tls-server.p12", server_store)
+
+    trust_store = ""
+    if mutual_tls:
+        trust_store_path = ssl_dir / "tls-server-trust.p12"
+        trust_store_path.unlink(missing_ok=True)
+        subprocess.run(
+            [
+                "keytool",
+                "-importcert",
+                "-noprompt",
+                "-alias",
+                "cpp-ssl-it-ca",
+                "-file",
+                str(fixtures_root / "tls" / "ca.crt"),
+                "-keystore",
+                str(trust_store_path),
+                "-storetype",
+                "PKCS12",
+                "-storepass",
+                STORE_PASSWORD,
+            ],
+            check=True,
+        )
+        trust_store = trust_store_path.as_posix()
+
+    text = properties.read_text(encoding="utf-8")
+    settings = {
+        "dn_rpc_address": "localhost",
+        "enable_thrift_ssl": "true",
+        "thrift_ssl_client_auth": str(mutual_tls).lower(),
+        "key_store_path": server_store.as_posix(),
+        "key_store_pwd": STORE_PASSWORD,
+        "trust_store_path": trust_store,
+        "trust_store_pwd": STORE_PASSWORD if mutual_tls else "",
+        "ssl_protocol": "TLS",
+    }
+    for key, value in settings.items():
+        text = replace_property(text, key, value)
+    properties.write_text(text, encoding="utf-8", newline="\n")
+
+
+def main() -> int:
+    if len(sys.argv) != 4 or sys.argv[3] not in ("plain", "tls", "mtls"):
+        print(
+            "usage: configure_iotdb_ssl_it.py <dist-root> <fixtures-root> 
<plain|tls|mtls>",
+            file=sys.stderr,
+        )
+        return 2
+    configure(Path(sys.argv[1]).resolve(), Path(sys.argv[2]).resolve(), 
sys.argv[3])
+    return 0
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())
diff --git a/iotdb-client/client-cpp/test/scripts/run_cpp_it_phases.py 
b/iotdb-client/client-cpp/test/scripts/run_cpp_it_phases.py
new file mode 100644
index 00000000000..ee342397d3a
--- /dev/null
+++ b/iotdb-client/client-cpp/test/scripts/run_cpp_it_phases.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Run C++ client tests against plain TLS and mutual TLS IoTDB servers."""
+
+from __future__ import annotations
+
+import argparse
+import os
+import socket
+import subprocess
+import sys
+import time
+from pathlib import Path
+
+
+def run(
+    command: list[str],
+    cwd: Path,
+    env: dict[str, str] | None = None,
+    shell: bool = False,
+) -> None:
+    print(f"+ {' '.join(command)}", flush=True)
+    subprocess.run(command, cwd=cwd, env=env, shell=shell, check=True)
+
+
+def server_script(dist_root: Path, action: str) -> Path:
+    if sys.platform == "win32":
+        return dist_root / "sbin" / "windows" / f"{action}-standalone.bat"
+    return dist_root / "sbin" / f"{action}-standalone.sh"
+
+
+def stop_server(dist_root: Path, env: dict[str, str]) -> None:
+    subprocess.run(
+        [str(server_script(dist_root, "stop"))],
+        cwd=dist_root,
+        env=env,
+        shell=sys.platform == "win32",
+        check=False,
+    )
+    time.sleep(10)
+
+
+def wait_for_rpc_port(timeout_seconds: int) -> None:
+    deadline = time.monotonic() + timeout_seconds
+    consecutive_successes = 0
+    while time.monotonic() < deadline:
+        try:
+            with socket.create_connection(("127.0.0.1", 6667), timeout=1):
+                consecutive_successes += 1
+                if consecutive_successes == 3:
+                    return
+        except OSError:
+            consecutive_successes = 0
+        time.sleep(1)
+    raise TimeoutError(f"IoTDB RPC port did not become ready within 
{timeout_seconds} seconds")
+
+
+def start_server(dist_root: Path, wait_seconds: int, env: dict[str, str]) -> 
None:
+    run(
+        [str(server_script(dist_root, "start"))],
+        dist_root,
+        env=env,
+        shell=sys.platform == "win32",
+    )
+    wait_for_rpc_port(wait_seconds)
+
+
+def main() -> int:
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("build_dir", type=Path)
+    parser.add_argument("dist_root", type=Path)
+    parser.add_argument("fixtures_root", type=Path)
+    parser.add_argument("--config", default="Release")
+    parser.add_argument("--wait-seconds", type=int, default=120)
+    parser.add_argument(
+        "--modes",
+        nargs="+",
+        choices=("plain", "tls", "mtls"),
+        default=("plain", "tls", "mtls"),
+    )
+    args = parser.parse_args()
+
+    build_dir = args.build_dir.resolve()
+    dist_root = args.dist_root.resolve()
+    fixtures_root = args.fixtures_root.resolve()
+    configure_script = Path(__file__).with_name("configure_iotdb_ssl_it.py")
+    ctest = ["ctest", "--output-on-failure", "-C", args.config]
+    server_env = os.environ.copy()
+    if sys.platform == "win32":
+        # Node scripts pause after the Java process exits so an interactive 
console
+        # stays open. In CI those paused cmd.exe children keep the job's 
standard
+        # handles open after the tests finish, preventing Maven from returning.
+        server_env["IOTDB_NO_PAUSE"] = "1"
+
+    for mode in args.modes:
+        stop_server(dist_root, server_env)
+        run(
+            [sys.executable, str(configure_script), str(dist_root), 
str(fixtures_root), mode],
+            configure_script.parent,
+        )
+        try:
+            start_server(dist_root, args.wait_seconds, server_env)
+            env = server_env.copy()
+            if mode == "mtls":
+                env["IOTDB_CPP_SSL_MUTUAL_AUTH"] = "1"
+            label = {"plain": "plain", "tls": "tls-only", "mtls": 
"mutual-auth"}[mode]
+            run(ctest + ["-L", label], build_dir, env)
+        finally:
+            stop_server(dist_root, server_env)
+    return 0
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())
diff --git a/iotdb-client/client-cpp/third-party/README.md 
b/iotdb-client/client-cpp/third-party/README.md
index a430adc30c5..1817c0fe11f 100644
--- a/iotdb-client/client-cpp/third-party/README.md
+++ b/iotdb-client/client-cpp/third-party/README.md
@@ -68,8 +68,8 @@ Alternatively copy files manually from the URLs listed in
 
 | Platform   | Typical files |
 |------------|---------------|
-| `linux/`   | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, 
`m4-1.4.19.tar.gz`, `flex-2.6.4.tar.gz`, `bison-3.8.tar.gz` (+ 
`openssl-3.5.0.tar.gz` only when `WITH_SSL=ON` and no system OpenSSL is 
present) |
-| `mac/`     | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz` (Xcode CLT 
usually provides m4/flex/bison) |
-| `windows/` | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, 
`win_flex_bison-2.5.25.zip` (or any `win_flex_bison*.zip`; skip if flex/bison 
already on `PATH`) |
+| `linux/`   | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, 
`m4-1.4.19.tar.gz`, `flex-2.6.4.tar.gz`, `bison-3.8.tar.gz`, 
`openssl-3.5.8.tar.gz` when `WITH_SSL=ON` |
+| `mac/`     | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, 
`openssl-3.5.8.tar.gz` (Xcode CLT usually provides m4/flex/bison) |
+| `windows/` | `thrift-0.24.0.tar.gz`, `boost_1_60_0.tar.gz`, 
`openssl-3.5.8.tar.gz`, `win_flex_bison-2.5.25.zip` (or any 
`win_flex_bison*.zip`; skip if flex/bison already on `PATH`) |
 
 Download URLs: see the *Offline build* table in [`README.md`](../README.md).
diff --git a/pom.xml b/pom.xml
index 8a4e3dda50b..e23bf05a485 100644
--- a/pom.xml
+++ b/pom.xml
@@ -766,6 +766,10 @@
                             <!-- bundled third-party NOTICE / license texts 
for the C++ client package -->
                             
<exclude>**/package-metadata/third_party/NOTICE</exclude>
                             
<exclude>**/package-metadata/third_party/licenses/**</exclude>
+                            <!-- generated certificates and private keys used 
only by C++ TLS tests -->
+                            <exclude>**/client-cpp/test/fixtures/**</exclude>
+                            <exclude>test/fixtures/**/*.crt</exclude>
+                            <exclude>test/fixtures/**/*.key</exclude>
                             <!-- bundled third-party NOTICE / license texts 
for the AINode package -->
                             
<exclude>**/ainode/src/assembly/resources/NOTICE-binary</exclude>
                             
<exclude>**/ainode/src/assembly/resources/licenses/**</exclude>
diff --git a/scripts/sbin/windows/start-confignode.bat 
b/scripts/sbin/windows/start-confignode.bat
index 3cedfe3c6a9..d6b68aecaa3 100644
--- a/scripts/sbin/windows/start-confignode.bat
+++ b/scripts/sbin/windows/start-confignode.bat
@@ -174,8 +174,8 @@ goto finally
 
 :err
 echo JAVA_HOME environment variable must be set!
-pause
+if not defined IOTDB_NO_PAUSE pause
 
 :finally
 @ENDLOCAL
-pause
+if not defined IOTDB_NO_PAUSE pause
diff --git a/scripts/sbin/windows/start-datanode.bat 
b/scripts/sbin/windows/start-datanode.bat
index 761641c22fc..20675e362c3 100755
--- a/scripts/sbin/windows/start-datanode.bat
+++ b/scripts/sbin/windows/start-datanode.bat
@@ -217,10 +217,10 @@ goto finally
 
 :err
 echo JAVA_HOME environment variable must be set!
-pause
+if not defined IOTDB_NO_PAUSE pause
 
 
 @REM 
-----------------------------------------------------------------------------
 :finally
 @ENDLOCAL
-pause
+if not defined IOTDB_NO_PAUSE pause
diff --git a/scripts/sbin/windows/start-standalone.bat 
b/scripts/sbin/windows/start-standalone.bat
index 9020539b8b9..abba8fc4eb6 100644
--- a/scripts/sbin/windows/start-standalone.bat
+++ b/scripts/sbin/windows/start-standalone.bat
@@ -38,7 +38,12 @@ IF EXIST "%IOTDB_HOME%\sbin\windows\start-datanode.bat" (
 )
 
 start cmd /c %CONFIGNODE_START_PATH%
-TIMEOUT /T 5 /NOBREAK
+if defined IOTDB_NO_PAUSE (
+  @REM TIMEOUT fails immediately when CI redirects stdin, so use a 
non-interactive delay.
+  ping 127.0.0.1 -n 6 >NUL
+) ELSE (
+  TIMEOUT /T 5 /NOBREAK
+)
 start cmd /c %DATANODE_START_PATH%
 @REM if you have turned on "-XX:+SafepointTimeout" and 
"-XX:SafepointTimeoutDelay=1000", you can use commands below instead to see 
safepoint logs
 @REM SET LOG_SAFEPOINT_PATH=%IOTDB_HOME%\logs\log_datanode_safepoint.log

Reply via email to