diff --git a/.clang-format b/.clang-format index 525f5600..5846e195 100644 --- a/.clang-format +++ b/.clang-format @@ -28,7 +28,7 @@ SpacesInAngles: 'false' SpacesInContainerLiterals: 'false' SpacesInParentheses: 'false' SpacesInSquareBrackets: 'false' -Standard: c++17 +Standard: c++20 UseTab: Never SortIncludes: true ColumnLimit: 100 diff --git a/.drone.jsonnet b/.drone.jsonnet index 4876ce13..5257565c 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -1,5 +1,3 @@ -local docker_base = 'registry.oxen.rocks/lokinet-ci-'; - local submodule_commands = [ 'git fetch --tags', 'git submodule update --init --recursive --depth=1 --jobs=4', @@ -12,20 +10,38 @@ local submodules = { local apt_get_quiet = 'apt-get -o=Dpkg::Use-Pty=0 -q'; -local default_deps_nocxx = []; +local libngtcp2_deps = ['libgnutls28-dev', 'libprotobuf-dev', 'libngtcp2-dev', 'libngtcp2-crypto-gnutls-dev']; + +local default_deps_nocxx = [ + 'nlohmann-json3-dev', +] + libngtcp2_deps; + local default_deps = ['g++'] + default_deps_nocxx; -local docker_base = 'registry.oxen.rocks/lokinet-ci-'; +local default_test_deps = libngtcp2_deps; + +local docker_base = 'registry.oxen.rocks/'; + +// cmake options for static deps mirror +local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https://oxen.rocks/deps ' else ''); + +local debian_backports(distro, pkgs) = [ + 'echo "deb http://deb.debian.org/debian ' + distro + '-backports main" >/etc/apt/sources.list.d/' + distro + '-backports.list', + 'eatmydata ' + apt_get_quiet + ' update', + 'eatmydata ' + apt_get_quiet + ' install -y ' + std.join(' ', std.map(function(x) x + '/' + distro + '-backports', pkgs)), +]; // Do something on a debian-like system local debian_pipeline(name, image, arch='amd64', deps=default_deps, - oxen_repo=false, + oxen_repo=true, kitware_repo=''/* ubuntu codename, if wanted */, allow_fail=false, + cmake_pkg='cmake', build=['echo "Error: drone build argument not set"', 'exit 1'], + extra_setup=[], extra_steps=[]) = { kind: 'pipeline', @@ -39,7 +55,7 @@ local debian_pipeline(name, image: image, pull: 'always', [if allow_fail then 'failure']: 'ignore', - environment: { SSH_KEY: { from_secret: 'SSH_KEY' } }, + environment: { SSH_KEY: { from_secret: 'SSH_KEY' }, WINEDEBUG: '-all' }, commands: [ 'echo "Building on ${DRONE_STAGE_MACHINE}"', 'echo "man-db man-db/auto-update boolean false" | debconf-set-selections', @@ -59,9 +75,9 @@ local debian_pipeline(name, 'echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ ' + kitware_repo + ' main" >/etc/apt/sources.list.d/kitware.list', 'eatmydata ' + apt_get_quiet + ' update', ] else [] - ) + [ + ) + extra_setup + [ 'eatmydata ' + apt_get_quiet + ' dist-upgrade -y', - 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y cmake make git ccache ' + std.join(' ', deps), + 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y ' + cmake_pkg + ' make git ccache ca-certificates ' + std.join(' ', deps), ] + build, }, ] + extra_steps, @@ -72,14 +88,18 @@ local debian_build(name, image, arch='amd64', deps=default_deps, + test_deps=default_test_deps, build_type='Release', lto=false, werror=true, cmake_extra='', + local_mirror=true, + shared_libs=true, jobs=6, tests=true, - oxen_repo=false, + oxen_repo=true, kitware_repo=''/* ubuntu codename, if wanted */, + extra_setup=[], allow_fail=false) = debian_pipeline( name, @@ -94,21 +114,38 @@ local debian_build(name, 'cd build', 'cmake .. -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always -DCMAKE_BUILD_TYPE=' + build_type + ' ' + (if werror then '-DWARNINGS_AS_ERRORS=ON ' else '') + + (if shared_libs then '-DBUILD_SHARED_LIBS=ON ' else '') + '-DUSE_LTO=' + (if lto then 'ON ' else 'OFF ') + '-DWITH_TESTS=' + (if tests then 'ON ' else 'OFF ') + - cmake_extra, + cmake_extra + + ci_dep_mirror(local_mirror), 'make VERBOSE=1 -j' + jobs, ], + extra_setup=extra_setup, extra_steps=(if tests then [{ name: 'tests', image: image, pull: 'always', [if allow_fail then 'failure']: 'ignore', - commands: [ - 'cd build', - './tests/testAll --colour-mode ansi', - ], + commands: + [apt_get_quiet + ' install -y eatmydata'] + ( + if std.length(test_deps) > 0 then ( + if oxen_repo then [ + 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y lsb-release', + 'cp utils/deb.oxen.io.gpg /etc/apt/trusted.gpg.d', + 'echo deb http://deb.oxen.io $$(lsb_release -sc) main >/etc/apt/sources.list.d/oxen.list', + ] else [] + ) + [ + 'eatmydata ' + apt_get_quiet + ' update', + 'eatmydata ' + apt_get_quiet + ' dist-upgrade -y', + 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y ' + std.join(' ', test_deps), + ] else [] + ) + [ + 'cd build', + './tests/testLogging --colour-mode ansi -d yes', + './tests/testAll --colour-mode ansi -d yes', + ], }] else []) ); // windows cross compile on debian @@ -119,6 +156,7 @@ local windows_cross_pipeline(name, lto=false, werror=false, cmake_extra='', + local_mirror=true, jobs=6, tests=true, winarch='x86-64', @@ -130,6 +168,7 @@ local windows_cross_pipeline(name, allow_fail=allow_fail, deps=[ 'g++-mingw-w64-' + winarch + '-posix', + 'wine', ], build=[ 'mkdir build', @@ -139,7 +178,8 @@ local windows_cross_pipeline(name, '-DUSE_LTO=' + (if lto then 'ON ' else 'OFF ') + '-DWITH_TESTS=' + (if tests then 'ON ' else 'OFF ') + '-DCMAKE_TOOLCHAIN_FILE=../cmake/mingw-' + winarch + '-toolchain.cmake ' + - cmake_extra, + cmake_extra + + ci_dep_mirror(local_mirror), 'make VERBOSE=1 -j' + jobs, ], extra_steps=(if tests then @@ -152,23 +192,26 @@ local windows_cross_pipeline(name, commands: [ apt_get_quiet + ' install -y --no-install-recommends wine64', 'cd build', - 'wine-stable ./tests/testAll.exe --colour-mode ansi', + 'wine-stable ./tests/testLogging.exe --colour-mode ansi -d yes', + 'wine-stable ./tests/testAll.exe --colour-mode ansi -d yes', ], }] else []) ); local clang(version) = debian_build( - 'Debian sid/clang-' + version + ' (amd64)', + 'Debian sid/clang-' + version, docker_base + 'debian-sid-clang', deps=['clang-' + version] + default_deps_nocxx, - cmake_extra='-DCMAKE_C_COMPILER=clang-' + version + ' -DCMAKE_CXX_COMPILER=clang++-' + version + ' ' + cmake_extra='-DCMAKE_C_COMPILER=clang-' + version + + ' -DCMAKE_CXX_COMPILER=clang++-' + version ); local full_llvm(version) = debian_build( - 'Debian sid/llvm-' + version + ' (amd64)', + 'Debian sid/llvm-' + version, docker_base + 'debian-sid-clang', deps=['clang-' + version, ' lld-' + version, ' libc++-' + version + '-dev', 'libc++abi-' + version + '-dev'] + default_deps_nocxx, + shared_libs=false, cmake_extra='-DCMAKE_C_COMPILER=clang-' + version + ' -DCMAKE_CXX_COMPILER=clang++-' + version + ' -DCMAKE_CXX_FLAGS="-stdlib=libc++ -fcolor-diagnostics" ' + @@ -180,6 +223,7 @@ local full_llvm(version) = debian_build( // Macos build local mac_pipeline(name, + arch='amd64', allow_fail=false, build=['echo "Error: drone build argument not set"', 'exit 1'], extra_steps=[]) @@ -187,7 +231,7 @@ local mac_pipeline(name, kind: 'pipeline', type: 'exec', name: name, - platform: { os: 'darwin', arch: 'amd64' }, + platform: { os: 'darwin', arch: arch }, steps: [ { name: 'submodules', commands: submodule_commands }, { @@ -203,21 +247,25 @@ local mac_pipeline(name, ] + extra_steps, }; local mac_builder(name, + arch='amd64', build_type='Release', werror=true, lto=false, cmake_extra='', + local_mirror=true, jobs=6, tests=true, allow_fail=false) - = mac_pipeline(name, allow_fail=allow_fail, build=[ + = mac_pipeline(name, arch=arch, allow_fail=allow_fail, build=[ 'mkdir build', 'cd build', 'cmake .. -DCMAKE_CXX_FLAGS=-fcolor-diagnostics -DCMAKE_BUILD_TYPE=' + build_type + ' ' + (if werror then '-DWARNINGS_AS_ERRORS=ON ' else '') + + '-DBUILD_SHARED_LIBS=ON ' + '-DUSE_LTO=' + (if lto then 'ON ' else 'OFF ') + '-DWITH_TESTS=' + (if tests then 'ON ' else 'OFF ') + - cmake_extra, + cmake_extra + + ci_dep_mirror(local_mirror), 'VERBOSE=1 make -j' + jobs, ], extra_steps= (if tests then @@ -226,7 +274,8 @@ local mac_builder(name, [if allow_fail then 'failure']: 'ignore', commands: [ 'cd build', - './tests/testAll --colour-mode ansi', + './tests/testLogging --colour-mode ansi -d yes', + './tests/testAll --colour-mode ansi -d yes', ], }] else [])); @@ -240,15 +289,19 @@ local static_build(name, oxen_repo=false, kitware_repo=''/* ubuntu codename, if wanted */, cmake_extra='', + local_mirror=true, + allow_fail=false, jobs=6) = debian_pipeline( name, image, arch=arch, deps=deps, + oxen_repo=oxen_repo, + allow_fail=allow_fail, build=[ 'export JOBS=' + jobs, - './utils/static-bundle.sh build ' + archive_name + ' -DSTATIC_LIBSTD=ON ' + cmake_extra, + './utils/static-bundle.sh build ' + archive_name + ' -DSTATIC_LIBSTD=ON ' + cmake_extra + ci_dep_mirror(local_mirror), 'cd build && ../utils/ci/drone-static-upload.sh', ] ); @@ -266,7 +319,7 @@ local static_build(name, 'echo "Building on ${DRONE_STAGE_MACHINE}"', apt_get_quiet + ' update', apt_get_quiet + ' install -y eatmydata', - 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y git clang-format-15 jsonnet', + 'eatmydata ' + apt_get_quiet + ' install --no-install-recommends -y git clang-format-19 jsonnet', './utils/ci/drone-format-verify.sh', ], }], @@ -278,17 +331,18 @@ local static_build(name, type: 'docker', steps: [{ name: 'build', - image: 'node:19-bullseye', + image: docker_base + 'debian-stable', pull: 'always', environment: { SSH_KEY: { from_secret: 'SSH_KEY' } }, commands: [ 'echo "Building on ${DRONE_STAGE_MACHINE}"', apt_get_quiet + ' update', - apt_get_quiet + ' install -y python3-requests rsync', - 'npm i docsify-cli -g', - 'npm i docsify -g', + apt_get_quiet + ' install -y rsync python3-venv', 'cd docs/api/', - 'make', + 'python3 -m venv .venv', + '. .venv/bin/activate', + 'pip install -r requirements.txt', + 'make build-all', '../../utils/ci/drone-docs-upload.sh', ], }], @@ -296,46 +350,43 @@ local static_build(name, }, // Various debian builds - debian_build('Debian sid (amd64)', docker_base + 'debian-sid'), - debian_build('Debian sid/Debug (amd64)', docker_base + 'debian-sid', build_type='Debug'), - clang(14), - full_llvm(14), + debian_build('Debian sid', docker_base + 'debian-sid'), + debian_build('Debian sid/Debug', docker_base + 'debian-sid', build_type='Debug'), + debian_build('Debian testing', docker_base + 'debian-testing'), + clang(17), + full_llvm(17), debian_build('Debian stable (i386)', docker_base + 'debian-stable/i386'), - debian_build('Debian buster (amd64)', docker_base + 'debian-buster'), - debian_build('Ubuntu latest (amd64)', docker_base + 'ubuntu-rolling'), - debian_build('Ubuntu LTS (amd64)', docker_base + 'ubuntu-lts'), - debian_build('Ubuntu bionic (amd64)', - docker_base + 'ubuntu-bionic', - deps=['g++-8'] + default_deps_nocxx, - kitware_repo='bionic', - cmake_extra='-DCMAKE_C_COMPILER=gcc-8 -DCMAKE_CXX_COMPILER=g++-8'), + debian_build('Debian 11', docker_base + 'debian-bullseye', extra_setup=debian_backports('bullseye', ['cmake'])), + debian_build('Ubuntu latest', docker_base + 'ubuntu-rolling'), + debian_build('Ubuntu LTS', docker_base + 'ubuntu-lts'), // ARM builds (ARM64 and armhf) debian_build('Debian sid (ARM64)', docker_base + 'debian-sid', arch='arm64', jobs=4), debian_build('Debian stable (armhf)', docker_base + 'debian-stable/arm32v7', arch='arm64', jobs=4), - // Windows builds (x64) - windows_cross_pipeline('Windows (amd64)', docker_base + 'debian-win32-cross-wine'), - // Macos builds: - mac_builder('macOS (Release)'), - mac_builder('macOS (Debug)', build_type='Debug'), + mac_builder('macOS Intel (Release)'), + mac_builder('macOS Arm64 (Release)', arch='arm64'), + mac_builder('macOS Arm64 (Debug)', arch='arm64', build_type='Debug'), // Static lib builds - static_build('Static Linux amd64', docker_base + 'debian-stable', 'libsession-util-linux-amd64-TAG.tar.xz'), - static_build('Static Linux i386', docker_base + 'debian-stable', 'libsession-util-linux-i386-TAG.tar.xz'), - static_build('Static Linux arm64', docker_base + 'debian-stable', 'libsession-util-linux-arm64-TAG.tar.xz', arch='arm64'), - static_build('Static Linux armhf', docker_base + 'debian-stable/arm32v7', 'libsession-util-linux-armhf-TAG.tar.xz', arch='arm64'), + static_build('Static Linux/amd64', docker_base + 'debian-stable', 'libsession-util-linux-amd64-TAG.tar.xz'), + static_build('Static Linux/i386', docker_base + 'debian-stable', 'libsession-util-linux-i386-TAG.tar.xz'), + static_build('Static Linux/arm64', docker_base + 'debian-stable', 'libsession-util-linux-arm64-TAG.tar.xz', arch='arm64'), + static_build('Static Linux/armhf', docker_base + 'debian-stable/arm32v7', 'libsession-util-linux-armhf-TAG.tar.xz', arch='arm64'), static_build('Static Windows x64', docker_base + 'debian-win32-cross', 'libsession-util-windows-x64-TAG.zip', deps=['g++-mingw-w64-x86-64-posix'], cmake_extra='-DCMAKE_CXX_FLAGS=-fdiagnostics-color=always -DCMAKE_TOOLCHAIN_FILE=../cmake/mingw-x86-64-toolchain.cmake'), + /* currently broken: static_build('Static Windows x86', docker_base + 'debian-win32-cross', 'libsession-util-windows-x86-TAG.zip', deps=['g++-mingw-w64-i686-posix'], + allow_fail=true, cmake_extra='-DCMAKE_CXX_FLAGS=-fdiagnostics-color=always -DCMAKE_TOOLCHAIN_FILE=../cmake/mingw-i686-toolchain.cmake'), + */ debian_pipeline( 'Static Android', docker_base + 'android', @@ -347,13 +398,13 @@ local static_build(name, ] ), - mac_pipeline('Static macOS', build=[ + mac_pipeline('Static macOS', arch='arm64', build=[ 'export JOBS=6', './utils/macos.sh', 'cd build-macos && ../utils/ci/drone-static-upload.sh', ]), - mac_pipeline('Static iOS', build=[ + mac_pipeline('Static iOS', arch='arm64', build=[ 'export JOBS=6', './utils/ios.sh libsession-util-ios-TAG', 'cd build-ios && ../utils/ci/drone-static-upload.sh', diff --git a/.gitignore b/.gitignore index 52afeba1..1fe18bd5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /build*/ /compile_commands.json /.cache/ +/.vscode/ +.DS_STORE diff --git a/.gitmodules b/.gitmodules index 98d3ec63..e3b3ed5e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ -[submodule "external/oxen-encoding"] - path = external/oxen-encoding - url = https://github.com/oxen-io/oxen-encoding.git [submodule "external/libsodium-internal"] path = external/libsodium-internal - url = https://github.com/jagerman/libsodium-internal.git + url = https://github.com/session-foundation/libsodium-internal.git [submodule "tests/Catch2"] path = tests/Catch2 url = https://github.com/catchorg/Catch2 @@ -13,3 +10,15 @@ [submodule "external/zstd"] path = external/zstd url = https://github.com/facebook/zstd.git +[submodule "external/nlohmann-json"] + path = external/nlohmann-json + url = https://github.com/nlohmann/json.git +[submodule "external/oxen-libquic"] + path = external/oxen-libquic + url = https://github.com/oxen-io/oxen-libquic.git +[submodule "external/protobuf"] + path = external/protobuf + url = https://github.com/protocolbuffers/protobuf.git +[submodule "external/oxen-logging"] + path = external/oxen-logging + url = https://github.com/oxen-io/oxen-logging.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 5dd7edac..661bfe32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ -cmake_minimum_required(VERSION 3.13...3.23) +cmake_minimum_required(VERSION 3.14...3.23) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Has to be set before `project()`, and ignored on non-macos: -set(CMAKE_OSX_DEPLOYMENT_TARGET 10.13 CACHE STRING "macOS deployment target (Apple clang only)") +set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "macOS deployment target (Apple clang only)") set(LANGS C CXX) find_program(CCACHE_PROGRAM ccache) @@ -16,12 +16,16 @@ if(CCACHE_PROGRAM) endforeach() endif() - project(libsession-util - VERSION 1.1.0 + VERSION 1.4.0 DESCRIPTION "Session client utility library" LANGUAGES ${LANGS}) +message(STATUS "${PROJECT_NAME} v${PROJECT_VERSION}") + +set(LIBSESSION_LIBVERSION ${PROJECT_VERSION}) + +include(GNUInstallDirs) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") @@ -36,29 +40,50 @@ else() endif() -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_POLICY_VERSION_MINIMUM 3.5) set(default_static_libstd OFF) if(WIN32) set(default_static_libstd ON) endif() -if(MINGW OR ANDROID OR IOS OR STATIC_BUNDLE) - set(use_lto_default OFF) +option(BUILD_SHARED_LIBS "Build as shared library" OFF) + +if(BUILD_SHARED_LIBS) + set(static_default OFF) else() - set(use_lto_default ON) + set(static_default ON) endif() +option(BUILD_STATIC_DEPS "Build all dependencies statically rather than trying to link to them on the system" ${static_default}) +option(STATIC_BUNDLE "Build a single static .a containing everything (both code and dependencies)" ${static_default}) +if(BUILD_SHARED_LIBS OR libsession_IS_TOPLEVEL_PROJECT) + set(install_default ON) +else() + set(install_default OFF) +endif() + +option(LIBSESSION_INSTALL "Install libsession-util libraries and headers to cmake install target; defaults to ON if BUILD_SHARED_LIBS is enabled or when building the top-level project" ${install_default}) + +if(MINGW OR ANDROID OR IOS) # OR STATIC_BUNDLE) + set(use_lto_default OFF) +else() + set(use_lto_default ON) +endif() option(WARNINGS_AS_ERRORS "Treat all compiler warnings as errors" OFF) + option(STATIC_LIBSTD "Statically link libstdc++/libgcc" ${default_static_libstd}) -option(STATIC_BUNDLE "Build a single static .a containing everything (both code and dependencies)" OFF) -option(BUILD_SHARED_LIBS "Build as shared library" OFF) + option(USE_LTO "Use Link-Time Optimization" ${use_lto_default}) +# Provide this as an option for now because GMP and Desktop are sometimes unhappy with each other. +option(ENABLE_ONIONREQ "Build with onion request functionality" ON) + if(USE_LTO) include(CheckIPOSupported) check_ipo_supported(RESULT IPO_ENABLED OUTPUT ipo_error) @@ -86,23 +111,40 @@ if(STATIC_LIBSTD) endif() endif() +include(AddStaticBundleLib) # Always build PIC set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_subdirectory(external) + +if(ENABLE_ONIONREQ) + if(NOT TARGET nettle::nettle) + if(BUILD_STATIC_DEPS) + message(FATAL_ERROR "Internal error: nettle::nettle target (expected via libquic BUILD_STATIC_DEPS) not found") + else() + find_package(PkgConfig REQUIRED) + pkg_check_modules(NETTLE REQUIRED IMPORTED_TARGET nettle) + add_library(nettle INTERFACE) + target_link_libraries(nettle INTERFACE PkgConfig::NETTLE) + add_library(nettle::nettle ALIAS nettle) + endif() + endif() +endif() + add_subdirectory(src) +add_subdirectory(proto) + +if (BUILD_STATIC_DEPS) + include(StaticBuild) +endif() if(STATIC_BUNDLE) + include(combine_archives) - combine_archives(session-util - crypto - config - version - libsodium::sodium-internal - libzstd_static) + combine_archives(session-util libsession-static-bundle "${LIBSESSION_STATIC_BUNDLE_LIBS}") set(lib_folder lib) if(IOS) set(lib_folder "${lib_folder}-${ARCH}") @@ -117,3 +159,26 @@ option(WITH_TESTS "Enable unit tests" ${libsession_IS_TOPLEVEL_PROJECT}) if(WITH_TESTS) add_subdirectory(tests) endif() + + +if(LIBSESSION_INSTALL) + + install( + TARGETS ${libsession_export_targets} + EXPORT libsessionConfig + DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) + + install(DIRECTORY include/session DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + PATTERN *.h PATTERN *.hpp) + + set(libsession_target_links) + foreach(tgt ${libsession_export_targets}) + set(libsession_target_links "${libsession_target_links} -lsession-${tgt}") + endforeach() + configure_file(libsession-util.pc.in libsession-util.pc @ONLY) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libsession-util.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig + ) + +endif() diff --git a/cmake/AddStaticBundleLib.cmake b/cmake/AddStaticBundleLib.cmake new file mode 100644 index 00000000..2e37c0b6 --- /dev/null +++ b/cmake/AddStaticBundleLib.cmake @@ -0,0 +1,36 @@ + +set(LIBSESSION_STATIC_BUNDLE_LIBS "" CACHE INTERNAL "list of libs to go into the static bundle lib") + +function(_libsession_static_bundle_append tgt) + list(APPEND LIBSESSION_STATIC_BUNDLE_LIBS "${tgt}") + set(LIBSESSION_STATIC_BUNDLE_LIBS "${LIBSESSION_STATIC_BUNDLE_LIBS}" CACHE INTERNAL "") +endfunction() + +# Call as: +# +# libsession_static_bundle(target [target2 ...]) +# +# to append the given target(s) to the list of libraries that will be combined to make the static +# bundled libsession-util.a. +function(libsession_static_bundle) + foreach(tgt IN LISTS ARGN) + if(TARGET "${tgt}" AND NOT "${tgt}" IN_LIST LIBSESSION_STATIC_BUNDLE_LIBS) + get_target_property(tgt_type ${tgt} TYPE) + + if(tgt_type STREQUAL STATIC_LIBRARY) + message(STATUS "Adding ${tgt} to libsession-util bundled library list") + _libsession_static_bundle_append("${tgt}") + endif() + + if(tgt_type STREQUAL INTERFACE_LIBRARY) + get_target_property(tgt_link_deps ${tgt} INTERFACE_LINK_LIBRARIES) + else() + get_target_property(tgt_link_deps ${tgt} LINK_LIBRARIES) + endif() + + if(tgt_link_deps) + libsession_static_bundle(${tgt_link_deps}) + endif() + endif() + endforeach() +endfunction() diff --git a/cmake/GenVersion.cmake b/cmake/GenVersion.cmake index 20c4a81a..3ac32ff5 100644 --- a/cmake/GenVersion.cmake +++ b/cmake/GenVersion.cmake @@ -41,7 +41,7 @@ else() OUTPUT_VARIABLE git_tag OUTPUT_STRIP_TRAILING_WHITESPACE) - if(git_tag) + if(git_tag AND git_tag MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+$") message(STATUS "${git_commit} is tagged (${git_tag}); tagging version as 'release'") set(vfull "v${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") set(PROJECT_VERSION_TAG "release") @@ -49,6 +49,9 @@ else() if (NOT git_tag STREQUAL "${vfull}") message(FATAL_ERROR "This commit is tagged, but the tag (${git_tag}) does not match the project version (${vfull})!") endif() + elseif(git_tag) + message(WARNING "Did not recognize git tag (${git_tag}) for ${git_commit}; tagging with commit hash") + set(PROJECT_VERSION_TAG "${git_commit}") else() message(STATUS "Did not find a git tag for ${git_commit}; tagging version with the commit hash") set(PROJECT_VERSION_TAG "${git_commit}") diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake new file mode 100644 index 00000000..49863f8c --- /dev/null +++ b/cmake/StaticBuild.cmake @@ -0,0 +1,224 @@ +# cmake bits to do a full static build, downloading and building all dependencies. + +# Most of these are CACHE STRINGs so that you can override them using -DWHATEVER during cmake +# invocation to override. + +set(LOCAL_MIRROR "" CACHE STRING "local mirror path/URL for lib downloads") + +include(ExternalProject) + +set(DEPS_DESTDIR ${CMAKE_BINARY_DIR}/static-deps) +set(DEPS_SOURCEDIR ${CMAKE_BINARY_DIR}/static-deps-sources) + +file(MAKE_DIRECTORY ${DEPS_DESTDIR}/include) + +add_library(libsession-external-libs INTERFACE IMPORTED GLOBAL) +target_include_directories(libsession-external-libs SYSTEM BEFORE INTERFACE ${DEPS_DESTDIR}/include) + +set(deps_cc "${CMAKE_C_COMPILER}") +set(deps_cxx "${CMAKE_CXX_COMPILER}") + + +function(expand_urls output source_file) + set(expanded) + foreach(mirror ${ARGN}) + list(APPEND expanded "${mirror}/${source_file}") + endforeach() + set(${output} "${expanded}" PARENT_SCOPE) +endfunction() + +function(add_static_target target ext_target libname) + add_library(${target} STATIC IMPORTED GLOBAL) + add_dependencies(${target} ${ext_target}) + target_link_libraries(${target} INTERFACE libsession-external-libs) + set_target_properties(${target} PROPERTIES + IMPORTED_LOCATION ${DEPS_DESTDIR}/lib/${libname} + ) + if(ARGN) + target_link_libraries(${target} INTERFACE ${ARGN}) + endif() + libsession_static_bundle(${target}) +endfunction() + + + +set(cross_host "") +set(cross_rc "") +if(CMAKE_CROSSCOMPILING) + if(APPLE AND NOT ARCH_TRIPLET AND APPLE_TARGET_TRIPLE) + set(ARCH_TRIPLET "${APPLE_TARGET_TRIPLE}") + endif() + set(cross_host "--host=${ARCH_TRIPLET}") + if (ARCH_TRIPLET MATCHES mingw AND CMAKE_RC_COMPILER) + set(cross_rc "WINDRES=${CMAKE_RC_COMPILER}") + endif() +endif() +if(ANDROID) + set(android_toolchain_suffix linux-android) + set(android_compiler_suffix linux-android${ANDROID_PLATFORM_LEVEL}) + if(CMAKE_ANDROID_ARCH_ABI MATCHES x86_64) + set(cross_host "--host=x86_64-linux-android") + set(android_compiler_prefix x86_64) + set(android_compiler_suffix linux-android${ANDROID_PLATFORM_LEVEL}) + set(android_toolchain_prefix x86_64) + set(android_toolchain_suffix linux-android) + elseif(CMAKE_ANDROID_ARCH_ABI MATCHES x86) + set(cross_host "--host=i686-linux-android") + set(android_compiler_prefix i686) + set(android_compiler_suffix linux-android${ANDROID_PLATFORM_LEVEL}) + set(android_toolchain_prefix i686) + set(android_toolchain_suffix linux-android) + elseif(CMAKE_ANDROID_ARCH_ABI MATCHES armeabi-v7a) + set(cross_host "--host=armv7a-linux-androideabi") + set(android_compiler_prefix armv7a) + set(android_compiler_suffix linux-androideabi${ANDROID_PLATFORM_LEVEL}) + set(android_toolchain_prefix arm) + set(android_toolchain_suffix linux-androideabi) + elseif(CMAKE_ANDROID_ARCH_ABI MATCHES arm64-v8a) + set(cross_host "--host=aarch64-linux-android") + set(android_compiler_prefix aarch64) + set(android_compiler_suffix linux-android${ANDROID_PLATFORM_LEVEL}) + set(android_toolchain_prefix aarch64) + set(android_toolchain_suffix linux-android) + else() + message(FATAL_ERROR "unknown android arch: ${CMAKE_ANDROID_ARCH_ABI}") + endif() + set(deps_cc "${ANDROID_TOOLCHAIN_ROOT}/bin/${android_compiler_prefix}-${android_compiler_suffix}-clang") + set(deps_cxx "${ANDROID_TOOLCHAIN_ROOT}/bin/${android_compiler_prefix}-${android_compiler_suffix}-clang++") + set(deps_ld "${ANDROID_TOOLCHAIN_ROOT}/bin/${android_compiler_prefix}-${android_toolchain_suffix}-ld") + set(deps_ranlib "${ANDROID_TOOLCHAIN_ROOT}/bin/${android_toolchain_prefix}-${android_toolchain_suffix}-ranlib") + set(deps_ar "${ANDROID_TOOLCHAIN_ROOT}/bin/${android_toolchain_prefix}-${android_toolchain_suffix}-ar") +endif() + +set(deps_CFLAGS "-O2") +set(deps_CXXFLAGS "-O2") + +if(CMAKE_C_COMPILER_LAUNCHER) + set(deps_cc "${CMAKE_C_COMPILER_LAUNCHER} ${deps_cc}") +endif() +if(CMAKE_CXX_COMPILER_LAUNCHER) + set(deps_cxx "${CMAKE_CXX_COMPILER_LAUNCHER} ${deps_cxx}") +endif() + +if(WITH_LTO) + set(deps_CFLAGS "${deps_CFLAGS} -flto") +endif() + +if(APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET) + if(SDK_NAME) + set(deps_CFLAGS "${deps_CFLAGS} -m${SDK_NAME}-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") + set(deps_CXXFLAGS "${deps_CXXFLAGS} -m${SDK_NAME}-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") + else() + set(deps_CFLAGS "${deps_CFLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") + set(deps_CXXFLAGS "${deps_CXXFLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") + endif() +endif() + +if(_winver) + set(deps_CFLAGS "${deps_CFLAGS} -D_WIN32_WINNT=${_winver}") + set(deps_CXXFLAGS "${deps_CXXFLAGS} -D_WIN32_WINNT=${_winver}") +endif() + + +if("${CMAKE_GENERATOR}" STREQUAL "Unix Makefiles") + set(_make $(MAKE)) +else() + set(_make make) +endif() + + +# Builds a target; takes the target name (e.g. "readline") and builds it in an external project with +# target name suffixed with `_external`. Its upper-case value is used to get the download details +# (from the variables set above). The following options are supported and passed through to +# ExternalProject_Add if specified. If omitted, these defaults are used: +set(build_def_DEPENDS "") +set(build_def_PATCH_COMMAND "") +set(build_def_CONFIGURE_COMMAND ./configure ${cross_host} --disable-shared --prefix=${DEPS_DESTDIR} --with-pic + "CC=${deps_cc}" "CXX=${deps_cxx}" "CFLAGS=${deps_CFLAGS}" "CXXFLAGS=${deps_CXXFLAGS}" ${cross_rc}) +set(build_def_CONFIGURE_EXTRA "") +set(build_def_BUILD_COMMAND ${_make}) +set(build_def_INSTALL_COMMAND ${_make} install) +set(build_def_BUILD_BYPRODUCTS ${DEPS_DESTDIR}/lib/lib___TARGET___.a ${DEPS_DESTDIR}/include/___TARGET___.h) + +function(build_external target) + set(options DEPENDS PATCH_COMMAND CONFIGURE_COMMAND CONFIGURE_EXTRA BUILD_COMMAND INSTALL_COMMAND BUILD_BYPRODUCTS) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "${options}") + foreach(o ${options}) + if(NOT DEFINED arg_${o}) + set(arg_${o} ${build_def_${o}}) + endif() + endforeach() + string(REPLACE ___TARGET___ ${target} arg_BUILD_BYPRODUCTS "${arg_BUILD_BYPRODUCTS}") + + string(TOUPPER "${target}" prefix) + expand_urls(urls ${${prefix}_SOURCE} ${${prefix}_MIRROR}) + set(extract_ts) + if(NOT CMAKE_VERSION VERSION_LESS 3.24) + set(extract_ts DOWNLOAD_EXTRACT_TIMESTAMP ON) + endif() + ExternalProject_Add("${target}_external" + DEPENDS ${arg_DEPENDS} + BUILD_IN_SOURCE ON + PREFIX ${DEPS_SOURCEDIR} + URL ${urls} + URL_HASH ${${prefix}_HASH} + DOWNLOAD_NO_PROGRESS ON + PATCH_COMMAND ${arg_PATCH_COMMAND} + CONFIGURE_COMMAND ${arg_CONFIGURE_COMMAND} ${arg_CONFIGURE_EXTRA} + BUILD_COMMAND ${arg_BUILD_COMMAND} + INSTALL_COMMAND ${arg_INSTALL_COMMAND} + BUILD_BYPRODUCTS ${arg_BUILD_BYPRODUCTS} + EXCLUDE_FROM_ALL ON + ${extract_ts} + ) +endfunction() + + +set(apple_cflags_arch) +set(apple_cxxflags_arch) +set(apple_ldflags_arch) +set(gmp_build_host "${cross_host}") +if(APPLE AND CMAKE_CROSSCOMPILING) + if(gmp_build_host MATCHES "^(.*-.*-)ios([0-9.]+)(-.*)?$") + set(gmp_build_host "${CMAKE_MATCH_1}darwin${CMAKE_MATCH_2}${CMAKE_MATCH_3}") + endif() + if(gmp_build_host MATCHES "^(.*-.*-.*)-simulator$") + set(gmp_build_host "${CMAKE_MATCH_1}") + endif() + + set(apple_arch) + if(ARCH_TRIPLET MATCHES "^(arm|aarch)64.*") + set(apple_arch "arm64") + elseif(ARCH_TRIPLET MATCHES "^x86_64.*") + set(apple_arch "x86_64") + else() + message(FATAL_ERROR "Don't know how to specify -arch for GMP for ${ARCH_TRIPLET} (${APPLE_TARGET_TRIPLE})") + endif() + + set(apple_cflags_arch " -arch ${apple_arch}") + set(apple_cxxflags_arch " -arch ${apple_arch}") + if(CMAKE_OSX_DEPLOYMENT_TARGET) + if (SDK_NAME) + set(apple_ldflags_arch " -m${SDK_NAME}-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") + elseif(CMAKE_OSX_DEPLOYMENT_TARGET) + set(apple_ldflags_arch " -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") + endif() + endif() + set(apple_ldflags_arch "${apple_ldflags_arch} -arch ${apple_arch}") + + if(CMAKE_OSX_SYSROOT) + foreach(f c cxx ld) + set(apple_${f}flags_arch "${apple_${f}flags_arch} -isysroot ${CMAKE_OSX_SYSROOT}") + endforeach() + endif() +elseif(gmp_build_host STREQUAL "") + set(gmp_build_host "--build=${CMAKE_LIBRARY_ARCHITECTURE}") +endif() + +link_libraries(-static-libstdc++) +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + link_libraries(-static-libgcc) +endif() +if(MINGW) + link_libraries(-Wl,-Bstatic -lpthread) +endif() diff --git a/cmake/combine_archives.cmake b/cmake/combine_archives.cmake index 53064a91..0807b0c6 100644 --- a/cmake/combine_archives.cmake +++ b/cmake/combine_archives.cmake @@ -1,4 +1,4 @@ -function(combine_archives output_archive) +function(combine_archives output_archive dep_target) set(FULL_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/lib${output_archive}.a) if(NOT APPLE) diff --git a/docs/api/.gitignore b/docs/api/.gitignore index b8f8e8f6..f1734b39 100644 --- a/docs/api/.gitignore +++ b/docs/api/.gitignore @@ -1,2 +1,5 @@ api/* api +dist +*.mdx +.venv diff --git a/docs/api/Makefile b/docs/api/Makefile index 2f9f8352..c2463f91 100644 --- a/docs/api/Makefile +++ b/docs/api/Makefile @@ -1,26 +1,36 @@ H_FILES = $(wildcard ../../include/session/config/*.h ../../include/session/config/*/*.h) HPP_FILES = $(wildcard ../../include/session/config/*.hpp ../../include/session/config/*/*.hpp) -.PHONY: all -all: h-docs hpp-docs +.PHONY: clean +clean: + rm -rf ./dist -.PHONY: hpp-docs -hpp-docs: - ./make-docs.sh libsession-util-cpp $(HPP_FILES) +.PHONY: build-hpp +build-hpp: + ./make-docs.sh dist/libsession-util-cpp $(HPP_FILES) -.PHONY: h-docs -h-docs: - ./make-docs.sh libsession-util-c $(H_FILES) +.PHONY: build-h +build-h: + ./make-docs.sh dist/libsession-util-c $(H_FILES) -.PHONY: run-c -run-c: - docsify serve libsession-util-c +.PHONY: build-all +build-all: build-h build-hpp -.PHONY: run-cpp -run-cpp: - docsify serve libsession-util-cpp +.PHONY: serve-c +serve-c: + cd dist/libsession-util-c/site && python -m http.server 8000 && cd - +.PHONY: serve-cpp +serve-cpp: + cd dist/libsession-util-cpp/site && python -m http.server 8001 && cd - -.PHONY: clean -clean: - rm -rf ./libsession-util-c ./libsession-util-cpp +# Developer commands: Build and serve a site inside of dist/ using the mkdocs development server. +# NOTE: Any changes to dist/ will be hot-reloaded but are not tracked by git. + +.PHONY: dev-c +dev-c: build-h + cd dist/libsession-util-c && mkdocs serve && cd - + +.PHONY: dev-cpp +dev-cpp: build-hpp + cd dist/libsession-util-cpp && mkdocs serve && cd - diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 00000000..561bc36f --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,56 @@ +# Documentation Website + +# Getting Started + +## Writing Documentation + +The [api-to-markdown.py](./api-to-markdown.py) script parses specially formatted docstring comments to generate API documentation. We parse the file looking for `///` comment blocks beginning with `/// API: /`. You can read more about the format in the script itself. + +## Install dependencies + +```sh +pip install -r requirements.txt +``` + +## Building + +Building happens in the `dist` directory. + +```sh +# Build libsession-util C API functions +make build-h + +# Build libsession-util C++ API functions +make build-cpp + +# Build both C and C++ API functions +make build-all +``` + +## Hosting + +```sh +# Serve libsession-util C API functions on localhost:8000 +make serve-c + +# Serve libsession-util C++ API functions on localhost:8001 +make serve-cpp +``` + +## Developing + +> [!WARNING] +> Any changes to `dist` directory will be hot-reloaded but are not tracked by git. + +```sh +# Serve libsession-util C API functions on localhost:8000 +make dev-c + +# Serve libsession-util C++ API functions on localhost:8000 +make dev-cpp +``` + +# Resources + +- [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) +- [MkDocs](https://www.mkdocs.org/) diff --git a/docs/api/api-to-markdown.py b/docs/api/api-to-markdown.py index 4a6aa9c0..f8f96c08 100755 --- a/docs/api/api-to-markdown.py +++ b/docs/api/api-to-markdown.py @@ -7,8 +7,8 @@ import fileinput from enum import Enum, auto import json -import requests import argparse +import requests parser = argparse.ArgumentParser() parser.add_argument( @@ -47,6 +47,7 @@ # # "Inputs: none." # "Outputs: none." +# "Member variable." # "Inputs:" followed by markdown (typically an unordered list) until the next match from this list. # "Outputs:" followed by markdown # "Example input:" followed by a code block (i.e. containing json) @@ -91,6 +92,7 @@ DEV_RPC_START = re.compile(r"^Dev-API:\s*([\w/:]+)(.*)$") IN_NONE = re.compile(r"^Inputs?: *[nN]one\.?$") IN_SOME = re.compile(r"^Inputs?:\s*$") +MEMBER_VAR = re.compile(r"^Member +[vV]ar(?:iable)?\.?$") DECL_SOME = re.compile(r"^Declaration?:\s*$") OUT_SOME = re.compile(r"^Outputs?:\s*$") EXAMPLE_IN = re.compile(r"^Example [iI]nputs?:\s*$") @@ -159,6 +161,7 @@ class Parsing(Enum): description, decl, inputs, outputs = "", "", "", "" done_desc = False no_inputs = False + member_var = False examples = [] cur_ex_in = None old_names = [] @@ -194,6 +197,9 @@ class Parsing(Enum): error("found multiple Inputs:") inputs, no_inputs, mode = MD_NO_INPUT, True, Parsing.NONE + elif re.search(MEMBER_VAR, line): + member_var, no_inputs, mode = True, True, Parsing.DESC + elif re.search(DECL_SOME, line): if inputs: error("found multiple Syntax:") @@ -243,7 +249,7 @@ class Parsing(Enum): except Exception as e: error("failed to parse json example input as json") - result = requests.post(args.rpc + "/json_rpc", json={"jsonrpc": "2.0", "id": "0", "method": rpc_name, "params": params}).json() + result = requests.post(args.rpc + "/json_rpc", json={"jsonrpc": "2.0", "id": "0", "method": rpc_name, "params": params}, timeout=30).json() if 'error' in result: error(f"JSON fetched example returned an error: {result['error']}") elif 'result' not in result: @@ -285,7 +291,7 @@ class Parsing(Enum): # We hit the end of the commented section if not description or inputs.isspace(): problems.append("endpoint has no description") - if not inputs or inputs.isspace(): + if (not inputs or inputs.isspace()) and not member_var: problems.append( "endpoint has no inputs description; perhaps you need to add 'Inputs: none.'?" ) @@ -321,7 +327,9 @@ class Parsing(Enum): {MD_DECL_HEADER} {decl} - +""" + if not member_var: + md = md + f""" {MD_INPUT_HEADER} {inputs} @@ -367,18 +375,23 @@ class Parsing(Enum): for v in endpoints.values(): v.sort(key=lambda x: x[0]) -os.makedirs(args.out, exist_ok=True) +output_path = os.path.dirname(os.path.realpath(__file__)) + f'/{args.out}' +os.makedirs(output_path, exist_ok=True) + +# copy the mkdocs.yml file to the root of the output directory +shutil.copyfile('mkdocs.yml', f"{output_path}/mkdocs.yml") +print(f"Copied mkdocs.yml => {output_path}/mkdocs.yml") -static_path = os.path.dirname(os.path.realpath(__file__)) + '/static' +STATIC_FOLDER = 'docs' +static_path = os.path.dirname(os.path.realpath(__file__)) + f'/{STATIC_FOLDER}' -for f in ('index.md', 'sidebar.md'): - shutil.copy(f"{static_path}/{f}", f"{args.out}/{f}") - print(f"Copied static/{f} => {args.out}/{f}") +# copy the static folder over to the output directory +shutil.copytree(static_path, f"{output_path}/{STATIC_FOLDER}") preamble_prefix = static_path + '/preamble-' for cat, eps in endpoints.items(): - out = f"{args.out}/{cat}.md" + out = f"{output_path}/{STATIC_FOLDER}/{cat}.md" with open(out, "w") as f: preamble = f"{preamble_prefix}{cat}.md" if os.path.isfile(preamble): @@ -388,7 +401,7 @@ class Parsing(Enum): f.write("\n\n") else: print(f"Warning: {preamble} doesn't exist, writing generic preamble for {cat}", file=sys.stderr) - f.write(f"# {cat} endpoints\n\n") + f.write(f"# {cat.replace('_', ' ').title()}\n\n") for _, md in eps: f.write(md) diff --git a/docs/config-merge-logic.md b/docs/api/docs/config_merge_logic.md similarity index 98% rename from docs/config-merge-logic.md rename to docs/api/docs/config_merge_logic.md index 8a0d586e..2ea265fa 100644 --- a/docs/config-merge-logic.md +++ b/docs/api/docs/config_merge_logic.md @@ -1,3 +1,5 @@ +# Config Merge Logic + When we have competing config message, we need completely consistent logic for merging them, that is both forwards and backwards compatible (that is: old clients with new data, and new clients with old data, need to produce an agreeable result). @@ -16,7 +18,7 @@ The way this is implemented here is as follows: - whichever one wins, any client that observes a conflict (i.e. same seqno) needs to merge the two messages, increment the seqno, and push the new one to the swarm. -# Structure of a config message +## Structure of a config message A decrypted config message consists of outer data (which contains generic information about the config message), and the inner message data, which contains the actual application-specific @@ -27,7 +29,7 @@ needs to decrypt it. (How to obtain that key is use-case dependent and outside document). The specifics of encryption are covered in the [Message Encryption](#message-encryption) section, below. -## Application-side config data +### Application-side config data To start with the inner config data (which will be under the `"&"` key of the outermost data message; see description in the [outer metadata](#config-message-outer-metadata), this is a dict @@ -52,7 +54,7 @@ allows arbitrary values inside lists, doesn't impose list ordering or uniqueness lists/dicts. All of those are excluded from config data messages so as to make merging of messages feasible and deterministic. -## Config message outer metadata +### Config message outer metadata - The top-level structure of a config message is always a dict, with keys as follows (note that the restrictions described above for the application data do *not* apply to the outer config message @@ -75,7 +77,7 @@ feasible and deterministic. where authentication of the message creator is required. This field, when present, *must* be the last key (i.e. no top-level keys that sort after `~` are permitted). -# Config diffs +## Config diffs To enable clients to merge config messages (even of potential content from future clients it does not understand) any config changes are included in the "diff" section of the config (keys `"<"` and @@ -99,7 +101,7 @@ not understand) any config changes are included in the "diff" section of the con the main application config data, but with placeholder values to indicate changes (rather than duplicating values). -## Config diff updates +### Config diff updates A config diff itself (i.e. the third element of a `"<"` tuple, or the `"="` value) is a dict that largely mirrors the inner config data structure (i.e. under the `"&"` outer config key) but that @@ -125,11 +127,11 @@ A diff update itself is a dict such that: empty if there were only additions or only removals. Similarly to dict handling, removing *all* values implicitly removes the set itself, and adding to a non-existent set autovivifies the set. -# Client update behaviour +## Client update behaviour When there are multiple conflicting config messages clients resolve according to several rules. -## Ignored updates +### Ignored updates Clients ignore updates according to two criteria: @@ -154,7 +156,7 @@ Clients ignore updates according to two criteria: - missing or invalid signature (in contexts where signatures are required) - etc. -## Non-conflicting updates +### Non-conflicting updates If a client is making a configuration change and there is only a single current valid config message (i.e. no conflicts to resolve) then the update procedure is straightforward: @@ -169,7 +171,7 @@ If a client is making a configuration change and there is only a single current 4. A current diff is constructed for any values being assigned, and stored under the `"="` key of the config message. -## Conflict resolution +### Conflict resolution Conflict resolution logic kicks in if, after removing ignored messages from consideration, there are still multiple valid config messages: these messages must be merged and the merge update published. @@ -205,7 +207,7 @@ Merging is performed as follows: constructed and written into the `"="` key. - Otherwise (i.e. only merge changes) the current diff key `"="` is set to an empty dict. -# Examples +## Examples The following depict several examples showing how update rules work. Values are shown in not-quite-JSON (i.e. we add comments) for human readability, but in reality will be bt-encoded. @@ -218,7 +220,7 @@ order of the hashes with the same XXX seqno value (lower letters = earlier-sorti All examples use a "within 5" rule for determining how the seqno cutoff for conflict resolution, and are not using signatures. -## Ordinary update +### Ordinary update Suppose an update begins from the following data (with seqno 122), and updates have all been linear and orderly (i.e. there have been no recent config conflicts): @@ -286,7 +288,7 @@ like this: ``` -## Large, but still ordinary, update +### Large, but still ordinary, update Suppose an update begins from the following data (with seqno 123), and updates have all been linear and orderly (i.e. there have been no recent config conflicts): @@ -400,7 +402,7 @@ The overall record of this change looks as follows. } ``` -## Simple conflict resolution +### Simple conflict resolution Suppose two clients now push update with `seqno=125` where one client sets `["int1"]` to `5` and another removes element `["dictB"]["foo"]`. @@ -464,7 +466,7 @@ Since all clients will produce an identical message, even if multiple clients pu at once, it will simply be de-duplicated and stored only once. -## Stale messages +### Stale messages If a message arrives with a seqno that is not within the most recent five seqno values of the largest-seqno message then it is simply dropped. For instance supposed we have seqno 126 (from the @@ -473,7 +475,7 @@ out of date and has a delayed update still to go out). This message neither com current seqno (126) nor any of the historic ones (122 through 125), and so it is discarded. -## Complex conflict resolution +### Complex conflict resolution Suppose that while the resolution from the previous example is happening there is another client that is somewhat out of date and submitting a configuration update with `seqno=124` (we'll label @@ -621,7 +623,7 @@ This final 127 update thus becomes: } ``` -# Message Encryption +## Message Encryption All messages are stored in encrypted form; we select XChaCha20-Poly1305 encryption for its excellent properties. diff --git a/docs/api/static/index.md b/docs/api/docs/index.md similarity index 85% rename from docs/api/static/index.md rename to docs/api/docs/index.md index ed10ba40..ae9dfcdb 100644 --- a/docs/api/static/index.md +++ b/docs/api/docs/index.md @@ -1,4 +1,4 @@ -# Libsession Util API Functions +# Getting Started These pages describe the available API functions available from libsession util library. These endpoints are used for querying and modifying the config struct handled by libsession. diff --git a/docs/api/docs/javascripts/katex.js b/docs/api/docs/javascripts/katex.js new file mode 100644 index 00000000..8c642137 --- /dev/null +++ b/docs/api/docs/javascripts/katex.js @@ -0,0 +1,10 @@ +document$.subscribe(({ body }) => { + renderMathInElement(body, { + delimiters: [ + { left: "$$", right: "$$", display: true }, + { left: "$", right: "$", display: false }, + { left: "\\(", right: "\\)", display: false }, + { left: "\\[", right: "\\]", display: true } + ], + }) +}) diff --git a/docs/api/docs/stylesheets/extra.css b/docs/api/docs/stylesheets/extra.css new file mode 100644 index 00000000..0401811c --- /dev/null +++ b/docs/api/docs/stylesheets/extra.css @@ -0,0 +1,17 @@ +:root > * { + --md-accent-fg-color: #00aa59; + --md-accent-fg-color--light: #00f782; + --md-accemt-fg-color--dark: #00aa59; + --md-primary-fg-color: #00aa59; + --md-primary-fg-color--light: #00f782; + --md-primary-fg-color--dark: #00aa59; +} + +.md-sidebar.md-sidebar--secondary { + width: unset; + max-width: 500px; +} + +.md-content { + min-width: 40vw; +} diff --git a/docs/api/make-docs.sh b/docs/api/make-docs.sh index 7acd5b15..a79dfac5 100755 --- a/docs/api/make-docs.sh +++ b/docs/api/make-docs.sh @@ -1,11 +1,5 @@ #!/bin/bash -# The following npm packages must be installed -# docsify-cli docsify-themeable docsify-katex@1.4.4 katex marked@4 - -# To customise the theme see: -# https://jhildenbiddle.github.io/docsify-themeable/#/customization - set -e if [ "$(basename $(pwd))" != "api" ]; then @@ -20,79 +14,6 @@ if [ -d "$destdir" ]; then rm -rf "$destdir" fi -npx docsify init --local "$destdir" - -rm -Rf "$destdir"/vendor/themes -rm -f "$destdir"/README.md - -if [ -n "$NPM_PACKAGES" ]; then - npm_dir="$NPM_PACKAGES/lib/node_modules" -elif [ -n "$NODE_PATH" ]; then - npm_dir="$NODE_PATH" -elif [ -d "$HOME/node_modules" ]; then - npm_dir="$HOME/node_modules" -elif [ -d "/usr/local/lib/node_modules" ]; then - npm_dir="/usr/local/lib/node_modules" -else - echo "Can't determine your node_modules path; set NPM_PACKAGES or NODE_PATH appropriately" >&2 - exit 1 -fi - -cp $npm_dir/docsify/lib/plugins/search.min.js "$destdir"/vendor -cp $npm_dir/prismjs/components/prism-{json,python,http}.min.js "$destdir"/vendor -cp $npm_dir/docsify-themeable/dist/css/theme-simple.css "$destdir"/vendor -cp $npm_dir/docsify-themeable/dist/css/theme-simple-dark.css "$destdir"/vendor -cp $npm_dir/docsify-themeable/dist/js/docsify-themeable.min.js "$destdir"/vendor -cp $npm_dir/marked/marked.min.js "$destdir"/vendor -cp $npm_dir/katex/dist/katex.min.js "$destdir"/vendor -cp $npm_dir/katex/dist/katex.min.css "$destdir"/vendor -cp -R $npm_dir/katex/dist/fonts "$destdir"/vendor -cp $npm_dir/docsify-katex/dist/docsify-katex.js "$destdir"/vendor ./api-to-markdown.py --out="$destdir" "$@" - -perl -ni -e ' -BEGIN { $first = 0; } -if (m{^\s*\s*$}) { - if (not $first) { - $first = false; - print qq{ - \n}; - } -} else { - s{.*}{Libsession Utils API}; - s{(name="description" content=)"[^"]*"}{$1"libsession-util function documentation"}; - s{^\s*\s*$}{}; - if (m{^\s*}) { - print qq{ - - - - - - - - - - - - \n}; - } - print; -}' "$destdir"/index.html +cd $destdir && mkdocs build && cd - diff --git a/docs/api/mkdocs.yml b/docs/api/mkdocs.yml new file mode 100644 index 00000000..297b4359 --- /dev/null +++ b/docs/api/mkdocs.yml @@ -0,0 +1,73 @@ +# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json +site_name: Libsession Utils API Functions +repo_url: https://github.com/session-foundation/libsession-util +copyright: Copyright © 2025 - Session Technology Foundation (Session Technology Stiftung) +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/session-foundation/libsession-util +extra_css: + - stylesheets/extra.css + - https://unpkg.com/katex@0/dist/katex.min.css +extra_javascript: + - javascripts/katex.js + - https://unpkg.com/katex@0/dist/katex.min.js + - https://unpkg.com/katex@0/dist/contrib/auto-render.min.js +markdown_extensions: + - pymdownx.arithmatex: + generic: true + - pymdownx.critic + - pymdownx.caret + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.mark + - pymdownx.snippets + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: python/name:pymdownx.superfences.fence_code_format + - pymdownx.tilde +plugins: + - privacy + - search +theme: + name: material + palette: + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: custom + accent: custom + toggle: + icon: material/brightness-7 + name: Switch to dark mode + + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: custom + accent: custom + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + # Content + - content.code.copy + # Navigation + - navigation.footer + - navigation.instant + - navigation.instant.progress + - navigation.path + - navigation.top + - navigation.tracking + # Search + - search.highlight + - search.share + - search.suggest + # Table of Contents + - toc.follow diff --git a/docs/api/requirements.txt b/docs/api/requirements.txt new file mode 100644 index 00000000..c2abdaa3 --- /dev/null +++ b/docs/api/requirements.txt @@ -0,0 +1,38 @@ +babel==2.17.0 +backrefs==5.8 +cairocffi==1.7.1 +CairoSVG==2.7.1 +certifi==2025.1.31 +cffi==1.17.1 +charset-normalizer==3.4.1 +click==8.1.8 +colorama==0.4.6 +cssselect2==0.8.0 +defusedxml==0.7.1 +ghp-import==2.1.0 +idna==3.10 +Jinja2==3.1.6 +Markdown==3.7 +MarkupSafe==3.0.2 +mergedeep==1.3.4 +mkdocs==1.6.1 +mkdocs-get-deps==0.2.0 +mkdocs-material==9.6.8 +mkdocs-material-extensions==1.3.1 +packaging==24.2 +paginate==0.5.7 +pathspec==0.12.1 +pillow==10.4.0 +platformdirs==4.3.6 +pycparser==2.22 +Pygments==2.19.1 +pymdown-extensions==10.14.3 +python-dateutil==2.9.0.post0 +PyYAML==6.0.2 +pyyaml_env_tag==0.1 +requests==2.32.3 +six==1.17.0 +tinycss2==1.4.0 +urllib3==2.3.0 +watchdog==6.0.0 +webencodings==0.5.1 diff --git a/docs/api/static/sidebar.md b/docs/api/static/sidebar.md deleted file mode 100644 index e622fe5b..00000000 --- a/docs/api/static/sidebar.md +++ /dev/null @@ -1,9 +0,0 @@ -- [Base](base.md) -- [Community](community.md) -- [Contacts](contacts.md) -- [Convo Info Volatile](convo_info_volatile.md) -- [Encrypt](encrypt.md) -- [Error](error.md) -- [User Groups](user_groups.md) -- [User Profile](user_profile.md) -- [Utils](util.md) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 9802ac83..5506fd41 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,13 +1,73 @@ +option(SUBMODULE_CHECK "Enables checking that vendored library submodules are up to date" ON) +if(SUBMODULE_CHECK) + find_package(Git) + if(GIT_FOUND) + function(check_submodule relative_path) + execute_process(COMMAND git rev-parse "HEAD" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path} OUTPUT_VARIABLE localHead) + execute_process(COMMAND git rev-parse "HEAD:external/${relative_path}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE checkedHead) + string(COMPARE EQUAL "${localHead}" "${checkedHead}" upToDate) + if (upToDate) + message(STATUS "Submodule 'external/${relative_path}' is up-to-date") + else() + message(FATAL_ERROR "Submodule 'external/${relative_path}' is not up-to-date. Please update with\ngit submodule update --init --recursive\nor run cmake with -DSUBMODULE_CHECK=OFF") + endif() + + # Extra arguments check nested submodules + foreach(submod ${ARGN}) + execute_process(COMMAND git rev-parse "HEAD" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path}/${submod} OUTPUT_VARIABLE localHead) + execute_process(COMMAND git rev-parse "HEAD:${submod}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path} OUTPUT_VARIABLE checkedHead) + string(COMPARE EQUAL "${localHead}" "${checkedHead}" upToDate) + if (NOT upToDate) + message(FATAL_ERROR "Nested submodule '${relative_path}/${submod}' is not up-to-date. Please update with\ngit submodule update --init --recursive\nor run cmake with -DSUBMODULE_CHECK=OFF") + endif() + endforeach() + endfunction () + + message(STATUS "Checking submodules") + check_submodule(ios-cmake) + check_submodule(libsodium-internal) + check_submodule(oxen-libquic external/oxen-logging external/oxen-encoding) + check_submodule(nlohmann-json) + check_submodule(zstd) + check_submodule(protobuf) + endif() +endif() + +if(NOT BUILD_STATIC_DEPS AND NOT FORCE_ALL_SUBMODULES) + find_package(PkgConfig REQUIRED) +endif() + +macro(libsession_system_or_submodule BIGNAME smallname pkgconf subdir) + option(FORCE_${BIGNAME}_SUBMODULE "force using ${smallname} submodule" OFF) + if(NOT BUILD_STATIC_DEPS AND NOT FORCE_${BIGNAME}_SUBMODULE AND NOT FORCE_ALL_SUBMODULES) + pkg_check_modules(${BIGNAME} ${pkgconf} IMPORTED_TARGET GLOBAL) + endif() + if(${BIGNAME}_FOUND) + add_library(${smallname} INTERFACE IMPORTED GLOBAL) + if(NOT TARGET PkgConfig::${BIGNAME} AND CMAKE_VERSION VERSION_LESS "3.21") + # Work around cmake bug 22180 (PkgConfig::THING not set if no flags needed) + else() + target_link_libraries(${smallname} INTERFACE PkgConfig::${BIGNAME}) + endif() + message(STATUS "Found system ${smallname} ${${BIGNAME}_VERSION}") + else() + message(STATUS "using ${smallname} submodule") + add_subdirectory(${subdir}) + endif() + if(TARGET ${smallname} AND NOT TARGET ${smallname}::${smallname}) + add_library(${smallname}::${smallname} ALIAS ${smallname}) + endif() + if(BUILD_STATIC_DEPS AND STATIC_BUNDLE) + libsession_static_bundle(${smallname}::${smallname}) + endif() +endmacro() + + set(deps_cc "${CMAKE_C_COMPILER}") set(cross_host "") set(cross_rc "") if(CMAKE_CROSSCOMPILING) if(APPLE_TARGET_TRIPLE) - if(PLATFORM MATCHES "OS64" OR PLATFORM MATCHES "SIMULATORARM64") - set(APPLE_TARGET_TRIPLE aarch64-apple-ios) - elseif(PLATFORM MATCHES "SIMULATOR64") - set(APPLE_TARGET_TRIPLE x86_64-apple-ios) - endif() set(cross_host "--host=${APPLE_TARGET_TRIPLE}") elseif(ANDROID) if(CMAKE_ANDROID_ARCH_ABI MATCHES x86_64) @@ -40,9 +100,31 @@ if(CMAKE_CROSSCOMPILING) endif() endif() +set(LIBQUIC_BUILD_TESTS OFF CACHE BOOL "") +if(ENABLE_ONIONREQ) + libsession_system_or_submodule(OXENQUIC quic liboxenquic>=1.3.0 oxen-libquic) +endif() + +if(NOT TARGET oxenc::oxenc) + # The oxenc target will already exist if we load libquic above via submodule + set(OXENC_BUILD_TESTS OFF CACHE BOOL "") + set(OXENC_BUILD_DOCS OFF CACHE BOOL "") + libsession_system_or_submodule(OXENC oxenc liboxenc>=1.3.0 oxen-libquic/external/oxen-encoding) +endif() -add_subdirectory(oxen-encoding) +if(NOT TARGET oxen::logging) + add_subdirectory(oxen-libquic/external/oxen-logging) +endif() + +oxen_logging_add_source_dir("${PROJECT_SOURCE_DIR}") +# Apple xcode 15 has a completely broken std::source_location; we can't fix it, but at least we can +# hack up the source locations to hide the path that it uses (which is the useless path to +# oxen/log.hpp where the info/critical/etc. bodies are). +if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL AppleClang AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16) + message(WARNING "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} is broken: filenames in logging statements will not display properly") + oxen_logging_add_source_dir("${CMAKE_CURRENT_SOURCE_DIR}/oxen-libquic/external/oxen-logging/include/oxen") +endif() if(CMAKE_C_COMPILER_LAUNCHER) set(deps_cc "${CMAKE_C_COMPILER_LAUNCHER} ${deps_cc}") @@ -63,7 +145,28 @@ if(APPLE) endif() -add_subdirectory(libsodium-internal) +function(libsodium_internal_subdir) + set(BUILD_SHARED_LIBS OFF) + add_subdirectory(libsodium-internal) +endfunction() +libsodium_internal_subdir() +libsession_static_bundle(libsodium::sodium-internal) + + +set(protobuf_VERBOSE ON CACHE BOOL "" FORCE) +set(protobuf_INSTALL ON CACHE BOOL "" FORCE) +set(protobuf_WITH_ZLIB OFF CACHE BOOL "" FORCE) +set(protobuf_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(protobuf_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(protobuf_BUILD_LIBPROTOC OFF CACHE BOOL "" FORCE) +set(protobuf_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(protobuf_ABSL_PROVIDER "module" CACHE STRING "" FORCE) +set(protobuf_BUILD_PROTOC_BINARIES OFF CACHE BOOL "") +set(protobuf_BUILD_PROTOBUF_BINARIES ON CACHE BOOL "" FORCE) +libsession_system_or_submodule(PROTOBUF_LITE protobuf_lite protobuf-lite>=3.21 protobuf) +if(TARGET PkgConfig::PROTOBUF_LITE AND NOT TARGET protobuf::libprotobuf-lite) + add_library(protobuf::libprotobuf-lite ALIAS PkgConfig::PROTOBUF_LITE) +endif() set(ZSTD_BUILD_PROGRAMS OFF CACHE BOOL "") @@ -72,7 +175,7 @@ set(ZSTD_BUILD_CONTRIB OFF CACHE BOOL "") set(ZSTD_BUILD_SHARED OFF CACHE BOOL "") set(ZSTD_BUILD_STATIC ON CACHE BOOL "") set(ZSTD_MULTITHREAD_SUPPORT OFF CACHE BOOL "") -add_subdirectory(zstd/build/cmake) +add_subdirectory(zstd/build/cmake EXCLUDE_FROM_ALL) # zstd's cmake doesn't properly set up include paths on its targets, so we have to wrap it in an # interface target that does: add_library(libzstd_static_fixed_includes INTERFACE) @@ -84,3 +187,9 @@ export( NAMESPACE libsession:: FILE libsessionZstd.cmake ) +libsession_static_bundle(libzstd_static) + + +set(JSON_BuildTests OFF CACHE INTERNAL "") +set(JSON_Install ON CACHE INTERNAL "") # Required to export targets that we use +libsession_system_or_submodule(NLOHMANN nlohmann_json nlohmann_json>=3.7.0 nlohmann-json) diff --git a/external/nlohmann-json b/external/nlohmann-json new file mode 160000 index 00000000..9cca280a --- /dev/null +++ b/external/nlohmann-json @@ -0,0 +1 @@ +Subproject commit 9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03 diff --git a/external/oxen-encoding b/external/oxen-encoding deleted file mode 160000 index fc85dfd3..00000000 --- a/external/oxen-encoding +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fc85dfd352e8474bc7195b0ba881838bd72ebea6 diff --git a/external/oxen-libquic b/external/oxen-libquic new file mode 160000 index 00000000..88fdfea1 --- /dev/null +++ b/external/oxen-libquic @@ -0,0 +1 @@ +Subproject commit 88fdfea11aa1535de79f2e492e5f14897de4e689 diff --git a/external/oxen-logging b/external/oxen-logging new file mode 160000 index 00000000..6ae91a24 --- /dev/null +++ b/external/oxen-logging @@ -0,0 +1 @@ +Subproject commit 6ae91a2417c4a9e55a3b312ba4b43019a13f003b diff --git a/external/protobuf b/external/protobuf new file mode 160000 index 00000000..f0dc78d7 --- /dev/null +++ b/external/protobuf @@ -0,0 +1 @@ +Subproject commit f0dc78d7e6e331b8c6bb2d5283e06aa26883ca7c diff --git a/include/session/blinding.h b/include/session/blinding.h new file mode 100644 index 00000000..e3724883 --- /dev/null +++ b/include/session/blinding.h @@ -0,0 +1,160 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "export.h" +#include "platform.h" + +/// API: crypto/session_blind15_key_pair +/// +/// This function attempts to generate a blind15 key pair. +/// +/// Inputs: +/// - `ed25519_seckey` -- [in] the Ed25519 private key of the sender (64 bytes). +/// - `server_pk` -- [in] the public key of the open group server to generate the +/// blinded id for (32 bytes). +/// - `blinded_pk_out` -- [out] pointer to a buffer of at least 32 bytes where the blinded_pk will +/// be written if generation was successful. +/// - `blinded_sk_out` -- [out] pointer to a buffer of at least 32 bytes where the blinded_sk will +/// be written if generation was successful. +/// +/// Outputs: +/// - `bool` -- True if the key was successfully generated, false if generation failed. +LIBSESSION_EXPORT bool session_blind15_key_pair( + const unsigned char* ed25519_seckey, /* 64 bytes */ + const unsigned char* server_pk, /* 32 bytes */ + unsigned char* blinded_pk_out, /* 32 byte output buffer */ + unsigned char* blinded_sk_out /* 32 byte output buffer */); + +/// API: crypto/session_blind25_key_pair +/// +/// This function attempts to generate a blind25 key pair. +/// +/// Inputs: +/// - `ed25519_seckey` -- [in] the Ed25519 private key of the sender (64 bytes). +/// - `server_pk` -- [in] the public key of the open group server to generate the +/// blinded id for (32 bytes). +/// - `blinded_pk_out` -- [out] pointer to a buffer of at least 32 bytes where the blinded_pk will +/// be written if generation was successful. +/// - `blinded_sk_out` -- [out] pointer to a buffer of at least 32 bytes where the blinded_sk will +/// be written if generation was successful. +/// +/// Outputs: +/// - `bool` -- True if the key was successfully generated, false if generation failed. +LIBSESSION_EXPORT bool session_blind25_key_pair( + const unsigned char* ed25519_seckey, /* 64 bytes */ + const unsigned char* server_pk, /* 32 bytes */ + unsigned char* blinded_pk_out, /* 32 byte output buffer */ + unsigned char* blinded_sk_out /* 32 byte output buffer */); + +/// API: crypto/session_blind_version_key_pair +/// +/// This function attempts to generate a blind-version key pair. +/// +/// Inputs: +/// - `ed25519_seckey` -- [in] the Ed25519 private key of the user (64 bytes). +/// - `blinded_pk_out` -- [out] pointer to a buffer of at least 32 bytes where the blinded_pk will +/// be written if generation was successful. +/// - `blinded_sk_out` -- [out] pointer to a buffer of at least 64 bytes where the blinded_sk will +/// be written if generation was successful. +/// +/// Outputs: +/// - `bool` -- True if the key was successfully generated, false if generation failed. +LIBSESSION_EXPORT bool session_blind_version_key_pair( + const unsigned char* ed25519_seckey, /* 64 bytes */ + unsigned char* blinded_pk_out, /* 32 byte output buffer */ + unsigned char* blinded_sk_out /* 64 byte output buffer */); + +/// API: crypto/session_blind15_sign +/// +/// This function attempts to generate a signature for a message using a blind15 private key. +/// +/// Inputs: +/// - `ed25519_seckey` -- [in] the Ed25519 private key of the sender (64 bytes). +/// - `server_pk` -- [in] the public key of the open group server to generate the +/// blinded id for (32 bytes). +/// - `msg` -- [in] Pointer to a data buffer containing the message to generate a signature for. +/// - `msg_len` -- [in] Length of `msg` +/// - `blinded_sig_out` -- [out] pointer to a buffer of at least 64 bytes where the signature will +/// be written if generation was successful. +/// +/// Outputs: +/// - `bool` -- True if the signature was successfully generated, false if generation failed. +LIBSESSION_EXPORT bool session_blind15_sign( + const unsigned char* ed25519_seckey, /* 64 bytes */ + const unsigned char* server_pk, /* 32 bytes */ + const unsigned char* msg, + size_t msg_len, + unsigned char* blinded_sig_out /* 64 byte output buffer */); + +/// API: crypto/session_blind25_sign +/// +/// This function attempts to generate a signature for a message using a blind25 private key. +/// +/// Inputs: +/// - `ed25519_seckey` -- [in] the Ed25519 private key of the sender (64 bytes). +/// - `server_pk` -- [in] the public key of the open group server to generate the +/// blinded id for (32 bytes). +/// - `msg` -- [in] Pointer to a data buffer containing the message to generate a signature for. +/// - `msg_len` -- [in] Length of `msg` +/// - `blinded_sig_out` -- [out] pointer to a buffer of at least 64 bytes where the signature will +/// be written if generation was successful. +/// +/// Outputs: +/// - `bool` -- True if the signature was successfully generated, false if generation failed. +LIBSESSION_EXPORT bool session_blind25_sign( + const unsigned char* ed25519_seckey, /* 64 bytes */ + const unsigned char* server_pk, /* 32 bytes */ + const unsigned char* msg, + size_t msg_len, + unsigned char* blinded_sig_out /* 64 byte output buffer */); + +/// Computes a verifiable version-blinded signature that validates with the version-blinded pubkey +/// that would be returned from blind_version_key_pair. +/// +/// Takes the Ed25519 secret key (64 bytes), unix timestamp, method, path, and optional body. +/// Returns a version-blinded signature. +LIBSESSION_EXPORT bool session_blind_version_sign_request( + const unsigned char* ed25519_seckey, /* 64 bytes */ + uint64_t timestamp, + const char* method, + const char* path, + const unsigned char* body, /* optional */ + size_t body_len, + unsigned char* blinded_sig_out /* 64 byte output buffer */); + +/// Computes a verifiable version-blinded signature that validates with the version-blinded pubkey +/// that would be returned from blind_version_key_pair. +/// +/// Takes the Ed25519 secret key (64 bytes), platform and unix timestamp. Returns a version-blinded +/// signature. +LIBSESSION_EXPORT bool session_blind_version_sign( + const unsigned char* ed25519_seckey, /* 64 bytes */ + CLIENT_PLATFORM platform, + uint64_t timestamp, + unsigned char* blinded_sig_out /* 64 byte output buffer */); + +/// API: crypto/session_blind25_sign +/// +/// This function attempts to generate a signature for a message using a blind25 private key. +/// +/// Inputs: +/// - `session_id` -- [in] the session_id to compare (66 bytes with a 05 prefix). +/// - `blinded_id` -- [in] the blinded_id to compare, can be either 15 or 25 blinded (66 bytes). +/// - `server_pk` -- [in] the public key of the open group server to the blinded id came from (64 +/// bytes). +/// +/// Outputs: +/// - `bool` -- True if the session_id matches the blinded_id, false if not. +LIBSESSION_EXPORT bool session_id_matches_blinded_id( + const char* session_id, /* 66 bytes */ + const char* blinded_id, /* 66 bytes */ + const char* server_pk /* 64 bytes */); + +#ifdef __cplusplus +} +#endif diff --git a/include/session/blinding.hpp b/include/session/blinding.hpp new file mode 100644 index 00000000..fed3d8ff --- /dev/null +++ b/include/session/blinding.hpp @@ -0,0 +1,220 @@ +#pragma once + +#include +#include +#include + +#include "platform.hpp" +#include "sodium_array.hpp" + +namespace session { + +/// Helper functions for community 25xxx blinded ID from a Session pubkey and the server pubkey. +/// This is a verifiable Ed25519 pubkey (prefixed with 25) and is fully deterministic. +/// +/// Details: we first compute a blinding factor: +/// +/// k = H(SESSION_ID || SERVER_PK) mod L, where H = 64-byte BLAKE2b +/// +/// Next we convert the Session ID (an X25519 pubkey) to Ed25519, and also clear the sign bit +/// (i.e. take the positive). Thus we give up one bit of the pubkey (because the initial X25519 +/// -> Ed25519 conversion lost it). +/// +/// X = session id x25519 pubkey +/// A = |Ed(X)| -- i.e. + of the two possible Ed25519 pubkey alternatives for X +/// +/// then the blinded pubkey is prefix "25" followed by `kA`. +/// +/// To create such a signature, starting from the underlying Ed25519 keypair from seed `s`, with +/// private scalar `z` (NOT the seed) and public key point `S`, we use the same blinding factor +/// to compute private scalar `a` associated with `A`: +/// +/// a = z if S is positive (i.e. sign bit is 0) +/// a = -z if S is negative (sign bit is 1) +/// +/// which yields our blinded private scalar `ka`. +/// +/// From here we generate a signature very similarly to EdDSA, but adapted to this different +/// private signing mechanism. For a message `M`: +/// +/// r = H64(H32(seed, key="SessCommBlind25_seed") || kA || M, key="SessCommBlind25_sig") mod L +/// +/// analagously to Ed25519's +/// +/// r = SHA512(SHA512(seed)[32:64] || M) mod L +/// +/// but using BLAKE2b 64-byte and 32-byte keyed hashes instead of SHA512. (We also include the +/// `A` in the hash so that the same message with different server_pks will result in different +/// `r` values). +/// +/// From there we follow the standard EdDSA construction: +/// +/// R = rG +/// S = r + H(R || kA || M) ka (mod L) +/// +/// (using the standard Ed25519 SHA-512 here for H, so that this is verifiable as a standard +/// Ed25519 signature). +/// +/// This (R, S) signature is then Ed25519-verifiable using pubkey kA. + +/// Returns the blinding factor for 15 blinding. Typically this isn't used directly, but is +/// exposed for debugging/testing. Takes server pk in bytes, not hex. +std::array blind15_factor(std::span server_pk); + +/// Returns the blinding factor for 25 blinding. Typically this isn't used directly, but is +/// exposed for debugging/testing. Takes session id and server pk in bytes, not hex. session +/// id can be 05-prefixed (33 bytes) or unprefixed (32 bytes). +std::array blind25_factor( + std::span session_id, std::span server_pk); + +/// Computes the two possible 15-blinded ids from a session id and server pubkey. Values accepted +/// and returned are hex-encoded. +std::array blind15_id(std::string_view session_id, std::string_view server_pk); + +/// Similar to the above, but takes the session id and pubkey as byte values instead of hex, and +/// returns a single 33-byte value (instead of a 66-digit hex value). Unlike the string version, +/// session_id here may be passed unprefixed (i.e. 32 bytes instead of 33 with the 05 prefix). Only +/// the *positive* possible ID is returned: the alternative can be computed by flipping the highest +/// bit of byte 32, i.e.: `result[32] ^= 0x80`. +std::vector blind15_id( + std::span session_id, std::span server_pk); + +/// Computes the 25-blinded id from a session id and server pubkey. Values accepted and +/// returned are hex-encoded. +std::string blind25_id(std::string_view session_id, std::string_view server_pk); + +/// Same as above, but takes the session id and pubkey as byte values instead of hex, and returns a +/// 33-byte value (instead of a 66-digit hex value). Unlike the string version, session_id here may +/// be passed unprefixed (i.e. 32 bytes instead of 33 with the 05 prefix). +std::vector blind25_id( + std::span session_id, std::span server_pk); + +/// Computes the 15-blinded id from a 32-byte Ed25519 pubkey, i.e. from the known underlying Ed25519 +/// pubkey behind a (X25519) Session ID. Unlike blind15_id, knowing the true Ed25519 pubkey allows +/// thie method to compute the correct sign and so using this does not require considering that the +/// resulting blinded ID might need to have a sign flipped. +/// +/// If the `session_id` is a non-null pointer then it must point at an empty string to be populated +/// with the session_id associated with `ed_pubkey`. This is here for consistency with +/// `blinded25_id_from_ed`, but unlike the 25 version, this value is not read if non-empty, and is +/// not an optimization (that is: it is purely for convenience and is no more efficient to use this +/// than it is to compute it yourself). +std::vector blinded15_id_from_ed( + std::span ed_pubkey, + std::span server_pk, + std::vector* session_id = nullptr); + +/// Computes the 25-blinded id from a 32-byte Ed25519 pubkey, i.e. from the known underlying Ed25519 +/// pubkey behind a (X25519) Session ID. This will be the same as blind25_id (if given the X25519 +/// pubkey that the Ed25519 converts to), but is more efficient when the Ed25519 pubkey is already +/// known. +/// +/// The session_id argument is provided to optimize input or output of the session ID derived from +/// the Ed25519 pubkey: if already computed, this argument can be a pointer to a 33-byte string +/// containing the precomputed value (to avoid needing to compute it again). If unknown but needed +/// then a pointer to an empty string can be given to computed and stored the value here. Otherwise +/// (if omitted or nullptr) then the value will temporarily computed within the function. +std::vector blinded25_id_from_ed( + std::span ed_pubkey, + std::span server_pk, + std::vector* session_id = nullptr); + +/// Computes a 15-blinded key pair. +/// +/// Takes the Ed25519 secret key (64 bytes, or 32-byte seed) and the server pubkey (in hex (64 +/// digits) or bytes (32 bytes)). Returns the blinded public key and private key (NOT a seed). +/// +/// Can optionally also return the blinding factor, k, by providing a pointer to a uc32 (or +/// cleared_uc32); if non-nullptr then k will be written to it. +/// +/// It is recommended to pass the full 64-byte libsodium-style secret key for `ed25519_sk` (i.e. +/// seed + appended pubkey) as with just the 32-byte seed the public key has to be recomputed. +std::pair, cleared_uc32> blind15_key_pair( + std::span ed25519_sk, + std::span server_pk, + std::array* k = nullptr); + +/// Computes a 25-blinded key pair. +/// +/// Takes the Ed25519 secret key (64 bytes, or 32-byte seed) and the server pubkey (in hex (64 +/// digits) or bytes (32 bytes)). Returns the blinded public key and private key (NOT a seed). +/// +/// Can optionally also return the blinding factor, k', by providing a pointer to a uc32 (or +/// cleared_uc32); if non-nullptr then k' will be written to it, where k' = ±k. Here, `k'` can be +/// negative to cancel out a negative in the true pubkey, which the remote client will always assume +/// is not present when it does a Session ID -> Ed25519 conversion for blinding purposes. +/// +/// It is recommended to pass the full 64-byte libsodium-style secret key for `ed25519_sk` (i.e. +/// seed + appended pubkey) as with just the 32-byte seed the public key has to be recomputed. +std::pair, cleared_uc32> blind25_key_pair( + std::span ed25519_sk, + std::span server_pk, + std::array* k_prime = nullptr); + +/// Computes a version-blinded key pair. +/// +/// Takes the Ed25519 secret key (64 bytes, or 32-byte seed). Returns the blinded public key and +/// blinded libsodium seed value. +/// +/// It is recommended to pass the full 64-byte libsodium-style secret key for `ed25519_sk` (i.e. +/// seed + appended pubkey) as with just the 32-byte seed the public key has to be recomputed. +std::pair, cleared_uc64> blind_version_key_pair( + std::span ed25519_sk); + +/// Computes a verifiable 15-blinded signature that validates with the blinded pubkey that would +/// be returned from blind15_key_pair(). +/// +/// Takes the Ed25519 secret key (64 bytes, or 32-byte seed) and the server pubkey (in hex (64 +/// digits) or bytes (32 bytes)). Returns the 64-byte signature. +/// +/// It is recommended to pass the full 64-byte libsodium-style secret key for `ed25519_sk` (i.e. +/// seed + appended pubkey) as with just the 32-byte seed the public key has to be recomputed. +std::vector blind15_sign( + std::span ed25519_sk, + std::string_view server_pk_in, + std::span message); + +/// Computes a verifiable 25-blinded signature that validates with the blinded pubkey that would +/// be returned from blind25_id(). +/// +/// Takes the Ed25519 secret key (64 bytes, or 32-byte seed) and the server pubkey (in hex (64 +/// digits) or bytes (32 bytes)). Returns the 64-byte signature. +/// +/// It is recommended to pass the full 64-byte libsodium-style secret key for `ed25519_sk` (i.e. +/// seed + appended pubkey) as with just the 32-byte seed the public key has to be recomputed. +std::vector blind25_sign( + std::span ed25519_sk, + std::string_view server_pk, + std::span message); + +/// Computes a verifiable version-blinded signature that validates with the version-blinded pubkey +/// that would be returned from blind_version_key_pair. +/// +/// Takes the Ed25519 secret key (64 bytes, or 32-byte seed), unix timestamp, method, path, and +/// optional body. +/// Returns the version-blinded signature. +std::vector blind_version_sign_request( + std::span ed25519_sk, + uint64_t timestamp, + std::string_view method, + std::string_view path, + std::optional> body); + +/// Computes a verifiable version-blinded signature that validates with the version-blinded pubkey +/// that would be returned from blind_version_key_pair. +/// +/// Takes the Ed25519 secret key (64 bytes, or 32-byte seed), current platform and unix timestamp. +/// Returns the version-blinded signature. +std::vector blind_version_sign( + std::span ed25519_sk, Platform platform, uint64_t timestamp); + +/// Takes in a standard session_id and returns a flag indicating whether it matches the given +/// blinded_id for a given server_pk. +/// +/// Takes either a 15 or 25 blinded_id (66 bytes) and the server pubkey (64 bytes). +/// +/// Returns a flag indicating whether the session_id matches the blinded_id. +bool session_id_matches_blinded_id( + std::string_view session_id, std::string_view blinded_id, std::string_view server_pk); + +} // namespace session diff --git a/include/session/config.hpp b/include/session/config.hpp index 45044615..1be5f283 100644 --- a/include/session/config.hpp +++ b/include/session/config.hpp @@ -15,8 +15,20 @@ namespace session::config { -// FIXME: for multi-message we encode to longer and then split it up -inline constexpr int MAX_MESSAGE_SIZE = 76800; // 76.8kB = Storage server's limit +// The maximum size of a single message in storage server. For larger configs we have to split a +// config message into a multipart message. +inline constexpr int MAX_MESSAGE_SIZE = 76'800; // 76.8kB = Storage server's limit + +// Maximum size of a post-compression outgoing multipart config message (before chopping into +// multiple parts, and so not counting the small encryption and encoding overhead of each piece) +// that we will allow. This is not a strict network limit, but rather is simply to prevent bugs +// that have some sort of accidental runaway size. This limit does not apply to *incoming* +// multipart messages so that if a future version changes the limit it will not break compatibility +// with existing clients. +// +// Also note that we encode part index and length as 1 byte each, so this must be small enough to +// not exceed 255 parts (but that would be insane anyway). +inline constexpr int MAX_MULTIPART_SIZE = 5'000'000; // Application data data types: using scalar = std::variant; @@ -60,6 +72,11 @@ struct missing_signature : signature_error { struct config_parse_error : config_error { using config_error::config_error; }; +/// Type thrown for some bad value in a config (e.g. missing required key, or key with an +/// unexpected/unhandled value). +struct config_value_error : config_parse_error { + using config_parse_error::config_parse_error; +}; /// Class for a parsed, read-only config message; also serves as the base class of a /// MutableConfigMessage which allows setting values. @@ -87,13 +104,13 @@ class ConfigMessage { /// (so that they can return a reference to it). seqno_hash_t seqno_hash_{0, {0}}; - bool verified_signature_ = false; + std::optional> verified_signature_; // This will be set during construction from configs based on the merge result: - // -1 means we had to merge one or more configs together into a new merged config - // >= 0 indicates the index of the config we used if we did not merge (i.e. there was only one + // nullopt means we had to merge one or more configs together into a new merged config + // If set to a value then the value is the index of the config we used (i.e. there was only one // config, or there were multiple but one of them referenced all the others). - int unmerged_ = -1; + std::optional unmerged_; public: constexpr static int DEFAULT_DIFF_LAGS = 5; @@ -103,10 +120,12 @@ class ConfigMessage { /// message. It can also throw to abort message construction (that is: returning false skips /// the message when loading multiple messages, but can still continue with other messages; /// throwing aborts the entire construction). - using verify_callable = std::function; + using verify_callable = std::function data, std::span signature)>; /// Signing function: this is passed the data to be signed and returns the 64-byte signature. - using sign_callable = std::function; + using sign_callable = + std::function(std::span data)>; ConfigMessage(); ConfigMessage(const ConfigMessage&) = default; @@ -119,11 +138,11 @@ class ConfigMessage { /// Initializes a config message by parsing a serialized message. Throws on any error. See the /// vector version below for argument descriptions. explicit ConfigMessage( - ustring_view serialized, + std::span serialized, verify_callable verifier = nullptr, sign_callable signer = nullptr, int lag = DEFAULT_DIFF_LAGS, - bool signature_optional = false); + bool trust_signature = false); /// Constructs a new ConfigMessage by loading and potentially merging multiple serialized /// ConfigMessages together, according to the config conflict resolution rules. The result @@ -147,10 +166,6 @@ class ConfigMessage { /// diffs that exceeding this lag value will have those early lagged diffs dropping during /// loading. /// - /// signature_optional - if true then accept a message with no signature even when a verifier is - /// set, thus allowing unsigned messages (though messages with an invalid signature are still - /// not allowed). This option is ignored when verifier is not set. - /// /// error_handler - if set then any config message parsing error will be passed to this function /// for handling with the index of `configs` that failed and the error exception: the callback /// typically warns and, if the overall construction should abort, rethrows the error. If this @@ -159,11 +174,10 @@ class ConfigMessage { /// `[](size_t, const auto& e) { throw e; }` can be used to make any parse error of any message /// fatal. explicit ConfigMessage( - const std::vector& configs, + const std::vector>& configs, verify_callable verifier = nullptr, sign_callable signer = nullptr, int lag = DEFAULT_DIFF_LAGS, - bool signature_optional = false, std::function error_handler = nullptr); /// Returns a read-only reference to the contained data. (To get a mutable config object use @@ -203,18 +217,21 @@ class ConfigMessage { /// After loading multiple config files this flag indicates whether or not we had to produce a /// new, merged configuration message (true) or did not need to merge (false). (For config /// messages that were not loaded from serialized data this is always true). - bool merged() const { return unmerged_ == -1; } + bool merged() const { return !unmerged_; } /// After loading multiple config files this field contains the index of the single config we /// used if we didn't need to merge (that is: there was only one config or one config that /// superceded all the others). If we had to merge (or this wasn't loaded from serialized - /// data), this will return -1. - int unmerged_index() const { return unmerged_; } - - /// Returns true if this message contained a valid, verified signature when it was parsed. - /// Returns false otherwise (e.g. not loaded from verification at all; loaded without a - /// verification function; or had no signature and a signature wasn't required). - bool verified_signature() const { return verified_signature_; } + /// data), this will return std::nullopt. + const std::optional& unmerged_index() const { return unmerged_; } + + /// Read-only access to the optional verified signature if this message contained a valid, + /// verified signature when it was parsed. Returns nullopt otherwise (e.g. not loaded from + /// verification at all; loaded without a verification function; or had no signature and a + /// signature wasn't required). + const std::optional>& verified_signature() { + return verified_signature_; + } /// Constructs a new MutableConfigMessage from this config message with an incremented seqno. /// The new config message's diff will reflect changes made after this construction. @@ -228,10 +245,11 @@ class ConfigMessage { /// typically for a local serialization value that isn't being pushed to the server). Note that /// signing is always disabled if there is no signing callback set, regardless of the value of /// this argument. - virtual ustring serialize(bool enable_signing = true); + virtual std::vector serialize(bool enable_signing = true); protected: - ustring serialize_impl(const oxenc::bt_dict& diff, bool enable_signing = true); + std::vector serialize_impl( + const oxenc::bt_dict& diff, bool enable_signing = true); }; // Constructor tag @@ -279,22 +297,20 @@ class MutableConfigMessage : public ConfigMessage { /// constructor only increments seqno once while the indirect version would increment twice in /// the case of a required merge conflict resolution. explicit MutableConfigMessage( - const std::vector& configs, + const std::vector>& configs, verify_callable verifier = nullptr, sign_callable signer = nullptr, int lag = DEFAULT_DIFF_LAGS, - bool signature_optional = false, std::function error_handler = nullptr); /// Wrapper around the above that takes a single string view to load a single message, doesn't /// take an error handler and instead always throws on parse errors (the above also throws for /// an erroneous single message, but with a less specific "no valid config messages" error). explicit MutableConfigMessage( - ustring_view config, + std::span config, verify_callable verifier = nullptr, sign_callable signer = nullptr, - int lag = DEFAULT_DIFF_LAGS, - bool signature_optional = false); + int lag = DEFAULT_DIFF_LAGS); /// Does the same as the base incrementing, but also records any diff info from the current /// MutableConfigMessage. *this* object gets pruned and signed as part of this call. If the @@ -333,15 +349,56 @@ class MutableConfigMessage : public ConfigMessage { /// pruning. bool prune(); - /// Calculates the hash of the current message. Can optionally be given the already-serialized - /// value, if available; if empty/omitted, `serialize()` will be called to compute it. + /// Calculates the hash of the current message. const hash_t& hash() override; protected: - const hash_t& hash(ustring_view serialized); + /// Internal version of hash() that takes the already-serialized value, to avoid needing a call + /// to `serialize()` when such a call has already been done for other reasons. + const hash_t& hash(std::span serialized); void increment_impl(); }; +/// API: base/verify_config_sig +/// +/// Verifies a config message signature, throwing a missing_signature or signature_error exception +/// if the signature is missing or invalid. +/// +/// A config message signature is always in the "~" key of a config message, which must be the +/// very last key of the message, and signs the config value up to (but not including) the ~ +/// key-value pair in the serialized config message. +/// +/// For instance, for a config message of: +/// +/// d[...configdata...]1:~64:[sigdata]e +/// +/// the signature signs the value `d[...configdata...]` (i.e. the `1:~64:[sigdata]` signature +/// pair, and the final closing `e` of the config message, are not included). No keys may +/// follow the signature key/value. +/// +/// Inputs: +/// - `dict` -- a `bt_dict_consumer` positioned at or before the "~" key where the signature is +/// expected. (If the bt_dict_consumer has already consumed the "~" key then this call will fail +/// as if the signature was missing). +/// - `verifier` -- a callback to invoke to verify the signature of the message. If the callback is +/// empty then the signature will be ignored (it is neither required nor verified). +/// - `verified_signature` is a pointer to a std::optional array of signature data; if this is +/// specified and not nullptr then the optional with be emplaced with the signature bytes if the +/// signature successfully validates. +/// - `trust_signature` allows setting `verified_signature` when a signature is present but no +/// verifier is provided to verify it. Without specifying this, `verified_signature` will only be +/// set when the signature actually validates via a `verifier` call. This is primarily used +/// during restoring config dumps. +/// +/// Outputs: +/// - returns with no value on success +/// - throws on failure +void verify_config_sig( + oxenc::bt_dict_consumer dict, + const ConfigMessage::verify_callable& verifier, + std::optional>* verified_signature = nullptr, + bool trust_signature = false); + } // namespace session::config namespace oxenc::detail { diff --git a/include/session/config/base.h b/include/session/config/base.h index de9806e3..67789fc1 100644 --- a/include/session/config/base.h +++ b/include/session/config/base.h @@ -42,44 +42,6 @@ typedef struct config_object { /// - `conf` -- [in] Pointer to config_object object LIBSESSION_EXPORT void config_free(config_object* conf); -typedef enum config_log_level { - LOG_LEVEL_DEBUG = 0, - LOG_LEVEL_INFO, - LOG_LEVEL_WARNING, - LOG_LEVEL_ERROR -} config_log_level; - -/// API: base/config_set_logger -/// -/// Sets a logging function; takes the log function pointer and a context pointer (which can be NULL -/// if not needed). The given function pointer will be invoked with one of the above values, a -/// null-terminated c string containing the log message, and the void* context object given when -/// setting the logger (this is for caller-specific state data and won't be touched). -/// -/// The logging function must have signature: -/// -/// void log(config_log_level lvl, const char* msg, void* ctx); -/// -/// Can be called with callback set to NULL to clear an existing logger. -/// -/// The config object itself has no log level: the caller should filter by level as needed. -/// -/// Declaration: -/// ```cpp -/// VOID config_set_logger( -/// [in, out] config_object* conf, -/// [in] void(*)(config_log_level, const char*, void*) callback, -/// [in] void* ctx -/// ); -/// ``` -/// -/// Inputs: -/// - `conf` -- [in] Pointer to config_object object -/// - `callback` -- [in] Callback function -/// - `ctx` --- [in, optional] Pointer to an optional context. Set to NULL if unused -LIBSESSION_EXPORT void config_set_logger( - config_object* conf, void (*callback)(config_log_level, const char*, void*), void* ctx); - /// API: base/config_storage_namespace /// /// Returns the numeric namespace in which config messages of this type should be stored. @@ -91,6 +53,17 @@ LIBSESSION_EXPORT void config_set_logger( /// - `int16_t` -- integer of the namespace LIBSESSION_EXPORT int16_t config_storage_namespace(const config_object* conf); +/// Struct containing a list of C strings. Typically where this is returned by this API it must be +/// freed (via `free()`) when done with it. +/// +/// When returned as a pointer by a libsession-util function this is allocated in such a way that +/// just the outer config_string_list can be free()d to free both the list *and* the inner `value` +/// and pointed-at values. +typedef struct config_string_list { + char** value; // array of null-terminated C strings + size_t len; // length of `value` +} config_string_list; + /// API: base/config_merge /// /// Merges the config object with one or more remotely obtained config strings. After this call the @@ -117,13 +90,15 @@ LIBSESSION_EXPORT int16_t config_storage_namespace(const config_object* conf); /// - `count` -- [in] is the length of all three arrays. /// /// Outputs: -/// - `int` -- -LIBSESSION_EXPORT int config_merge( +/// - `config_string_list*` -- pointer to the list of successfully parsed hashes; the pointer +/// belongs to the caller and must be freed when done with it. + +LIBSESSION_EXPORT config_string_list* config_merge( config_object* conf, - const char** msg_hashes, - const unsigned char** configs, + const char* const* msg_hashes, + const unsigned char* const* configs, const size_t* lengths, - size_t count); + size_t count) LIBSESSION_WARN_UNUSED; /// API: base/config_needs_push /// @@ -148,10 +123,12 @@ LIBSESSION_EXPORT bool config_needs_push(const config_object* conf); typedef struct config_push_data { // The config seqno (to be provided later in `config_confirm_pushed`). seqno_t seqno; - // The config message to push (binary data, not null-terminated). - unsigned char* config; - // The length of `config` - size_t config_len; + // Array of config message(s) to push (each is binary data, not null-terminated). + unsigned char** config; + // Array of lengths of the messages in `config` + size_t* config_lens; + // Length of config and config_len arrays. + size_t n_configs; // Array of obsolete message hashes to delete; each element is a null-terminated C string char** obsolete; // length of `obsolete` @@ -186,24 +163,28 @@ LIBSESSION_EXPORT config_push_data* config_push(config_object* conf); /// API: base/config_confirm_pushed /// /// Reports that data obtained from `config_push` has been successfully stored on the server with -/// message hash `msg_hash`. The seqno value is the one returned by the config_push call that -/// yielded the config data. +/// message hash(es) `msg_hashes` containing `hashes_len` hashes. The seqno value is the one +/// returned by the config_push call that yielded the config data. The order of hashes in the +/// msg_hashes array is unimportant. /// /// Declaration: /// ```cpp /// VOID config_confirm_pushed( /// [in, out] config_object* conf, -/// [out] seqno_t seqno, -/// [out] const char* msg_hash +/// [in] seqno_t seqno, +/// [in] const char* const* msg_hashes +/// [in] size_t hashes_len /// ); /// ``` /// /// Inputs: /// - `conf` -- [in] Pointer to config_object object -/// - `seqno` -- [out] Value returned by config_push call -/// - `msg_hash` -- [out] Value returned by config_push call +/// - `seqno` -- [in] Value returned by config_push call +/// - `msg_hashes` -- [in] array of message hashes (null terminated C strings) returned by the +/// storage server when stored. +/// - `hashes_len` -- [in] length of the `msg_hashes` array LIBSESSION_EXPORT void config_confirm_pushed( - config_object* conf, seqno_t seqno, const char* msg_hash); + config_object* conf, seqno_t seqno, const char* const* msg_hashes, size_t hashes_len); /// API: base/config_dump /// @@ -229,7 +210,10 @@ LIBSESSION_EXPORT void config_confirm_pushed( /// - `conf` -- [in] Pointer to config_object object /// - `out` -- [out] Pointer to the output location /// - `outlen` -- [out] Length of output -LIBSESSION_EXPORT void config_dump(config_object* conf, unsigned char** out, size_t* outlen); +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool config_dump(config_object* conf, unsigned char** out, size_t* outlen); /// API: base/config_needs_dump /// @@ -251,24 +235,46 @@ LIBSESSION_EXPORT void config_dump(config_object* conf, unsigned char** out, siz /// - `bool` -- True if config has changed since last call to `dump()` LIBSESSION_EXPORT bool config_needs_dump(const config_object* conf); -/// Struct containing a list of C strings. Typically where this is returned by this API it must be -/// freed (via `free()`) when done with it. -typedef struct config_string_list { - char** value; // array of null-terminated C strings - size_t len; // length of `value` -} config_string_list; +/// API: base/config_curr_hashes +/// +/// Obtains the hashes of the current config state. Note that this will be empty if the current +/// hash is unknown or not yet determined (for example, because the current state is dirty or +/// because the most recent push is still pending and we don't know the hash yet). +/// +/// See also config_active_hashes(), which you often want to use instead of this. +/// +/// The returned pointer belongs to the caller and must be freed via `free()` when done with it. +/// +/// Declaration: +/// ```cpp +/// CONFIG_STRING_LIST* config_curr_hashes( +/// [in] const config_object* conf +/// ); +/// +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to config_object object +/// +/// Outputs: +/// - `config_string_list*` -- pointer to the list of hashes; the pointer belongs to the caller +LIBSESSION_EXPORT config_string_list* config_curr_hashes(const config_object* conf) + LIBSESSION_WARN_UNUSED; -/// API: base/config_current_hashes +/// API: base/config_active_hashes +/// +/// Obtains the hashes of currently active config messages. This includes both the hashes of the +/// current config (as returned by config_curr_hashes), but also any partially-arrived multipart +/// config messages for which we are still waiting for remaining parts to complete the config. /// -/// Obtains the current active hashes. Note that this will be empty if the current hash is unknown -/// or not yet determined (for example, because the current state is dirty or because the most -/// recent push is still pending and we don't know the hash yet). +/// The returned array of hashes have no particular ordering (and may differ from one call to the +/// next even without any change to the values considered as a set). /// /// The returned pointer belongs to the caller and must be freed via `free()` when done with it. /// /// Declaration: /// ```cpp -/// CONFIG_STRING_LIST* config_current_hashes( +/// CONFIG_STRING_LIST* config_active_hashes( /// [in] const config_object* conf /// ); /// @@ -278,8 +284,53 @@ typedef struct config_string_list { /// - `conf` -- [in] Pointer to config_object object /// /// Outputs: -/// - `config_string_list*` -- point to the list of hashes, pointer belongs to the caller -LIBSESSION_EXPORT config_string_list* config_current_hashes(const config_object* conf); +/// - `config_string_list*` -- pointer to the list of hashes; the pointer belongs to the caller +LIBSESSION_EXPORT config_string_list* config_active_hashes(const config_object* conf) + LIBSESSION_WARN_UNUSED; + +/// API: base/config_old_hashes +/// +/// Obtains the known old hashes. Note that this will be empty if there are no old hashes or +/// the config is in a dirty state (in which case these should be retrieved via the `push` +/// function). Calling this function, or the `push` function, will clear the stored old_hashes. +/// +/// The returned pointer belongs to the caller and must be freed via `free()` when done with it. +/// +/// Declaration: +/// ```cpp +/// CONFIG_STRING_LIST* config_old_hashes( +/// [in] const config_object* conf +/// ); +/// +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to config_object object +/// +/// Outputs: +/// - `config_string_list*` -- pointer to the list of hashes; the pointer belongs to the caller +LIBSESSION_EXPORT config_string_list* config_old_hashes(config_object* conf) LIBSESSION_WARN_UNUSED; + +/// API: base/config_get_keys +/// +/// Obtains the current group decryption keys. +/// +/// Returns a buffer where each consecutive 32 bytes is an encryption key for the object, in +/// priority order (i.e. the key at 0 is the encryption key, and the first decryption key). +/// +/// This function is mainly for debugging/diagnostics purposes; most config types have one single +/// key (based on the secret key), and multi-keyed configs such as groups have their own methods for +/// encryption/decryption that are already aware of the multiple keys. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config_object object +/// - `len` -- [out] Pointer where the number of keys will be written (that is: the returned pointer +/// will be to a buffer which has a size of of this value times 32). +/// +/// Outputs: +/// - `unsigned char*` -- pointer to newly malloced key data (a multiple of 32 bytes); the pointer +/// belongs to the caller and must be `free()`d when done with it. +LIBSESSION_EXPORT unsigned char* config_get_keys(const config_object* conf, size_t* len); /// Config key management; see the corresponding method docs in base.hpp. All `key` arguments here /// are 32-byte binary buffers (and since fixed-length, there is no keylen argument). @@ -294,7 +345,7 @@ LIBSESSION_EXPORT config_string_list* config_current_hashes(const config_object* /// /// Declaration: /// ```cpp -/// VOID config_add_key( +/// BOOL config_add_key( /// [in, out] config_object* conf, /// [in] const unsigned char* key /// ); @@ -304,7 +355,10 @@ LIBSESSION_EXPORT config_string_list* config_current_hashes(const config_object* /// Inputs: /// - `conf` -- [in, out] Pointer to config_object object /// - `key` -- [in] Pointer to the binary key object, must be 32 bytes -LIBSESSION_EXPORT void config_add_key(config_object* conf, const unsigned char* key); +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool config_add_key(config_object* conf, const unsigned char* key); /// API: base/config_add_key_low_prio /// @@ -313,7 +367,7 @@ LIBSESSION_EXPORT void config_add_key(config_object* conf, const unsigned char* /// /// Declaration: /// ```cpp -/// VOID config_add_key_low_prio( +/// BOOL config_add_key_low_prio( /// [in, out] config_object* conf, /// [in] const unsigned char* key /// ); @@ -323,7 +377,10 @@ LIBSESSION_EXPORT void config_add_key(config_object* conf, const unsigned char* /// Inputs: /// - `conf` -- [in, out] Pointer to config_object object /// - `key` -- [in] Pointer to the binary key object, must be 32 bytes -LIBSESSION_EXPORT void config_add_key_low_prio(config_object* conf, const unsigned char* key); +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool config_add_key_low_prio(config_object* conf, const unsigned char* key); /// API: base/config_clear_keys /// @@ -446,6 +503,65 @@ LIBSESSION_EXPORT const unsigned char* config_key(const config_object* conf, siz /// - `char*` -- encryption domain C-str used to encrypt values LIBSESSION_EXPORT const char* config_encryption_domain(const config_object* conf); +/// API: base/config_set_sig_keys +/// +/// Sets an Ed25519 keypair pair for signing and verifying config messages. When set, this adds an +/// additional signature for verification into the config message (*after* decryption) that +/// validates a config message. +/// +/// This is used in config contexts where the encryption/decryption keys are insufficient for +/// permission verification to produce new messages, such as in groups where non-admins need to be +/// able to decrypt group data, but are not permitted to push new group data. In such a case only +/// the admins have the secret key with which messages can be signed; regular users can only read, +/// but cannot write, config messages. +/// +/// When a signature public key (with or without a secret key) is set the config object enters a +/// "signing-required" mode, which has some implications worth noting: +/// - incoming messages must contain a signature that verifies with the public key; messages +/// without such a signature will be dropped as invalid. +/// - because of the above, a config object cannot push config updates without the secret key: +/// thus any attempt to modify the config message with a pubkey-only config object will raise +/// an exception. +/// +/// Inputs: +/// - `secret` -- pointer to a 64-byte sodium-style Ed25519 "secret key" buffer (technically the +/// seed+precomputed pubkey concatenated together) that sets both the secret key and public key. +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool config_set_sig_keys(config_object* conf, const unsigned char* secret); + +/// API: base/config_set_sig_pubkey +/// +/// Sets a Ed25519 signing pubkey which incoming messages must be signed by to be acceptable. This +/// is intended for use when the secret key is not known (see `config_set_sig_keys()` to set both +/// secret and pubkey keys together). +/// +/// Inputs: +/// - `pubkey` -- pointer to the 32-byte Ed25519 pubkey that must have signed incoming messages. +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool config_set_sig_pubkey(config_object* conf, const unsigned char* pubkey); + +/// API: base/config_get_sig_pubkey +/// +/// Returns a pointer to the 32-byte Ed25519 signing pubkey, if set. Returns nullptr if there is no +/// current signing pubkey. +/// +/// Inputs: none. +/// +/// Outputs: +/// - pointer to the 32-byte pubkey, or NULL if not set. +LIBSESSION_EXPORT const unsigned char* config_get_sig_pubkey(const config_object* conf); + +/// API: base/config_clear_sig_keys +/// +/// Drops the signature pubkey and/or secret key, if the object has them. +/// +/// Inputs: none. +LIBSESSION_EXPORT void config_clear_sig_keys(config_object* conf); + #ifdef __cplusplus } // extern "C" #endif diff --git a/include/session/config/base.hpp b/include/session/config/base.hpp index 987e587d..eeda53e1 100644 --- a/include/session/config/base.hpp +++ b/include/session/config/base.hpp @@ -1,16 +1,32 @@ #pragma once +#include + #include +#include +#include #include #include +#include +#include #include #include #include #include +#include "../hash.hpp" +#include "../logging.hpp" +#include "../sodium_array.hpp" #include "base.h" #include "namespaces.hpp" +using namespace std::literals; + +namespace oxenc { +class bt_dict_producer; +class bt_dict_consumer; +} // namespace oxenc + namespace session::config { template @@ -25,9 +41,6 @@ template static constexpr bool is_dict_value = is_dict_subtype || is_one_of; -// Levels for the logging callback -enum class LogLevel { debug = 0, info, warning, error }; - /// Our current config state enum class ConfigState : int { /// Clean means the config is confirmed stored on the server and we haven't changed anything. @@ -43,9 +56,97 @@ enum class ConfigState : int { Waiting = 2, }; +using Ed25519PubKey = std::array; +using Ed25519Secret = sodium_array; + +// Helper base class for holding a config signing keypair +class ConfigSig { + protected: + // Contains an optional signing keypair; if the public key is set then incoming messages must + // contain a valid signature from that key to be loaded. If the private key is set then a + // signature will be added to the message signed by that key. (Note that if a public key is set + // but not a private key then this config object cannot push config changes!) + std::optional _sign_pk = std::nullopt; + Ed25519Secret _sign_sk; + + ConfigSig() = default; + + // Returns a blake2b 32-byte hash of the config signing seed using hash key `key`. `key` must + // be 64 bytes or less, and should generally be unique for each key use case. + // + // Throws if a secret key hasn't been set via `set_sig_keys`. + std::array seed_hash(std::string_view key) const; + + virtual void set_verifier(ConfigMessage::verify_callable v) = 0; + virtual void set_signer(ConfigMessage::sign_callable v) = 0; + + // Meant to be called from the subclass constructor after other necessary initialization; calls + // set_sig_keys, set_sig_pubkey, or clear_sig_keys() for you, based on which are non-nullopt. + // + // Throws if given invalid data (i.e. wrong key size, or mismatched pubkey/secretkey). + void init_sig_keys( + std::optional> ed25519_pubkey, + std::optional> ed25519_secretkey); + + public: + virtual ~ConfigSig() = default; + + /// API: base/ConfigSig::set_sig_keys + /// + /// Sets an Ed25519 keypair pair for signing and verifying config messages. When set, this adds + /// an additional signature for verification into the config message (*after* decryption) that + /// validates a config message. + /// + /// This is used in config contexts where the encryption/decryption keys are insufficient for + /// permission verification to produce new messages, such as in groups where non-admins need to + /// be able to decrypt group data, but are not permitted to push new group data. In such a case + /// only the admins have the secret key with which messages can be signed; regular users can + /// only read, but cannot write, config messages. + /// + /// When a signature public key (with or without a secret key) is set the config object enters + /// a "signing-required" mode, which has some implications worth noting: + /// - incoming messages must contain a signature that verifies with the public key; messages + /// without such a signature will be dropped as invalid. + /// - because of the above, a config object cannot push config updates without the secret key: + /// thus any attempt to modify the config message with a pubkey-only config object will raise + /// an exception. + /// + /// Inputs: + /// - `secret` -- the 64-byte sodium-style Ed25519 "secret key" (actually the seed+pubkey + /// concatenated together) that sets both the secret key and public key. + void set_sig_keys(std::span secret); + + /// API: base/ConfigSig::set_sig_pubkey + /// + /// Sets a Ed25519 signing pubkey which incoming messages must be signed by to be acceptable. + /// This is intended for use when the secret key is not known (see `set_sig_keys()` to set both + /// secret and pubkey keys together). + /// + /// Inputs: + /// - `pubkey` -- the 32 byte Ed25519 pubkey that must have signed incoming messages + void set_sig_pubkey(std::span pubkey); + + /// API: base/ConfigSig::get_sig_pubkey + /// + /// Returns a const reference to the 32-byte Ed25519 signing pubkey, if set. + /// + /// Inputs: none. + /// + /// Outputs: + /// - reference to the 32-byte pubkey, or `std::nullopt` if not set. + const std::optional>& get_sig_pubkey() const { return _sign_pk; } + + /// API: base/ConfigSig::clear_sig_keys + /// + /// Drops the signature pubkey and/or secret key, if the object has them. + /// + /// Inputs: none. + void clear_sig_keys(); +}; + /// Base config type for client-side configs containing common functionality needed by all config /// sub-types. -class ConfigBase { +class ConfigBase : public ConfigSig { private: // The object (either base config message or MutableConfigMessage) that stores the current // config message. Subclasses do not directly access this: instead they call `dirty()` if they @@ -61,24 +162,114 @@ class ConfigBase { // element will be used when encrypting a new message to push. When decrypting, we attempt each // of them, starting with .front(), until decryption succeeds. using Key = std::array; - Key* _keys = nullptr; - size_t _keys_size = 0; - size_t _keys_capacity = 0; + sodium_vector _keys; - // Contains the current active message hash, as fed into us in `confirm_pushed()`. Empty if we - // don't know it yet. When we dirty the config this value gets moved into `old_hashes_` to be - // removed by the next push. - std::string _curr_hash; + // Contains the current active message hash(es), as fed into us in `confirm_pushed()`. + // Typically just one hash, but multiple will occur when dealing with multipart config messages. + // Empty if we don't know it yet. When we dirty the config these hashes get moved into + // `old_hashes_` to be removed by the next push. + std::unordered_set _curr_hashes; // Contains obsolete known message hashes that are obsoleted by the most recent merge or push; - // these are returned (and cleared) when `push` is called. + // these are returned (and cleared) when `push` or `old_hashes` are called. std::unordered_set _old_hashes; + struct PartialMessage { + int index; // 0-based index of this part + std::string message_id; // storage server message hash of this part + std::vector data; // Data chunk + + PartialMessage( + int index, std::string_view message_id, std::span data) : + index{index}, message_id{message_id}, data{data.begin(), data.end()} {} + }; + struct PartialMessages { + bool done = false; // Will be true if this is an already-processed multipart set. We keep + // such stubs around after completing them so that we can optimize away + // reprocessing duplicate parts that might arrive in the near future. + + int size = 0; // Total number of parts of this multipart message, if still being + // accumulated. 0 if the message is done. + + std::list parts; // The individual message parts, in ascending order. + + // The expiry of this message info. This gets updated whenever we receive a new part of the + // same message set, and when we complete processing. + std::chrono::system_clock::time_point expiry; + + // Shortcut for resetting all fields as appropriate for a finished record: this clears + // parts, sets size to 0, and updates the expiry to now plus the given expiry duration + // (usually the MULTIPART_MAX_REMEMBER value). + void finish(std::chrono::milliseconds lifetime) { + done = true; + size = 0; + parts.clear(); + expiry = std::chrono::system_clock::now() + lifetime; + } + }; + + // Partial message sets that we have received but not yet been able to join into a full message. + // The key is a hash of the final combined data (included in each part to identify related + // parts) used as a unique identifier and checksum; the value is the PartialMessages struct + // containing set metadata and individual parts. (This is an ordered hash, because we relying + // on the keys being sorted when dumping our state to a config item.) + std::map _multiparts; + + // Parses a new multipart message, handling parsing, adding to _multiparts, etc. This is called + // by _merge when it finds a `m`-type message for handling. + // + // - msg_id is the storage-server assigned message id (which is, in current implementation a + // base64 encoded hash, but storage server is allowed to do whatever it wants for this). + // - message is the full message body received (i.e. including the `m` message type prefix). + // + // Returns pair of: + // - true/false indicating whether the single given message was accepted (i.e. parsed correctly + // and didn't have invalid parameters). + // - optional pair that will be non-null only when this message part completed a set of message + // parts resulting in a new, previously unseen message. The first element is a list of + // individual message ids (which will include the input one); the second is the reconstituted + // (and decompressed, if needed) final message body. + // + // For new parts that don't complete a set, errors, and already seen messages the optional + // value will be nullopt. + std::pair, std::vector>>> + _handle_multipart(std::string_view msg_id, std::span message); + + // Writes multipart data into the sub-dict of the dump data. + void _dump_multiparts(oxenc::bt_dict_producer&& multi) const; + + // Loads multipart data from the sub-dict of the dump data. + void _load_multiparts(oxenc::bt_dict_consumer&& multi); + + // Cleans up any expired multipart data. + void _expire_multiparts(); + protected: // Constructs a base config by loading the data from a dump as produced by `dump()`. If the // dump is nullopt then an empty base config is constructed with no config settings and seqno // set to 0. - explicit ConfigBase(std::optional dump = std::nullopt); + // + // Can optionally be passed a pubkey or secretkey (or both, but the pubkey can be obtained from + // the secretkey automatically): if either is given, the config object is set up to require + // verification of incoming messages using the associated pubkey, and will be signed using the + // secretkey (if a secret key is given). + explicit ConfigBase( + std::optional> dump = std::nullopt, + std::optional> ed25519_pubkey = std::nullopt, + std::optional> ed25519_secretkey = std::nullopt); + + // Initializes the base config object with dump data and keys; this is typically invoked by the + // constructor, but is exposed to subclasses so that they can delay initial processing by + // default-constructing the base class and then calling this from their own constructor. This + // two-step call pattern is *required* when using extra data in particular [because the virtual + // load_extra_data call and any derived class fields are not available before derived class + // construction]. + // + // This method must not be called outside derived class construction! + void init( + std::optional> dump = std::nullopt, + std::optional> ed25519_pubkey = std::nullopt, + std::optional> ed25519_secretkey = std::nullopt); // Tracks whether we need to dump again; most mutating methods should set this to true (unless // calling set_state, which sets to to true implicitly). @@ -89,16 +280,19 @@ class ConfigBase { // deleted at the next push. void set_state(ConfigState s); - // Invokes the `logger` callback if set, does nothing if there is no logger. - void log(LogLevel lvl, std::string msg) { - if (logger) - logger(lvl, std::move(msg)); - } - // Returns a reference to the current MutableConfigMessage. If the current message is not // already dirty (i.e. Clean or Waiting) then calling this increments the seqno counter. MutableConfigMessage& dirty(); + void set_verifier(ConfigMessage::verify_callable v) override; + void set_signer(ConfigMessage::sign_callable s) override; + + // Virtual method to be overloaded by derived classes. Protobuf wrapped messages are used + // for legacy types, so we need different logic depending on the class in question. All new + // types will reject the protobuf and directly handle the binary data. Old types will try + // protobuf parsing on incoming messages and handle all outgoing messages directly in binary + virtual bool accepts_protobuf() const { return false; } + public: // class for proxying subfield access; this class should never be stored but only used // ephemerally (most of its methods are rvalue-qualified). This lets constructs such as @@ -212,7 +406,7 @@ class ConfigBase { /// API: base/ConfigBase::DictFieldProxy::assign_if_changed /// /// Takes a value and assigns it to the dict only if that value is different. - /// Will avoid dirtying the config if the assignement isnt changing anything + /// Will avoid dirtying the config if the assignement isn't changing anything /// /// Inputs: /// - `value` -- This will be assigned to the dict if it has changed @@ -343,15 +537,18 @@ class ConfigBase { /// API: base/ConfigBase::DictFieldProxy::uview /// - /// Returns the value as a ustring_view, if it exists and is a string; nullopt otherwise. + /// Returns the value as a std::span, if it exists and is a string; + /// nullopt otherwise. /// /// Inputs: None /// /// Outputs: - /// - `std::optional` -- Returns a value as a view if it exists - std::optional uview() const { + /// - `std::optional>` -- Returns a value as a view if it + /// exists + std::optional> uview() const { if (auto* s = get_clean()) - return ustring_view{reinterpret_cast(s->data()), s->size()}; + return std::span{ + reinterpret_cast(s->data()), s->size()}; return std::nullopt; } @@ -458,17 +655,17 @@ class ConfigBase { /// - `value` -- replaces current value with given string view void operator=(std::string_view value) { *this = std::string{value}; } - /// API: base/ConfigBase::DictFieldProxy::operator=(ustring_view) + /// API: base/ConfigBase::DictFieldProxy::operator=(std::span) /// - /// Replaces the current value with the given ustring_view. This also auto-vivifies any - /// intermediate dicts needed to reach the given key, including replacing non-dict values if - /// they currently exist along the path (this makes a copy). + /// Replaces the current value with the given std::span. This also + /// auto-vivifies any intermediate dicts needed to reach the given key, including replacing + /// non-dict values if they currently exist along the path (this makes a copy). /// /// Inputs: - /// - `value` -- replaces current value with given ustring_view + /// - `value` -- replaces current value with given std::span /// - /// Same as above, but takes a ustring_view - void operator=(ustring_view value) { + /// Same as above, but takes a std::span + void operator=(std::span value) { *this = std::string{reinterpret_cast(value.data()), value.size()}; } @@ -644,29 +841,62 @@ class ConfigBase { }; protected: - /// API: base/ConfigBase::extra_data + /// API: base/ConfigBase::_merge /// - /// Called when dumping to obtain any extra data that a subclass needs to store to reconstitute - /// the object. The base implementation does nothing. The counterpart to this, - /// `load_extra_data()`, is called when loading from a dump that has extra data; a subclass - /// should either override both (if it needs to serialize extra data) or neither (if it needs no - /// extra data). Internally this extra data (if non-empty) is stored in the "+" key of the - /// dump. + /// Internal implementation of merge. This takes all of the messages pulled down from the server + /// and does whatever is necessary to merge (or replace) the current values. /// - /// Inputs: None + /// Values are pairs of the message hash (as provided by the server) and the raw message body. + /// + /// After this call the caller should check `needs_push()` to see if the data on hand was + /// updated and needs to be pushed to the server again (for example, because the data contained + /// conflicts that required another update to resolve). + /// + /// Returns the ids of the given config messages that were successfully parsed. + /// + /// Will throw on serious error (i.e. if neither the current nor any of the given configs are + /// parseable). This should not happen (the current config, at least, should always be + /// re-parseable). + /// + /// Inputs: + /// - `configs` -- span of pairs containing the message hash and the raw message body /// /// Outputs: - /// - `oxenc::bt_dict` -- Returns a btdict of the data - virtual oxenc::bt_dict extra_data() const { return {}; } + /// - unordered_set of successfully parsed hashes. Note that this does not mean the hash was + /// recent or that it changed the config, merely that the returned hash was properly parsed + /// and processed as a config message, even if it was too old to be useful (or was already + /// known to be included). + std::unordered_set _merge( + std::span>> configs); + + /// API: base/ConfigBase::extra_data + /// + /// Called when dumping to obtain any extra data that a subclass needs to store to reconstitute + /// the object. The base implementation does nothing (i.e. extra data will be an empty dict). + /// The counterpart to this, `load_extra_data()`, is called when loading from a dump that has + /// extra data; a subclass should either override both (if it needs to serialize extra data) or + /// neither (if it needs no extra data). Internally this extra data is stored in the "+" key of + /// the dump. + /// + /// Note that loading extra properly requires two-step construction: the subclass constructor + /// must construct the ConfigBase object without extra data, and then call `init_from_dump()` + /// from within its own constructor to load the dump. Failing to do this two-step + /// initialization will result in the subclass load_extra_data not being called (because the + /// subclass instance, and thus the overridden method, does not yet exist during the ConfigBase + /// constructor). + /// + /// Inputs: + /// - `extra` -- An empty dict producer into which extra data can be added. + virtual void extra_data(oxenc::bt_dict_producer&&) const {} /// API: base/ConfigBase::load_extra_data /// - /// Called when constructing from a dump that has extra data. The base implementation does - /// nothing. + /// Called when constructing from a dump with the extra data dict. The base implementation does + /// nothing. See extra_data() for a description. /// /// Inputs: - /// - `extra` -- bt_dict containing a previous dump of data - virtual void load_extra_data(oxenc::bt_dict extra) {} + /// - `extra` -- bt_dict_consumer over the extra data subdict. + virtual void load_extra_data(oxenc::bt_dict_consumer&&) {} /// API: base/ConfigBase::load_key /// @@ -679,17 +909,21 @@ class ConfigBase { /// /// Inputs: /// - `ed25519_secret_key` -- key is loaded for encryption - void load_key(ustring_view ed25519_secretkey); + void load_key(std::span ed25519_secretkey); public: - virtual ~ConfigBase(); + virtual ~ConfigBase() = default; + + // Object is non-movable and non-copyable; you need to hold it in a smart pointer if it needs to + // be managed. + ConfigBase(ConfigBase&&) = delete; + ConfigBase(const ConfigBase&) = delete; + ConfigBase& operator=(ConfigBase&&) = delete; + ConfigBase& operator=(const ConfigBase&) = delete; // Proxy class providing read and write access to the contained config data. const DictFieldRoot data{*this}; - // If set then we log things by calling this callback - std::function logger; - /// API: base/ConfigBase::storage_namespace /// /// Accesses the storage namespace where this config type is to be stored/loaded from. See @@ -746,11 +980,17 @@ class ConfigBase { /// /// Values are pairs of the message hash (as provided by the server) and the raw message body. /// + /// For backwards compatibility, for certain message types (ones that have a + /// `accepts_protobuf()` override returning true) optional protobuf unwrapping of the incoming + /// message is performed; if successful then the unwrapped raw value is used; if the protobuf + /// unwrapping fails, the value is used directly as a raw value. + /// /// After this call the caller should check `needs_push()` to see if the data on hand was /// updated and needs to be pushed to the server again (for example, because the data contained /// conflicts that required another update to resolve). /// - /// Returns the number of the given config messages that were successfully parsed. + /// Returns a set of all config message ids that were successfully parsed (regardless of whether + /// they had any effect on the config). /// /// Will throw on serious error (i.e. if neither the current nor any of the given configs are /// parseable). This should not happen (the current config, at least, should always be @@ -758,19 +998,32 @@ class ConfigBase { /// /// Declaration: /// ```cpp - /// int merge(const std::vector>& configs); - /// int merge(const std::vector>& configs); + /// std::unordered_set merge( + /// const std::vector>>& configs); + /// std::unordered_set merge( + /// const std::vector>>& configs); /// ``` /// /// Inputs: - /// - `configs` -- vector of pairs containing the message hash and the raw message body + /// - `configs` -- vector of pairs containing the message hash and the raw message body (or + /// protobuf-wrapped raw message for certain config types). /// /// Outputs: - /// - `int` -- Returns how many config messages that were successfully parsed - virtual int merge(const std::vector>& configs); - - // Same as merge (above )but takes the values as ustring's as sometimes that is more convenient. - int merge(const std::vector>& configs); + /// - unordered set of successfully parsed hashes. Note that this does not mean the hash was + /// recent or that it changed the config, merely that the returned hash was properly parsed + /// and processed as a config message, even if it was too old to be useful (or was already + /// known to be included). For *multipart* message parts that form a complete set of parts + /// (considering all input messages plus any previously stored incomplete parts), the hash + /// will be included if the resulting reconstituted multipart message was a valid config; for + /// parts that do not complete a message set, inclusion in the return value is based only on + /// whether the multipart part itself looked valid. + std::unordered_set merge( + const std::vector>>& configs); + + // Same as above, but takes values as std::spans (because sometimes that is + // more convenient). + std::unordered_set merge( + const std::vector>>& configs); /// API: base/ConfigBase::is_dirty /// @@ -780,12 +1033,12 @@ class ConfigBase { /// Inputs: None /// /// Outputs: - /// - `bool` -- Returns true if changes havent been serialized + /// - `bool` -- Returns true if changes haven't been serialized bool is_dirty() const { return _state == ConfigState::Dirty; } /// API: base/ConfigBase::is_clean /// - /// Returns true if we are curently clean (i.e. our current config is stored on the server and + /// Returns true if we are currently clean (i.e. our current config is stored on the server and /// unmodified). /// /// Inputs: None @@ -794,16 +1047,103 @@ class ConfigBase { /// - `bool` -- Returns true if changes have been serialized bool is_clean() const { return _state == ConfigState::Clean; } - /// API: base/ConfigBase::current_hashes + /// API: base/ConfigBase::current_state_string() + /// + /// Returns one of "clean", "pending", or "DIRTY" depending on the current state. This is + /// primarily intended for logging. + /// + /// Inputs: None + /// + /// Outputs: + /// - `std::string_view` -- fixed string (clean, DIRTY, or pending) describing the current + /// state. + std::string_view current_state_string() const { + switch (_state) { + case ConfigState::Clean: return "clean"; + case ConfigState::Dirty: return "DIRTY"; + case ConfigState::Waiting: return "pending"; + } + assert(!"invalid state value!"); + return ""; + } + + /// API: base/ConfigBase::is_readonly + /// + /// Returns true if this config object is in read-only mode: specifically that means that this + /// config object can only absorb new config entries but is incapable of producing new entries, + /// and thus cannot modify or merge configs. + /// + /// This currently happens for config messages that require verification of a signature but do + /// not have the private keys required to *produce* a signature. For private config types, such + /// as single-user configs, this will never be the case (as those can only be decrypted in the + /// first place if you possess the private key). Note, however, that additional conditions for + /// read-only could be added in the future, so this being true should not *strictly* be + /// interpreted as a cannot-sign issue. + /// + /// There are some consequences of being readonly: + /// + /// - any attempt to modify config values will throw an exception. + /// - when multiple conflicting config objects are loaded only the "best" (i.e. higher seqno, + /// with ties determined by hashed value) config is loaded; if values need to be merged this + /// config will ignore the alternate values until someone who can produce a signature produces + /// a merged config that properly incorporates (and signs) the updated config. + /// - read-only configurations never have anything to push, that is, `needs_push()` will always + /// be false. + /// - it is still possible to `push()` a config anyway, but this only returns the current config + /// and signature of the message currently being used, and *never* returns any obsolete + /// hashes. Typically this is unlikely to be useful, as it is expected that only signers (who + /// can update and merge) are likely also the only ones who can actually push new configs to + /// the swarm. + /// - read-only configurations do not reliably track obsolete hashes as the obsolescence logic + /// depends on the results of merging, which read-only configs do not support. (If you do + /// call `push()`, you'll always just get back an empty list of obsolete hashes). + /// + /// Inputs: None + /// + /// Outputs: + /// - `bool` true if this config object is read-only + bool is_readonly() const { return _config->verifier && !_config->signer; } + + /// API: base/ConfigBase::curr_hashes + /// + /// The hashes of the current config state. This can be empty if the current hashes are not + /// known (i.e. a push is still in progress) or the current state is not clean (i.e. a push is + /// needed or pending). + /// + /// See also active_hashes(), which you often want instead of this. + /// + /// Inputs: None + /// + /// Outputs: + /// - `std::unordered_set` -- Returns current config hashes + const std::unordered_set& curr_hashes() const; + + /// API: base/ConfigBase::active_hashes + /// + /// Returns the hashes of known config messages that we think should be kept alive on the + /// server: this includes the message(s) comprising the current config (as returned by + /// curr_hashes()) but *also* includes any partial multi-messages for which we have not yet + /// received the full set, as those messages may be part of an impending config update that + /// cannot be processed until the full set is received. + /// + /// Inputs: None + /// + /// Outputs: + /// - `std::unordered_set` -- Returns active (current and in-progress partial) + /// config hashes + std::unordered_set active_hashes() const; + + /// API: base/ConfigBase::old_hashes /// - /// The current config hash(es); this can be empty if the current hash is unknown or the current - /// state is not clean (i.e. a push is needed or pending). + /// The old config hash(es); this can be empty if there are no old hashes or if the config is in + /// a dirty state (in which case these should be retrieved via the `push` function). Calling + /// this function or the `push` function will clear the stored old_hashes. /// /// Inputs: None /// /// Outputs: - /// - `std::vector` -- Returns current config hashes - std::vector current_hashes() const; + /// - `std::vector` -- Returns old config hashes + std::vector old_hashes(); /// API: base/ConfigBase::needs_push /// @@ -822,19 +1162,20 @@ class ConfigBase { /// /// Returns a tuple of three elements: /// - the seqno value of the data - /// - the data message to push to the server + /// - a vector of data messages to push to the server (will be > 1 if the config message has to + /// be split into multi-part messages). /// - a list of known message hashes that are obsoleted by this push. /// /// Additionally, if the internal state is currently dirty (i.e. there are unpushed changes), /// the internal state will be marked as awaiting-confirmation. Any further data changes made /// after this call will re-dirty the data (incrementing seqno and requiring another push). /// - /// The client is expected to send a sequence request to the server that stores the message and + /// The client is expected to send a sequence request to the server that stores the messages and /// deletes the hashes (if any). It is strongly recommended to use a sequence rather than a - /// batch so that the deletions won't happen if the store fails for some reason. + /// batch so that the deletions won't happen if the store(s) fail for some reason. /// /// Upon successful completion of the store+deletion requests the client should call - /// `confirm_pushed` with the seqno value to confirm that the message has been stored. + /// `confirm_pushed` with the seqno value to confirm that the message(s) have been stored. /// /// Subclasses that need to perform pre-push tasks (such as pruning stale data) can override /// this to prune and then call the base method to perform the actual push generation. @@ -842,24 +1183,28 @@ class ConfigBase { /// Inputs: None /// /// Outputs: - /// - `std::tuple>` - Returns a tuple containing + /// - `std::tuple, std::vector>` - Returns a + /// tuple containing /// - `seqno_t` -- sequence number - /// - `ustring` -- data message to push to the server + /// - `std::vector` -- data message to push to the server /// - `std::vector` -- list of known message hashes - virtual std::tuple> push(); + virtual std::tuple>, std::vector> + push(); /// API: base/ConfigBase::confirm_pushed /// /// Should be called after the push is confirmed stored on the storage server swarm to let the - /// object know the config message has been stored and, ideally, that the obsolete messages + /// object know the config message has been stored and (ideally) that the obsolete messages /// returned by `push()` are deleted. Once this is called `needs_push` will start returning /// false until something changes. Takes the seqno that was pushed so that the object can /// ensure that the latest version was pushed (i.e. in case there have been other changes since - /// the `push()` call that returned this seqno). + /// the `push()` call that returned this seqno), and a set of message hashes that were returned + /// by the storage server corresponding to the storage messages (typically 1, but can be + /// multiple for large, multipart config messages). /// - /// Ideally the caller should have both stored the returned message and deleted the given - /// messages. The deletion step isn't critical (it is just cleanup) and callers should call - /// this as long as the store succeeded even if there were errors in the deletions. + /// Ideally the caller should have both stored the returned messages and deleted the given + /// obsolete ones. The deletion step isn't critical (it is just cleanup) and callers should + /// call this as long as the store succeeded even if there were errors in the deletions. /// /// It is safe to call this multiple times with the same seqno value, and with out-of-order /// seqnos (e.g. calling with seqno 122 after having called with 123; the duplicates and earlier @@ -867,21 +1212,33 @@ class ConfigBase { /// /// Inputs: /// - `seqno` -- sequence number that was pushed - /// - `msg_hash` -- message hash that was pushed - virtual void confirm_pushed(seqno_t seqno, std::string msg_hash); + /// - `msg_hashes` -- unordered set of message hashes that were pushed. + virtual void confirm_pushed(seqno_t seqno, std::unordered_set msg_hashes); /// API: base/ConfigBase::dump /// /// Returns a dump of the current state for storage in the database; this value would get passed /// into the constructor to reconstitute the object (including the push/not pushed status). This /// method is *not* virtual: if subclasses need to store extra data they should set it in the - /// `subclass_data` field. + /// `subclass_data` field. Resets the `needs_dump()` flag to false. /// /// Inputs: None /// /// Outputs: - /// - `ustring` -- Returns binary data of the state dump - ustring dump(); + /// - `std::vector` -- Returns binary data of the state dump + std::vector dump(); + + /// API: base/ConfigBase::make_dump + /// + /// Returns a dump of the current state; unlike `dump()` this does *not* update the internal + /// needs_dump flag; it is mostly used internally (by `dump()`), but can also be called + /// externally for debugging purposes. + /// + /// Inputs: None + /// + /// Outputs: + /// - `std::vector` -- Returns binary data of the state dump + std::vector make_dump() const; /// API: base/ConfigBase::needs_dump /// @@ -894,6 +1251,29 @@ class ConfigBase { /// - `bool` -- Returns true if something has changed since last call to dump virtual bool needs_dump() const { return _needs_dump; } + /// API: base/ConfigBase::MULTIPART_MAX_WAIT + /// + /// This value controls how long we will store incomplete multipart messages since the last part + /// of such a message that we received, in the hopes of getting the rest of the parts soon. The + /// default is a week: although long, this allows for extended downtime of a multidevice client + /// that uploads only some of the parts before going offline. + /// + /// Storage of such incomplete sets requires storing (via dump data) all the partial data until + /// the full set is received. + /// + /// Note that only *incomplete* partial sets are affected by this (and stored); we also + /// separately retain metadata about *completed* multipart sets (see MULTIPART_MAX_REMEMBER). + std::chrono::milliseconds MULTIPART_MAX_WAIT = 7 * 24h; + + /// API: base/ConfigBase::MULTIPART_MAX_REMEMBER + /// + /// This value controls how long we retain the hashes of *completed* multipart config sets (so + /// that we can know to ignore duplicate message parts of messages we have already processed). + /// + /// Unlike MULTIPART_MAX_WAIT, such storage does not include the data itself, but merely the + /// (final) config hash and timestamp of the recombined parts needed for deduplication. + std::chrono::milliseconds MULTIPART_MAX_REMEMBER = 14 * 24h; + /// API: base/ConfigBase::add_key /// /// Encryption key methods. For classes that have a single, static key (such as user profile @@ -917,21 +1297,29 @@ class ConfigBase { /// Will throw a std::invalid_argument if the key is not 32 bytes. /// /// Inputs: - /// - `ustring_view key` -- 32 byte binary key + /// - `std::span key` -- 32 byte binary key /// - `high_priority` -- Whether to add to front or back of key list. If true then key is added - /// to beginning and replace highest-priority key for encryption - void add_key(ustring_view key, bool high_priority = true); + /// to beginning and replace highest-priority key for encryption + /// - `dirty_config` -- if true then mark the config as dirty (incrementing seqno and needing a + /// push) if the first key (i.e. the key used for encryption) is changed as a result of this + /// call. Ignored if the config is not modifiable. + void add_key( + std::span key, + bool high_priority = true, + bool dirty_config = false); /// API: base/ConfigBase::clear_keys /// /// Clears all stored encryption/decryption keys. This is typically immediately followed with /// one or more `add_key` call to replace existing keys. Returns the number of keys removed. /// - /// Inputs: None + /// Inputs: + /// - `dirty_config` -- if this removes a key then mark the config as dirty (incrementing seqno + /// and requiring a push). Only has an effect if the config is modifiable. /// /// Outputs: /// - `int` -- Returns number of keys removed - int clear_keys(); + int clear_keys(bool dirty_config = false); /// API: base/ConfigBase::remove_key /// @@ -944,21 +1332,42 @@ class ConfigBase { /// Inputs: /// - `key` -- the key to remove from the key list /// - `from` -- optional agrument to specify which position to remove from, usually omitted + /// - `dirty_config` -- if true, and the *first* key (the encryption key) is removed from the + /// list then mark the config as dirty (incrementing seqno and requiring a re-push). Ignored + /// if the config is not modifiable. /// /// Outputs: /// - `bool` -- Returns true if found and removed - bool remove_key(ustring_view key, size_t from = 0); + bool remove_key(std::span key, size_t from = 0, bool dirty_config = false); + + /// API: base/ConfigBase::replace_keys + /// + /// Replaces the full set of keys with the given vector of keys. This is equivalent to calling + /// `clear_keys()` and then `add_key` with the keys, in order (and so the first key in the + /// vector becomes the highest priority, i.e. the key used for encryption). + /// + /// Inputs: + /// - `new_keys` -- the new decryption keys; the first key becomes the new encryption key + /// - `dirty_config` -- if true then set the config status to dirty (incrementing seqno and + /// requiring a repush) if the old and new first key are not the same. Ignored if the config + /// is not modifiable. + void replace_keys( + const std::vector>& new_keys, bool dirty_config = false); /// API: base/ConfigBase::get_keys /// /// Returns a vector of encryption keys, in priority order (i.e. element 0 is the encryption /// key, and the first decryption key). /// + /// This method is mainly for debugging/diagnostics purposes; most config types have one single + /// key (based on the secret key), and multi-keyed configs such as groups have their own methods + /// for encryption/decryption that are already aware of the multiple keys. + /// /// Inputs: None /// /// Outputs: - /// - `std::vector` -- Returns vector of encryption keys - std::vector get_keys() const; + /// - `std::vector>` -- Returns vector of encryption keys + std::vector> get_keys() const; /// API: base/ConfigBase::key_count /// @@ -979,7 +1388,7 @@ class ConfigBase { /// /// Outputs: /// - `bool` -- Returns true if it does exist - bool has_key(ustring_view key) const; + bool has_key(std::span key) const; /// API: base/ConfigBase::key /// @@ -991,9 +1400,9 @@ class ConfigBase { /// - `i` -- keys position in key list /// /// Outputs: - /// - `ustring_view` -- binary data of the key - ustring_view key(size_t i = 0) const { - assert(i < _keys_size); + /// - `std::span` -- binary data of the key + std::span key(size_t i = 0) const { + assert(i < _keys.size()); return {_keys[i].data(), _keys[i].size()}; } }; @@ -1040,24 +1449,47 @@ inline const internals& unbox(const config_object* conf) { return *static_cast*>(conf->internals); } -// Sets an error message in the internals.error string and updates the last_error pointer in the -// outer (C) config_object struct to point at it. -void set_error(config_object* conf, std::string e); - -// Same as above, but gets the error string out of an exception and passed through a return value. -// Intended to simplify catch-and-return-error such as: -// try { -// whatever(); -// } catch (const std::exception& e) { -// return set_error(conf, LIB_SESSION_ERR_OHNOES, e); -// } -inline int set_error(config_object* conf, int errcode, const std::exception& e) { - set_error(conf, e.what()); - return errcode; +template +void copy_c_str(char (&dest)[N], std::string_view src) { + if (src.size() >= N) + src.remove_suffix(src.size() - N - 1); + std::memcpy(dest, src.data(), src.size()); + dest[src.size()] = 0; } -// Copies a value contained in a string into a new malloced char buffer, returning the buffer and -// size via the two pointer arguments. -void copy_out(ustring_view data, unsigned char** out, size_t* outlen); +// Wraps a labmda and, if an exception is thrown, sets an error message in the internals.error +// string and updates the last_error pointer in the outer (C) config_object struct to point at it. +// +// No return value: accepts void and pointer returns; pointer returns will become nullptr on error +template +decltype(auto) wrap_exceptions(config_object* conf, Call&& f) { + using Ret = std::invoke_result_t; + + try { + conf->last_error = nullptr; + return std::invoke(std::forward(f)); + } catch (const std::exception& e) { + copy_c_str(conf->_error_buf, e.what()); + conf->last_error = conf->_error_buf; + } + if constexpr (std::is_pointer_v) + return static_cast(nullptr); + else + static_assert(std::is_void_v, "Don't know how to return an error value!"); +} + +// Same as above but accepts callbacks with value returns on errors: returns `f()` on success, +// `error_return` on exception +template +Ret wrap_exceptions(config_object* conf, Call&& f, Ret error_return) { + try { + conf->last_error = nullptr; + return std::invoke(std::forward(f)); + } catch (const std::exception& e) { + copy_c_str(conf->_error_buf, e.what()); + conf->last_error = conf->_error_buf; + } + return error_return; +} } // namespace session::config diff --git a/include/session/config/community.hpp b/include/session/config/community.hpp index a3110cbd..232e53e3 100644 --- a/include/session/config/community.hpp +++ b/include/session/config/community.hpp @@ -18,12 +18,19 @@ struct community { // 267 = len('https://') + 253 (max valid DNS name length) + len(':XXXXX') static constexpr size_t BASE_URL_MAX_LENGTH = 267; static constexpr size_t ROOM_MAX_LENGTH = 64; + static constexpr std::string_view qs_pubkey{"?public_key="}; + static constexpr size_t FULL_URL_MAX_LENGTH = BASE_URL_MAX_LENGTH + 3 /* '/r/' */ + + ROOM_MAX_LENGTH + qs_pubkey.size() + + 64 /*pubkey hex*/ + 1 /*null terminator*/; community() = default; // Constructs an empty community struct from url, room, and pubkey. `base_url` will be // normalized if not already. pubkey is 32 bytes. - community(std::string_view base_url, std::string_view room, ustring_view pubkey); + community( + std::string_view base_url, + std::string_view room, + std::span pubkey); // Same as above, but takes pubkey as an encoded (hex or base32z or base64) string. community(std::string_view base_url, std::string_view room, std::string_view pubkey_encoded); @@ -85,13 +92,13 @@ struct community { /// /// Declaration: /// ```cpp - /// void set_pubkey(ustring_view pubkey); + /// void set_pubkey(std::span pubkey); /// void set_pubkey(std::string_view pubkey); /// ``` /// /// Inputs: /// - `pubkey` -- Pubkey to be stored - void set_pubkey(ustring_view pubkey); + void set_pubkey(std::span pubkey); void set_pubkey(std::string_view pubkey); /// API: community/community::base_url @@ -133,8 +140,8 @@ struct community { /// Inputs: None /// /// Outputs: - /// - `const ustring&` -- Returns the pubkey - const ustring& pubkey() const { return pubkey_; } + /// - `const std::vector&` -- Returns the pubkey + const std::vector& pubkey() const { return pubkey_; } /// API: community/community::pubkey_hex /// @@ -176,7 +183,8 @@ struct community { /// - `std::string` -- Returns the Full URL std::string full_url() const; - /// API: community/community::full_url(std::string_view,std::string_view,ustring_view) + /// API: community/community::full_url(std::string_view,std::string_view,std::span) /// /// Constructs and returns the full URL for a given base, room, and pubkey. Currently this /// returns it in a Session-compatibility form (https://server.com/RoomName?public_key=....), @@ -191,7 +199,9 @@ struct community { /// Outputs: /// - `std::string` -- Returns the Full URL static std::string full_url( - std::string_view base_url, std::string_view room, ustring_view pubkey); + std::string_view base_url, + std::string_view room, + std::span pubkey); /// API: community/community::canonical_url /// @@ -259,8 +269,9 @@ struct community { /// - `std::tuple` -- Tuple of 3 components of the url /// - `std::string` -- canonical url, normalized /// - `std::string` -- room name, *not* normalized - /// - `ustring` -- binary of the server pubkey - static std::tuple parse_full_url(std::string_view full_url); + /// - `std::vector` -- binary of the server pubkey + static std::tuple> parse_full_url( + std::string_view full_url); /// API: community/community::parse_partial_url /// @@ -274,9 +285,10 @@ struct community { /// - `std::tuple` -- Tuple of 3 components of the url /// - `std::string` -- canonical url, normalized /// - `std::string` -- room name, *not* normalized - /// - `std::optional` -- optional binary of the server pubkey if present - static std::tuple> parse_partial_url( - std::string_view url); + /// - `std::optional>` -- optional binary of the server pubkey if + /// present + static std::tuple>> + parse_partial_url(std::string_view url); protected: // The canonical base url and room (i.e. lower-cased, URL cleaned up): @@ -285,7 +297,7 @@ struct community { // `someroom` and this could `SomeRoom`). Omitted if not available. std::optional localized_room_; // server pubkey - ustring pubkey_; + std::vector pubkey_; // Construction without a pubkey for when pubkey isn't known yet but will be set shortly // after constructing (or when isn't needed, such as when deleting). @@ -337,7 +349,7 @@ struct comm_iterator_helper { continue; } - ustring_view pubkey{ + std::span pubkey{ reinterpret_cast(pubkey_raw->data()), pubkey_raw->size()}; if (!it_room) { diff --git a/include/session/config/contacts.h b/include/session/config/contacts.h index 7c6bc0e4..e2752153 100644 --- a/include/session/config/contacts.h +++ b/include/session/config/contacts.h @@ -74,7 +74,7 @@ LIBSESSION_EXPORT int contacts_init( const unsigned char* ed25519_secretkey, const unsigned char* dump, size_t dumplen, - char* error) __attribute__((warn_unused_result)); + char* error) LIBSESSION_WARN_UNUSED; /// API: contacts/contacts_get /// @@ -99,8 +99,9 @@ LIBSESSION_EXPORT int contacts_init( /// Output: /// - `bool` -- Returns true if contact exsts LIBSESSION_EXPORT bool contacts_get( - config_object* conf, contacts_contact* contact, const char* session_id) - __attribute__((warn_unused_result)); + config_object* conf, + contacts_contact* contact, + const char* session_id) LIBSESSION_WARN_UNUSED; /// API: contacts/contacts_get_or_construct /// @@ -130,8 +131,9 @@ LIBSESSION_EXPORT bool contacts_get( /// Output: /// - `bool` -- Returns true if contact exsts LIBSESSION_EXPORT bool contacts_get_or_construct( - config_object* conf, contacts_contact* contact, const char* session_id) - __attribute__((warn_unused_result)); + config_object* conf, + contacts_contact* contact, + const char* session_id) LIBSESSION_WARN_UNUSED; /// API: contacts/contacts_set /// @@ -150,8 +152,8 @@ LIBSESSION_EXPORT bool contacts_get_or_construct( /// - `contact` -- [in] Pointer containing the contact info data /// /// Output: -/// - `void` -- Returns Nothing -LIBSESSION_EXPORT void contacts_set(config_object* conf, const contacts_contact* contact); +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool contacts_set(config_object* conf, const contacts_contact* contact); // NB: wrappers for set_name, set_nickname, etc. C++ methods are deliberately omitted as they would // save very little in actual calling code. The procedure for updating a single field without them diff --git a/include/session/config/contacts.hpp b/include/session/config/contacts.hpp index 1cad126a..757e6cd0 100644 --- a/include/session/config/contacts.hpp +++ b/include/session/config/contacts.hpp @@ -62,11 +62,11 @@ struct contact_info { // conversation. notify_mode notifications = notify_mode::defaulted; int64_t mute_until = 0; // If non-zero, disable notifications until the given unix timestamp - // (overriding whatever the current `notifications` value is until the - // timestamp expires). + // (seconds, overriding whatever the current `notifications` value is + // until the timestamp expires). expiration_mode exp_mode = expiration_mode::none; // The expiry time; none if not expiring. std::chrono::seconds exp_timer{0}; // The expiration timer (in seconds) - int64_t created = 0; // Unix timestamp when this contact was added + int64_t created = 0; // Unix timestamp (seconds) when this contact was added explicit contact_info(std::string sid); @@ -90,6 +90,7 @@ struct contact_info { /// - `name` -- Name to assign to the contact void set_name(std::string name); void set_nickname(std::string nickname); + void set_nickname_truncated(std::string nickname); private: friend class Contacts; @@ -118,7 +119,9 @@ class Contacts : public ConfigBase { /// /// Outputs: /// - `Contact` - Constructor - Contacts(ustring_view ed25519_secretkey, std::optional dumped); + Contacts( + std::span ed25519_secretkey, + std::optional> dumped); /// API: contacts/Contacts::storage_namespace /// @@ -206,6 +209,17 @@ class Contacts : public ConfigBase { /// - `nickname` -- string of the contacts nickname void set_nickname(std::string_view session_id, std::string nickname); + /// API: contacts/contacts::set_nickname_truncated + /// + /// Alternative to `set()` for setting a single field. The same as `set_name` except truncates + /// the value when it's too long. (If setting multiple fields at once you should use `set()` + /// instead). + /// + /// Inputs: + /// - `session_id` -- hex string of the session id + /// - `nickname` -- string of the contacts nickname + void set_nickname_truncated(std::string_view session_id, std::string nickname); + /// API: contacts/contacts::set_profile_pic /// /// Alternative to `set()` for setting a single field. (If setting multiple fields at once you @@ -323,6 +337,8 @@ class Contacts : public ConfigBase { /// - `bool` - Returns true if the contact list is empty bool empty() const { return size() == 0; } + bool accepts_protobuf() const override { return true; } + struct iterator; /// API: contacts/contacts::begin /// diff --git a/include/session/config/convo_info_volatile.h b/include/session/config/convo_info_volatile.h index fc94ab1a..952b6ff7 100644 --- a/include/session/config/convo_info_volatile.h +++ b/include/session/config/convo_info_volatile.h @@ -24,6 +24,12 @@ typedef struct convo_info_volatile_community { bool unread; // true if marked unread } convo_info_volatile_community; +typedef struct convo_info_volatile_group { + char group_id[67]; // in hex; 66 hex chars + null terminator. Begins with "03". + int64_t last_read; // ms since unix epoch + bool unread; // true if marked unread +} convo_info_volatile_group; + typedef struct convo_info_volatile_legacy_group { char group_id[67]; // in hex; 66 hex chars + null terminator. Looks just like a Session ID, // though isn't really one. @@ -74,7 +80,7 @@ LIBSESSION_EXPORT int convo_info_volatile_init( const unsigned char* ed25519_secretkey, const unsigned char* dump, size_t dumplen, - char* error) __attribute__((warn_unused_result)); + char* error) LIBSESSION_WARN_UNUSED; /// API: convo_info_volatile/convo_info_volatile_get_1to1 /// @@ -101,8 +107,9 @@ LIBSESSION_EXPORT int convo_info_volatile_init( /// Outputs: /// - `bool` - Returns true if the conversation exists LIBSESSION_EXPORT bool convo_info_volatile_get_1to1( - config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) - __attribute__((warn_unused_result)); + config_object* conf, + convo_info_volatile_1to1* convo, + const char* session_id) LIBSESSION_WARN_UNUSED; /// API: convo_info_volatile/convo_info_volatile_get_or_construct_1to1 /// @@ -133,8 +140,9 @@ LIBSESSION_EXPORT bool convo_info_volatile_get_1to1( /// Outputs: /// - `bool` - Returns true if the conversation exists LIBSESSION_EXPORT bool convo_info_volatile_get_or_construct_1to1( - config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) - __attribute__((warn_unused_result)); + config_object* conf, + convo_info_volatile_1to1* convo, + const char* session_id) LIBSESSION_WARN_UNUSED; /// API: convo_info_volatile/convo_info_volatile_get_community /// @@ -167,7 +175,7 @@ LIBSESSION_EXPORT bool convo_info_volatile_get_community( config_object* conf, convo_info_volatile_community* comm, const char* base_url, - const char* room) __attribute__((warn_unused_result)); + const char* room) LIBSESSION_WARN_UNUSED; /// API: convo_info_volatile/convo_info_volatile_get_or_construct_community /// @@ -189,7 +197,7 @@ LIBSESSION_EXPORT bool convo_info_volatile_get_community( /// /// Declaration: /// ```cpp -/// BOOL convo_info_volatile_get_or_constructcommunity( +/// BOOL convo_info_volatile_get_or_construct_community( /// [in] config_object* conf, /// [out] convo_info_volatile_community* comm, /// [in] const char* base_url, @@ -206,13 +214,75 @@ LIBSESSION_EXPORT bool convo_info_volatile_get_community( /// - `pubkey` -- [in] 32 byte binary data of the pubkey /// /// Outputs: -/// - `bool` - Returns true if the community exists +/// - `bool` - Returns true if the call succeeds LIBSESSION_EXPORT bool convo_info_volatile_get_or_construct_community( config_object* conf, convo_info_volatile_community* convo, const char* base_url, const char* room, - unsigned const char* pubkey) __attribute__((warn_unused_result)); + unsigned const char* pubkey) LIBSESSION_WARN_UNUSED; + +/// API: convo_info_volatile/convo_info_volatile_get_group +/// +/// Fills `convo` with the conversation info given a group ID (specified as a null-terminated +/// hex string), if the conversation exists, and returns true. If the conversation does not exist +/// then `convo` is left unchanged and false is returned. On error, false is returned and the error +/// is set in conf->last_error (on non-error, last_error is cleared). +/// +/// Declaration: +/// ```cpp +/// BOOL convo_info_volatile_get_group( +/// [in] config_object* conf, +/// [out] convo_info_volatile_group* convo, +/// [in] const char* id +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `convo` -- [out] Pointer to group +/// - `id` -- [in] Null terminated hex string (66 chars, beginning with 03) specifying the ID of the +/// group +/// +/// Outputs: +/// - `bool` - Returns true if the group exists +LIBSESSION_EXPORT bool convo_info_volatile_get_group( + config_object* conf, + convo_info_volatile_group* convo, + const char* id) LIBSESSION_WARN_UNUSED; + +/// API: convo_info_volatile/convo_info_volatile_get_or_construct_group +/// +/// Same as the above except that when the conversation does not exist, this sets all the convo +/// fields to defaults and loads it with the given id. +/// +/// Returns true as long as it is given a valid group id (i.e. 66 hex chars beginning with "03"). A +/// false return is considered an error, and means the id was not a valid session id; an error +/// string will be set in `conf->last_error`. +/// +/// This is the method that should usually be used to create or update a conversation, followed by +/// setting fields in the convo, and then giving it to convo_info_volatile_set(). +/// +/// Declaration: +/// ```cpp +/// BOOL convo_info_volatile_get_or_construct_group( +/// [in] config_object* conf, +/// [out] convo_info_volatile_group* convo, +/// [in] const char* id +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `convo` -- [out] Pointer to group +/// - `id` -- [in] Null terminated hex string specifying the ID of the group +/// +/// Outputs: +/// - `bool` - Returns true if the call succeeds +LIBSESSION_EXPORT bool convo_info_volatile_get_or_construct_group( + config_object* conf, + convo_info_volatile_group* convo, + const char* id) LIBSESSION_WARN_UNUSED; /// API: convo_info_volatile/convo_info_volatile_get_legacy_group /// @@ -233,13 +303,14 @@ LIBSESSION_EXPORT bool convo_info_volatile_get_or_construct_community( /// Inputs: /// - `conf` -- [in] Pointer to the config object /// - `convo` -- [out] Pointer to legacy group -/// - `id` -- [in] Null terminated jex string specifying the ID of the legacy group +/// - `id` -- [in] Null terminated hex string specifying the ID of the legacy group /// /// Outputs: -/// - `bool` - Returns true if the community exists +/// - `bool` - Returns true if the legacy group exists LIBSESSION_EXPORT bool convo_info_volatile_get_legacy_group( - config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) - __attribute__((warn_unused_result)); + config_object* conf, + convo_info_volatile_legacy_group* convo, + const char* id) LIBSESSION_WARN_UNUSED; /// API: convo_info_volatile/convo_info_volatile_get_or_construct_legacy_group /// @@ -265,13 +336,14 @@ LIBSESSION_EXPORT bool convo_info_volatile_get_legacy_group( /// Inputs: /// - `conf` -- [in] Pointer to the config object /// - `convo` -- [out] Pointer to legacy group -/// - `id` -- [in] Null terminated jex string specifying the ID of the legacy group +/// - `id` -- [in] Null terminated hex string specifying the ID of the legacy group /// /// Outputs: -/// - `bool` - Returns true if the community exists +/// - `bool` - Returns true if the call succeeds LIBSESSION_EXPORT bool convo_info_volatile_get_or_construct_legacy_group( - config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) - __attribute__((warn_unused_result)); + config_object* conf, + convo_info_volatile_legacy_group* convo, + const char* id) LIBSESSION_WARN_UNUSED; /// API: convo_info_volatile/convo_info_volatile_set_1to1 /// @@ -288,7 +360,10 @@ LIBSESSION_EXPORT bool convo_info_volatile_get_or_construct_legacy_group( /// Inputs: /// - `conf` -- [in] Pointer to the config object /// - `convo` -- [in] Pointer to conversation info structure -LIBSESSION_EXPORT void convo_info_volatile_set_1to1( +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool convo_info_volatile_set_1to1( config_object* conf, const convo_info_volatile_1to1* convo); /// API: convo_info_volatile/convo_info_volatile_set_community @@ -306,9 +381,33 @@ LIBSESSION_EXPORT void convo_info_volatile_set_1to1( /// Inputs: /// - `conf` -- [in] Pointer to the config object /// - `convo` -- [in] Pointer to community info structure -LIBSESSION_EXPORT void convo_info_volatile_set_community( +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool convo_info_volatile_set_community( config_object* conf, const convo_info_volatile_community* convo); +/// API: convo_info_volatile/convo_info_volatile_set_group +/// +/// Adds or updates a group from the given convo info +/// +/// Declaration: +/// ```cpp +/// VOID convo_info_volatile_set_group( +/// [in] config_object* conf, +/// [in] const convo_info_volatile_group* convo +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `convo` -- [in] Pointer to group info structure +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool convo_info_volatile_set_group( + config_object* conf, const convo_info_volatile_group* convo); + /// API: convo_info_volatile/convo_info_volatile_set_legacy_group /// /// Adds or updates a legacy group from the given convo info @@ -323,8 +422,11 @@ LIBSESSION_EXPORT void convo_info_volatile_set_community( /// /// Inputs: /// - `conf` -- [in] Pointer to the config object -/// - `convo` -- [in] Pointer to community info structure -LIBSESSION_EXPORT void convo_info_volatile_set_legacy_group( +/// - `convo` -- [in] Pointer to legacy group info structure +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool convo_info_volatile_set_legacy_group( config_object* conf, const convo_info_volatile_legacy_group* convo); /// API: convo_info_volatile/convo_info_volatile_erase_1to1 @@ -374,6 +476,27 @@ LIBSESSION_EXPORT bool convo_info_volatile_erase_1to1(config_object* conf, const LIBSESSION_EXPORT bool convo_info_volatile_erase_community( config_object* conf, const char* base_url, const char* room); +/// API: convo_info_volatile/convo_info_volatile_erase_group +/// +/// Erases a group. Returns true if the group was found and removed, false if the group was not +/// present. You must not call this during iteration. +/// +/// Declaration: +/// ```cpp +/// BOOL convo_info_volatile_erase_group( +/// [in] config_object* conf, +/// [in] const char* group_id +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `group_id` -- [in] Null terminated hex string +/// +/// Outputs: +/// - `bool` - Returns true if group was found and removed +LIBSESSION_EXPORT bool convo_info_volatile_erase_group(config_object* conf, const char* group_id); + /// API: convo_info_volatile/convo_info_volatile_erase_legacy_group /// /// Erases a legacy group. Returns true if the group was found @@ -451,6 +574,24 @@ LIBSESSION_EXPORT size_t convo_info_volatile_size_1to1(const config_object* conf /// - `size_t` -- number of communities LIBSESSION_EXPORT size_t convo_info_volatile_size_communities(const config_object* conf); +/// API: convo_info_volatile/convo_info_volatile_size_groups +/// +/// Returns the number of groups. +/// +/// Declaration: +/// ```cpp +/// SIZE_T convo_info_volatile_size_groups( +/// [in] const config_object* conf +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `size_t` -- number of groups +LIBSESSION_EXPORT size_t convo_info_volatile_size_groups(const config_object* conf); + /// API: convo_info_volatile/convo_info_volatile_size_legacy_groups /// /// Returns the number of legacy groups. @@ -479,15 +620,18 @@ typedef struct convo_info_volatile_iterator convo_info_volatile_iterator; /// ```cpp /// convo_info_volatile_1to1 c1; /// convo_info_volatile_community c2; -/// convo_info_volatile_legacy_group c3; +/// convo_info_volatile_group c3; +/// convo_info_volatile_legacy_group c4; /// convo_info_volatile_iterator *it = convo_info_volatile_iterator_new(my_convos); /// for (; !convo_info_volatile_iterator_done(it); convo_info_volatile_iterator_advance(it)) { /// if (convo_info_volatile_it_is_1to1(it, &c1)) { /// // use c1.whatever /// } else if (convo_info_volatile_it_is_community(it, &c2)) { /// // use c2.whatever -/// } else if (convo_info_volatile_it_is_legacy_group(it, &c3)) { +/// } else if (convo_info_volatile_it_is_group(it, &c3)) { /// // use c3.whatever +/// } else if (convo_info_volatile_it_is_legacy_group(it, &c4)) { +/// // use c4.whatever /// } /// } /// convo_info_volatile_iterator_free(it); @@ -557,6 +701,29 @@ LIBSESSION_EXPORT convo_info_volatile_iterator* convo_info_volatile_iterator_new LIBSESSION_EXPORT convo_info_volatile_iterator* convo_info_volatile_iterator_new_communities( const config_object* conf); +/// API: convo_info_volatile/convo_info_volatile_iterator_new_groups +/// +/// The same as `convo_info_volatile_iterator_new` except that this iterates *only* over one type of +/// conversation. You still need to use `convo_info_volatile_it_is_group` (or the alternatives) to +/// load the data in each pass of the loop. (You can, however, safely ignore the bool return value +/// of the `it_is_whatever` function: it will always be true for the particular type being iterated +/// over). +/// +/// Declaration: +/// ```cpp +/// CONVO_INFO_VOLATILE_ITERATOR* convo_info_volatile_iterator_new_groups( +/// [in] const config_object* conf +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `convo_info_volatile_iterator*` -- Iterator +LIBSESSION_EXPORT convo_info_volatile_iterator* convo_info_volatile_iterator_new_groups( + const config_object* conf); + /// API: convo_info_volatile/convo_info_volatile_iterator_new_legacy_groups /// /// The same as `convo_info_volatile_iterator_new` except that this iterates *only* over one type of @@ -656,7 +823,7 @@ LIBSESSION_EXPORT bool convo_info_volatile_it_is_1to1( /// ```cpp /// BOOL convo_info_volatile_it_is_community( /// [in] convo_info_volatile_iterator* it, -/// [out] convo_info_volatile_1to1* c +/// [out] convo_info_volatile_community* c /// ); /// ``` /// @@ -672,6 +839,28 @@ LIBSESSION_EXPORT bool convo_info_volatile_it_is_1to1( LIBSESSION_EXPORT bool convo_info_volatile_it_is_community( convo_info_volatile_iterator* it, convo_info_volatile_community* c); +/// API: convo_info_volatile/convo_info_volatile_it_is_group +/// +/// If the current iterator record is a group conversation this sets the details into `g` and +/// returns true. Otherwise it returns false. +/// +/// Declaration: +/// ```cpp +/// BOOL convo_info_volatile_it_is_group( +/// [in] convo_info_volatile_iterator* it, +/// [out] convo_info_volatile_group* g +/// ); +/// ``` +/// +/// Inputs: +/// - `it` -- [in] The convo_info_volatile_iterator +/// - `c` -- [out] Pointer to the convo_info_volatile, will be populated if true +/// +/// Outputs: +/// - `bool` -- True if the record is a group conversation +LIBSESSION_EXPORT bool convo_info_volatile_it_is_group( + convo_info_volatile_iterator* it, convo_info_volatile_group* c); + /// API: convo_info_volatile/convo_info_volatile_it_is_legacy_group /// /// If the current iterator record is a legacy group conversation this sets the details into `c` and @@ -680,8 +869,8 @@ LIBSESSION_EXPORT bool convo_info_volatile_it_is_community( /// Declaration: /// ```cpp /// BOOL convo_info_volatile_it_is_legacy_group( -/// [in] convo_info_volatile_iterator* it, -/// [out] convo_info_volatile_1to1* c +/// [in] convo_info_volatile_iterator* it, +/// [out] convo_info_volatile_legacy_group* c /// ); /// ``` /// diff --git a/include/session/config/convo_info_volatile.hpp b/include/session/config/convo_info_volatile.hpp index 482906af..3871a694 100644 --- a/include/session/config/convo_info_volatile.hpp +++ b/include/session/config/convo_info_volatile.hpp @@ -14,12 +14,14 @@ using namespace std::literals; extern "C" { struct convo_info_volatile_1to1; struct convo_info_volatile_community; +struct convo_info_volatile_group; struct convo_info_volatile_legacy_group; } namespace session::config { class ConvoInfoVolatile; +class val_loader; /// keys used in this config, either currently or in the past (so that we don't reuse): /// @@ -41,14 +43,18 @@ class ConvoInfoVolatile; /// included, but will be 0 if no messages are read. /// u - will be present and set to 1 if this conversation is specifically marked unread. /// +/// g - group conversations (aka new, non-legacy closed groups). The key is the group identifier +/// (beginning with 03). Values are dicts with keys: +/// r - the unix timestamp (in integer milliseconds) of the last-read message. Always +/// included, but will be 0 if no messages are read. +/// u - will be present and set to 1 if this conversation is specifically marked unread. +/// /// C - legacy group conversations (aka closed groups). The key is the group identifier (which /// looks indistinguishable from a Session ID, but isn't really a proper Session ID). Values /// are dicts with keys: /// r - the unix timestamp (integer milliseconds) of the last-read message. Always included, /// but will be 0 if no messages are read. /// u - will be present and set to 1 if this conversation is specifically marked unread. -/// -/// c - reserved for future tracking of new group conversations. namespace convo { @@ -58,6 +64,8 @@ namespace convo { protected: void load(const dict& info_dict); + friend class session::config::val_loader; + friend class session::config::ConvoInfoVolatile; }; struct one_to_one : base { @@ -82,8 +90,6 @@ namespace convo { // Internal ctor/method for C API implementations: one_to_one(const struct convo_info_volatile_1to1& c); // From c struct void into(convo_info_volatile_1to1& c) const; // Into c struct - - friend class session::config::ConvoInfoVolatile; }; struct community : config::community, base { @@ -103,6 +109,23 @@ namespace convo { friend struct session::config::comm_iterator_helper; }; + struct group : base { + std::string id; // 66 hex digits starting with "03" + + /// API: convo_info_volatile/group::group + /// + /// Constructs an empty group from an id + /// + /// Inputs: + /// - `group_id` -- hex string of group_id, 66 hex bytes starting with "03" + explicit group(std::string&& group_id); + explicit group(std::string_view group_id); + + // Internal ctor/method for C API implementations: + group(const struct convo_info_volatile_group& c); // From c struct + void into(convo_info_volatile_group& c) const; // Into c struct + }; + struct legacy_group : base { std::string id; // in hex, indistinguishable from a Session ID @@ -124,12 +147,9 @@ namespace convo { // Internal ctor/method for C API implementations: legacy_group(const struct convo_info_volatile_legacy_group& c); // From c struct void into(convo_info_volatile_legacy_group& c) const; // Into c struct - - private: - friend class session::config::ConvoInfoVolatile; }; - using any = std::variant; + using any = std::variant; } // namespace convo class ConvoInfoVolatile : public ConfigBase { @@ -151,7 +171,9 @@ class ConvoInfoVolatile : public ConfigBase { /// the secret key. /// - `dumped` -- either `std::nullopt` to construct a new, empty object; or binary state data /// that was previously dumped from an instance of this class by calling `dump()`. - ConvoInfoVolatile(ustring_view ed25519_secretkey, std::optional dumped); + ConvoInfoVolatile( + std::span ed25519_secretkey, + std::optional> dumped); /// API: convo_info_volatile/ConvoInfoVolatile::storage_namespace /// @@ -204,11 +226,13 @@ class ConvoInfoVolatile : public ConfigBase { /// Inputs: None /// /// Outputs: - /// - `std::tuple>` - Returns a tuple containing + /// - `std::tuple, std::vector>` - Returns a + /// tuple containing /// - `seqno_t` -- sequence number - /// - `ustring` -- data message to push to the server + /// - `std::vector>` -- data message(s) to push to the server /// - `std::vector` -- list of known message hashes - std::tuple> push() override; + std::tuple>, std::vector> push() + override; /// API: convo_info_volatile/ConvoInfoVolatile::get_1to1 /// @@ -249,6 +273,18 @@ class ConvoInfoVolatile : public ConfigBase { /// - `std::optional` - Returns a community std::optional get_community(std::string_view partial_url) const; + /// API: convo_info_volatile/ConvoInfoVolatile::get_group + /// + /// Looks up and returns a group conversation by ID. The ID is a 66-character hex string + /// beginning with "03". Returns nullopt if there is no record of the group conversation. + /// + /// Inputs: + /// - `pubkey_hex` -- Hex string of the group ID + /// + /// Outputs: + /// - `std::optional` - Returns a group + std::optional get_group(std::string_view pubkey_hex) const; + /// API: convo_info_volatile/ConvoInfoVolatile::get_legacy_group /// /// Looks up and returns a legacy group conversation by ID. The ID looks like a hex Session ID, @@ -275,6 +311,19 @@ class ConvoInfoVolatile : public ConfigBase { /// - `convo::one_to_one` - Returns a contact convo::one_to_one get_or_construct_1to1(std::string_view session_id) const; + /// API: convo_info_volatile/ConvoInfoVolatile::get_or_construct_group + /// + /// These are the same as the above `get` methods (without "_or_construct" in the name), except + /// that when the conversation doesn't exist a new one is created, prefilled with the + /// pubkey/url/etc. + /// + /// Inputs: + /// - `pubkey_hex` -- Hex string pubkey + /// + /// Outputs: + /// - `convo::group` - Returns a group + convo::group get_or_construct_group(std::string_view pubkey_hex) const; + /// API: convo_info_volatile/ConvoInfoVolatile::get_or_construct_legacy_group /// /// These are the same as the above `get` methods (without "_or_construct" in the name), except @@ -306,7 +355,8 @@ class ConvoInfoVolatile : public ConfigBase { /// std::string_view base_url, std::string_view room, std::string_view pubkey_hex) /// const; /// convo::community get_or_construct_community( - /// std::string_view base_url, std::string_view room, ustring_view pubkey) const; + /// std::string_view base_url, std::string_view room, std::span + /// pubkey) const; /// ``` /// /// Inputs: @@ -319,7 +369,9 @@ class ConvoInfoVolatile : public ConfigBase { convo::community get_or_construct_community( std::string_view base_url, std::string_view room, std::string_view pubkey_hex) const; convo::community get_or_construct_community( - std::string_view base_url, std::string_view room, ustring_view pubkey) const; + std::string_view base_url, + std::string_view room, + std::span pubkey) const; /// API: convo_info_volatile/ConvoInfoVolatile::get_or_construct_community(full_url) /// @@ -347,6 +399,7 @@ class ConvoInfoVolatile : public ConfigBase { /// Declaration: /// ```cpp /// void set(const convo::one_to_one& c); + /// void set(const convo::group& c); /// void set(const convo::legacy_group& c); /// void set(const convo::community& c); /// void set(const convo::any& c); // Variant which can be any of the above @@ -356,6 +409,7 @@ class ConvoInfoVolatile : public ConfigBase { /// - `c` -- struct containing any contact, community or group void set(const convo::one_to_one& c); void set(const convo::legacy_group& c); + void set(const convo::group& c); void set(const convo::community& c); void set(const convo::any& c); // Variant which can be any of the above @@ -365,7 +419,7 @@ class ConvoInfoVolatile : public ConfigBase { // Drills into the nested dicts to access community details; if the second argument is // non-nullptr then it will be set to the community's pubkey, if it exists. DictFieldProxy community_field( - const convo::community& og, ustring_view* get_pubkey = nullptr) const; + const convo::community& og, std::span* get_pubkey = nullptr) const; public: /// API: convo_info_volatile/ConvoInfoVolatile::erase_1to1 @@ -392,6 +446,17 @@ class ConvoInfoVolatile : public ConfigBase { /// - `bool` - Returns true if found and removed, otherwise false bool erase_community(std::string_view base_url, std::string_view room); + /// API: convo_info_volatile/ConvoInfoVolatile::erase_group + /// + /// Removes a group conversation. Returns true if found and removed, false if not present. + /// + /// Inputs: + /// - `pubkey_hex` -- String of the group pubkey + /// + /// Outputs: + /// - `bool` - Returns true if found and removed, otherwise false + bool erase_group(std::string_view pubkey_hex); + /// API: convo_info_volatile/ConvoInfoVolatile::erase_legacy_group /// /// Removes a legacy group conversation. Returns true if found and removed, false if not @@ -423,6 +488,7 @@ class ConvoInfoVolatile : public ConfigBase { /// - `bool` - Returns true if found and removed, otherwise false bool erase(const convo::one_to_one& c); bool erase(const convo::community& c); + bool erase(const convo::group& c); bool erase(const convo::legacy_group& c); bool erase(const convo::any& c); // Variant of any of them @@ -438,6 +504,7 @@ class ConvoInfoVolatile : public ConfigBase { /// size_t size() const; /// size_t size_1to1() const; /// size_t size_communities() const; + /// size_t size_groups() const; /// size_t size_legacy_groups() const; /// ``` /// @@ -447,9 +514,11 @@ class ConvoInfoVolatile : public ConfigBase { /// - `size_t` - Returns the number of conversations size_t size() const; - /// Returns the number of 1-to-1, community, and legacy group conversations, respectively. + /// Returns the number of 1-to-1, community, group, and legacy group conversations, + /// respectively. size_t size_1to1() const; size_t size_communities() const; + size_t size_groups() const; size_t size_legacy_groups() const; /// API: convo_info_volatile/ConvoInfoVolatile::empty @@ -462,6 +531,8 @@ class ConvoInfoVolatile : public ConfigBase { /// - `bool` -- Returns true if the convesation list is empty bool empty() const { return size() == 0; } + bool accepts_protobuf() const override { return true; } + struct iterator; /// API: convo_info_volatile/ConvoInfoVolatile::begin /// @@ -474,6 +545,8 @@ class ConvoInfoVolatile : public ConfigBase { /// // use dm->session_id, dm->last_read, etc. /// } else if (const auto* og = std::get_if(&convo)) { /// // use og->base_url, og->room, om->last_read, etc. + /// } else if (const auto* cg = std::get_if(&convo)) { + /// // use cg->id, cg->last_read /// } else if (const auto* lcg = std::get_if(&convo)) { /// // use lcg->id, lcg->last_read /// } @@ -483,6 +556,9 @@ class ConvoInfoVolatile : public ConfigBase { /// This iterates through all conversations in sorted order (sorted first by convo type, then by /// id within the type). /// + /// The `begin_TYPE()` versions of the iterator return an iterator that loops only through the + /// given `TYPE` of conversations. (The .end() iterator works for all the iterator variations). + /// /// It is NOT permitted to add/modify/remove records while iterating; performing modifications /// based on a condition requires two passes: one to collect the required changes, and another /// to apply them key by key. @@ -492,6 +568,7 @@ class ConvoInfoVolatile : public ConfigBase { /// iterator begin() const; /// subtype_iterator begin_1to1() const; /// subtype_iterator begin_communities() const; + /// subtype_iterator begin_groups() const; /// subtype_iterator begin_legacy_groups() const; /// ``` /// @@ -503,7 +580,8 @@ class ConvoInfoVolatile : public ConfigBase { /// API: convo_info_volatile/ConvoInfoVolatile::end /// - /// Iterator for passing the end of the conversations + /// Iterator for passing the end of the conversations. This works for both the all-convo + /// iterator (`begin()`) and the type-specific iterators (e.g. `begin_groups()`). /// /// Inputs: None /// @@ -517,10 +595,12 @@ class ConvoInfoVolatile : public ConfigBase { /// Returns an iterator that iterates only through one type of conversations subtype_iterator begin_1to1() const { return {data}; } subtype_iterator begin_communities() const { return {data}; } + subtype_iterator begin_groups() const { return {data}; } subtype_iterator begin_legacy_groups() const { return {data}; } using iterator_category = std::input_iterator_tag; - using value_type = std::variant; + using value_type = + std::variant; using reference = value_type&; using pointer = value_type*; using difference_type = std::ptrdiff_t; @@ -528,15 +608,18 @@ class ConvoInfoVolatile : public ConfigBase { struct iterator { protected: std::shared_ptr _val; - std::optional _it_11, _end_11, _it_lgroup, _end_lgroup; + std::optional _it_11, _end_11, _it_group, _end_group, _it_lgroup, + _end_lgroup; std::optional _it_comm; void _load_val(); iterator() = default; // Constructs an end tombstone - explicit iterator( + iterator( const DictFieldRoot& data, - bool oneto1 = true, - bool communities = true, - bool legacy_groups = true); + bool oneto1, + bool communities, + bool groups, + bool legacy_groups); + explicit iterator(const DictFieldRoot& data) : iterator(data, true, true, true, true) {} friend class ConvoInfoVolatile; public: @@ -561,6 +644,7 @@ class ConvoInfoVolatile : public ConfigBase { data, std::is_same_v, std::is_same_v, + std::is_same_v, std::is_same_v) {} friend class ConvoInfoVolatile; diff --git a/include/session/config/encrypt.hpp b/include/session/config/encrypt.hpp index 1fd9c771..19750960 100644 --- a/include/session/config/encrypt.hpp +++ b/include/session/config/encrypt.hpp @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include "../types.hpp" @@ -33,8 +35,11 @@ namespace session::config { /// - `domain` -- short string for the keyed hash /// /// Outputs: -/// - `ustring` -- Returns the encrypted message bytes -ustring encrypt(ustring_view message, ustring_view key_base, std::string_view domain); +/// - `std::vector` -- Returns the encrypted message bytes +std::vector encrypt( + std::span message, + std::span key_base, + std::string_view domain); /// API: encrypt/encrypt_inplace /// @@ -45,9 +50,18 @@ ustring encrypt(ustring_view message, ustring_view key_base, std::string_view do /// - `message` -- message to encrypt /// - `key_base` -- Fixed key that all clients, must be 32 bytes. /// - `domain` -- short string for the keyed hash -void encrypt_inplace(ustring& message, ustring_view key_base, std::string_view domain); +void encrypt_inplace( + std::vector& message, + std::span key_base, + std::string_view domain); -/// Constant amount of extra bytes required to be appended when encrypting. +/// API: encrypt/ENCRYPT_DATA_OVERHEAD +/// +/// This value contains the constant amount of extra bytes required for encryption as performed by +/// `encrypt()`/`decrypt()`/`encrypt_inplace()`/`decrypt_inplace()`. +/// +/// That is, for some message `m`, encrypt_overhead() is the difference between m.size() and +/// encrypt(m).size(). constexpr size_t ENCRYPT_DATA_OVERHEAD = 40; // ABYTES + NPUBBYTES /// Thrown if decrypt() fails. @@ -67,8 +81,11 @@ struct decrypt_error : std::runtime_error { /// - `domain` -- short string for the keyed hash /// /// Outputs: -/// - `ustring` -- Returns the decrypt message bytes -ustring decrypt(ustring_view ciphertext, ustring_view key_base, std::string_view domain); +/// - `std::vector` -- Returns the decrypt message bytes +std::vector decrypt( + std::span ciphertext, + std::span key_base, + std::string_view domain); /// API: encrypt/decrypt_inplace /// @@ -79,7 +96,10 @@ ustring decrypt(ustring_view ciphertext, ustring_view key_base, std::string_view /// - `ciphertext` -- message to decrypt /// - `key_base` -- Fixed key that all clients, must be 32 bytes. /// - `domain` -- short string for the keyed hash -void decrypt_inplace(ustring& ciphertext, ustring_view key_base, std::string_view domain); +void decrypt_inplace( + std::vector& ciphertext, + std::span key_base, + std::string_view domain); /// Returns the target size of the message with padding, assuming an additional `overhead` bytes of /// overhead (e.g. from encrypt() overhead) will be appended. Will always return a value >= s + @@ -105,6 +125,6 @@ inline constexpr size_t padded_size(size_t s, size_t overhead = ENCRYPT_DATA_OVE /// - `data` -- the data; this is modified in place /// - `overhead` -- encryption overhead to account for to reach the desired padded size. The /// default, if omitted, is the space used by the `encrypt()` function defined above. -void pad_message(ustring& data, size_t overhead = ENCRYPT_DATA_OVERHEAD); +void pad_message(std::vector& data, size_t overhead = ENCRYPT_DATA_OVERHEAD); } // namespace session::config diff --git a/include/session/config/groups/info.h b/include/session/config/groups/info.h new file mode 100644 index 00000000..3b253831 --- /dev/null +++ b/include/session/config/groups/info.h @@ -0,0 +1,236 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../base.h" +#include "../profile_pic.h" + +LIBSESSION_EXPORT extern const size_t GROUP_INFO_NAME_MAX_LENGTH; +LIBSESSION_EXPORT extern const size_t GROUP_INFO_DESCRIPTION_MAX_LENGTH; + +/// API: groups/groups_info_init +/// +/// Constructs a group info config object and sets a pointer to it in `conf`. +/// +/// When done with the object the `config_object` must be destroyed by passing the pointer to +/// config_free() (in `session/config/base.h`). +/// +/// Inputs: +/// - `conf` -- [out] Pointer to the config object +/// - `ed25519_pubkey` -- [in] 32-byte pointer to the group's public key +/// - `ed25519_secretkey` -- [in] optional 64-byte pointer to the group's secret key +/// (libsodium-style 64 byte value). Pass as NULL for a non-admin member. +/// - `dump` -- [in] if non-NULL this restores the state from the dumped byte string produced by a +/// past instantiation's call to `dump()`. To construct a new, empty object this should be NULL. +/// - `dumplen` -- [in] the length of `dump` when restoring from a dump, or 0 when `dump` is NULL. +/// - `error` -- [out] the pointer to a buffer in which we will write an error string if an error +/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a +/// buffer of at least 256 bytes. +/// +/// Outputs: +/// - `int` -- Returns 0 on success; returns a non-zero error code and write the exception message +/// as a C-string into `error` (if not NULL) on failure. +LIBSESSION_EXPORT int groups_info_init( + config_object** conf, + const unsigned char* ed25519_pubkey, + const unsigned char* ed25519_secretkey, + const unsigned char* dump, + size_t dumplen, + char* error) LIBSESSION_WARN_UNUSED; + +/// API: groups_info/groups_info_get_name +/// +/// Returns a pointer to the currently-set name (null-terminated), or NULL if there is no name at +/// all. Should be copied right away as the pointer may not remain valid beyond other API calls. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `char*` -- Pointer to the currently-set name as a null-terminated string, or NULL if there is +/// no name +LIBSESSION_EXPORT const char* groups_info_get_name(const config_object* conf); + +/// API: groups_info/groups_info_set_name +/// +/// Sets the group's name to the null-terminated C string. Returns 0 on success, non-zero on +/// error (and sets the config_object's error string). +/// +/// If the given name is longer than GROUP_INFO_NAME_MAX_LENGTH (100) bytes then it will be +/// truncated. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `name` -- [in] Pointer to the name as a null-terminated C string +/// +/// Outputs: +/// - `int` -- Returns 0 on success, non-zero on error +LIBSESSION_EXPORT int groups_info_set_name(config_object* conf, const char* name); + +/// API: groups_info/groups_info_get_description +/// +/// Returns a pointer to the currently-set description (null-terminated), or NULL if there is no +/// description at all. Should be copied right away as the pointer may not remain valid beyond +/// other API calls. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `char*` -- Pointer to the currently-set description as a null-terminated string, or NULL if +/// there is no description +LIBSESSION_EXPORT const char* groups_info_get_description(const config_object* conf); + +/// API: groups_info/groups_info_set_description +/// +/// Sets the group's description to the null-terminated C string. Returns 0 on success, non-zero on +/// error (and sets the config_object's error string). +/// +/// If the given description is longer than GROUP_INFO_DESCRIPTION_MAX_LENGTH (2000) bytes then it +/// will be truncated. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `description` -- [in] Pointer to the description as a null-terminated C string +/// +/// Outputs: +/// - `int` -- Returns 0 on success, non-zero on error +LIBSESSION_EXPORT int groups_info_set_description(config_object* conf, const char* description); + +/// API: groups_info/groups_info_get_pic +/// +/// Obtains the current profile pic. The pointers in the returned struct will be NULL if a profile +/// pic is not currently set, and otherwise should be copied right away (they will not be valid +/// beyond other API calls on this config object). +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `user_profile_pic` -- Pointer to the currently-set profile pic (despite the "user_profile" in +/// the struct name, this is the group's profile pic). +LIBSESSION_EXPORT user_profile_pic groups_info_get_pic(const config_object* conf); + +/// API: groups_info/groups_info_set_pic +/// +/// Sets a user profile +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `pic` -- [in] Pointer to the pic +/// +/// Outputs: +/// - `int` -- Returns 0 on success, non-zero on error +LIBSESSION_EXPORT int groups_info_set_pic(config_object* conf, user_profile_pic pic); + +/// API: groups_info/groups_info_get_expiry_timer +/// +/// Gets the group's message expiry timer (seconds). Returns 0 if not set. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `int` -- Returns the expiry timer in seconds. Returns 0 if not set +LIBSESSION_EXPORT int groups_info_get_expiry_timer(const config_object* conf); + +/// API: groups_info/groups_info_set_expiry_timer +/// +/// Sets the group's message expiry timer (seconds). Setting 0 (or negative) will clear the current +/// timer. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `expiry` -- [in] Integer of the expiry timer in seconds +LIBSESSION_EXPORT void groups_info_set_expiry_timer(config_object* conf, int expiry); + +/// API: groups_info/groups_info_get_created +/// +/// Returns the timestamp (unix time, in seconds) when the group was created. Returns 0 if unset. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `int64_t` -- Unix timestamp when the group was created (if set by an admin). +LIBSESSION_EXPORT int64_t groups_info_get_created(const config_object* conf); + +/// API: groups_info/groups_info_set_created +/// +/// Sets the creation time (unix timestamp, in seconds) when the group was created. Setting 0 +/// clears the value. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `ts` -- [in] the unix timestamp, or 0 to clear a current value. +LIBSESSION_EXPORT void groups_info_set_created(config_object* conf, int64_t ts); + +/// API: groups_info/groups_info_get_delete_before +/// +/// Returns the delete-before timestamp (unix time, in seconds); clients should delete all messages +/// from the group with timestamps earlier than this value, if set. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `int64_t` -- Unix timestamp before which messages should be deleted. Returns 0 if not set. +LIBSESSION_EXPORT int64_t groups_info_get_delete_before(const config_object* conf); + +/// API: groups_info/groups_info_set_delete_before +/// +/// Sets the delete-before time (unix timestamp, in seconds) before which messages should be +/// deleted. Setting 0 clears the value. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `ts` -- [in] the unix timestamp, or 0 to clear a current value. +LIBSESSION_EXPORT void groups_info_set_delete_before(config_object* conf, int64_t ts); + +/// API: groups_info/groups_info_get_attach_delete_before +/// +/// Returns the delete-before timestamp (unix time, in seconds) for attachments; clients should drop +/// all attachments from messages from the group with timestamps earlier than this value, if set. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `int64_t` -- Unix timestamp before which messages should be deleted. Returns 0 if not set. +LIBSESSION_EXPORT int64_t groups_info_get_attach_delete_before(const config_object* conf); + +/// API: groups_info/groups_info_set_attach_delete_before +/// +/// Sets the delete-before time (unix timestamp, in seconds) for attachments; attachments should be +/// dropped from messages older than this value. Setting 0 clears the value. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `ts` -- [in] the unix timestamp, or 0 to clear a current value. +LIBSESSION_EXPORT void groups_info_set_attach_delete_before(config_object* conf, int64_t ts); + +/// API: groups_info/groups_info_is_destroyed +/// +/// Returns true if this group has been marked destroyed by an admin, which indicates to a receiving +/// client that they should destroy it locally. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `true` if the group has been nuked, `false` otherwise. +LIBSESSION_EXPORT bool groups_info_is_destroyed(const config_object* conf); + +/// API: groups_info/groups_info_destroy_group +/// +/// Nukes a group from orbit. This is permanent (i.e. there is no removing this setting once set). +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +LIBSESSION_EXPORT void groups_info_destroy_group(config_object* conf); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/include/session/config/groups/info.hpp b/include/session/config/groups/info.hpp new file mode 100644 index 00000000..64c2e48f --- /dev/null +++ b/include/session/config/groups/info.hpp @@ -0,0 +1,317 @@ +#pragma once + +#include +#include +#include + +#include "../base.hpp" +#include "../namespaces.hpp" +#include "../profile_pic.hpp" + +namespace session::config::groups { + +using namespace std::literals; + +/// keys used in this config, either currently or in the past (so that we don't reuse): +/// +/// ! - set to true if the group has been destroyed (and should be removed from receiving clients) +/// c - creation unix timestamp (seconds) +/// d - delete before timestamp: this instructs receiving clients that they should delete all +/// messages with a timestamp < the set value. +/// D - delete attachments before - same as above, but specific to attachments. +/// E - disappearing message timer (seconds) if the delete-after-send disappearing messages mode is +/// enabled for the group. Omitted if disappearing messages is disabled. +/// n - utf8 group name (human-readable); may not contain nulls, max length 100. +/// o - utf8 group description (human-readable); may not contain nulls, max length 2000. +/// p - group profile url +/// q - group profile decryption key (binary) + +class Info : public ConfigBase { + + public: + /// Limits for the name & description strings, in bytes. If longer, we truncate to these + /// lengths: + static constexpr size_t NAME_MAX_LENGTH = 100; // same as base_group_info::NAME_MAX_LENGTH + static constexpr size_t DESCRIPTION_MAX_LENGTH = 600; + + // No default constructor + Info() = delete; + + /// API: groups/Info::Info + /// + /// Constructs a group info config object from existing data (stored from `dump()`). + /// + /// To construct a blank info object (i.e. with no pre-existing dumped data to load) pass + /// `std::nullopt` as the third argument. + /// + /// Encryption keys must be loaded before the Info object can be modified or parse other Info + /// messages, and are typically loaded by providing the `Info` object to the `Keys` class. + /// + /// Inputs: + /// - `ed25519_pubkey` is the public key of this group, used to validate config messages. + /// Config messages not signed with this key will be rejected. + /// - `ed25519_secretkey` is the secret key of the group, used to sign pushed config messages. + /// This is only possessed by the group admin(s), and must be provided in order to make and + /// push config changes. + /// - `dumped` -- either `std::nullopt` to construct a new, empty object; or binary state data + /// that was previously dumped from an instance of this class by calling `dump()`. + Info(std::span ed25519_pubkey, + std::optional> ed25519_secretkey, + std::optional> dumped); + + /// API: groups/Info::storage_namespace + /// + /// Returns the Info namespace. Is constant, will always return Namespace::GroupInfo + /// + /// Inputs: None + /// + /// Outputs: + /// - `Namespace` - Will return Namespace::GroupInfo + Namespace storage_namespace() const override { return Namespace::GroupInfo; } + + /// API: groups/Info::encryption_domain + /// + /// Returns the encryption domain used when encrypting messages of this type. + /// + /// Inputs: None + /// + /// Outputs: + /// - `const char*` - Will return "groups::Info" + const char* encryption_domain() const override { return "groups::Info"; } + + /// API: groups/Info::id + /// + /// Contains the (read-only) id of this group, that is, 03 followed by the pubkey in hex. (This + /// is equivalent to a 05-prefixed session_id, but is the group-specific identifier). + /// + /// Inputs: None + /// + /// Outputs: + /// - `std::string` containing the hex group id/pubkey + const std::string id; + + /// API: groups/Info::get_name + /// + /// Returns the group name, or std::nullopt if there is no group name set. + /// + /// Inputs: None + /// + /// Outputs: + /// - `std::optional` - Returns the group name if it is set + std::optional get_name() const; + + /// API: groups/Info::set_name + /// + /// Sets the group name; if given an empty string then the name is removed. + /// + /// If given a name longer than `Info::NAME_MAX_LENGTH` (100) bytes an error will be thrown. + /// + /// Inputs: + /// - `new_name` -- The name to be put into the group Info + void set_name(std::string_view new_name); + + /// API: groups/Info::set_name_truncated + /// + /// Sets the group name; if given an empty string then the name is removed. + /// + /// If given a name longer than `Info::NAME_MAX_LENGTH` (100) bytes it will be truncated. + /// + /// Inputs: + /// - `new_name` -- The name to be put into the group Info + void set_name_truncated(std::string new_name); + + /// API: groups/Info::get_description + /// + /// Returns the group description, or std::nullopt if there is no group description set. + /// + /// If given a description longer than `Info::DESCRIPTION_MAX_LENGTH` bytes it will be + /// truncated. + /// + /// Inputs: None + /// + /// Outputs: + /// - `std::optional` - Returns the group description if it is set + std::optional get_description() const; + + /// API: groups/Info::set_description + /// + /// Sets the optional group description; if given an empty string then an existing description + /// is removed. + /// + /// Inputs: + /// - `new_desc` -- The new description to be put into the group Info + void set_description(std::string_view new_desc); + + /// API: groups/Info::set_description_truncated + /// + /// Sets the optional group description; if given an empty string then an existing description + /// is removed. The same as `set_description` but if the name is too long it'll be truncated. + /// + /// Inputs: + /// - `new_desc` -- The new description to be put into the group Info + void set_description_truncated(std::string new_desc); + + /// API: groups/Info::get_profile_pic + /// + /// Gets the group's current profile pic URL and decryption key. The returned object will + /// evaluate as false if the URL and/or key are not set. + /// + /// Declaration: + /// ```cpp + /// profile_pic get_group_pic() const; + /// ``` + /// + /// Inputs: None + /// + /// Outputs: + /// - `profile_pic` - Returns the group's profile pic + profile_pic get_profile_pic() const; + + /// API: groups/Info::set_profile_pic + /// + /// Sets the group's current profile pic to a new URL and decryption key. Clears both if either + /// one is empty. + /// + /// Declaration: + /// ```cpp + /// void set_profile_pic(std::string_view url, std::span key); + /// void set_profile_pic(profile_pic pic); + /// ``` + /// + /// Inputs: + /// - First function: + /// - `url` -- URL pointing to the profile pic + /// - `key` -- Decryption key + /// - Second function: + /// - `pic` -- Profile pic object + void set_profile_pic(std::string_view url, std::span key); + void set_profile_pic(profile_pic pic); + + /// API: groups/Info::set_expiry_timer + /// + /// Sets (or clears) the group's message expiry timer. If > 0s the setting becomes the + /// delete-after-send value; if omitted or given a 0 or negative duration then the expiring + /// message timer is disabled for the group. + /// + /// Inputs: + /// - `expiration_timer` -- how long the expiration timer should be, defaults to zero (disabling + /// message expiration) if the argument is omitted. + void set_expiry_timer(std::chrono::seconds expiration_timer = 0min); + + /// API: groups/Info::get_expiry_timer + /// + /// Returns the group's current message expiry timer, or `std::nullopt` if no expiry timer is + /// set. If not nullopt then the expiry will always be >= 1s. + /// + /// Note that groups only support expire-after-send expiry timers and so there is no separate + /// expiry type setting. + /// + /// Inputs: none + /// + /// Outputs: + /// - `std::chrono::seconds` -- the expiry timer duration + std::optional get_expiry_timer() const; + + /// API: groups/Info::set_created + /// + /// Sets the created timestamp. It's recommended (but not required) that you only set this if + /// not already set. + /// + /// Inputs: + /// - `session_id` -- hex string of the session id + /// - `timestamp` -- standard unix timestamp when the group was created + void set_created(int64_t timestamp); + + /// API: groups/Info::get_created + /// + /// Returns the creation timestamp, if set/known. + /// + /// Inputs: none. + /// + /// Outputs: + /// - `std::optional` -- the unix timestamp when the group was created, or nullopt if + /// the creation timestamp is not set. + std::optional get_created() const; + + /// API: groups/Info::set_delete_before + /// + /// Sets a "delete before" unix timestamp: this instructs clients to delete all messages from + /// the closed group history with a timestamp earlier than this value. Returns nullopt if no + /// delete-before timestamp is set. + /// + /// The given value is checked for sanity (e.g. if you pass milliseconds it will be + /// interpreted as such) + /// + /// Inputs: + /// - `timestamp` -- the new unix timestamp before which clients should delete messages. Pass 0 + /// (or negative) to disable the delete-before timestamp. + void set_delete_before(int64_t timestamp); + + /// API: groups/Info::get_delete_before + /// + /// Returns the delete-before unix timestamp (seconds) for the group; clients should delete all + /// messages from the closed group with timestamps earlier than this value, if set. + /// + /// Returns std::nullopt if no delete-before timestamp is set. + /// + /// Inputs: none. + /// + /// Outputs: + /// - `int64_t` -- the unix timestamp for which all older messages shall be delete + std::optional get_delete_before() const; + + /// API: groups/Info::set_delete_attach_before + /// + /// Sets a "delete attachments before" unix timestamp: this instructs clients to drop the + /// attachments (though not necessarily the messages themselves; see `get_delete_before` for + /// that) from any messages older than the given timestamp. Returns nullopt if no + /// delete-attachments-before timestamp is set. + /// + /// The given value is checked for sanity (e.g. if you pass milliseconds it will be + /// interpreted as such) + /// + /// Inputs: + /// - `timestamp` -- the new unix timestamp before which clients should delete attachments. Pass + /// 0 + /// (or negative) to disable the delete-attachment-before timestamp. + void set_delete_attach_before(int64_t timestamp); + + /// API: groups/Info::get_delete_attach_before + /// + /// Returns the delete-attachments-before unix timestamp (seconds) for the group; clients should + /// delete all messages from the closed group with timestamps earlier than this value, if set. + /// + /// Returns std::nullopt if no delete-attachments-before timestamp is set. + /// + /// Inputs: none. + /// + /// Outputs: + /// - `int64_t` -- the unix timestamp for which all older message attachments shall be deleted + std::optional get_delete_attach_before() const; + + /// API: groups/Info::destroy_group + /// + /// Sets the group as permanently deleted, and set this status in the group's config. Receiving + /// clients are supposed to remove the conversation from their conversation list when this + /// happens. + /// + /// This change is permanent; the flag cannot be unset once set! + /// + /// Inputs: + /// + /// None: this call is destructive and permanent. Be careful! + void destroy_group(); + + /// API: groups/Info::is_destroyed + /// + /// Returns true if this group has been marked destroyed; the receiving client is expected to + /// delete it. + /// + /// Inputs: none. + /// + /// Outputs: + /// - `true` if the group has been destroyed, `false` otherwise. + bool is_destroyed() const; +}; + +} // namespace session::config::groups diff --git a/include/session/config/groups/keys.h b/include/session/config/groups/keys.h new file mode 100644 index 00000000..f0ee5025 --- /dev/null +++ b/include/session/config/groups/keys.h @@ -0,0 +1,623 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../base.h" +#include "../util.h" + +// This is an opaque type analagous to `config_object` but specific to the groups keys object. +// +// It is constructed via groups_keys_init and destructed via groups_keys_free. +typedef struct config_group_keys { + // Internal opaque object pointer; calling code should leave this alone. + void* internals; + + // When an error occurs in the C API this string will be set to the specific error message. May + // be empty. + const char* last_error; + + // Sometimes used as the backing buffer for `last_error`. Should not be touched externally. + char _error_buf[256]; + +} config_group_keys; + +/// API: groups/groups_keys_init +/// +/// Constructs a group keys management config object and sets a pointer to it in `conf`. +/// +/// Note that this is *not* a regular `config_object` and thus does not use the usual +/// `config_free()` and similar methods from `session/config/base.h`; instead it must be managed by +/// the functions declared in the header. +/// +/// When no dump is provided the initial config_group_keys object will be created with no keys +/// loaded at all, these will be loaded later into this and the info/members objects when loading +/// keys via received config messages. If this is a brand new group then groups_keys_rekey() MUST be +/// called, otherwise the group will be in an invalid state. +/// +/// Inputs: +/// - `conf` -- [out] Pointer-pointer to a `config_group_keys` pointer (i.e. double pointer); the +/// pointer will be set to a new config_group_keys object on success. +/// +/// Intended use: +/// +/// ```C +/// config_group_keys* keys; +/// int rc = groups_keys_init(&keys, ...); +/// ``` +/// - `user_ed25519_secretkey` -- [in] 64-byte pointer to the **user**'s (not group's) secret +/// ed25519 key. (Used to be able to decrypt keys encrypted individually for us). +/// - `group_ed25519_pubkey` -- [in] 32-byte pointer to the group's public key +/// - `group_ed25519_secretkey` -- [in] optional 64-byte pointer to the group's secret key +/// (libsodium-style 64 byte value). Pass as NULL for a non-admin member. +/// - `group_info_conf` -- the group info config instance (keys will be added) +/// - `group_members_conf` -- the group members config instance (keys will be added) +/// - `dump` -- [in] if non-NULL this restores the state from the dumped byte string produced by a +/// past instantiation's call to `dump()`. To construct a new, empty object this should be NULL. +/// - `dumplen` -- [in] the length of `dump` when restoring from a dump, or 0 when `dump` is NULL. +/// - `error` -- [out] the pointer to a buffer in which we will write an error string if an error +/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a +/// buffer of at least 256 bytes. +/// +/// Outputs: +/// - `int` -- Returns 0 on success; returns a non-zero error code and write the exception message +/// as a C-string into `error` (if not NULL) on failure. +LIBSESSION_EXPORT int groups_keys_init( + config_group_keys** conf, + const unsigned char* user_ed25519_secretkey, + const unsigned char* group_ed25519_pubkey, + const unsigned char* group_ed25519_secretkey, + config_object* group_info_conf, + config_object* group_members_conf, + const unsigned char* dump, + size_t dumplen, + char* error) LIBSESSION_WARN_UNUSED; + +/// API: base/groups_keys_free +/// +/// Frees a config keys object created with groups_keys_init. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to config_group_keys object +LIBSESSION_EXPORT void groups_keys_free(config_group_keys* conf); + +/// API: base/groups_keys_storage_namespace +/// +/// Returns the numeric namespace in which config_group_keys messages should be stored. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to config_group_keys object +/// +/// Outputs: +/// - `int16_t` -- integer of the namespace +LIBSESSION_EXPORT int16_t groups_keys_storage_namespace(const config_group_keys* conf); + +/// API: groups/groups_keys_size +/// +/// Returns the number of decryption keys stored in this Keys object. Mainly for +/// debugging/information purposes. +/// +/// Inputs: +/// - `conf` -- keys config object +/// +/// Outputs: +/// - `size_t` number of keys +LIBSESSION_EXPORT size_t groups_keys_size(const config_group_keys* conf); + +/// API: groups/groups_keys_get_key +/// +/// Accesses the Nth encryption key, ordered from most-to-least recent starting from index 0. +/// Calling this with 0 thus returns the most-current key (which is also the current _en_cryption +/// key). +/// +/// This function is not particularly efficient and is not typically needed except for diagnostics: +/// instead encryption/decryption should be performed used the dedicated functions which +/// automatically manage the decryption keys. +/// +/// This function can be used to obtain all decryption keys by calling it with an incrementing value +/// until it returns nullptr (or alternatively, looping over `0 <= i < groups_keys_size`). +/// +/// Returns nullptr if N is >= the current number of decryption keys. +/// +/// The returned pointer points at a 32-byte binary value containing the key; it should be copied or +/// used at once as it may not remain valid past other calls to the keys object. It should *not* be +/// freed. +/// +/// Inputs: +/// - `conf` -- keys config object +/// - `N` -- the index of the key to obtain +/// +/// Outputs: +/// - `const unsigned char*` -- pointer to the 32-byte key, or nullptr if there +LIBSESSION_EXPORT const unsigned char* groups_keys_get_key(const config_group_keys* conf, size_t N); + +/// API: groups/groups_keys_is_admin +/// +/// Returns true if this object has the group private keys, i.e. the user is an all-powerful +/// wiz^H^H^Hadmin of the group. +/// +/// Inputs: +/// - `conf` -- the groups config object +/// +/// Outputs: +/// - `true` if we have admin keys, `false` otherwise. +LIBSESSION_EXPORT bool groups_keys_is_admin(const config_group_keys* conf); + +/// API: groups/groups_keys_load_admin_key +/// +/// Loads the admin keys, effectively upgrading this keys object from a member to an admin. +/// +/// This does nothing if the keys object already has admin keys. +/// +/// Inputs: +/// - `conf` -- the groups keys config object +/// - `secret` -- pointer to the 32-byte group seed. (This a 64-byte libsodium "secret key" begins +/// with the seed, this can also be a given a pointer to such a value). +/// - `group_info_conf` -- the group info config instance (the key will be added) +/// - `group_members_conf` -- the group members config instance (the key will be added) +/// +/// Outputs: +/// - `true` if the object has been upgraded to admin status, or was already admin status; `false` +/// if the given seed value does not match the group's public key. If this returns `true` then +/// after the call a call to `groups_keys_is_admin` would also return `true`. +LIBSESSION_EXPORT bool groups_keys_load_admin_key( + config_group_keys* conf, + const unsigned char* secret, + config_object* group_info_conf, + config_object* group_members_conf); + +/// API: groups/groups_keys_rekey +/// +/// Generates a new encryption key for the group and returns an encrypted key message to be pushed +/// to the swarm containing the key, encrypted for the members of the group. +/// +/// The returned binary key message to be pushed is written into a newly-allocated buffer. A +/// pointer to this buffer is set in the pointer-pointer `out` argument, and its length is set in +/// the `outlen` pointer. +/// +/// See Keys::rekey in the C++ API for more details about intended use. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `info` -- [in] Pointer to group Info object +/// - `members` -- [in] Pointer to group Members object +/// - `out` -- [out] Will be set to a pointer to the message to be pushed (only if the function +/// returns true). This value must be used immediately (it is not guaranteed to remain valid +/// beyond other calls to the config object), and must not be freed (i.e. ownership remains with +/// the keys config object). +/// - `outlen` -- [out] Length of the output value. Only set when the function returns true. +/// +/// Output: +/// - `bool` -- Returns true on success, false on failure. +LIBSESSION_EXPORT bool groups_keys_rekey( + config_group_keys* conf, + config_object* info, + config_object* members, + const unsigned char** out, + size_t* outlen) LIBSESSION_WARN_UNUSED; + +/// API: groups/groups_keys_pending_config +/// +/// If a `rekey()` is currently in progress (and not yet confirmed, or possibly lost), this returns +/// the config message that should be pushed. As with the result of `rekey()` the pointer ownership +/// remains with the keys config object, and the value should be used/copied immediately. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `out` -- [out] Pointer-pointer that will be updated to point at the config data. Only set if +/// this function returns true! +/// - `outlen` -- [out] Pointer to the config data size (only set if the function returns true). +/// +/// Outputs: +/// - `bool` -- true if `out` and `outlen` have been updated to point to a pending config message; +/// false if there is no pending config message. +LIBSESSION_EXPORT bool groups_keys_pending_config( + const config_group_keys* conf, + const unsigned char** out, + size_t* outlen) LIBSESSION_WARN_UNUSED; + +/// API: groups/groups_keys_load_message +/// +/// Loads a key config message downloaded from the swarm, and loads the key into the info/member +/// configs. +/// +/// Such messages should be processed via this method *before* attempting to load config messages +/// downloaded from an info/members namespace. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `msg_hash` -- [in] Null-terminated C string containing the message hash +/// - `data` -- [in] Pointer to the incoming key config message +/// - `datalen` -- [in] length of `data` +/// - `timestamp_ms` -- [in] the timestamp (from the swarm) of the message +/// - `info` -- [in] the info config object to update with newly discovered keys +/// - `members` -- [in] the members config object to update with newly discovered keys +/// +/// Outputs: +/// Returns `true` if the message was parsed successfully (whether or not any new keys were +/// decrypted or loaded). Returns `false` on failure to parse (and sets `conf->last_error`). +LIBSESSION_EXPORT bool groups_keys_load_message( + config_group_keys* conf, + const char* msg_hash, + const unsigned char* data, + size_t datalen, + int64_t timestamp_ms, + config_object* info, + config_object* members) LIBSESSION_WARN_UNUSED; + +/// API: groups/groups_keys_active_hashes +/// +/// Returns the hashes of currently active keys messages, that is, messages that have a decryption +/// key that new devices or clients might require; these are the messages that should have their +/// expiries renewed periodically. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the keys config object +/// +/// Outputs: +/// - `config_string_list*` -- pointer to an array of message hashes. The returned pointer belongs +/// to the caller and must be free()d when done. +LIBSESSION_EXPORT config_string_list* groups_keys_active_hashes(const config_group_keys* conf); + +/// API: groups/groups_keys_needs_rekey +/// +/// Checks whether a rekey is required (for instance, because of key generation conflict). Note +/// that this is *not* a check for when members changed (such rekeys are up to the caller to +/// manage), but mergely whether a rekey is needed after loading one or more config messages. +/// +/// See the C++ Keys::needs_rekey and Keys::rekey descriptions for more details. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `bool` -- `true` if `rekey()` needs to be called, `false` otherwise. +LIBSESSION_EXPORT bool groups_keys_needs_rekey(const config_group_keys* conf) + LIBSESSION_WARN_UNUSED; +/// API: groups/groups_keys_needs_dump +/// +/// Checks whether a groups_keys_dump needs to be called to save state. This is analagous to +/// config_dump, but specific for the group keys object. The value becomes false as soon as +/// `groups_keys_dump` is called, and remains false until the object's state is mutated (e.g. by +/// rekeying or loading new config messages). +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `bool` -- `true` if a dump is needed, `false` otherwise. +LIBSESSION_EXPORT bool groups_keys_needs_dump(const config_group_keys* conf) LIBSESSION_WARN_UNUSED; + +/// API: groups/groups_keys_dump +/// +/// Produces a dump of the keys object state to be stored by the application to later restore the +/// object by passing the dump into the constructor. This is analagous to config_dump, but specific +/// for the group keys object. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `out` -- [out] Pointer-pointer to a data buffer; this will be set to a newly malloc'd pointer +/// containing the dump data. The caller is responsible for freeing the data when done! +/// - `outlen` -- [out] Pointer to a size_t where the length of `out` will be stored. +LIBSESSION_EXPORT void groups_keys_dump( + config_group_keys* conf, unsigned char** out, size_t* outlen); + +/// API: groups/groups_keys_key_supplement +/// +/// Generates a supplemental key message for one or more session IDs. This is used to distribute +/// existing active keys to a new member so that that member can access existing keys, configs, and +/// messages. Only admins can call this. +/// +/// The recommended order of operations for adding such a member is: +/// - add the member to Members +/// - generate the key supplement +/// - push new members & key supplement (ideally in a batch) +/// - send invite details, auth signature, etc. to the new user +/// +/// To add a member *without* giving them access to old messages you would use groups_keys_rekey() +/// instead of this method. +/// +/// Inputs: +/// - `conf` -- pointer to the keys config object +/// - `sids` -- array of session IDs of the members to generate a supplemental key for; each element +/// must be an ordinary (null-terminated) C string containing the 66-character session id. +/// - `sids_len` -- length of the `sids` array +/// - `message` -- pointer-pointer that will be set to a newly allocated buffer containing the +/// message that should be sent to the swarm. The caller must free() the pointer when finished to +/// not leak the message memory (but only if the function returns true). +/// - `message_len` -- pointer to a `size_t` that will be set to the length of the `message` buffer. +/// +/// Outputs: +/// - `true` and sets `*message` and `*message_len` on success; returns `false` and does not set +/// them on failure. +LIBSESSION_EXPORT bool groups_keys_key_supplement( + config_group_keys* conf, + const char* const* sids, + size_t sids_len, + unsigned char** message, + size_t* message_len); + +/// API: groups/groups_keys_current_generation +/// +/// Returns the current generation number for the latest keys message. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `int` -- latest keys generation number +LIBSESSION_EXPORT int groups_keys_current_generation(config_group_keys* conf); + +/// API: groups/groups_keys_swarm_make_subaccount +/// +/// Constructs a swarm subaccount signing value that a member can use to access messages in the +/// swarm. The member will have read and write access, but not delete access. Requires group +/// admins keys. +/// +/// Inputs: +/// - `conf` -- the config object +/// - `session_id` -- the session ID of the member (in hex) +/// - `sign_value` -- [out] pointer to a 100 byte (or larger) buffer where the 100 byte signing +/// value will be written. This is the value that should be sent to a member to allow +/// authentication. +/// +/// Outputs: +/// - `true` -- if making the subaccount succeeds, false if it fails (e.g. because of an invalid +/// session id, or not being an admin). If a failure occurs, sign_value will not be written to. +LIBSESSION_EXPORT bool groups_keys_swarm_make_subaccount( + config_group_keys* conf, const char* session_id, unsigned char* sign_value); + +/// API: groups/groups_keys_swarm_make_subaccount_flags +/// +/// Same as groups_keys_swarm_make_subaccount, but lets you specify whether the write/del flags are +/// present. +/// +/// +/// Inputs: +/// - `conf` -- the config object +/// - `session_id` -- the member session id (hex c string) +/// - `write` -- if true then the member shall be allowed to submit messages into the group account +/// of the swarm and extend (but not shorten) the expiry of messages in the group account. If +/// false then the user can only retrieve messages. Typically this is true. +/// - `del` -- if true (default is false) then the user shall be allowed to delete messages from the +/// swarm. This permission can be used to appoint a sort of "moderator" who can delete messages +/// without having the full admin group keys. Typically this is false. +/// - `sign_value` -- pointer to a buffer with at least 100 bytes where the 100 byte signing value +/// will be written. +/// +/// Outputs: +/// - `bool` - same as groups_keys_swarm_make_subaccount +LIBSESSION_EXPORT bool groups_keys_swarm_make_subaccount_flags( + config_group_keys* conf, + const char* session_id, + bool write, + bool del, + unsigned char* sign_value); + +/// API: groups/groups_keys_swarm_verify_subaccount +/// +/// Verifies that a received subaccount signing value (allegedly produced by +/// groups_keys_swarm_make_subaccount) is a valid subaccount signing value for the given group +/// pubkey, including a proper signature by an admin of the group. The signing value must have read +/// permission, but parameters can be given to also require write or delete permissions. A +/// subaccount signing value should always be checked for validity using this before creating a +/// group that would depend on it. +/// +/// Inputs: +/// - note that this function does *not* take a config object as it is intended for use to validate +/// an invitation before constructing the keys config objects. +/// - `groupid` -- the group id/pubkey, in hex, beginning with "03". +/// - `session_ed25519_secretkey` -- the user's Session ID secret key (64 bytes). +/// - `signing_value` -- the 100-byte subaccount signing value to validate +/// +/// The key will require read and write access to be acceptable. (See the _flags version if you +/// need something else). +/// +/// Outputs: +/// - `true` if `signing_value` is a valid subaccount signing value for `groupid` with (at least) +/// read and write permissions, `false` if the signing value does not validate or does not meet +/// the requirements. +LIBSESSION_EXPORT bool groups_keys_swarm_verify_subaccount( + const char* group_id, + const unsigned char* session_ed25519_secretkey, + const unsigned char* signing_value); + +/// API: groups/groups_keys_swarm_verify_subaccount_flags +/// +/// Same as groups_keys_swarm_verify_subaccount, except that you can specify whether you want to +/// require the write and or delete flags. +/// +/// Inputs: +/// - same as groups_keys_swarm_verify_subaccount +/// - `write` -- if true, require that the signing_value has write permission (i.e. that the +/// user will be allowed to post messages). +/// - `del` -- if true, required that the signing_value has delete permissions (i.e. that the +/// user will be allowed to remove storage messages from the group's swarm). Note that this +/// permission is about forcible swarm message deletion, and has no effect on an ability to +/// submit a deletion meta-message to the group (which only requires writing a message). +LIBSESSION_EXPORT bool groups_keys_swarm_verify_subaccount_flags( + const char* group_id, + const unsigned char* session_ed25519_secretkey, + const unsigned char* signing_value, + bool write, + bool del); + +/// API: groups/groups_keys_swarm_subaccount_sign +/// +/// This helper function generates the required signature for swarm subaccount authentication, +/// given the user's keys and swarm auth keys (as provided by an admin, produced via +/// `groups_keys_swarm_make_subaccount`). +/// +/// Storage server subaccount authentication requires passing the three values in the returned +/// struct in the storage server request. +/// +/// This version of the function writes base64-encoded values to the output parameters; there is +/// also a `_binary` version that writes raw values. +/// +/// Inputs: +/// - `conf` -- the keys config object +/// - `msg` -- the binary data that needs to be signed (which depends on the storage server request +/// being made; for example, "retrieve9991234567890123" for a retrieve request to namespace 999 +/// made at unix time 1234567890.123; see storage server RPC documentation for details). +/// - `msg_len` -- the length of the `msg` buffer +/// - `signing_value` -- the 100-byte subaccount signing value, as produced by an admin's +/// `swarm_make_subaccount` and provided to this member. +/// - `subaccount` -- [out] a C string buffer of *at least* 49 bytes where the null-terminated +/// 48-byte base64-encoded subaccount value will be written. This is the value to pass as +/// `subaccount` for storage server subaccount authentication. +/// - `subaccount_sig` -- [out] a C string buffer of *at least* 89 bytes where the null-terminated, +/// 88-ascii-character base64-encoded version of the 64-byte admin signature authorizing this +/// subaccount will be written. This is the value to be passed as `subaccount_sig` for storage +/// server subaccount authentication. +/// - `signature` -- [out] a C string buffer of *at least* 89 bytes where the null-terminated, +/// 88-character request signature will be written, base64 encoded. This is passes as the +/// `signature` value, alongside `subaccount`/`subaccoung_sig` to perform subaccount signature +/// authentication. +/// +/// Outputs: +/// - true if the values were written, false if an error occured (e.g. from an invalid signing_value +/// or cryptography error). +LIBSESSION_EXPORT bool groups_keys_swarm_subaccount_sign( + config_group_keys* conf, + const unsigned char* msg, + size_t msg_len, + const unsigned char* signing_value, + + char* subaccount, + char* subaccount_sig, + char* signature); + +/// API: groups/groups_keys_swarm_subaccount_sign_binary +/// +/// Does exactly the same as groups_keys_swarm_subaccount_sign except that the subaccount, +/// subaccount_sig, and signature values are written in binary (without null termination) of exactly +/// 36, 64, and 64 bytes, respectively. +/// +/// Inputs: +/// - see groups_keys_swarm_subaccount_sign +/// - `subaccount`, `subaccount_sig`, and `signature` are binary output buffers of size 36, 64, and +/// 64, respectively. +/// +/// Outputs: +/// See groups_keys_swarm_subaccount. +LIBSESSION_EXPORT bool groups_keys_swarm_subaccount_sign_binary( + config_group_keys* conf, + const unsigned char* msg, + size_t msg_len, + const unsigned char* signing_value, + + unsigned char* subaccount, + unsigned char* subaccount_sig, + unsigned char* signature); + +/// API: groups/groups_keys_swarm_subaccount_token +/// +/// Constructs the subaccount token for a session id. The main use of this is to submit a swarm +/// token revocation; for issuing subaccount tokens you want to use +/// `groups_keys_swarm_make_subaccount` instead. This will produce the same subaccount token that +/// `groups_keys_swarm_make_subaccount` implicitly creates that can be passed to a swarm to add a +/// revocation for that subaccount. +/// +/// This is recommended to be used when removing a non-admin member to prevent their access. +/// (Note, however, that there are circumstances where this can fail to prevent access, and so +/// should be combined with proper member removal and key rotation so that even if the member +/// gains access to messages, they cannot read them). +/// +/// Inputs: +/// - `conf` -- the keys config object +/// - `session_id` -- the session ID of the member (in hex) +/// - `token` -- [out] a 36-byte buffer into which to write the subaccount token. +/// +/// Outputs: +/// - true if the call succeeded, false if an error occured. +LIBSESSION_EXPORT bool groups_keys_swarm_subaccount_token( + config_group_keys* conf, const char* session_id, unsigned char* token); + +/// API: groups/groups_keys_swarm_subaccount_token_flags +/// +/// Same as `groups_keys_swarm_subaccount_token`, but takes `write` and `del` flags for creating a +/// token matching a user with non-standard permissions. +/// +/// Inputs: +/// - `conf` -- the keys config object +/// - `session_id` -- the session ID of the member (in hex) +/// - `write`, `del` -- see groups_keys_swarm_make_subaccount_flags +/// - `token` -- [out] a 36-byte buffer into which to write the subaccount token. +/// +/// Outputs: +/// - true if the call succeeded, false if an error occured. +LIBSESSION_EXPORT bool groups_keys_swarm_subaccount_token_flags( + config_group_keys* conf, + const char* session_id, + bool write, + bool del, + unsigned char* token); + +/// API: groups/groups_keys_encrypt_message +/// +/// Encrypts a message using the most recent group encryption key of this object. +/// +/// The message will be compressed (if that reduces the size), padded, authored, and signed before +/// being encrypted. Decryption and verification (and decompression, if compression was applied) is +/// performed by passing such a message into groups_keys_decrypt_message. +/// +/// Note: this method can fail if there are no encryption keys at all, or if the incoming message +/// decompresses to a huge value (more than 1MB). If it fails then `ciphertext_out` is set to NULL +/// and should not be read or free()d. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `plaintext_in` -- [in] Pointer to a data buffer containing the unencrypted data. +/// - `plaintext_len` -- [in] Length of `plaintext_in` +/// - `ciphertext_out` -- [out] Pointer-pointer to an output buffer; a new buffer is allocated, the +/// encrypted data written to it, and then the pointer to that buffer is stored here. This +/// buffer must be `free()`d by the caller when done with it! +/// - `ciphertext_len` -- [out] Pointer to a size_t where the length of `ciphertext_out` is stored. +LIBSESSION_EXPORT void groups_keys_encrypt_message( + const config_group_keys* conf, + const unsigned char* plaintext_in, + size_t plaintext_len, + unsigned char** ciphertext_out, + size_t* ciphertext_len); + +/// API: groups/groups_keys_decrypt_message +/// +/// Attempts to decrypt a message using all of the known active encryption keys of this object. The +/// message will be de-padded, decompressed (if compressed), and have its signature verified after +/// decryption. +/// +/// Upon failure this returns false and sets `conf.last_error` to a string containing a diagnostic +/// reason the decryption failed (intended for logging, not for end-user display). +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `ciphertext_in` -- [in] Pointer to a data buffer containing the encrypted data (as was +/// produced by `groups_keys_encrypt_message`). +/// - `ciphertext_len` -- [in] Length of `ciphertext_in` +/// - `session_id_out` -- [out] pointer to a buffer of at least 67 bytes where the null-terminated, +/// hex-encoded session_id of the message's author will be written if decryption/verification was +/// successful. +/// - `plaintext_out` -- [out] Pointer-pointer to an output buffer; a new buffer is allocated, the +/// decrypted/decompressed data written to it, and then the pointer to that buffer is stored here. +/// This buffer must be `free()`d by the caller when done with it *unless* the function returns +/// false, in which case the buffer pointer will not be set. +/// - `plaintext_len` -- [out] Pointer to a size_t where the length of `plaintext_out` is stored. +/// Not touched if the function returns false. +/// +/// Outputs: +/// - `bool` -- True if the message was successfully decrypted, false if decryption (or parsing or +/// decompression) failed with all of our known keys. If (and only if) true is returned then +/// `plaintext_out` must be freed when done with it. If false is returned then `conf.last_error` +/// will contain a diagnostic message describing why the decryption failed. +LIBSESSION_EXPORT bool groups_keys_decrypt_message( + config_group_keys* conf, + const unsigned char* cipherext_in, + size_t cipherext_len, + char* session_id_out, + unsigned char** plaintext_out, + size_t* plaintext_len); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/include/session/config/groups/keys.hpp b/include/session/config/groups/keys.hpp new file mode 100644 index 00000000..3e20c9b7 --- /dev/null +++ b/include/session/config/groups/keys.hpp @@ -0,0 +1,755 @@ +#pragma once + +#include +#include + +#include "../../config.hpp" +#include "../base.hpp" +#include "../namespaces.hpp" +#include "members.hpp" + +namespace session::config::groups { + +class Members; +class Info; + +using namespace std::literals; + +/// This "config" isn't exactly a regular config type that inherits from ConfigBase; in particular: +/// - it doesn't encrypt the message (but merely contains encrypted elements within it) +/// - it doesn't merge +/// - it does have a concept analogous to the message seqno +/// - conflict resolution involves regenerating and distributing new keys; nothing gets merged. +/// - it cares strongly about when new configs were pushed (configs expire after having been +/// replaced for a certain amount of time, not by being updated). +/// - its internal state isn't fully serialized when pushing updates +/// - messages don't contain the outer layer of config messages (where config metadata, references +/// to other objects, etc.) that ConfigBase-derived type hold. +/// - it isn't compressed (since most of the data fields are encrypted or random, compression +/// reduction would be minimal). +/// +/// Fields used for generating key messages (in ascii order): +/// # -- 24-byte nonce used for all the encrypted values in this message; required. +/// +/// For non-supplemental messages: +/// +/// G -- monotonically incrementing counter identifying key generation changes +/// K -- encrypted copy of the key for admins (omitted for `+` incremental key messages) +/// k -- packed bytes of encrypted keys for non-admin members; this is a single byte string in which +/// each 48 bytes is a separate encrypted value. +/// +/// For supplemental messages: +/// + -- encrypted supplemental key info list; this is a list of encrypted values, encrypted for +/// each member to whom keys are being disclosed. The *decrypted* value of these entries are +/// the same value (encrypted separately for each member) which is a bt-encoded list of dicts +/// where each dict contains keys: +/// - g -- the key generation +/// - k -- the key itself (32 bytes). +/// - t -- the storage timestamp of the key (so that recipients know when keys expire) +/// G -- the maximum generation of the keys included in this message; this is used to track when +/// this message can be allowed to expire. +/// +/// And finally, for both types: +/// +/// ~ -- signature of the message signed by the group's master keypair, signing the message value up +/// to but not including the ~ keypair. The signature must be the last key in the dict (thus +/// `~` since it is the largest 7-bit ascii character value). Note that this signature +/// mechanism works exactly the same as the signature on regular config messages. +/// +/// Fields used for dumping the config (in ascii order): +/// A -- active config messages list. A list of lists, where each list has: +/// - as first argument the generation number +/// - the rest are the hashes valid for that generation number +/// L -- a list of dict representing all the keys. Each dict has: +/// - k -- same as "For supplemental messages" +/// - g -- same as "For supplemental messages" +/// - t -- same as "For supplemental messages" +/// P -- pending key as a dict (if present). +/// - c -- the pending config message to push +/// - g -- same as "For supplemental messages" +/// - k -- same as "For supplemental messages" +/// +/// Some extra details: +/// +/// - each copy of the encryption key uses xchacha20_poly1305 using the `#` nonce +/// - the `k` members list gets padded with junk entries up to the next multiple of 75 (for +/// non-supplemental messages). +/// - the decryption key for the admin version of the key is H(admin_seed, +/// key="SessionGroupKeyAdmin") +/// - the encryption key for a member is H(a'B || A' || B, key="SessionGroupKeyMember") where a'/A' +/// is the group Ed25519 master key converted to X25519, and b/B is the member's X25519 keypair +/// (i.e. B is the non-05-prefixed session_id). +/// - the decryption key is calculated by the member using `bA' || A' || B` +/// - A new key and nonce is created from a 56-byte H(M0 || M1 || ... || Mn || g || S, +/// key="SessionGroupKeyGen"), where S = H(group_seed, key="SessionGroupKeySeed"). + +class Keys : public ConfigSig { + + Ed25519Secret user_ed25519_sk; + + struct key_info { + std::array key; + std::chrono::system_clock::time_point timestamp; // millisecond precision + int64_t generation; + + auto cmpval() const { return std::tie(generation, timestamp, key); } + bool operator<(const key_info& b) const { return cmpval() < b.cmpval(); } + bool operator>(const key_info& b) const { return cmpval() > b.cmpval(); } + bool operator<=(const key_info& b) const { return cmpval() <= b.cmpval(); } + bool operator>=(const key_info& b) const { return cmpval() >= b.cmpval(); } + bool operator==(const key_info& b) const { return cmpval() == b.cmpval(); } + bool operator!=(const key_info& b) const { return cmpval() != b.cmpval(); } + }; + + /// Vector of keys that is kept sorted by generation/timestamp/key. This gets pruned as keys + /// have been superceded by another key for a sufficient amount of time (see KEY_EXPIRY). + sodium_vector keys_; + + /// Hashes of messages we have successfully parsed; used for deciding what needs to be renewed. + std::map> active_msgs_; + + sodium_cleared> pending_key_; + sodium_vector pending_key_config_; + int64_t pending_gen_ = -1; + + bool needs_dump_ = false; + + ConfigMessage::verify_callable verifier_; + ConfigMessage::sign_callable signer_; + + void set_verifier(ConfigMessage::verify_callable v) override { verifier_ = std::move(v); } + void set_signer(ConfigMessage::sign_callable s) override { signer_ = std::move(s); } + + std::vector sign(std::span data) const; + + // Checks for and drops expired keys. + void remove_expired(); + + // Loads existing state from a previous dump of keys data + void load_dump(std::span dump); + + // Inserts a key into the correct place in `keys_`. + void insert_key(std::string_view message_hash, key_info&& key); + + // Returned the blinding factor for a given session X25519 pubkey. This depends on the group's + // seed and thus is only obtainable by an admin account. + std::array subaccount_blind_factor( + const std::array& session_xpk) const; + + public: + /// The multiple of members keys we include in the message; we add junk entries to the key list + /// to reach a multiple of this. 45 is chosen because it's a decently large human-round number + /// that should still fit within 2.5kiB size limitation for push notifications. + static constexpr int MESSAGE_KEY_MULTIPLE = 45; + + // 45 because: + // 2 // for the 'de' delimiters of the outer dict + // + 3 + 2 + 12 // for the `1:g` and `iNNNNNNNNNNe` generation keypair + // + 3 + 3 + 24 // for the `1:n`, `24:`, and 24 byte nonce + // + 3 + 3 + 48 // for the `1:K`, `48:`, and 48 byte ciphertexted key + // + 3 + 6 // for the `1:k` and `NNNNN:` key and prefix of the keys pair + // + N * 48 // for the packed encryption keys + // + 3 + 3 + 64; // for the `1:~` and `64:` and 64 byte signature + // = 177 + 48N + // + // and N=45 puts us a little bit under 2.5kiB (which is the limit we have on the push + // notification server because after base64 encoding it gets close to the 4kiB limit for push + // notification content). + + /// A key expires when it has been surpassed by another key for at least this amount of time. + /// We default this to double the 30 days that we strictly need to avoid race conditions with + /// 30-day old config messages that might need the key for a client that is only very rarely + /// online. + static constexpr auto KEY_EXPIRY = 2 * 30 * 24h; + + /// The maximum uncompressed message size we allow in message decryption/encryption. + static constexpr size_t MAX_PLAINTEXT_MESSAGE_SIZE = 1'000'000; + + // No default constructor + Keys() = delete; + + /// API: groups/Keys::Keys + /// + /// Constructs a group members config object from existing data (stored from `dump()`) and a + /// list of encryption keys for encrypting new and decrypting existing messages. + /// + /// To construct a blank info object (i.e. with no pre-existing dumped data to load) pass + /// `std::nullopt` as the last argument. + /// + /// When no dump is provided the initial Keys object will be created with no keys loaded at all, + /// these will be loaded later into this and the info/members objects when loading keys via + /// received config messages. If this is a brand new group then rekey() MUST be called, + /// otherwise the group will be in an invalid state. + /// + /// Inputs: + /// - `user_ed25519_secretkey` is the ed25519 secret key backing the current user's session ID, + /// and is used to decrypt incoming keys. It is required. + /// - `group_ed25519_pubkey` is the public key of the group, used to verify message signatures + /// on key updates. Required. Should not include the `03` prefix. + /// - `group_ed25519_secretkey` is the secret key of the group, used to encrypt, decrypt, and + /// sign config messages. This is only possessed by the group admin(s), and must be provided + /// in order to make and push config changes. + /// - `dumped` -- either `std::nullopt` to construct a new, empty object; or binary state data + /// that was previously dumped from an instance of this class by calling `dump()`. + /// - `info` and `members` -- will be loaded with the group keys, if present in the dump. + Keys(std::span user_ed25519_secretkey, + std::span group_ed25519_pubkey, + std::optional> group_ed25519_secretkey, + std::optional> dumped, + Info& info, + Members& members); + + /// API: groups/Keys::storage_namespace + /// + /// Returns the Keys namespace. Is constant, will always return Namespace::GroupKeys + /// + /// Inputs: None + /// + /// Outputs: + /// - `Namespace` - Will return Namespace::GroupKeys + Namespace storage_namespace() const { return Namespace::GroupKeys; } + + /// API: groups/Keys::encryption_domain + /// + /// Returns the encryption domain used when encrypting messages of this type. + /// + /// Inputs: None + /// + /// Outputs: + /// - `const char*` - Will return "groups::Keys" + const char* encryption_domain() const { return "groups::Keys"; } + + /// API: groups/Keys::group_keys + /// + /// Returns all the unexpired decryption keys that we know about. Keys are returned ordered + /// from most-recent to least-recent (and so the first one is meant to be used as the encryption + /// key), including a pending key if this object is in the process of pushing a new keys + /// message. + /// + /// This isn't typically directly needed: this object manages the key lists in the `info` and + /// `members` objects itself. + /// + /// Inputs: none. + /// + /// Outputs: + /// - `std::vector>` - vector of encryption keys. + std::vector> group_keys() const; + + /// API: groups/Keys::size + /// + /// Returns the number of distinct decryption keys that we know about. Mainly for + /// debugging/information purposes. + /// + /// Inputs: none + /// + /// Outputs: + /// - `size_t` of the number of keys we know about + size_t size() const; + + /// API: groups/Keys::encryption_key + /// + /// Accesses the current encryption key: that is, the most current group decryption key. Throws + /// if there are no encryption keys at all. (This is essentially the same as `group_keys()[0]`, + /// except for the throwing and avoiding needing to constructor a vector). + /// + /// You normally don't need to call this; the key is used automatically by methods such as + /// encrypt_message() that need it. + /// + /// Inputs: none. + /// + /// Outputs: + /// - `std::span` of the most current group encryption key. + std::span group_enc_key() const; + + /// API: groups/Keys::is_admin + /// + /// True if we have admin permissions (i.e. we know the group's master secret key). + /// + /// Inputs: none. + /// + /// Outputs: + /// - `true` if this object knows the group's master key + bool admin() const { return _sign_sk && _sign_pk; } + + /// API: groups/Keys::load_admin_key + /// + /// Loads the group secret key into the Keys object (as well as passing it along to the Info and + /// Members objects). + /// + /// The primary use of this is when accepting a promotion-to-admin: the Keys object would be + /// constructed as a regular member (without the admin key) then this method "upgrades" the + /// object with the group signing key. + /// + /// This will do nothing if the secret key is already known; it will throw if + /// the given secret key does not yield the group's public key. The given key can be either the + /// 32 byte seed, or the libsodium 64 byte "secret key" (which is just the seed and cached + /// public key stuck together). + /// + /// Inputs: + /// - `secret` -- the group's 64-byte secret key or 32-byte seed + /// - `info` and `members` -- will be loaded with the group keys if the key is loaded + /// successfully. + /// + /// Outputs: nothing. After a successful call, `admin()` will return true. Throws if the given + /// secret key does not match the group's pubkey. + void load_admin_key(std::span secret, Info& info, Members& members); + + /// API: groups/Keys::rekey + /// + /// Generate a new encryption key for the group and returns an encrypted key message to be + /// pushed to the swarm containing the key, encrypted for the members of the given + /// config::groups::Members object. This can only be done by an admin account (i.e. we must + /// have the group's private key). + /// + /// This method is intended to be called in these situations: + /// - potentially after loading new keys config messages (see `needs_rekey()`) + /// - when removing a member to switch to a new encryption key for the group that excludes that + /// member. + /// - when adding a member *and* switching to a new encryption key (without making the old key + /// available to the member) so that the new member cannot decipher pre-existing configs and + /// messages. + /// + /// This method is closely coupled to the group's Info and Members configs: it updates their + /// encryption keys and sets them as dirty, requiring a re-push to re-encrypt each of them. + /// Typically a rekey is performed as follows: + /// + /// - `rekey()` is called, returning the new keys config. + /// - `info.push()` is called to get the new info config (re-encrypted with the new key) + /// - `members.push()` is called to get the new members config (using the new key) + /// - all three new configs are pushed (ideally all at once, in a single batch request). + /// + /// Inputs: + /// - `Info` - the group's Info; it will be dirtied after the rekey and will require a push. + /// - `Members` - the current Members config for the group. When removing one or more members + /// this should be the list of members with the specific members already removed. The members + /// config will be dirtied after the rekey and will require a push. + /// + /// Outputs: + /// - `std::span` containing the data that needs to be pushed to the config + /// keys namespace + /// for the group. (This can be re-obtained from `pending_config()` if needed until it has + /// been confirmed or superceded). This data must be consumed or copied from the returned + /// string_view immediately: it will not be valid past other calls on the Keys config object. + std::span rekey(Info& info, Members& members); + + /// API: groups/Keys::key_supplement + /// + /// Generates a supplemental key message for one or more session IDs. This is used to + /// distribute existing active keys to a new member so that that member can access existing + /// keys, configs, and messages. Only admins can call this. + /// + /// The recommended order of operations for adding such a member is: + /// - add the member to Members + /// - generate the key supplement + /// - push new members & key supplement (ideally in a batch) + /// - send invite details, auth signature, etc. to the new user + /// + /// To add a member *without* giving them access you would use rekey() instead of this method. + /// + /// Inputs: + /// - `sid` or `sids` -- session ID(s) of the members to generate a supplemental key for (there + /// are two versions of this function, one taking a single ID and one taking a vector). + /// Session IDs are specified in hex. + /// + /// Outputs: + /// - `std::vector` containing the message that should be pushed to the swarm + /// containing encrypted + /// keys for the given user(s). + std::vector key_supplement(const std::vector& sids) const; + std::vector key_supplement(std::string sid) const { + return key_supplement(std::vector{{std::move(sid)}}); + } + + /// API: groups/current_generation + /// + /// Returns the current generation number for the latest keys message. + /// + /// Inputs: none. + /// + /// Outputs: + /// - `int` -- latest keys generation number. + int current_generation() const { return keys_.empty() ? 0 : keys_.back().generation; } + + /// API: groups/Keys::swarm_make_subaccount + /// + /// Constructs a swarm subaccount signing value that a member can use to access messages in the + /// swarm. Requires group admins keys. + /// + /// Inputs: + /// - `session_id` -- the session ID of the member (in hex) + /// - `write` -- if true (which is the default if omitted) then the member shall be allowed to + /// submit messages into the group account of the swarm and extend (but not shorten) the + /// expiry of messages in the group account. If false then the user can only retrieve + /// messages. + /// - `del` -- if true (default is false) then the user shall be allowed to delete messages + /// from the swarm. This permission can be used to appoint a sort of "moderator" who can + /// delete messages without having the full admin group keys. + /// + /// Outputs: + /// - `std::vector` -- contains a subaccount swarm signing value; this can be + /// passed (by the user) + /// into `swarm_subaccount_sign` to sign a value suitable for swarm authentication. + /// (Internally this packs the flags, blinding factor, and group admin signature together and + /// will be 4 + 32 + 64 = 100 bytes long). + /// + /// This value must be provided to the user so that they can authentication. The user should + /// call `swarm_verify_subaccount` to verify that the signing value was indeed signed by a + /// group admin before using/storing it. + /// + /// The signing value produced will be the same (for a given `session_id`/`write`/`del` + /// values) when constructed by any admin of the group. + std::vector swarm_make_subaccount( + std::string_view session_id, bool write = true, bool del = false) const; + + /// API: groups/Keys::swarm_verify_subaccount + /// + /// Verifies that a received subaccount signing value (allegedly produced by + /// swarm_make_subaccount) is a valid subaccount signing value for the given group pubkey, + /// including a proper signature by an admin of the group. The signing value must have read + /// permission, but parameters can be given to also require write or delete permissions. A + /// subaccount signing value should always be checked for validity using this before creating a + /// group that would depend on it. + /// + /// There are two versions of this function: a static one callable without having a Keys + /// instance that takes the group id and user's session Ed25519 secret key as arguments; and a + /// member function that omits these first two arguments (using the ones from the Keys + /// instance). + /// + /// Inputs: + /// - `groupid` -- the group id/pubkey, in hex, beginning with "03". + /// - `session_ed25519_secretkey` -- the user's Session ID secret key. + /// - `signing_value` -- the subaccount signing value to validate + /// - `write` -- if true, require that the signing_value has write permission (i.e. that the + /// user will be allowed to post messages). + /// - `del` -- if true, required that the signing_value has delete permissions (i.e. that the + /// user will be allowed to remove storage messages from the group's swarm). Note that this + /// permission is about forcible swarm message deletion, and has no effect on an ability to + /// submit a deletion meta-message to the group (which only requires writing a message). + /// + /// Outputs: + /// - `true` if `signing_value` is a valid subaccount signing value for `groupid` with read (and + /// possible write and/or del permissions, if requested). `false` if the signing value does + /// not validate or does not meet the requirements. + static bool swarm_verify_subaccount( + std::string group_id, + std::span session_ed25519_secretkey, + std::span signing_value, + bool write = false, + bool del = false); + bool swarm_verify_subaccount( + std::span signing_value, + bool write = false, + bool del = false) const; + + /// API: groups/Keys::swarm_auth + /// + /// This struct containing the storage server authentication values for subaccount + /// authentication. The three strings in this struct may be either raw bytes, or base64 + /// encoded, depending on the `binary` parameter passed to `swarm_subaccount_sign`. + /// + /// `.subaccount` is the value to be passed as the "subaccount" authentication parameter. (It + /// consists of permission flags followed by a blinded public key.) + /// + /// `.subaccount_sig` is the value to be passed as the "subaccount_sig" authentication + /// parameter. (It consists of an admin-produced signature of the subaccount, providing + /// permission for that token to be used for authentication). + /// + /// `.signature` is the value to be passed as the "signature" authentication parameter. (It is + /// an Ed25519 signature that validates using the blinded public key inside `subaccount`). + /// + /// Inputs: none. + struct swarm_auth { + std::string subaccount; + std::string subaccount_sig; + std::string signature; + }; + + /// API: groups/Keys::swarm_subaccount_sign + /// + /// This helper function generates the required signature for swarm subaccount authentication, + /// given the user's keys and swarm auth keys (as provided by an admin, produced via + /// `swarm_make_subaccount`). + /// + /// Storage server subaccount authentication requires passing the three values in the returned + /// struct in the storage server request. (See Keys::swarm_auth for details). + /// + /// Inputs: + /// - `msg` -- the data that needs to be signed (which depends on the storage server request + /// being made; for example, "retrieve9991234567890123" for a retrieve request to namespace + /// 999 made at unix time 1234567890.123; see storage server RPC documentation for details). + /// - `signing_value` -- the 100-byte subaccount signing value, as produced by an admin's + /// `swarm_make_subaccount` and provided to this member. + /// - `binary` -- if set to true then the returned values will be binary. If omitted (or + /// explicitly false), the returned struct values will be base64-encoded suitable for direct + /// passing as JSON values to the storage server without further encoding/modification. + /// + /// Outputs: + /// - struct containing three binary values enabling swarm authentication (see description + /// above). + swarm_auth swarm_subaccount_sign( + std::span msg, + std::span signing_value, + bool binary = false) const; + + /// API: groups/Keys::swarm_subaccount_token + /// + /// Constructs the subaccount token for a session id. The main use of this is to submit a swarm + /// token revocation; for issuing subaccount tokens you want to use `swarm_make_subaccount` + /// instead. This will produce the same subaccount token that `swarm_make_subaccount` + /// implicitly creates that can be passed to a swarm to add a revocation for that subaccount. + /// + /// This is recommended to be used when removing a non-admin member to prevent their access. + /// (Note, however, that there are circumstances where this can fail to prevent access, and so + /// should be combined with proper member removal and key rotation so that even if the member + /// gains access to messages, they cannot read them). + /// + /// Inputs: + /// - `session_id` -- the session ID of the member (in hex) + /// - `write`, `del` -- optional; see `swarm_make_subaccount`. The same arguments should be + /// provided (or omitted) as were used in `swarm_make_subaccount`. + /// + /// Outputs: + /// - 36 byte token that can be used for swarm token revocation. + std::vector swarm_subaccount_token( + std::string_view session_id, bool write = true, bool del = false) const; + + /// API: groups/Keys::pending_config + /// + /// If a rekey has been performed but not yet confirmed then this will contain the config + /// message to be pushed to the swarm. If there is no push current pending then this returns + /// nullopt. The value should be used immediately (i.e. the std::span may + /// not remain valid if other calls to the config object are made). + /// + /// Inputs: None + /// + /// Outputs: + /// - `std::optional>` -- returns a populated config message that + /// should be pushed, + /// if not yet confirmed, otherwise when no pending update is present this returns nullopt. + std::optional> pending_config() const; + + /// API: groups/Keys::pending_key + /// + /// After calling rekey() this contains the new group encryption key *before* it is confirmed + /// pushed into the swarm. This is primarily intended for internal use as this key is generally + /// already propagated to the member/info lists when rekeying occurs. + /// + /// The pending key is dropped when an incoming keys message is successfully loaded with either + /// the pending key itself, or a keys message with a higher generation. + /// + /// Inputs: None + /// + /// Outputs: + /// - `std::optional>` the encryption key generated by the last + /// `rekey()` call. + /// This is set to a new key when `rekey()` is called, and is cleared when any config message + /// is successfully loaded by `load_key`. + std::optional> pending_key() const; + + /// API: groups/Keys::load_key + /// + /// Loads a key pulled down from the swarm into this Keys object. + /// + /// A Session client must process messages from the keys namespace *before* other group config + /// messages as new key messages may contain encryption keys needed to decrypt the other group + /// config message types. + /// + /// It is safe to load the same config multiple times, and to load expired configs; such cases + /// would typically not change the keys, but are allowed anyway. + /// + /// This method should always be wrapped in a `try/catch`: if the given configuration data is + /// malformed or is not properly signed an exception will be raised (but the Keys object remains + /// usable). + /// + /// Inputs: + /// - `hash` - the message hash from the swarm + /// - `data` - the full stored config message value + /// - `timestamp_ms` - the timestamp (from the swarm) when this message was stored (used to + /// track when other keys expire). + /// - `info` - the given group::Info object's en/decryption key list will be updated to match + /// this object's key list. + /// - `members` - the given group::Members object's en/decryption key list will be updated to + /// match this object's key list. + /// + /// Outputs: + /// - throws `std::runtime_error` (typically a subclass thereof) on failure to parse. + /// - returns true if we found a key for us in the message, false if we did not. Note that this + /// is mainly informative and does not signal an error: false could mean, for instance, be a + /// supplemental message that wasn't for us. Note also that true doesn't mean keys changed: + /// it could mean we decrypted one for us, but already had it. + bool load_key_message( + std::string_view hash, + std::span data, + int64_t timestamp_ms, + Info& info, + Members& members); + + /// API: groups/Keys::active_hashes + /// + /// Returns a set of message hashes of messages that contain currently active decryption keys. + /// These are the messages that should be periodically renewed by clients with write access to + /// keep them alive for other accounts (or devices) who might need them in the future. + /// + /// Inputs: none + /// + /// Outputs: + /// - vector of message hashes + std::unordered_set active_hashes() const; + + /// API: groups/Keys::needs_rekey + /// + /// Returns true if the key list requires a new key to be generated and pushed to the server (by + /// calling `rekey()`). This will only be true for admin accounts (as only admin accounts can + /// call rekey()). Note that this value will also remain true until the pushed data is fetched + /// and loaded via `load_key_message`. + /// + /// Note that this not only tracks when an automatic `rekey()` is needed because of a key + /// collision (such as two admins removing different members at the same time); there are other + /// situations in which rekey() should also be called (such as when kicking a member) that are + /// not reflected by this flag. + /// + /// The recommended use of this method is to call it immediately after fetching messages from + /// the group config namespace of the swarm, whether or not new configs were retrieved, but + /// after processing incoming new config messages that were pulled down. + /// + /// Unlike regular config messages, there is no need to confirm the push: confirmation (and + /// adoption of the new keys) happens when the new keys arrived back down from the swarm in the + /// next fetch. + /// + /// Inputs: None + /// + /// Outputs: + /// - `true` if a rekey is needed, `false` otherwise. + bool needs_rekey() const; + + /// API: groups/Keys::needs_dump + /// + /// Returns true if this Keys config has changes, either made directly or from incoming configs, + /// that need to be dumped to the database (made since the last call to `dump()`), false if no + /// changes have been made. + /// + /// Inputs: None + /// + /// Outputs: + /// - `true` if state needs to be dumped, `false` if state hasn't changed since the last + /// call to `dump()`. + bool needs_dump() const; + + /// API: groups/Keys::dump + /// + /// Returns a dump of the current state of this keys config that allows the Keys object to be + /// reinstantiated from scratch. Updates the internal needs_dump flag to false. + /// + /// Although this can be called at any time, it is recommended to only do so when + /// `needs_dump()` returns true. + /// + /// Inputs: None + /// + /// Outputs: + /// - opaque binary data containing the group keys and other Keys config data that can be passed + /// to the `Keys` constructor to reinitialize a Keys object with the current state. + std::vector dump(); + + /// API: groups/Keys::make_dump + /// + /// Returns a dump of the current state; unlike `dump()` this does *not* update the internal + /// needs_dump flag; it is mostly used internally (by `dump()`), but can also be called + /// externally for debugging purposes. + /// + /// Inputs: None + /// + /// Outputs: + /// - `std::vector` -- Returns binary data of the state dump + std::vector make_dump() const; + + /// API: groups/Keys::encrypt_message + /// + /// Compresses, signs, and encrypts group message content. + /// + /// This method is passed a binary value containing a group message (typically a serialized + /// protobuf, but this method doesn't care about the specific data). That data will be, in + /// order: + /// - compressed (but only if this actually reduces the data size) + /// - signed by the user's underlying session Ed25519 pubkey + /// - tagged with the user's underlying session Ed25519 pubkey (from which the session id can be + /// computed). + /// - all of the above encoded into a bt-encoded dict + /// - suffix-padded with null bytes so that the final output value will be a multiple of 256 + /// bytes + /// - encrypted with the most-current group encryption key + /// + /// Since compression and padding is applied as part of this method, it is not required that the + /// given message include its own padding (and in fact, such padding will typically be + /// compressed down to nothing (if non-random)). + /// + /// This final encrypted value is then returned to be pushed to the swarm as-is (i.e. not + /// further wrapped). For users downloading the message, all of the above is processed in + /// reverse by passing the returned message into `decrypt_message()`. + /// + /// The current implementation uses XChaCha20-Poly1305 for encryption and zstd for compression; + /// the bt-encoded value is a dict consisting of keys: + /// - "": the version of this encoding, currently set to 1. This *MUST* be bumped if this is + /// changed in such a way that older clients will not be able to properly decrypt such a + /// message. + /// - "a": the *Ed25519* pubkey (32 bytes) of the author of the message. (This will be + /// converted to a x25519 pubkey to extract the sender's session id when decrypting). + /// - "s": signature by "a" of whichever of "d" or "z" are included in the data. + /// Exacly one of: + /// - "d": the uncompressed data (which must be non-empty if present) + /// - "z": the zstd-compressed data (which must be non-empty if present) + /// + /// When compression is enabled (by omitting the `compress` argument or specifying it as true) + /// then ZSTD compression will be *attempted* on the plaintext message and will be used if the + /// compressed data is smaller than the uncompressed data. If disabled, or if compression does + /// not reduce the size, then the message will not be compressed. + /// + /// This method will throw on failure, which can happen in two cases: + /// - if there no encryption keys are available at all (which should not occur in normal use). + /// - if given a plaintext buffer larger than 1MB (even if the compressed version would be much + /// smaller). It is recommended that clients impose their own limits much smaller than this + /// on data passed into encrypt_message; this limitation is in *this* function to match the + /// `decrypt_message` limit which is merely intended to guard against decompression memory + /// exhaustion attacks. + /// + /// Inputs: + /// - `plaintext` -- the binary message to encrypt. + /// - `compress` -- can be specified as `false` to forcibly disable compression. Normally + /// omitted, to use compression if and only if it reduces the size. + /// - `padding` -- the padding multiple: padding will be added as needed to attain a multiple of + /// this value for the final result. 0 or 1 disables padding entirely. Normally omitted to + /// use the default of next-multiple-of-256. + /// + /// Outputs: + /// - `ciphertext` -- the encrypted, etc. value to send to the swarm + std::vector encrypt_message( + std::span plaintext, + bool compress = true, + size_t padding = 256) const; + + /// API: groups/Keys::decrypt_message + /// + /// Decrypts group message content that was presumably encrypted with `encrypt_message`, + /// verifies the sender signature, decompresses the message (if necessary) and then returns the + /// author pubkey and the plaintext data. + /// + /// To prevent against memory exhaustion attacks, this method will fail if the value is + /// a compressed value that would decompress to a value larger than 1MB. + /// + /// Inputs: + /// - `ciphertext` -- an encrypted, encoded, signed, (possibly) compressed message as produced + /// by `encrypt_message()`. + /// + /// Outputs: + /// - `std::pair>` -- the session ID (in hex) and the + /// plaintext binary + /// data that was encrypted. + /// + /// On failure this throws a std::exception-derived exception with a `.what()` string containing + /// some diagnostic info on what part failed. Typically a production session client would catch + /// (and possibly log) but otherwise ignore such exceptions and just not process the message if + /// it throws. + std::pair> decrypt_message( + std::span ciphertext) const; +}; + +} // namespace session::config::groups diff --git a/include/session/config/groups/members.h b/include/session/config/groups/members.h new file mode 100644 index 00000000..d502fbe2 --- /dev/null +++ b/include/session/config/groups/members.h @@ -0,0 +1,363 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../base.h" +#include "../profile_pic.h" +#include "../util.h" + +enum groups_members_status { STATUS_SENT = 1, STATUS_FAILED = 2, STATUS_NOT_SENT = 3 }; +enum groups_members_remove_status { REMOVED_MEMBER = 1, REMOVED_MEMBER_AND_MESSAGES = 2 }; + +/// A convenience status enum for the group member, this enum is returned by a function which +/// reviews the various member status values (eg. invite, promotion, etc.) and returns a single +/// consolidated value +typedef enum GROUP_MEMBER_STATUS { + GROUP_MEMBER_STATUS_INVITE_UNKNOWN = 0, + GROUP_MEMBER_STATUS_INVITE_NOT_SENT = 1, + GROUP_MEMBER_STATUS_INVITE_SENDING = 2, + GROUP_MEMBER_STATUS_INVITE_FAILED = 3, + GROUP_MEMBER_STATUS_INVITE_SENT = 4, + GROUP_MEMBER_STATUS_INVITE_ACCEPTED = 5, + GROUP_MEMBER_STATUS_PROMOTION_UNKNOWN = 6, + GROUP_MEMBER_STATUS_PROMOTION_NOT_SENT = 7, + GROUP_MEMBER_STATUS_PROMOTION_SENDING = 8, + GROUP_MEMBER_STATUS_PROMOTION_FAILED = 9, + GROUP_MEMBER_STATUS_PROMOTION_SENT = 10, + GROUP_MEMBER_STATUS_PROMOTION_ACCEPTED = 11, + GROUP_MEMBER_STATUS_REMOVED_UNKNOWN = 12, + GROUP_MEMBER_STATUS_REMOVED = 13, + GROUP_MEMBER_STATUS_REMOVED_MEMBER_AND_MESSAGES = 14, +} GROUP_MEMBER_STATUS; + +typedef struct config_group_member { + char session_id[67]; // in hex; 66 hex chars + null terminator. + + // These two will be 0-length strings when unset: + char name[101]; + user_profile_pic profile_pic; + + bool admin; + int invited; // 0 == unset, STATUS_SENT = invited, STATUS_FAILED = invite failed to send, + // STATUS_NOT_SENT = invite hasn't been sent yet + int promoted; // same value as `invited`, but for promotion-to-admin + int removed; // 0 == unset, REMOVED_MEMBER = removed, REMOVED_MEMBER_AND_MESSAGES = remove + // member and their messages + bool supplement; + +} config_group_member; + +/// API: groups/groups_members_init +/// +/// Constructs a group members config object and sets a pointer to it in `conf`. +/// +/// When done with the object the `config_object` must be destroyed by passing the pointer to +/// config_free() (in `session/config/base.h`). +/// +/// Inputs: +/// - `conf` -- [out] Pointer to the config object +/// - `ed25519_pubkey` -- [in] 32-byte pointer to the group's public key +/// - `ed25519_secretkey` -- [in] optional 64-byte pointer to the group's secret key +/// (libsodium-style 64 byte value). Pass as NULL for a non-admin member. +/// - `dump` -- [in] if non-NULL this restores the state from the dumped byte string produced by a +/// past instantiation's call to `dump()`. To construct a new, empty object this should be NULL. +/// - `dumplen` -- [in] the length of `dump` when restoring from a dump, or 0 when `dump` is NULL. +/// - `error` -- [out] the pointer to a buffer in which we will write an error string if an error +/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a +/// buffer of at least 256 bytes. +/// +/// Outputs: +/// - `int` -- Returns 0 on success; returns a non-zero error code and write the exception message +/// as a C-string into `error` (if not NULL) on failure. +LIBSESSION_EXPORT int groups_members_init( + config_object** conf, + const unsigned char* ed25519_pubkey, + const unsigned char* ed25519_secretkey, + const unsigned char* dump, + size_t dumplen, + char* error) LIBSESSION_WARN_UNUSED; + +/// API: groups/groups_members_get +/// +/// Fills `member` with the member info given a session ID (specified as a null-terminated hex +/// string), if the member exists, and returns true. If the member does not exist then `member` +/// is left unchanged and false is returned. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `member` -- [out] the member info data +/// - `session_id` -- [in] null terminated hex string +/// +/// Output: +/// - `bool` -- Returns true if member exists +LIBSESSION_EXPORT bool groups_members_get( + config_object* conf, + config_group_member* member, + const char* session_id) LIBSESSION_WARN_UNUSED; + +/// API: groups/groups_members_get_or_construct +/// +/// Same as the above `groups_members_get()` except that when the member does not exist, this sets +/// all the member fields to defaults and loads it with the given session_id. +/// +/// Returns true as long as it is given a valid session_id. A false return is considered an error, +/// and means the session_id was not a valid session_id. +/// +/// This is the method that should usually be used to create or update a member, followed by +/// setting fields in the member, and then giving it to groups_members_set(). +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `member` -- [out] the member info data +/// - `session_id` -- [in] null terminated hex string +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs (e.g. because of an +/// invalid session_id). +LIBSESSION_EXPORT bool groups_members_get_or_construct( + config_object* conf, + config_group_member* member, + const char* session_id) LIBSESSION_WARN_UNUSED; + +/// API: groups/groups_members_set +/// +/// Adds or updates a member from the given member info struct. +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `member` -- [in] Pointer containing the member info data +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool groups_members_set(config_object* conf, const config_group_member* member); + +/// API: groups/groups_members_get_status +/// +/// This function goes through the various status values and returns a single consolidated +/// status for the member. +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `member` -- [in] The member to retrieve the status for +/// +/// Outputs: +/// - `GROUP_MEMBER_STATUS` -- Returns an enum indicating the consolidated status of this member in +/// the group. +LIBSESSION_EXPORT GROUP_MEMBER_STATUS +groups_members_get_status(const config_object* conf, const config_group_member* member); + +/// API: groups/groups_members_set_invite_sent +/// +/// This marks the user as having a pending invitation in the group, and that an invitation message +/// has been sent to them. +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `session_id` -- [in] null terminated hex string +/// +/// Outputs: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool groups_members_set_invite_sent(config_object* conf, const char* session_id); + +/// API: groups/groups_members_set_invite_not_sent +/// +/// This resets the invite status of a user to `STATUS_NOT_SENT`. +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `session_id` -- [in] null terminated hex string +/// +/// Outputs: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool groups_members_set_invite_not_sent( + config_object* conf, const char* session_id); + +/// API: groups/groups_members_set_invite_failed +/// +/// This marks the user as being invited to the group, but that their invitation message failed to +/// send (this is intended as a signal to other clients that the invitation should be reissued). +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `session_id` -- [in] null terminated hex string +/// +/// Outputs: +/// - `bool` -- True if change was successful +LIBSESSION_EXPORT bool groups_members_set_invite_failed( + config_object* conf, const char* session_id); + +/// API: groups/groups_members_set_invite_accepted +/// +/// This clears the "invited" and "supplement" flags for this user, thus indicating that the +/// user has accepted an invitation and is now a regular member of the group. +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `session_id` -- [in] null terminated hex string +/// +/// Outputs: +/// - `bool` -- True if change was successful +LIBSESSION_EXPORT bool groups_members_set_invite_accepted( + config_object* conf, const char* session_id); + +/// API: groups/groups_members_set_promoted +/// +/// This marks the user as having a pending promotion-to-admin in the group, waiting for the +/// promotion message to be sent to them. +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `session_id` -- [in] null terminated hex string +/// +/// Outputs: +/// - `bool` -- True if change was successful +LIBSESSION_EXPORT bool groups_members_set_promoted(config_object* conf, const char* session_id); + +/// API: groups/groups_members_set_promotion_sent +/// +/// This marks the user as having a pending promotion-to-admin in the group, and that a +/// promotion message has been sent to them. +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `session_id` -- [in] null terminated hex string +/// +/// Outputs: +/// - `bool` -- True if change was successful +LIBSESSION_EXPORT bool groups_members_set_promotion_sent( + config_object* conf, const char* session_id); + +/// API: groups/groups_members_set_promotion_failed +/// +/// This marks the user as being promoted to an admin, but that their promotion message failed +/// to send (this is intended as a signal to other clients that the promotion should be +/// reissued). +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `session_id` -- [in] null terminated hex string +/// +/// Outputs: +/// - `bool` -- True if change was successful +LIBSESSION_EXPORT bool groups_members_set_promotion_failed( + config_object* conf, const char* session_id); + +/// API: groups/groups_members_set_promotion_accepted +/// +/// This marks the user as having accepted a promotion to admin in the group. +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `session_id` -- [in] null terminated hex string +/// +/// Outputs: +/// - `bool` -- True if change was successful +LIBSESSION_EXPORT bool groups_members_set_promotion_accepted( + config_object* conf, const char* session_id); + +/// API: groups/groups_members_set_removed +/// +/// Sets the "removed" flag for this user. This marks the user as pending removal from the +/// group. The optional `messages` parameter can be specified as true if we want to remove +/// any messages sent by the member upon a successful removal. +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `session_id` -- [in] null terminated hex string +/// - `messages` -- [in] Flag which controls whether any messages sent by the member should also be +/// removed upon a successful member removal. +/// +/// Outputs: +/// - `bool` -- True if change was successful +LIBSESSION_EXPORT bool groups_members_set_removed( + config_object* conf, const char* session_id, bool messages); + +/// API: groups/groups_members_erase +/// +/// Erases a member from the member list. session_id is in hex. Returns true if the member was +/// found and removed, false if the member was not present. You must not call this during +/// iteration; see details below. +/// +/// Typically this should be followed by a group rekey (so that the removed member cannot read the +/// group). +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `session_id` -- [in] Text containing null terminated hex string +/// +/// Outputs: +/// - `bool` -- True if erasing was successful +LIBSESSION_EXPORT bool groups_members_erase(config_object* conf, const char* session_id); + +/// API: groups/groups_members_size +/// +/// Returns the number of group members. +/// +/// Inputs: +/// - `conf` -- input - Pointer to the config object +/// +/// Outputs: +/// - `size_t` -- number of contacts +LIBSESSION_EXPORT size_t groups_members_size(const config_object* conf); + +typedef struct groups_members_iterator { + void* _internals; +} groups_members_iterator; + +/// API: groups/groups_members_iterator_new +/// +/// Starts a new iterator. +/// +/// Functions for iterating through the entire member list, in sorted order. Intended use is: +/// +/// group_member m; +/// groups_members_iterator *it = groups_members_iterator_new(group); +/// for (; !groups_members_iterator_done(it, &c); groups_members_iterator_advance(it)) { +/// // c.session_id, c.name, etc. are loaded +/// } +/// groups_members_iterator_free(it); +/// +/// It is NOT permitted to add/remove/modify members while iterating. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `groups_members_iterator*` -- pointer to the new iterator +LIBSESSION_EXPORT groups_members_iterator* groups_members_iterator_new(const config_object* conf); + +/// API: groups/groups_members_iterator_free +/// +/// Frees an iterator once no longer needed. +/// +/// Inputs: +/// - `it` -- [in] Pointer to the groups_members_iterator +LIBSESSION_EXPORT void groups_members_iterator_free(groups_members_iterator* it); + +/// API: groups/groups_members_iterator_done +/// +/// Returns true if iteration has reached the end. Otherwise `m` is populated and false is +/// returned. +/// +/// Inputs: +/// - `it` -- [in] Pointer to the groups_members_iterator +/// - `m` -- [out] Pointer to the config_group_member, will be populated if false is returned +/// +/// Outputs: +/// - `bool` -- True if iteration has reached the end +LIBSESSION_EXPORT bool groups_members_iterator_done( + groups_members_iterator* it, config_group_member* m); + +/// API: groups/groups_members_iterator_advance +/// +/// Advances the iterator. +/// +/// Inputs: +/// - `it` -- [in] Pointer to the groups_members_iterator +LIBSESSION_EXPORT void groups_members_iterator_advance(groups_members_iterator* it); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/include/session/config/groups/members.hpp b/include/session/config/groups/members.hpp new file mode 100644 index 00000000..d35fa52c --- /dev/null +++ b/include/session/config/groups/members.hpp @@ -0,0 +1,557 @@ +#pragma once + +#include +#include +#include + +#include "../base.hpp" +#include "../namespaces.hpp" +#include "../profile_pic.hpp" + +struct config_group_member; + +namespace session::config::groups { + +using namespace std::literals; + +/// keys used in this config, either currently or in the past (so that we don't reuse): +/// +/// m - dict of members; each key is the member session id (33 bytes), each value is a dict +/// containing subkeys: +/// n - member name; this will always be set in the encoded message to prevent dict pruning, but +/// will be an empty string if there is no name. +/// p - member profile pic url +/// q - member profile pic decryption key (binary) +/// I - invite status; this will be one of: +/// - 1 if the invite has been issued but not yet accepted. +/// - 2 if an invite was created but failed to send for some reason (and thus can be resent) +/// - 3 if a member has been added to the group but the invite hasn't been sent yet. +/// - omitted once an invite is accepted. (This also gets omitted if the `A` admin flag gets +/// set). +/// s - invite supplemental keys flag (only set when `I` is set): if set (to 1) then this invite +/// was issued with the intention of sending the user the existing active decryption keys +/// (allowing them to access current messages); if omitted (with `I` set) then the invitation +/// was not meant to give access to past configs/messages (and was presumably issued with a +/// group rekey). +/// A - flag set to 1 if the member is an admin, omitted otherwise. +/// P - promotion (to admin) status; this will be one of: +/// - 1 if a promotion has been sent. +/// - 2 if a promotion was created but failed to send for some reason (and thus should be +/// resent) +/// - 3 if a member has been marked for promotion but the promotion hasn't been sent yet. +/// - omitted once the promotion is accepted (i.e. once `A` gets set). + +constexpr int STATUS_SENT = 1, STATUS_FAILED = 2, STATUS_NOT_SENT = 3; +constexpr int REMOVED_MEMBER = 1, REMOVED_MEMBER_AND_MESSAGES = 2; + +/// Struct containing member details +struct member { + /// A convenience status enum for the group member, this enum is returned by a function which + /// reviews the various member status values (eg. invite, promotion, etc.) and returns a single + /// consolidated value + enum class Status { + invite_unknown = 0, + invite_not_sent = 1, + invite_sending = 2, + invite_failed = 3, + invite_sent = 4, + invite_accepted = 5, + + promotion_unknown = 6, + promotion_not_sent = 7, + promotion_sending = 8, + promotion_failed = 9, + promotion_sent = 10, + promotion_accepted = 11, + + removed_unknown = 12, + removed = 13, + removed_including_messages = 14, + }; + + static constexpr size_t MAX_NAME_LENGTH = 100; + + explicit member(std::string sid); + + // Internal ctor/method for C API implementations: + explicit member(const config_group_member& c); // From c struct + + /// API: groups/member::session_id + /// + /// Member variable + /// + /// The member's session ID, in hex. + std::string session_id; + + /// API: groups/member::name + /// + /// Member variable + /// + /// The member's human-readable name. Optional. This is used by other members of the group to + /// display a member's details before having seen a message from that member. + std::string name; + + /// API: groups/member::profile_picture + /// + /// Member variable + /// + /// The member's profile picture (URL & decryption key). Optional. This is used by other + /// members of the group to display a member's details before having seen a message from that + /// member. + profile_pic profile_picture; + + /// API: groups/member::admin + /// + /// Member variable + /// + /// Flag that is set to indicate to the group that this member is an admin or has been promoted + /// to admin. + /// + /// Note that this is only informative but isn't a permission gate: someone could still possess + /// the admin keys without this (e.g. if they cleared the flag to appear invisible), or could + /// have lost (or never had) the keys even if this is set. + bool admin = false; + + /// API: groups/member::supplement + /// + /// Member variable + /// + /// Flag that is set to indicate to the group that this member was added with a supplemental key + /// rotation so that other admins can trigger the same key rotation method if they send a new + /// invitation to the same member. + /// + /// Note that this should be cleared when a member accepts an invitation. + bool supplement = false; + + // Flags to track an invited user. This value is typically not used directly, but rather via + // the `set_invited()`, `invite_pending()` and similar methods. + int invite_status = STATUS_NOT_SENT; + + /// API: groups/member::set_invite_sent + /// + /// This marks the user as having had an invitation message sent to them. + /// Inputs: none. + /// + void set_invite_sent() { invite_status = STATUS_SENT; } + + /// API: groups/member::set_invite_not_sent + /// + /// This resets the invite status of a user to `STATUS_NOT_SENT`. + /// + /// Inputs: none. + /// + void set_invite_not_sent() { invite_status = STATUS_NOT_SENT; } + + /// API: groups/member::set_invite_failed + /// + /// This marks the user to indicate that their invitation message failed to send (this is + /// intended as a signal to other clients that the invitation should be reissued). + /// + /// Inputs: none. + /// + void set_invite_failed() { invite_status = STATUS_FAILED; } + + /// API: groups/members::set_invite_accepted + /// + /// This clears the "invited" and "supplement" flags for this user, thus indicating that the + /// user has accepted an invitation and is now a regular member of the group. + /// + /// Inputs: none + void set_invite_accepted() { + invite_status = 0; + supplement = false; + } + + // Flags to track a promoted-to-admin user. This value is typically not used directly, but + // rather via the `set_promoted()`, `promotion_pending()` and similar methods. + int promotion_status = 0; + + /// API: groups/member::set_promoted + /// + /// This marks the user as having a pending promotion-to-admin in the group, waiting for the + /// promotion message to be sent to them. + /// + /// Inputs: none. + /// + void set_promoted() { + admin = true; + invite_status = 0; + promotion_status = STATUS_NOT_SENT; + } + + /// API: groups/member::set_promotion_sent + /// + /// This marks the user as having a pending promotion-to-admin in the group, and that a + /// promotion message has been sent to them. + /// + /// Inputs: none. + /// + void set_promotion_sent() { + admin = true; + invite_status = 0; + promotion_status = STATUS_SENT; + } + + /// API: groups/member::set_promotion_failed + /// + /// This marks the user as being promoted to an admin, but that their promotion message failed + /// to send (this is intended as a signal to other clients that the promotion should be + /// reissued). + /// + /// Inputs: none. + /// + void set_promotion_failed() { + admin = true; + invite_status = 0; + promotion_status = STATUS_FAILED; + } + + /// API: groups/member::set_promotion_accepted + /// + /// This marks the user as having accepted a promotion to admin in the group. + /// + /// Inputs: none. + /// + void set_promotion_accepted() { + admin = true; + invite_status = 0; + promotion_status = 0; + } + + // Flags to track a removed user. This value is typically not used directly, but + // rather via the `set_removed()`, `is_removed()` and similar methods. + int removed_status = 0; + + /// API: groups/member::set_removed + /// + /// Sets the "removed" flag for this user. This marks the user as pending removal from the + /// group. The optional `messages` parameter can be specified as true if we want to remove + /// any messages sent by the member upon a successful removal. + /// + /// Inputs: + /// - `messages`: can be specified as true to indicate any messages sent by the member + /// should also be removed upon a successful member removal. + void set_removed(bool messages = false) { + invite_status = 0; + promotion_status = 0; + removed_status = messages ? REMOVED_MEMBER_AND_MESSAGES : REMOVED_MEMBER; + } + + /// API: groups/member::into + /// + /// Converts the member info into a C struct. + /// + /// Inputs: + /// - `m` -- Reference to C struct to fill with group member info. + void into(config_group_member& m) const; + + /// API: groups/member::set_name + /// + /// Sets a name; this is exactly the same as assigning to .name directly, except that we throw + /// an exception if the given name is longer than MAX_NAME_LENGTH. + /// + /// Note that you can set a longer name directly into the `.name` member, but it will be + /// truncated when serializing the record. + /// + /// Inputs: + /// - `name` -- Name to assign to the contact + void set_name(std::string name); + + /// API: groups/member::set_name_truncated + /// + /// Sets a name; this is exactly the same as assigning to .name directly, except that we + /// truncate if the given name is longer than MAX_NAME_LENGTH. + /// + /// Note that you can set a longer name directly into the `.name` member, but it will be + /// truncated when serializing the record. + /// + /// Inputs: + /// - `name` -- Name to assign to the contact + void set_name_truncated(std::string name); + + private: + friend class Members; + void load(const dict& info_dict); +}; + +class Members : public ConfigBase { + + private: + std::unordered_set pending_send_ids; + + public: + // No default constructor + Members() = delete; + + /// API: groups/Members::Members + /// + /// Constructs a group members config object from existing data (stored from `dump()`) and a + /// list of encryption keys for encrypting new and decrypting existing messages. + /// + /// To construct a blank info object (i.e. with no pre-existing dumped data to load) pass + /// `std::nullopt` as the third argument. + /// + /// Encryption keys must be loaded before the Info object can be modified or parse other Info + /// messages, and are typically loaded by providing the `Info` object to the `Keys` class. + /// + /// Inputs: + /// - `ed25519_pubkey` is the public key of this group, used to validate config messages. + /// Config messages not signed with this key will be rejected. + /// - `ed25519_secretkey` is the secret key of the group, used to sign pushed config messages. + /// This is only possessed by the group admin(s), and must be provided in order to make and + /// push config changes. + /// - `dumped` -- either `std::nullopt` to construct a new, empty object; or binary state data + /// that was previously dumped from an instance of this class by calling `dump()`. + Members(std::span ed25519_pubkey, + std::optional> ed25519_secretkey, + std::optional> dumped); + + /// API: groups/Members::storage_namespace + /// + /// Returns the Members namespace. Is constant, will always return Namespace::GroupMembers + /// + /// Inputs: None + /// + /// Outputs: + /// - `Namespace` - Will return Namespace::GroupMembers + Namespace storage_namespace() const override { return Namespace::GroupMembers; } + + /// API: groups/Members::encryption_domain + /// + /// Returns the encryption domain used when encrypting messages of this type. + /// + /// Inputs: None + /// + /// Outputs: + /// - `const char*` - Will return "groups::Members" + const char* encryption_domain() const override { return "groups::Members"; } + + /// API: groups/Members::get + /// + /// Looks up and returns a member by hex session ID. Returns nullopt if the session ID was + /// not found, otherwise returns a filled out `member`. + /// + /// Inputs: + /// - `pubkey_hex` -- hex string of the session id + /// + /// Outputs: + /// - `std::optional` - Returns nullopt if session ID was not found, otherwise a + /// filled out `member` struct. + std::optional get(std::string_view pubkey_hex) const; + + /// API: groups/Members::get_or_construct + /// + /// Similar to get(), but if the session ID does not exist this returns a filled-out member + /// containing the session_id (all other fields will be empty/defaulted). This is intended to + /// be combined with `set` to set-or-create a record. + /// + /// NB: calling this does *not* add the session id to the member list when called: that requires + /// also calling `set` with this value. + /// + /// Inputs: + /// - `pubkey_hex` -- hex string of the session id + /// + /// Outputs: + /// - `member` - Returns a filled out member struct + member get_or_construct(std::string_view pubkey_hex) const; + + /// API: groups/Members::has_pending_send + /// + /// This function can be used to check if a member is pending send locally. + /// + /// Inputs: + /// - `pubkey_hex` -- hex string of the session id + /// + /// Outputs: + /// - `bool` - true if that sessionid is marked as pending send locally + bool has_pending_send(std::string pubkey_hex) const; + + /// API: groups/Members::set_pending_send + /// + /// This function can be used to set the pending send state of a member. + /// If that effectively made a change, it will set _needs_dump to true. + /// + /// Inputs: + /// - `pubkey_hex` -- hex string of the session id + /// - `pending` -- pending send state to set for that member + /// + void set_pending_send(std::string pubkey_hex, bool pending); + + /// API: groups/Members::get_status + /// + /// This function goes through the various status values and returns a single consolidated + /// status for the member. + /// + /// Inputs: + /// - `member` -- member value to to retrieve the status for + /// + /// Outputs: + /// - `status` - an enum indicating the consolidated status of this member in the group. + member::Status get_status(const member& member) const { + // If the member has been removed that trumps all other statuses + if (member.removed_status == REMOVED_MEMBER_AND_MESSAGES) + return member::Status::removed_including_messages; + else if (member.removed_status == REMOVED_MEMBER) + return member::Status::removed; + else if (member.removed_status > 0) + return member::Status::removed_unknown; + + // If the member is promoted then we return the relevant promoted status + if (member.admin) { + if (member.promotion_status == STATUS_NOT_SENT && has_pending_send(member.session_id)) + return member::Status::promotion_sending; + else if (member.promotion_status == STATUS_NOT_SENT) + return member::Status::promotion_not_sent; + else if (member.promotion_status == STATUS_FAILED) + return member::Status::promotion_failed; + else if (member.promotion_status == STATUS_SENT) + return member::Status::promotion_sent; + else if (member.promotion_status != 0) + return member::Status::promotion_unknown; + + return member::Status::promotion_accepted; + } + + // Otherwise the member is a standard member + if (member.invite_status == STATUS_NOT_SENT && has_pending_send(member.session_id)) + return member::Status::invite_sending; + else if (member.invite_status == STATUS_NOT_SENT) + return member::Status::invite_not_sent; + else if (member.invite_status == STATUS_FAILED) + return member::Status::invite_failed; + else if (member.invite_status == STATUS_SENT) + return member::Status::invite_sent; + else if (member.invite_status != 0) + return member::Status::invite_unknown; + + return member::Status::invite_accepted; + } + + /// API: groups/Members::set + /// + /// Sets or updates the various values associated with a member with the given info. The usual + /// use is to access the current info, change anything desired, then pass it back into set, + /// e.g.: + /// + /// ```cpp + /// auto m = members.get_or_construct(pubkey); + /// m.name = "Session User 42"; + /// members.set(m); + /// ``` + /// + /// Inputs: + /// - `member` -- member value to set + void set(const member& member); + + /// API: groups/Members::erase + /// + /// Removes a session ID from the member list, if present. + /// + /// Typically this call should be coupled with a re-key of the group's encryption key so that + /// the removed member cannot read the group. For example: + /// + /// bool removed = members.erase("050123456789abcdef..."); + /// // You can remove more than one at a time, if needed: + /// removed |= members.erase("050000111122223333..."); + /// + /// if (removed) { + /// auto new_keys_conf = keys.rekey(members); + /// members.add_key(*keys.pending_key(), true); + /// auto [seqno, new_memb_conf, obs] = members.push(); + /// + /// // Send the two new configs to the swarm (via a seqence of two `store`s): + /// // - new_keys_conf goes into the keys namespace + /// // - new_memb_conf goes into the members namespace + /// } + /// + /// Inputs: + /// - `session_id` the hex session ID of the member to remove + /// + /// Outputs: + /// - true if the member was found (and removed); false if the member was not in the list. + bool erase(std::string_view session_id); + + /// API: groups/Members::size + /// + /// Returns the number of members in the group. + /// + /// Inputs: None + /// + /// Outputs: + /// - `size_t` - number of members + size_t size() const; + + struct iterator; + /// API: groups/Members::begin + /// + /// Iterators for iterating through all members. Typically you access this implicit via a for + /// loop over the `Members` object: + /// + ///```cpp + /// for (auto& member : members) { + /// // use member.session_id, member.name, etc. + /// } + ///``` + /// + /// This iterates in sorted order through the session_ids. + /// + /// It is NOT permitted to add/modify/remove records while iterating; instead such modifications + /// require two passes: an iterator loop to collect the required modifications, then a second + /// pass to apply the modifications. + /// + /// Inputs: None + /// + /// Outputs: + /// - `iterator` - Returns an iterator for the beginning of the members + iterator begin() const { return iterator{data["m"].dict()}; } + + /// API: groups/Members::end + /// + /// Iterator for passing the end of the members + /// + /// Inputs: None + /// + /// Outputs: + /// - `iterator` - Returns an iterator for the end of the members + iterator end() const { return iterator{nullptr}; } + + using iterator_category = std::input_iterator_tag; + using value_type = member; + using reference = value_type&; + using pointer = value_type*; + using difference_type = std::ptrdiff_t; + + struct iterator { + private: + std::shared_ptr _val; + dict::const_iterator _it; + const dict* _members; + void _load_info(); + iterator(const dict* members) : _members{members} { + if (_members) { + _it = _members->begin(); + _load_info(); + } + } + friend class Members; + + public: + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const { return !(*this == other); } + bool done() const; // Equivalent to comparing against the end iterator + member& operator*() const { return *_val; } + member* operator->() const { return _val.get(); } + iterator& operator++(); + iterator operator++(int) { + auto copy{*this}; + ++*this; + return copy; + } + }; + + protected: + void extra_data(oxenc::bt_dict_producer&& extra) const override; + void load_extra_data(oxenc::bt_dict_consumer&& extra) override; +}; + +} // namespace session::config::groups diff --git a/include/session/config/namespaces.hpp b/include/session/config/namespaces.hpp index 394617c0..c5c29ec5 100644 --- a/include/session/config/namespaces.hpp +++ b/include/session/config/namespaces.hpp @@ -9,6 +9,14 @@ enum class Namespace : std::int16_t { Contacts = 3, ConvoInfoVolatile = 4, UserGroups = 5, + + // Messages sent to a closed group: + GroupMessages = 11, + // Groups config namespaces (i.e. for shared config of the group itself, not one user's group + // settings) + GroupKeys = 12, + GroupInfo = 13, + GroupMembers = 14, }; } // namespace session::config diff --git a/include/session/config/profile_pic.hpp b/include/session/config/profile_pic.hpp index 59601a14..7c82b457 100644 --- a/include/session/config/profile_pic.hpp +++ b/include/session/config/profile_pic.hpp @@ -1,9 +1,9 @@ #pragma once +#include +#include #include -#include "session/types.hpp" - namespace session::config { // Profile pic info. @@ -11,9 +11,9 @@ struct profile_pic { static constexpr size_t MAX_URL_LENGTH = 223; std::string url; - ustring key; + std::vector key; - static void check_key(ustring_view key) { + static void check_key(std::span key) { if (!(key.empty() || key.size() == 32)) throw std::invalid_argument{"Invalid profile pic key: 32 bytes required"}; } @@ -22,12 +22,14 @@ struct profile_pic { profile_pic() = default; // Constructs from a URL and key. Key must be empty or 32 bytes. - profile_pic(std::string_view url, ustring_view key) : url{url}, key{key} { + profile_pic(std::string_view url, std::span key) : + url{url}, key{to_vector(key)} { check_key(this->key); } - // Constructs from a string/ustring pair moved into the constructor - profile_pic(std::string&& url, ustring&& key) : url{std::move(url)}, key{std::move(key)} { + // Constructs from a string/std::vector pair moved into the constructor + profile_pic(std::string&& url, std::vector&& key) : + url{std::move(url)}, key{std::move(key)} { check_key(this->key); } @@ -64,7 +66,7 @@ struct profile_pic { /// /// Inputs: /// - `new_key` -- binary data of a new key to be set. Must be 32 bytes - void set_key(ustring new_key) { + void set_key(std::vector new_key) { check_key(new_key); key = std::move(new_key); } diff --git a/include/session/config/protos.hpp b/include/session/config/protos.hpp new file mode 100644 index 00000000..2880aed3 --- /dev/null +++ b/include/session/config/protos.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include + +#include "namespaces.hpp" + +namespace session::config::protos { + +/// API: config/protos::wrap_config +/// +/// Wraps a config message in endless layers of protobuf and unnecessary extra encryption and +/// then more protobuf as required by older clients for older config message types. +/// +/// Inputs: +/// - `ed25519_sk` a 32- or 64-byte, libsodium-style secret key value (if 32 then it is just the +/// seed). +/// - `data` the config data to wrap +/// - `seqno` the seqno value of the data +/// - `ns` the namespace of the config data +/// +/// Outputs: +/// Returns the wrapped config. Will throw on serious errors (e.g. `ed25519_sk` or `ns` are +/// invalid). +std::vector wrap_config( + std::span ed25519_sk, + std::span data, + int64_t seqno, + config::Namespace ns); + +/// API: config/protos::unwrap_config +/// +/// Unwraps a config message from endless layers of protobuf, extra encryption and then more +/// protobuf as required by older clients for older config message types. +/// +/// Inputs: +/// - `ed25519_sk` a 32- or 64-byte, libsodium-style secret key value (if 32 then it is just the +/// seed). +/// - `data` the incoming data that might be protobuf-wrapped +/// +/// Outputs: +/// +/// Returns the unwrapped, inner config value if this is a proper protobuf-wrapped message; throws +/// std::runtime_error if it is not (thus most likely indicating that this is a raw config value). +/// Throws a std::invalid_argument if the given ed25519_sk is invalid. (It is recommended that only +/// the std::runtime_error is caught for detecting non-wrapped input as the invalid secret key is +/// more serious). +std::vector unwrap_config( + std::span ed25519_sk, + std::span data, + config::Namespace ns); + +} // namespace session::config::protos diff --git a/include/session/config/user_groups.h b/include/session/config/user_groups.h index e7187a44..ef279c28 100644 --- a/include/session/config/user_groups.h +++ b/include/session/config/user_groups.h @@ -27,19 +27,50 @@ typedef struct ugroups_legacy_group_info { unsigned char enc_seckey[32]; // If `have_enc_keys`, this is the 32-byte secret key (no NULL // terminator). - int64_t disappearing_timer; // Minutes. 0 == disabled. - int priority; // pinned message priority; 0 = unpinned, negative = hidden, positive = pinned - // (with higher meaning pinned higher). - int64_t joined_at; // unix timestamp when joined (or re-joined) + int64_t disappearing_timer; // Seconds. 0 == disabled. + int priority; // pinned conversation priority; 0 = unpinned, negative = hidden, positive = + // pinned (with higher meaning pinned higher). + int64_t joined_at; // unix timestamp (seconds) when joined (or re-joined) CONVO_NOTIFY_MODE notifications; // When the user wants notifications - int64_t mute_until; // Mute notifications until this timestamp (overrides `notifications` - // setting until the timestamp) + int64_t mute_until; // Mute notifications until this timestamp (seconds, overrides + // `notifications` setting until the timestamp) + + bool invited; // True if this is in the invite-but-not-accepted state. // For members use the ugroups_legacy_group_members and associated calls. void* _internal; // Internal storage, do not touch. } ugroups_legacy_group_info; +/// Struct holding (non-legacy) group info; this struct owns allocated memory and *must* be freed +/// via either `ugroups_group_free()` or `ugroups_set_free_group()` when finished with it. +typedef struct ugroups_group_info { + char id[67]; // in hex; 66 hex chars + null terminator + + char name[101]; // Null-terminated C string (human-readable). Max length is 100 (plus 1 for + // null). Will always be set (even if an empty string). + + bool have_secretkey; // Will be true if the `secretkey` is populated + unsigned char secretkey[64]; // If `have_secretkey` is set then this is the libsodium-style + // "secret key" for the group (i.e. 32 byte seed + 32 byte pubkey) + bool have_auth_data; // Will be true if the `auth_data` is populated + unsigned char auth_data[100]; // If `have_auth_data` is set then this is the authentication + // signing value that can be used to produce signature values to + // access the swarm. + + int priority; // pinned conversation priority; 0 = unpinned, negative = hidden, positive = + // pinned (with higher meaning pinned higher). + int64_t joined_at; // unix timestamp (seconds) when joined (or re-joined) + CONVO_NOTIFY_MODE notifications; // When the user wants notifications + int64_t mute_until; // Mute notifications until this timestamp (seconds, overrides + // `notifications` setting until the timestamp) + + bool invited; // True if this is in the invite-but-not-accepted state. + + int removed_status; // Tracks why we were removed from the group. Values are 0:NOT_REMOVED, + // 1:KICKED_FROM_GROUP or 2:GROUP_DESTROYED +} ugroups_group_info; + typedef struct ugroups_community_info { char base_url[268]; // null-terminated (max length 267), normalized (i.e. always lower-case, // only has port if non-default, has trailing / removed) @@ -48,12 +79,15 @@ typedef struct ugroups_community_info { // info (that one is always forced lower-cased). unsigned char pubkey[32]; // 32 bytes (not terminated, can contain nulls) - int priority; // pinned message priority; 0 = unpinned, negative = hidden, positive = pinned - // (with higher meaning pinned higher). - int64_t joined_at; // unix timestamp when joined (or re-joined) + int priority; // pinned conversation priority; 0 = unpinned, negative = hidden, positive = + // pinned (with higher meaning pinned higher). + int64_t joined_at; // unix timestamp (seconds) when joined (or re-joined) CONVO_NOTIFY_MODE notifications; // When the user wants notifications - int64_t mute_until; // Mute notifications until this timestamp (overrides `notifications` - // setting until the timestamp) + int64_t mute_until; // Mute notifications until this timestamp (seconds, overrides + // `notifications` setting until the timestamp) + + bool invited; // True if this is in the invite-but-not-accepted state. + } ugroups_community_info; /// API: user_groups/user_groups_init @@ -85,14 +119,44 @@ LIBSESSION_EXPORT int user_groups_init( const unsigned char* ed25519_secretkey, const unsigned char* dump, size_t dumplen, - char* error) __attribute__((warn_unused_result)); + char* error) LIBSESSION_WARN_UNUSED; + +/// API: user_groups/user_groups_get_group +/// +/// Gets (non-legacy) group info into `group`, if the group was found. `group_id` is a +/// null-terminated C string containing the 66 character group id in hex (beginning with "03"). +/// +/// Inputs: +/// `conf` -- pointer to the group config object +/// `group` -- [out] `ugroups_group_info` struct into which to store the group info. +/// `group_id` -- C string containing the hex group id (starting with "03") +/// +/// Outputs: +/// Returns `true` and populates `group` if the group was found; returns false otherwise. +LIBSESSION_EXPORT bool user_groups_get_group( + config_object* conf, ugroups_group_info* group, const char* group_id); + +/// API: user_groups/user_groups_get_or_construct_group +/// +/// Gets (non-legacy) group info into `group`, if the group was found. Otherwise initialize `group` +/// to default values (and set its `.id` appropriately). +/// +/// Inputs: +/// `conf` -- pointer to the group config object +/// `group` -- [out] `ugroups_group_info` struct into which to store the group info. +/// `group_id` -- C string containing the hex group id (starting with "03") +/// +/// Outputs: +/// Returns `true` on success, `false` upon error (such as when given an invalid group id). +LIBSESSION_EXPORT bool user_groups_get_or_construct_group( + config_object* conf, ugroups_group_info* group, const char* group_id); /// API: user_groups/user_groups_get_community /// /// Gets community conversation info into `comm`, if the community info was found. `base_url` and -/// `room` are null-terminated c strings; pubkey is 32 bytes. base_url will be -/// normalized/lower-cased; room is case-insensitive for the lookup: note that this may well return -/// a community info with a different room capitalization than the one provided to the call. +/// `room` are null-terminated c strings. base_url will be normalized/lower-cased; room is +/// case-insensitive for the lookup: note that this may well return a community info with a +/// different room capitalization than the one provided to the call. /// /// Returns true if the community was found and `comm` populated; false otherwise. A false return /// can either be because it didn't exist (`conf->last_error` will be NULL) or because of some error @@ -118,7 +182,7 @@ LIBSESSION_EXPORT int user_groups_init( /// - `bool` -- Whether the function succeeded or not LIBSESSION_EXPORT bool user_groups_get_community( config_object* conf, ugroups_community_info* comm, const char* base_url, const char* room) - __attribute__((warn_unused_result)); + LIBSESSION_WARN_UNUSED; /// API: user_groups/user_groups_get_or_construct_community /// @@ -160,7 +224,7 @@ LIBSESSION_EXPORT bool user_groups_get_or_construct_community( ugroups_community_info* comm, const char* base_url, const char* room, - unsigned const char* pubkey) __attribute__((warn_unused_result)); + unsigned const char* pubkey) LIBSESSION_WARN_UNUSED; /// API: user_groups/user_groups_get_legacy_group /// @@ -186,7 +250,7 @@ LIBSESSION_EXPORT bool user_groups_get_or_construct_community( /// Outputs: /// - `ugroupts_legacy_group_info*` -- Pointer containing conversation info LIBSESSION_EXPORT ugroups_legacy_group_info* user_groups_get_legacy_group( - config_object* conf, const char* id) __attribute__((warn_unused_result)); + config_object* conf, const char* id) LIBSESSION_WARN_UNUSED; /// API: user_groups/user_groups_get_or_construct_legacy_group /// @@ -220,7 +284,7 @@ LIBSESSION_EXPORT ugroups_legacy_group_info* user_groups_get_legacy_group( /// Outputs: /// - `ugroupts_legacy_group_info*` -- Pointer containing conversation info LIBSESSION_EXPORT ugroups_legacy_group_info* user_groups_get_or_construct_legacy_group( - config_object* conf, const char* id) __attribute__((warn_unused_result)); + config_object* conf, const char* id) LIBSESSION_WARN_UNUSED; /// API: user_groups/ugroups_legacy_group_free /// @@ -256,6 +320,18 @@ LIBSESSION_EXPORT void ugroups_legacy_group_free(ugroups_legacy_group_info* grou LIBSESSION_EXPORT void user_groups_set_community( config_object* conf, const ugroups_community_info* group); +/// API: user_groups/user_groups_set_group +/// +/// Adds or updates a (non-legacy) group conversation from the given group info +/// +/// Inputs: +/// - `conf` -- [in] Pointer to config_object object +/// - `group` -- [in] Pointer to a group info object +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool user_groups_set_group(config_object* conf, const ugroups_group_info* group); + /// API: user_groups/user_groups_set_legacy_group /// /// Adds or updates a legacy group conversation from the into. This version of the method should @@ -274,7 +350,10 @@ LIBSESSION_EXPORT void user_groups_set_community( /// Inputs: /// - `conf` -- [in] Pointer to config_object object /// - `group` -- [in] Pointer to a legacy group info object -LIBSESSION_EXPORT void user_groups_set_legacy_group( +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool user_groups_set_legacy_group( config_object* conf, const ugroups_legacy_group_info* group); /// API: user_groups/user_groups_set_free_legacy_group @@ -294,7 +373,10 @@ LIBSESSION_EXPORT void user_groups_set_legacy_group( /// Inputs: /// - `conf` -- [in] Pointer to config_object object /// - `group` -- [in] Pointer to a legacy group info object -LIBSESSION_EXPORT void user_groups_set_free_legacy_group( +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool user_groups_set_free_legacy_group( config_object* conf, ugroups_legacy_group_info* group); /// API: user_groups/user_groups_erase_community @@ -322,6 +404,28 @@ LIBSESSION_EXPORT void user_groups_set_free_legacy_group( LIBSESSION_EXPORT bool user_groups_erase_community( config_object* conf, const char* base_url, const char* room); +/// API: user_groups/user_groups_erase_group +/// +/// Erases a group conversation from the conversation list. Returns true if the conversation was +/// found and removed, false if the conversation was not present. You must not call this during +/// iteration; see details below. +/// +/// Declaration: +/// ```cpp +/// BOOL user_groups_erase_group( +/// [in] config_object* conf, +/// [in] const char* group_id +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to config_object object +/// - `group_id` -- [in] null terminated string of the hex group id (starting with "03") +/// +/// Outputs: +/// - `bool` -- Returns True if conversation was found and removed +LIBSESSION_EXPORT bool user_groups_erase_group(config_object* conf, const char* group_id); + /// API: user_groups/user_groups_erase_legacy_group /// /// Erases a conversation from the conversation list. Returns true if the conversation was found @@ -344,6 +448,53 @@ LIBSESSION_EXPORT bool user_groups_erase_community( /// - `bool` -- Returns True if conversation was found and removed LIBSESSION_EXPORT bool user_groups_erase_legacy_group(config_object* conf, const char* group_id); +/// API: user_groups/ugroups_group_set_invited +/// +/// Call when we have been invited to a group to reset the 'kicked' state. +/// +/// Inputs: +/// - `group` -- [in] pointer to the group info which we should set to kicked +/// +LIBSESSION_EXPORT void ugroups_group_set_invited(ugroups_group_info* group); + +/// API: user_groups/ugroups_group_set_kicked +/// +/// Call when we have been kicked from a group; this clears group's secret key and auth key from the +/// group config setting. +/// +/// Inputs: +/// - `group` -- [in] pointer to the group info which we should set to kicked +/// +LIBSESSION_EXPORT void ugroups_group_set_kicked(ugroups_group_info* group); + +/// API: user_groups/ugroups_group_is_kicked +/// +/// Returns true if we have been kicked (i.e. our secret key and auth data are empty). +/// +/// Inputs: +/// - `group` -- [in] pointer to the group info to query +/// +LIBSESSION_EXPORT bool ugroups_group_is_kicked(const ugroups_group_info* group); + +/// API: user_groups/ugroups_group_set_destroyed +/// +/// Mark the group as permanently destroyed and clears auth_data & secret_key. This cannot be +/// unset once set. +/// +/// Inputs: +/// - `group` -- [in] pointer to the group info which we should set to kicked +/// +LIBSESSION_EXPORT void ugroups_group_set_destroyed(ugroups_group_info* group); + +/// API: user_groups/ugroups_group_is_destroyed +/// +/// Returns true if the group was destroyed by one of the admin. +/// +/// Inputs: +/// - `group` -- [in] pointer to the group info to query +/// +LIBSESSION_EXPORT bool ugroups_group_is_destroyed(const ugroups_group_info* group); + typedef struct ugroups_legacy_members_iterator ugroups_legacy_members_iterator; /// API: user_groups/ugroups_legacy_members_begin @@ -540,7 +691,7 @@ LIBSESSION_EXPORT size_t user_groups_size(const config_object* conf); /// API: user_groups/user_groups_size_communities /// -/// Returns the number of conversations of the specific type. +/// Returns the number of community conversations. /// /// Declaration: /// ```cpp @@ -556,9 +707,27 @@ LIBSESSION_EXPORT size_t user_groups_size(const config_object* conf); /// - `size_t` -- Returns the number of conversations LIBSESSION_EXPORT size_t user_groups_size_communities(const config_object* conf); +/// API: user_groups/user_groups_size_groups +/// +/// Returns the number of (non-legacy) group conversations. +/// +/// Declaration: +/// ```cpp +/// SIZE_T user_groups_size_groups( +/// [in] const config_object* conf +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to config_object object +/// +/// Outputs: +/// - `size_t` -- Returns the number of conversations +LIBSESSION_EXPORT size_t user_groups_size_groups(const config_object* conf); + /// API: user_groups/user_groups_size_legacy_groups /// -/// Returns the number of conversations of the specific type. +/// Returns the number of legacy group conversations. /// /// Declaration: /// ```cpp @@ -584,12 +753,15 @@ typedef struct user_groups_iterator user_groups_iterator; /// ```cpp /// ugroups_community_info c2; /// ugroups_legacy_group_info c3; +/// ugroups_group_info c4; /// user_groups_iterator *it = user_groups_iterator_new(my_groups); /// for (; !user_groups_iterator_done(it); user_groups_iterator_advance(it)) { /// if (user_groups_it_is_community(it, &c2)) { /// // use c2.whatever /// } else if (user_groups_it_is_legacy_group(it, &c3)) { /// // use c3.whatever +/// } else if (user_groups_it_is_group(it, &c4)) { +/// // use c4.whatever /// } /// } /// user_groups_iterator_free(it); @@ -657,6 +829,20 @@ LIBSESSION_EXPORT user_groups_iterator* user_groups_iterator_new_communities( LIBSESSION_EXPORT user_groups_iterator* user_groups_iterator_new_legacy_groups( const config_object* conf); +/// API: user_groups/user_groups_iterator_new_groups +/// +/// The same as `user_groups_iterator_new` except that this iterates *only* over one type of +/// conversation: non-legacy groups. You still need to use `user_groups_it_is_group` to load the +/// data in each pass of the loop. (You can, however, safely ignore the bool return value of the +/// `it_is_group` function: it will always be true for iterations for this iterator). +/// +/// Inputs: +/// - `conf` -- [in] Pointer to config_object object +/// +/// Outputs: +/// - `user_groups_iterator*` -- The Iterator +LIBSESSION_EXPORT user_groups_iterator* user_groups_iterator_new_groups(const config_object* conf); + /// API: user_groups/user_groups_iterator_free /// /// Frees an iterator once no longer needed. @@ -719,7 +905,7 @@ LIBSESSION_EXPORT void user_groups_iterator_advance(user_groups_iterator* it); /// ``` /// /// Inputs: -/// - `it` -- [in, out] The Iterator +/// - `it` -- [in] The iterator /// - `c` -- [out] sets details of community into here if true /// /// Outputs: @@ -727,7 +913,21 @@ LIBSESSION_EXPORT void user_groups_iterator_advance(user_groups_iterator* it); LIBSESSION_EXPORT bool user_groups_it_is_community( user_groups_iterator* it, ugroups_community_info* c); -/// API: user_groups/user_groups_it_is_community +/// API: user_groups/user_groups_it_is_group +/// +/// If the current iterator record is a non-legacy group conversation this sets the details into +/// `group` and returns true. Otherwise it returns false. +/// +/// Inputs: +/// - `it` -- [in] The Iterator +/// - `group` -- [out] sets details of the group into here (if true is returned) +/// +/// Outputs: +/// - `bool` -- Returns `true` and sets `group` if the group is a non-legacy group (aka closed +/// group). +LIBSESSION_EXPORT bool user_groups_it_is_group(user_groups_iterator* it, ugroups_group_info* group); + +/// API: user_groups/user_groups_it_is_legacy_group /// /// If the current iterator record is a legacy group conversation this sets the details into /// `c` and returns true. Otherwise it returns false. @@ -741,7 +941,7 @@ LIBSESSION_EXPORT bool user_groups_it_is_community( /// ``` /// /// Inputs: -/// - `it` -- [in, out] The Iterator +/// - `it` -- [in] The iterator /// - `c` -- [out] sets details of legacy group into here if true /// /// Outputs: diff --git a/include/session/config/user_groups.hpp b/include/session/config/user_groups.hpp index 0809df50..55febbc9 100644 --- a/include/session/config/user_groups.hpp +++ b/include/session/config/user_groups.hpp @@ -12,6 +12,7 @@ #include "notify.hpp" extern "C" { +struct ugroups_group_info; struct ugroups_legacy_group_info; struct ugroups_community_info; } @@ -20,26 +21,37 @@ namespace session::config { /// keys used in this config, either currently or in the past (so that we don't reuse): /// -/// C - dict of legacy groups; within this dict each key is the group pubkey (binary, 33 bytes) and -/// value is a dict containing keys: +/// *Within* the group dicts (i.e. not at the top level), we use these common values: /// -/// n - name (string). Always set, even if empty. -/// k - encryption public key (32 bytes). Optional. -/// K - encryption secret key (32 bytes). Optional. -/// m - set of member session ids (each 33 bytes). -/// a - set of admin session ids (each 33 bytes). -/// E - disappearing messages duration, in seconds, > 0. Omitted if disappearing messages is -/// disabled. (Note that legacy groups only support expire after-read) /// @ - notification setting (int). Omitted = use default setting; 1 = all, 2 = disabled, 3 = /// mentions-only. /// ! - mute timestamp: if set then don't show notifications for this contact's messages until /// this unix timestamp (i.e. overriding the current notification setting until the given /// time). -/// + - the conversation priority, for pinned/hidden messages. Integer. Omitted means not -/// pinned; -1 means hidden, and a positive value is a pinned message for which higher -/// priority values means the conversation is meant to appear earlier in the pinned -/// conversation list. +/// + - the conversation priority, for pinning/hiding this group in the conversation list. +/// Integer. Omitted means not pinned; -1 means hidden, and a positive value is a pinned +/// message for which higher priority values means the conversation is meant to appear +/// earlier in the pinned conversation list. +/// i - 1 if this is a pending invite (i.e. we have a request but haven't yet joined it), +/// deleted once joined. /// j - joined at unix timestamp. Omitted if 0. +/// n - the room/group/etc. friendly name. See details for each group type below. +/// +/// Top-level keys: +/// +/// g - dict of groups (AKA closed groups) for new-style closed groups (i.e. not legacy closed +/// groups; see below for those). Each key is the group's public key (without 0x03 prefix). +/// +/// K - group seed, if known (i.e. an admin). This is just the seed, which is just the first +/// half (32 bytes) of the 64-byte libsodium-style Ed25519 secret key value (i.e. it omits +/// the cached public key in the second half). This field is always set, but will be empty +/// if the seed is not known. +/// s - authentication signature; this is used by non-admins to authenticate. Omitted when K is +/// non-empty. +/// n - the room name, from a the group invitation; this is intended to be removed once the +/// invitation has been accepted, as the name contained in the group info supercedes this). +/// r - removed_status, tracks why we were removed from the group. +/// @, !, +, i, j -- see common values, above. /// /// o - dict of communities (AKA open groups); within this dict (which deliberately has the same /// layout as convo_info_volatile) each key is the SOGS base URL (in canonical form), and value @@ -51,36 +63,45 @@ namespace session::config { /// appropriate). For instance, a room name SudokuSolvers would be "sudokusolvers" in /// the outer key, with the capitalization variation in use ("SudokuSolvers") in this /// key. This key is *always* present (to keep the room dict non-empty). -/// @ - notification setting (see above). -/// ! - mute timestamp (see above). -/// + - the conversation priority, for pinned messages. Omitted means not pinned; -1 means -/// hidden; otherwise an integer value >0, where a higher priority means the -/// conversation is meant to appear earlier in the pinned conversation list. -/// j - joined at unix timestamp. Omitted if 0. +/// @, !, +, i, j - see common values, above. /// -/// c - reserved for future storage of new-style group info. +/// C - dict of legacy groups; within this dict each key is the group pubkey (binary, 33 bytes) and +/// value is a dict containing keys: +/// +/// n - name (string). Always set, even if empty (to make sure there is always something set to +/// keep the entry alive). +/// k - encryption public key (32 bytes). Optional. +/// K - encryption secret key (32 bytes). Optional. +/// m - set of member session ids (each 33 bytes). +/// a - set of admin session ids (each 33 bytes). +/// E - disappearing messages duration, in seconds, > 0. Omitted if disappearing messages is +/// disabled. (Note that legacy groups only support expire after-read) +/// @, !, +, i, j - see common values, above. /// Common base type with fields shared by all the groups struct base_group_info { + static constexpr size_t NAME_MAX_LENGTH = 100; // in bytes; name will be truncated if exceeded + int priority = 0; // The priority; 0 means unpinned, -1 means hidden, positive means // pinned higher (i.e. higher priority conversations come first). int64_t joined_at = 0; // unix timestamp (seconds) when the group was joined (or re-joined) notify_mode notifications = notify_mode::defaulted; // When the user wants notifications int64_t mute_until = 0; // unix timestamp (seconds) until which notifications are disabled + std::string name; // human-readable; always set for a legacy closed group, only used before + // joining a new closed group (after joining the group info provide the name) + + bool invited = false; // True if this is currently in the invite-but-not-accepted state. + protected: void load(const dict& info_dict); }; /// Struct containing legacy group info (aka "closed groups"). struct legacy_group_info : base_group_info { - static constexpr size_t NAME_MAX_LENGTH = 100; // in bytes; name will be truncated if exceeded - - std::string session_id; // The legacy group "session id" (33 bytes). - std::string name; // human-readable; this should normally always be set, but in theory could be - // set to an empty string. - ustring enc_pubkey; // bytes (32 or empty) - ustring enc_seckey; // bytes (32 or empty) + std::string session_id; // The legacy group "session id" (33 bytes). + std::vector enc_pubkey; // bytes (32 or empty) + std::vector enc_seckey; // bytes (32 or empty) std::chrono::seconds disappearing_timer{0}; // 0 == disabled. /// Constructs a new legacy group info from an id (which must look like a session_id). Throws @@ -162,6 +183,61 @@ struct legacy_group_info : base_group_info { void load(const dict& info_dict); }; +constexpr int NOT_REMOVED = 0, KICKED_FROM_GROUP = 1, GROUP_DESTROYED = 2; + +/// Struct containing new group info (aka "closed groups v2"). +struct group_info : base_group_info { + std::string id; // The group pubkey (66 hex digits); this is an ed25519 key, prefixed with "03" + // (to distinguish it from a 05 x25519 pubkey session id). + + /// Group secret key (64 bytes); this is only possessed by admins. + std::vector secretkey; + + /// Group authentication signing value (100 bytes); this is used by non-admins to authenticate + /// (using the swarm key generation functions in config::groups::Keys). This value will be + /// dropped when serializing an updated config message if `secretkey` is non-empty (i.e. if it + /// is an admin), and so does not need to be explicitly cleared when being promoted to admin. + /// + /// Producing and using this value is done with the groups::Keys `swarm` methods. + std::vector auth_data; + + /// Tracks why we were removed from the group. Values are: + /// - NOT_REMOVED: that we haven't been removed, + /// - KICKED_FROM_GROUP: we have been kicked from the group, + /// - GROUP_DESTROYED: the group was permanently destroyed so everyone got removed. + int removed_status = NOT_REMOVED; + + /// Constructs a new group info from an hex id (03 + pubkey). Throws if id is invalid. + explicit group_info(std::string gid); + + // Internal ctor/method for C API implementations: + group_info(const struct ugroups_group_info& c); // From c struct + void into(struct ugroups_group_info& c) const; // Into c struct + + /// Marks the group as kicked and clears auth_data & secret_key + void mark_kicked(); + + /// Marks the group as reinvited (i.e. revert a `mark_kicked` call) + /// Note: this only works when the group was not permanently deleted. + void mark_invited(); + + /// Returns true if we don't have room access, i.e. we were kicked and both secretkey and + /// auth_data are empty. + bool kicked() const; + + /// Mark the group as permanently destroyed and clears auth_data & secret_key. This cannot be + /// unset once set. + void mark_destroyed(); + + /// Returns true if the group was destroyed by one of the admin. + bool is_destroyed() const; + + private: + friend class UserGroups; + + void load(const dict& info_dict); +}; + /// Community (aka open group) info struct community_info : base_group_info, community { // Note that *changing* url/room/pubkey and then doing a set inserts a new room under the given @@ -178,10 +254,10 @@ struct community_info : base_group_info, community { void load(const dict& info_dict); friend class UserGroups; - friend class comm_iterator_helper; + friend struct comm_iterator_helper; }; -using any_group_info = std::variant; +using any_group_info = std::variant; class UserGroups : public ConfigBase { @@ -205,16 +281,18 @@ class UserGroups : public ConfigBase { /// /// Outputs: /// - `UserGroups` - Constructor - UserGroups(ustring_view ed25519_secretkey, std::optional dumped); + UserGroups( + std::span ed25519_secretkey, + std::optional> dumped); /// API: user_groups/UserGroups::storage_namespace /// - /// Returns the Contacts namespace. Is constant, will always return 5 + /// Returns the Contacts namespace. /// /// Inputs: None /// /// Outputs: - /// - `Namespace` - Returns 5 + /// - `Namespace` - Returns Namespace::UserGroups Namespace storage_namespace() const override { return Namespace::UserGroups; } /// API: user_groups/UserGroups::encryption_domain @@ -264,6 +342,20 @@ class UserGroups : public ConfigBase { /// found std::optional get_legacy_group(std::string_view pubkey_hex) const; + /// API: user_groups/UserGroups::get_group + /// + /// Looks up and returns a group (aka new closed group) by group ID (hex, looks like a Session + /// ID but starting with 03). Returns nullopt if the group was not found, otherwise returns a + /// filled out `group_info`. + /// + /// Inputs: + /// - `pubkey_hex` -- group ID (hex, looks like a session ID but starting 03 instead of 05) + /// + /// Outputs: + /// - `std::optional` - Returns the filled out group_info struct if found, nullopt + /// if not found. + std::optional get_group(std::string_view pubkey_hex) const; + /// API: user_groups/UserGroups::get_or_construct_community /// /// Same as `get_community`, except if the community isn't found a new blank one is created for @@ -276,7 +368,8 @@ class UserGroups : public ConfigBase { /// std::string_view room, /// std::string_view pubkey_encoded) const; /// community_info get_or_construct_community( - /// std::string_view base_url, std::string_view room, ustring_view pubkey) const; + /// std::string_view base_url, std::string_view room, std::span + /// pubkey) const; /// ``` /// /// Inputs: @@ -299,7 +392,9 @@ class UserGroups : public ConfigBase { std::string_view room, std::string_view pubkey_encoded) const; community_info get_or_construct_community( - std::string_view base_url, std::string_view room, ustring_view pubkey) const; + std::string_view base_url, + std::string_view room, + std::span pubkey) const; /// API: user_groups/UserGroups::get_or_construct_community(string_view) /// @@ -324,6 +419,30 @@ class UserGroups : public ConfigBase { /// - `legacy_group_info` - Returns the filled out legacy_group_info struct legacy_group_info get_or_construct_legacy_group(std::string_view pubkey_hex) const; + /// API: user_groups/UserGroups::get_or_construct_group + /// + /// Gets or constructs a blank group_info for the given group id. + /// + /// Inputs: + /// - `pubkey_hex` -- group ID (hex, looks like a session ID) + /// + /// Outputs: + /// - `group_info` - Returns the filled out group_info struct + group_info get_or_construct_group(std::string_view pubkey_hex) const; + + /// API: user_groups/UserGroups::create_group + /// + /// Constructs a `group_info` object with newly generated (random) keys and returns the + /// group_info containing these keys. The group will have the id and secretkey populated; other + /// fields are defaulted. You still need to pass this to `set()` to store it, after setting any + /// other fields as desired. + /// + /// Inputs: None + /// + /// Outputs: + /// - `group_info` - Returns a filled out group_info struct for a new, randomly generated group. + group_info create_group() const; + /// API: user_groups/UserGroups::set /// /// Inserts or replaces existing group info. For example, to update the info for a community @@ -336,19 +455,22 @@ class UserGroups : public ConfigBase { /// /// Declaration: /// ```cpp + /// void set(const group_info& info); /// void set(const community_info& info); /// void set(const legacy_group_info& info); /// ``` /// /// Inputs: - /// - `info` -- group info struct to insert. Can be either community_info or legacy_group_info + /// - `info` -- group info struct to insert. Can be `community_info`, `group_info`, or + /// `legacy_group_info`. + void set(const group_info& info); void set(const community_info& info); void set(const legacy_group_info& info); protected: // Drills into the nested dicts to access open group details DictFieldProxy community_field( - const community_info& og, ustring_view* get_pubkey = nullptr) const; + const community_info& og, std::span* get_pubkey = nullptr) const; void set_base(const base_group_info& bg, DictFieldProxy& info) const; @@ -366,6 +488,18 @@ class UserGroups : public ConfigBase { /// - `bool` - Returns true if found and removed, false otherwise bool erase_community(std::string_view base_url, std::string_view room); + /// API: user_groups/UserGroups::erase_group + /// + /// Removes a (new, closed) group conversation. Returns true if found and removed, false if not + /// present. + /// + /// Inputs: + /// - `pubkey_hex` -- group ID (hex, looks like a session ID, but starts with 03) + /// + /// Outputs: + /// - `bool` - Returns true if found and removed, false otherwise + bool erase_group(std::string_view pubkey_hex); + /// API: user_groups/UserGroups::erase_legacy_group /// /// Removes a legacy group conversation. Returns true if found and removed, false if not @@ -380,11 +514,12 @@ class UserGroups : public ConfigBase { /// API: user_groups/UserGroups::erase /// - /// Removes a conversation taking the community_info or legacy_group_info instance (rather than - /// the pubkey/url) for convenience. + /// Removes a conversation taking the community_info, group_info, or legacy_group_info instance + /// (rather than the pubkey/url) for convenience. /// /// Declaration: /// ```cpp + /// bool erase(const group_info& g); /// bool erase(const community_info& g); /// bool erase(const legacy_group_info& c); /// bool erase(const any_group_info& info); @@ -395,6 +530,7 @@ class UserGroups : public ConfigBase { /// /// Outputs: /// - `bool` - Returns true if found and removed, false otherwise + bool erase(const group_info& g); bool erase(const community_info& g); bool erase(const legacy_group_info& c); bool erase(const any_group_info& info); @@ -409,6 +545,16 @@ class UserGroups : public ConfigBase { /// - `size_t` - Returns the number of groups size_t size() const; + /// API: user_groups/UserGroups::size_groups + /// + /// Returns the number of (non-legacy) groups + /// + /// Inputs: None + /// + /// Outputs: + /// - `size_t` - Returns the number of groups + size_t size_groups() const; + /// API: user_groups/UserGroups::size_communities /// /// Returns the number of communities @@ -419,7 +565,7 @@ class UserGroups : public ConfigBase { /// - `size_t` - Returns the number of groups size_t size_communities() const; - /// API: user_groups/UserGroups::size_communities + /// API: user_groups/UserGroups::size_legacy_groups /// /// Returns the number of legacy groups /// @@ -439,6 +585,8 @@ class UserGroups : public ConfigBase { /// - `bool` - Returns true if the contact list is empty bool empty() const { return size() == 0; } + bool accepts_protobuf() const override { return true; } + struct iterator; /// API: user_groups/UserGroups::begin /// @@ -454,8 +602,8 @@ class UserGroups : public ConfigBase { /// } /// ``` /// - /// This iterates through all groups in sorted order (sorted first by convo type, then by - /// id within the type). + /// This iterates through all groups in sorted order (sorted first by convo type [groups, + /// communities, legacy groups], then by id within the type). /// /// It is NOT permitted to add/remove/modify records while iterating. If such is needed it must /// be done in two passes: once to collect the modifications, then a loop applying the collected @@ -480,13 +628,35 @@ class UserGroups : public ConfigBase { template struct subtype_iterator; - /// Returns an iterator that iterates only through one type of conversations. (The regular - /// `.end()` iterator is valid for testing the end of these iterations). + /// API: user_groups/UserGroups::begin_groups + /// + /// Inputs: None + /// + /// Outputs: + /// - an iterator that iterates only through group conversations. (The regular `.end()` + /// iterator is valid for testing the end of this iterator). + subtype_iterator begin_groups() const { return {data}; } + + /// API: user_groups/UserGroups::begin_communities + /// + /// Inputs: None + /// + /// Outputs: + /// - an iterator that iterates only through community conversations. (The regular `.end()` + /// iterator is valid for testing the end of this iterator). subtype_iterator begin_communities() const { return {data}; } + + /// API: user_groups/UserGroups::begin_legacy_groups + /// + /// Inputs: None + /// + /// Outputs: + /// - an iterator that iterates only through legacy group conversations. (The regular `.end()` + /// iterator is valid for testing the end of this iterator). subtype_iterator begin_legacy_groups() const { return {data}; } using iterator_category = std::input_iterator_tag; - using value_type = std::variant; + using value_type = std::variant; using reference = value_type&; using pointer = value_type*; using difference_type = std::ptrdiff_t; @@ -494,13 +664,19 @@ class UserGroups : public ConfigBase { struct iterator { protected: std::shared_ptr _val; + std::optional _it_group, _end_group; std::optional _it_comm; std::optional _it_legacy, _end_legacy; void _load_val(); iterator() = default; // Constructs an end tombstone explicit iterator( - const DictFieldRoot& data, bool communities = true, bool legacy_closed = true); + const DictFieldRoot& data, + bool communities = true, + bool legacy_closed = true, + bool groups = true); friend class UserGroups; + template + bool check_it(); public: bool operator==(const iterator& other) const; @@ -522,6 +698,7 @@ class UserGroups : public ConfigBase { subtype_iterator(const DictFieldRoot& data) : iterator( data, + std::is_same_v, std::is_same_v, std::is_same_v) {} friend class UserGroups; diff --git a/include/session/config/user_profile.h b/include/session/config/user_profile.h index 651f46af..87d2c0ce 100644 --- a/include/session/config/user_profile.h +++ b/include/session/config/user_profile.h @@ -45,11 +45,7 @@ LIBSESSION_EXPORT int user_profile_init( const unsigned char* ed25519_secretkey, const unsigned char* dump, size_t dumplen, - char* error) -#if defined(__GNUC__) || defined(__clang__) - __attribute__((warn_unused_result)) -#endif - ; + char* error) LIBSESSION_WARN_UNUSED; /// API: user_profile/user_profile_get_name /// diff --git a/include/session/config/user_profile.hpp b/include/session/config/user_profile.hpp index e077b40a..6b9dfa8e 100644 --- a/include/session/config/user_profile.hpp +++ b/include/session/config/user_profile.hpp @@ -25,7 +25,7 @@ using namespace std::literals; /// omitted if the setting has not been explicitly set (or has been explicitly cleared for some /// reason). -class UserProfile final : public ConfigBase { +class UserProfile : public ConfigBase { public: // No default constructor @@ -47,7 +47,9 @@ class UserProfile final : public ConfigBase { /// /// Outputs: /// - `UserProfile` - Constructor - UserProfile(ustring_view ed25519_secretkey, std::optional dumped); + UserProfile( + std::span ed25519_secretkey, + std::optional> dumped); /// API: user_profile/UserProfile::storage_namespace /// @@ -87,6 +89,15 @@ class UserProfile final : public ConfigBase { /// - `new_name` -- The name to be put into the user profile void set_name(std::string_view new_name); + /// API: user_profile/UserProfile::set_name_truncated + /// + /// Sets the user profile name; if given an empty string then the name is removed. Same as the + /// `set_name` function but truncates the name if it's too long. + /// + /// Inputs: + /// - `new_name` -- The name to be put into the user profile + void set_name_truncated(std::string new_name); + /// API: user_profile/UserProfile::get_profile_pic /// /// Gets the user's current profile pic URL and decryption key. The returned object will @@ -105,7 +116,7 @@ class UserProfile final : public ConfigBase { /// /// Declaration: /// ```cpp - /// void set_profile_pic(std::string_view url, ustring_view key); + /// void set_profile_pic(std::string_view url, std::span key); /// void set_profile_pic(profile_pic pic); /// ``` /// @@ -115,7 +126,7 @@ class UserProfile final : public ConfigBase { /// - `key` -- Decryption key /// - Second function: /// - `pic` -- Profile pic object - void set_profile_pic(std::string_view url, ustring_view key); + void set_profile_pic(std::string_view url, std::span key); void set_profile_pic(profile_pic pic); /// API: user_profile/UserProfile::get_nts_priority @@ -187,6 +198,8 @@ class UserProfile final : public ConfigBase { /// not, and `std::nullopt` to drop the setting from the config (and thus use the client's /// default). void set_blinded_msgreqs(std::optional enabled); + + bool accepts_protobuf() const override { return true; } }; } // namespace session::config diff --git a/include/session/curve25519.h b/include/session/curve25519.h new file mode 100644 index 00000000..7d77b175 --- /dev/null +++ b/include/session/curve25519.h @@ -0,0 +1,61 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "export.h" + +/// API: crypto/session_curve25519_key_pair +/// +/// Generates a random curve25519 key pair. +/// +/// Inputs: +/// - `curve25519_pk_out` -- [out] pointer to a buffer of 32 bytes where the public key will be +/// written. +/// - `curve25519_sk_out` -- [out] pointer to a buffer of 32 bytes where the private key will be +/// written. +/// +/// Outputs: +/// - `bool` -- True if the seed was successfully retrieved, false if failed. +LIBSESSION_EXPORT bool session_curve25519_key_pair( + unsigned char* curve25519_pk_out, /* 32 byte output buffer */ + unsigned char* curve25519_sk_out /* 32 byte output buffer */); + +/// API: crypto/session_to_curve25519_pubkey +/// +/// Generates a curve25519 public key for an ed25519 public key. +/// +/// Inputs: +/// - `ed25519_pubkey` -- the ed25519 public key (32 bytes). +/// - `curve25519_pk_out` -- [out] pointer to a buffer of 32 bytes where the public key will be +/// written. +/// +/// Outputs: +/// - `bool` -- True if the public key was successfully generated, false if failed. +LIBSESSION_EXPORT bool session_to_curve25519_pubkey( + const unsigned char* ed25519_pubkey, /* 32 bytes */ + unsigned char* curve25519_pk_out /* 32 byte output buffer */); + +/// API: crypto/session_to_curve25519_seckey +/// +/// Generates a curve25519 secret key given given either a libsodium-style secret key, 64 +/// bytes. Can also be passed as a 32-byte seed. +/// +/// Inputs: +/// - `ed25519_seckey` -- [in] the libsodium-style secret key, 64 bytes. Can also be +/// passed as a 32-byte seed. +/// - `curve25519_sk_out` -- [out] pointer to a buffer of 32 bytes where the secret key will be +/// written. +/// +/// Outputs: +/// - `bool` -- True if the secret key was successfully generated, false if failed. +LIBSESSION_EXPORT bool session_to_curve25519_seckey( + const unsigned char* ed25519_seckey, /* 64 bytes */ + unsigned char* curve25519_sk_out /* 32 byte output buffer */); + +#ifdef __cplusplus +} +#endif diff --git a/include/session/curve25519.hpp b/include/session/curve25519.hpp new file mode 100644 index 00000000..b476f5ac --- /dev/null +++ b/include/session/curve25519.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +#include "types.hpp" + +namespace session::curve25519 { + +/// Generates a random curve25519 key pair +std::pair, std::array> curve25519_key_pair(); + +/// API: curve25519/to_curve25519_pubkey +/// +/// Generates a curve25519 public key for an ed25519 public key. +/// +/// Inputs: +/// - `ed25519_pubkey` -- the ed25519 public key. +/// +/// Outputs: +/// - The curve25519 public key +std::array to_curve25519_pubkey(std::span ed25519_pubkey); + +/// API: curve25519/to_curve25519_seckey +/// +/// Generates a curve25519 secret key given given a libsodium-style secret key, 64 +/// bytes. +/// +/// Inputs: +/// - `ed25519_seckey` -- the libsodium-style secret key, 64 bytes. +/// +/// Outputs: +/// - The curve25519 secret key +std::array to_curve25519_seckey(std::span ed25519_seckey); + +} // namespace session::curve25519 diff --git a/include/session/ed25519.h b/include/session/ed25519.h new file mode 100644 index 00000000..82abd521 --- /dev/null +++ b/include/session/ed25519.h @@ -0,0 +1,100 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "export.h" + +/// API: crypto/session_ed25519_key_pair +/// +/// Generates a random ed25519 key pair. +/// +/// Inputs: +/// - `ed25519_pk_out` -- [out] pointer to a buffer of 32 bytes where the public key will be +/// written. +/// - `ed25519_sk_out` -- [out] pointer to a buffer of 64 bytes where the private key will be +/// written. +/// +/// Outputs: +/// - `bool` -- True if the seed was successfully retrieved, false if failed. +LIBSESSION_EXPORT bool session_ed25519_key_pair( + unsigned char* ed25519_pk_out, /* 32 byte output buffer */ + unsigned char* ed25519_sk_out /* 64 byte output buffer */); + +/// API: crypto/session_ed25519_key_pair_seed +/// +/// Generates a ed25519 key pair for a 32 byte seed. +/// +/// Inputs: +/// - `ed25519_seed` -- [in] the 32 byte seed. +/// - `ed25519_pk_out` -- [out] pointer to a buffer of 32 bytes where the public key will be +/// written. +/// - `ed25519_sk_out` -- [out] pointer to a buffer of 64 bytes where the private key will be +/// written. +/// +/// Outputs: +/// - `bool` -- True if the seed was successfully retrieved, false if failed. +LIBSESSION_EXPORT bool session_ed25519_key_pair_seed( + const unsigned char* ed25519_seed, /* 32 bytes */ + unsigned char* ed25519_pk_out, /* 32 byte output buffer */ + unsigned char* ed25519_sk_out /* 64 byte output buffer */); + +/// API: crypto/session_seed_for_ed_privkey +/// +/// Returns the seed for an ed25519 key pair given either the libsodium-style secret key, 64 +/// bytes. +/// +/// Inputs: +/// - `ed25519_privkey` -- [in] the libsodium-style secret key of the sender, 64 bytes. +/// - `ed25519_seed_out` -- [out] pointer to a buffer of 32 bytes where the seed will be written. +/// +/// Outputs: +/// - `bool` -- True if the seed was successfully retrieved, false if failed. +LIBSESSION_EXPORT bool session_seed_for_ed_privkey( + const unsigned char* ed25519_privkey, /* 64 bytes */ + unsigned char* ed25519_seed_out /* 32 byte output buffer */); + +/// API: crypto/session_ed25519_sign +/// +/// Generates a signature for the message using the libsodium-style ed25519 secret key, 64 bytes. +/// +/// Inputs: +/// - `ed25519_privkey` -- [in] the libsodium-style secret key of the sender, 64 bytes. +/// - `msg` -- [in] the data to generate a signature for. +/// - `msg_len` -- [in] the length of the `msg` data. +/// - `ed25519_sig_out` -- [out] pointer to a buffer of 64 bytes where the signature will be +/// written. +/// +/// Outputs: +/// - `bool` -- True if the seed was successfully retrieved, false if failed. +LIBSESSION_EXPORT bool session_ed25519_sign( + const unsigned char* ed25519_privkey, /* 64 bytes */ + const unsigned char* msg, + size_t msg_len, + unsigned char* ed25519_sig_out /* 64 byte output buffer */); + +/// API: crypto/session_ed25519_verify +/// +/// Verify a message and signature for a given pubkey. +/// +/// Inputs: +/// - `sig` -- [in] the signature to verify, 64 bytes. +/// - `pubkey` -- [in] the pubkey for the secret key that was used to generate the signature, 32 +/// bytes. +/// - `msg` -- [in] the data to verify the signature for. +/// - `msg_len` -- [in] the length of the `msg` data. +/// +/// Outputs: +/// - A flag indicating whether the signature is valid +LIBSESSION_EXPORT bool session_ed25519_verify( + const unsigned char* sig, /* 64 bytes */ + const unsigned char* pubkey, + const unsigned char* msg, + size_t msg_len); + +#ifdef __cplusplus +} +#endif diff --git a/include/session/ed25519.hpp b/include/session/ed25519.hpp new file mode 100644 index 00000000..264b2000 --- /dev/null +++ b/include/session/ed25519.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +#include "types.hpp" + +namespace session::ed25519 { + +/// Generates a random Ed25519 key pair +std::pair, std::array> ed25519_key_pair(); + +/// Given an Ed25519 seed this returns the associated Ed25519 key pair +std::pair, std::array> ed25519_key_pair( + std::span ed25519_seed); + +/// API: ed25519/seed_for_ed_privkey +/// +/// Returns the seed for an ed25519 key pair given either the libsodium-style secret key, 64 +/// bytes. If a 32-byte value is provided it is assumed to be the seed and the value will just +/// be returned directly. +/// +/// Inputs: +/// - `ed25519_privkey` -- the libsodium-style secret key of the sender, 64 bytes. Can also be +/// passed as a 32-byte seed. +/// +/// Outputs: +/// - The ed25519 seed +std::array seed_for_ed_privkey(std::span ed25519_privkey); + +/// API: ed25519/sign +/// +/// Generates a signature for the message using the libsodium-style ed25519 secret key, 64 bytes. +/// +/// Inputs: +/// - `ed25519_privkey` -- the libsodium-style secret key, 64 bytes. +/// - `msg` -- the data to generate a signature for. +/// +/// Outputs: +/// - The ed25519 signature +std::vector sign( + std::span ed25519_privkey, std::span msg); + +/// API: ed25519/verify +/// +/// Verify a message and signature for a given pubkey. +/// +/// Inputs: +/// - `sig` -- the signature to verify, 64 bytes. +/// - `pubkey` -- the pubkey for the secret key that was used to generate the signature, 32 bytes. +/// - `msg` -- the data to verify the signature for. +/// +/// Outputs: +/// - A flag indicating whether the signature is valid +bool verify( + std::span sig, + std::span pubkey, + std::span msg); + +} // namespace session::ed25519 diff --git a/include/session/export.h b/include/session/export.h index ab307f6f..892d68f2 100644 --- a/include/session/export.h +++ b/include/session/export.h @@ -6,3 +6,9 @@ #define LIBSESSION_EXPORT __attribute__((visibility("default"))) #endif #define LIBSESSION_C_API extern "C" LIBSESSION_EXPORT + +#ifdef __GNUC__ +#define LIBSESSION_WARN_UNUSED __attribute__((warn_unused_result)) +#else +#define LIBSESSION_WARN_UNUSED +#endif \ No newline at end of file diff --git a/include/session/fields.hpp b/include/session/fields.hpp index 6ca71a24..b70980d1 100644 --- a/include/session/fields.hpp +++ b/include/session/fields.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace session { diff --git a/include/session/file.hpp b/include/session/file.hpp new file mode 100644 index 00000000..a2bf747d --- /dev/null +++ b/include/session/file.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include +#include + +// Utility functions for working with files + +namespace session { + +namespace fs = std::filesystem; + +/// Opens a file for writing of binary data, setting up the returned ofstream with exceptions +/// enabled for any failures. This also throws if the file cannot be opened. If the file already +/// exists it will be truncated. +std::ofstream open_for_writing(const fs::path& filename); + +/// Opens a file for reading of binary data, setting up the returned ifstream with exceptions +/// enabled for any failures. This also throws if the file cannot be opened. +std::ifstream open_for_reading(const fs::path& filename); + +/// Reads a (binary) file from disk into the string `contents`. +std::string read_whole_file(const fs::path& filename); + +/// Dumps (binary) string contents to disk. The file is overwritten if it already exists. +void write_whole_file(const fs::path& filename, std::string_view contents = ""); + +} // namespace session diff --git a/include/session/hash.h b/include/session/hash.h new file mode 100644 index 00000000..e45bdd59 --- /dev/null +++ b/include/session/hash.h @@ -0,0 +1,36 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "export.h" + +/// API: crypto/session_hash +/// +/// Wrapper around the crypto_generichash_blake2b function. +/// +/// Inputs: +/// - `size` -- [in] length of the hash to be generated. +/// - `msg_in` -- [in] the message a hash should be generated for. +/// - `msg_len` -- [in] length of `msg_in`. +/// - `key_in` -- [in] an optional key to be used when generating the hash. +/// - `key_len` -- [in] length of `key_in`. +/// - `hash_out` -- [out] pointer to a buffer of at least `size` bytes where the +/// hash will be written. +/// +/// Outputs: +/// - `bool` -- True if the generation was successful, false if generation failed. +LIBSESSION_EXPORT bool session_hash( + size_t size, + const unsigned char* msg_in, + size_t msg_len, + const unsigned char* key_in, + size_t key_len, + unsigned char* hash_out); + +#ifdef __cplusplus +} +#endif diff --git a/include/session/hash.hpp b/include/session/hash.hpp new file mode 100644 index 00000000..24e1a238 --- /dev/null +++ b/include/session/hash.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include "types.hpp" + +namespace session::hash { + +/// API: hash/hash +/// +/// Wrapper around the crypto_generichash_blake2b function for generating a hash that takes a span +/// to write the hash into. The size of the hash is determined from the size of the span. +/// +/// Inputs: +/// - `hash` -- writable span in which to write the hash. The size of the span must be between 16 +/// and 64. +/// - `msg` -- the message to generate a hash for. +/// - `key` -- an optional key to be used when generating the hash. Can be omitted or an empty +/// string for an unkeyed hash. Must be less than 64 bytes long. +void hash( + std::span hash, + std::span msg, + std::optional> key = std::nullopt); + +/// API: hash/hash +/// +/// Wrapper around the crypto_generichash_blake2b function that returns a vector of the requested +/// size containing the hash. +/// +/// Inputs: +/// - `size` -- length of the hash to be generated. +/// - `msg` -- the message to generate a hash for. +/// - `key` -- an optional key to be used when generating the hash. Can be omitted or an empty +/// string for an unkeyed hash. +/// +/// Outputs: +/// - a `size` byte hash. +std::vector hash( + const size_t size, + std::span msg, + std::optional> key = std::nullopt); + +// Helper callable usable with unordered_map and similar to hash an array of chars by simply copying +// the first sizeof(size_t) bytes, suitable for use with pre-hashed values. +struct identity_hasher { + template + requires(N >= sizeof(size_t)) + constexpr size_t operator()(const std::array& v) const noexcept { + size_t out; + std::copy(v.begin(), v.begin() + sizeof(out), reinterpret_cast(&out)); + return out; + } +}; + +} // namespace session::hash diff --git a/include/session/log_level.h b/include/session/log_level.h new file mode 100644 index 00000000..bc941424 --- /dev/null +++ b/include/session/log_level.h @@ -0,0 +1,20 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: These values must match the values in spdlog::level::level_enum +typedef enum LOG_LEVEL { + LOG_LEVEL_TRACE = 0, + LOG_LEVEL_DEBUG = 1, + LOG_LEVEL_INFO = 2, + LOG_LEVEL_WARN = 3, + LOG_LEVEL_ERROR = 4, + LOG_LEVEL_CRITICAL = 5, + LOG_LEVEL_OFF = 6, +} LOG_LEVEL; + +#ifdef __cplusplus +} +#endif diff --git a/include/session/logging.h b/include/session/logging.h new file mode 100644 index 00000000..98811934 --- /dev/null +++ b/include/session/logging.h @@ -0,0 +1,73 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "export.h" +#include "log_level.h" + +/// API: session/session_add_logger_simple +/// +/// Registers a callback that is invoked when a message is logged. This callback is invoked with +/// just the log message. +/// +/// Inputs: +/// - `callback` -- [in] callback to be called when a new message should be logged. +LIBSESSION_EXPORT void session_add_logger_simple(void (*callback)(const char* msg, size_t msglen)); + +/// API: session/session_add_logger_full +/// +/// Registers a callback that is invoked when a message is logged. The callback is invoked with the +/// log message, the category name of the log message, and the level of the message. +/// +/// Inputs: +/// - `callback` -- [in] callback to be called when a new message should be logged. +LIBSESSION_EXPORT void session_add_logger_full(void (*callback)( + const char* msg, size_t msglen, const char* cat, size_t cat_len, LOG_LEVEL level)); + +/// API: session/session_logger_reset_level +/// +/// Resets the log level of all existing category loggers, and sets a new default for any created +/// after this call. If this has not been called, the default log level of category loggers is +/// info. +LIBSESSION_EXPORT void session_logger_reset_level(LOG_LEVEL level); + +/// API: session/session_logger_set_level_default +/// +/// Sets the log level of new category loggers initialized after this call, but does not change the +/// log level of already-initialized category loggers. +LIBSESSION_EXPORT void session_logger_set_level_default(LOG_LEVEL level); + +/// API: session/session_logger_get_level_default +/// +/// Gets the default log level of new loggers (since the last reset_level or set_level_default +/// call). +LIBSESSION_EXPORT LOG_LEVEL session_logger_get_level_default(); + +/// API: session/session_logger_set_level +/// +/// Set the log level of a specific logger category +LIBSESSION_EXPORT void session_logger_set_level(const char* cat_name, LOG_LEVEL level); + +/// API: session/session_logger_get_level +/// +/// Gets the log level of a specific logger category +LIBSESSION_EXPORT LOG_LEVEL session_logger_get_level(const char* cat_name); + +/// API: session/session_manual_log +/// +/// Logs the provided value via oxen::log, can be used to test that the loggers are working +/// correctly +LIBSESSION_EXPORT void session_manual_log(const char* msg); + +/// API: session/session_clear_loggers +/// +/// Clears all currently set loggers +LIBSESSION_EXPORT void session_clear_loggers(); + +#ifdef __cplusplus +} +#endif diff --git a/include/session/logging.hpp b/include/session/logging.hpp new file mode 100644 index 00000000..00499901 --- /dev/null +++ b/include/session/logging.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include + +#include "log_level.h" + +// forward declaration +namespace spdlog::level { +enum level_enum : int; +} + +namespace session { + +// This is working roughly like an enum class, but with some useful conversions and comparisons +// defined. We allow implicit conversion to this from a spdlog level_enum, and explicit conversion +// *to* a level_enum, as well as comparison operators (so that, for example, LogLevel::warn >= +// LogLevel::info). +struct LogLevel { + int level; + + LogLevel(spdlog::level::level_enum lvl); + explicit constexpr LogLevel(int lvl) : level{lvl} {} + + // Returns the log level as an spdlog enum (which is also a oxen::log::Level). + spdlog::level::level_enum spdlog_level() const; + + std::string_view to_string() const; + + static const LogLevel trace; + static const LogLevel debug; + static const LogLevel info; + static const LogLevel warn; + static const LogLevel error; + static const LogLevel critical; + + auto operator<=>(const LogLevel& other) const { return level <=> other.level; } +}; + +inline const LogLevel LogLevel::trace{LOG_LEVEL_TRACE}; +inline const LogLevel LogLevel::debug{LOG_LEVEL_DEBUG}; +inline const LogLevel LogLevel::info{LOG_LEVEL_INFO}; +inline const LogLevel LogLevel::warn{LOG_LEVEL_WARN}; +inline const LogLevel LogLevel::error{LOG_LEVEL_ERROR}; +inline const LogLevel LogLevel::critical{LOG_LEVEL_CRITICAL}; + +/// API: add_logger +/// +/// Adds a logger callback for oxen-logging log messages (such as from the network object). +/// +/// Inputs: +/// - `callback` -- [in] callback to be called when a new message should be logged. This +/// callback must be callable as one of: +/// +/// callback(std::string_view msg) +/// callback(std::string_view msg, std::string_view log_cat, LogLevel level) +/// +void add_logger(std::function cb); +void add_logger( + std::function cb); + +/// API: session/logger_reset_level +/// +/// Resets the log level of all existing category loggers, and sets a new default for any created +/// after this call. If this has not been called, the default log level of category loggers is +/// info. +/// +/// This function is simply a wrapper around oxen::log::reset_level +void logger_reset_level(LogLevel level); + +/// API: session/logger_set_level_default +/// +/// Sets the log level of new category loggers initialized after this call, but does not change the +/// log level of already-initialized category loggers. +/// +/// This function is simply a wrapper around oxen::log::set_level_default +void logger_set_level_default(LogLevel level); + +/// API: session/logger_get_level_default +/// +/// Gets the default log level of new loggers (since the last reset_level or set_level_default +/// call). +/// +/// This function is simply a wrapper around oxen::log::get_level_default +LogLevel logger_get_level_default(); + +/// API: session/logger_set_level +/// +/// Set the log level of a specific logger category +/// +/// This function is simply a wrapper around oxen::log::set_level +void logger_set_level(std::string cat_name, LogLevel level); + +/// API: session/logger_get_level +/// +/// Gets the log level of a specific logger category +/// +/// This function is simply a wrapper around oxen::log::get_level +LogLevel logger_get_level(std::string cat_name); + +/// API: session/manual_log +/// +/// Logs the provided value via oxen::log, can be used to test that the loggers are working +/// correctly +void manual_log(std::string_view msg); + +/// API: session/clear_loggers +/// +/// Clears all currently set loggers +void clear_loggers(); + +} // namespace session diff --git a/include/session/multi_encrypt.h b/include/session/multi_encrypt.h new file mode 100644 index 00000000..1e7f313a --- /dev/null +++ b/include/session/multi_encrypt.h @@ -0,0 +1,140 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "export.h" + +/// API: crypto/session_encrypt_for_multiple_simple +/// +/// This function performs 1-to-N or N-to-N encryptions (i.e. N encrypted payloads of either the +/// same value, or N separate values) using a random nonce and encodes the resulting encrypted data +/// in a self-contained bt-encoded value suitable for decrypting by a recipient via +/// `session_decrypt_for_multiple_simple`. +/// +/// Inputs: +/// - `out_len` -- pointer to a size_t where the length of the returned buffer will be written. +/// - `messages` -- array of pointers to messages to encrypt. This vector can either be a single +/// message to +/// separately encrypt the same message for each member, or a vector of the same length as +/// recipients to encrypt a different message for each member. +/// - `messages_lengths` -- array of the length of the buffers in `messages`. Must be the same +/// length as `messages`. +/// - `n_messages` -- the number of messages provided. +/// - `recipients` -- array of pointers to recipient X25519 pubkeys. Each pubkey is 32 bytes. +/// These are typically binary Session IDs, not including the 0x05 prefix. +/// - `n_recipients` -- the length of `recipients` +/// - `x25519_privkey` -- the X25519 private key of the sender (32 bytes). Note that this is *NOT* +/// the Ed25519 secret key; see the alternative version of the function below if you only have an +/// Ed25519 key. +/// - `x25519_pubkey` -- the X25519 public key of the sender (32 bytes). This needs to be known by +/// the recipient in order to decrypt the message; unlike session-protocol encryption, the sender +/// identity is not included in the message. +/// - `domain` -- a regular C string that uniquely identifies the "domain" of encryption, such as +/// "SessionGroupKickedMessage". The value is arbitrary: what matters is that it is unique for +/// different encryption types, and that both the sender and recipient use the same value. Max +/// length is 64 bytes. Using a domain is encouraged so that the resulting encryption key between +/// a sender and recipient will be different if the same keys are used for encryption of unrelated +/// data types. +/// - `nonce` -- optional nonce. Typically you should pass `NULL` here, which will cause a random +/// nonce to be used, but a 24-byte nonce can be specified for deterministic encryption. (Note +/// that steps should be taken to ensure the nonce is not reused if specifying a nonce). +/// - `pad` -- if set to a value greater than 1 then junk encrypted values will be added until there +/// are a multiple of this many encrypted values in total. The size of each junk entry will be +/// the same as the (encrypted) size of the first message; this padding is most useful when all +/// messages are the same size (or the same message) as with variable-sized messages the junk +/// entries will be somewhat identifiable. Set to 0 to disable junk entry padding. +/// +/// Outputs: +/// malloced buffer containing the encoded data, or NULL if encryption failed. It is the caller's +/// responsibility to `free()` this buffer (if non-NULL) when done with it! +LIBSESSION_EXPORT unsigned char* session_encrypt_for_multiple_simple( + size_t* out_len, + const unsigned char* const* messages, + const size_t* message_lengths, + size_t n_messages, + const unsigned char* const* recipients, + size_t n_recipients, + const unsigned char* x25519_privkey, + const unsigned char* x25519_pubkey, + const char* domain, + const unsigned char* nonce, + int pad); + +/// This does the same as the above, except that it takes a single, 64-byte libsodium-style Ed25519 +/// secret key instead of the x25519 privkey/pubkey argument pair. The X25519 keys are converted +/// from the Ed25519 key on the fly. +LIBSESSION_EXPORT unsigned char* session_encrypt_for_multiple_simple_ed25519( + size_t* out_len, + const unsigned char* const* messages, + const size_t* message_lengths, + size_t n_messages, + const unsigned char* const* recipients, + size_t n_recipients, + const unsigned char* ed25519_secret_key, + const char* domain, + const unsigned char* nonce, + int pad); + +/// API: crypto/session_decrypt_for_multiple_simple +/// +/// This function attempts to decrypt a message produced by `session_encrypt_for_multiple_simple`; +/// if encryption (of any of the contained messages) succeeds you get back the message, otherwise if +/// the message failed to parse or decryption of all parts fails, you get back NULL. +/// +/// Inputs: +/// - `out_len` -- pointer to a size_t where the length of the decrypted value will be written *if* +/// decryption succeeds. +/// - `encoded` -- the incoming message, produced by session_encrypt_for_multiple_simple +/// - `encoded_len` -- size of `encoded` +/// - `x25519_privkey` -- the X25519 private key of the receiver (32 bytes). Note that this is +/// *NOT* the Ed25519 secret key; see the alternative version of the function below if you only +/// have an Ed25519 key. +/// - `x25519_pubkey` -- the X25519 public key of the receiver (32 bytes). +/// - `sender_x25519_pubkey` -- the X25519 public key of the sender (32 bytes). Note that unlike +/// session encryption, the sender's identify is not available in the encrypted message itself. +/// - `domain` -- the encryption domain, which must be the same as the value used in +/// `session_encrypt_for_multiple_simple`. +/// +/// Outputs: +/// If decryption succeeds, returns a pointer to a malloc'ed buffer containing the decrypted message +/// data, with length stored in `out_len`. If parsing or decryption fails, returns NULL. If the +/// return is non-NULL it is the responsibility of the caller to free the returned pointer! +LIBSESSION_EXPORT unsigned char* session_decrypt_for_multiple_simple( + size_t* out_len, + const unsigned char* encoded, + size_t encoded_len, + const unsigned char* x25519_privkey, + const unsigned char* x25519_pubkey, + const unsigned char* sender_x25519_pubkey, + const char* domain); + +/// Same as above, but takes the recipients privkey/pubkey as a single Ed25519 secret key (64 bytes) +/// instead of a pair of X25519 argumensts. The sender pubkey is still specified as an X25519 +/// pubkey. +LIBSESSION_EXPORT unsigned char* session_decrypt_for_multiple_simple_ed25519_from_x25519( + size_t* out_len, + const unsigned char* encoded, + size_t encoded_len, + const unsigned char* ed25519_secret, + const unsigned char* sender_x25519_pubkey, + const char* domain); + +/// Same as above, but takes the recipients privkey/pubkey as a single Ed25519 secret key (64 bytes) +/// instead of a pair of X25519 argumensts, *and* takes the sender's pubkey as an Ed25519 public +/// key. This is the typically the version you want when the "sender" is a group or other +/// non-Session ID known by an Ed25519 pubkey (03... or other non-05 keys) rather than a Session ID. +LIBSESSION_EXPORT unsigned char* session_decrypt_for_multiple_simple_ed25519( + size_t* out_len, + const unsigned char* encoded, + size_t encoded_len, + const unsigned char* ed25519_secret, + const unsigned char* sender_ed25519_pubkey, + const char* domain); + +#ifdef __cplusplus +} +#endif diff --git a/include/session/multi_encrypt.hpp b/include/session/multi_encrypt.hpp new file mode 100644 index 00000000..08c78a4b --- /dev/null +++ b/include/session/multi_encrypt.hpp @@ -0,0 +1,391 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "sodium_array.hpp" +#include "types.hpp" + +// Helper functions for implementing multiply encrypted messages by creating separate copies of the +// message for each message recipient. This is used most prominently in group key update messages +// to send a copy of the symmetric encryption key to each of a set of recipients. +// +// Details: +// - we use xchacha20-poly1305 encryption +// - we use a single nonce for all the encryptions (rather than a separate one for each encrypted +// copy). Since we are use separate keys for each encryption, nonce reuse is not a concern. +// - we support padding with optional junk entries +// - we do not sign or identify the creator of the encrypted values here (i.e. it's the caller's +// responsibility to do that, if needed). +// - the encryption key for sender a/A, recipient b/B is: H(aB || A || B) = H(bA || A || B), and so +// the recipient needs to know the sender's pubkey to decrypt the message. +// - the general idea is for a potential recipient to brute-force attempt to decrypt all the +// messages to see if any work. +// - this approach is really only meant for limited size groups and is not intended for large scale. + +namespace session { + +namespace detail { + + void encrypt_multi_key( + std::array& key_out, + const unsigned char* a, + const unsigned char* A, + const unsigned char* B, + bool encrypting, + std::string_view domain); + + void encrypt_multi_impl( + std::vector& out, + std::span message, + const unsigned char* key, + const unsigned char* nonce); + + bool decrypt_multi_impl( + std::vector& out, + std::span ciphertext, + const unsigned char* key, + const unsigned char* nonce); + + inline void validate_multi_fields( + std::span nonce, + std::span privkey, + std::span pubkey) { + if (nonce.size() < 24) + throw std::logic_error{"nonce must be 24 bytes"}; + if (privkey.size() != 32) + throw std::logic_error{"privkey must be 32 bytes"}; + if (pubkey.size() != 32) + throw std::logic_error{"pubkey requires a 32-byte pubkey"}; + } + +} // namespace detail + +/// API: crypto/encrypt_multiple_message_overhead +/// +/// The number of bytes of overhead required per encrypted copy of the message as produced by +/// `encrypt_for_multiple`. This does not include the nonce or other data (such as the sender) +/// that likely needs to be transmitted as well. +extern const size_t encrypt_multiple_message_overhead; + +/// API: crypto/encrypt_for_multiple +/// +/// Encrypts a message multiple times for multiple recipients. `callable` is invoked once per +/// encrypted (or junk) value, passed as a `std::span`. +/// +/// Inputs: +/// - `messages` -- a vector of message bodies to encrypt. Must be either size 1, or of the same +/// size as recipients. If given a single message then that message is re-encrypted for each +/// recipient; if given multiple messages then the nth message is encrypted for the nth recipient. +/// See also session/util.hpp for a convenience function for converting other containers of +/// string-like values to this view vector type. +/// - `nonce` -- must be 24 bytes (can be longer, but only the first 24 will be used). Should be +/// secure random, or a cryptographically secure hash incorporating secret data. The nonce should +/// not be reused (if the same sender/recipient encryption/decryption key. +/// - `privkey` -- the sender's X25519 private key. Must be 32 bytes. *NOT* an Ed25519 secret key: +/// if that's what you have, you need to convert the privkey to X25519 first. +/// - `pubkey` -- the sender's X25519 public key. Can be empty to compute it from `privkey`. +/// - `recipients` -- vector of recipient X25519 public keys. Must be 32 bytes each (remove the 05 +/// if a session id). *NOT* Ed25519 pubkeys; conversion to X25519 may be required if that's what +/// you have. +/// - `domain` -- some unique fixed string known to both sides; this is used in the hashing function +/// used to generate individual keys for domain separation, and so should ideally have a different +/// value in different contexts (i.e. group keys uses one value, kicked messages use another, +/// etc.). *Can* be empty, but should be set to something. +/// - `call` -- this is invoked for each different encrypted value with a std::span; the caller +/// must copy as needed as the std::span doesn't remain valid past the call. +/// - `ignore_invalid_recipient` -- if given and true then any recipients that appear to have +/// invalid public keys (i.e. the shared key multiplication fails) will be silently ignored (the +/// callback will not be called). If not given (or false) then such a failure for any recipient +/// will raise an exception. +template +void encrypt_for_multiple( + const std::vector> messages, + const std::vector> recipients, + std::span nonce, + std::span privkey, + std::span pubkey, + std::string_view domain, + F&& call, + bool ignore_invalid_recipient = false) { + + detail::validate_multi_fields(nonce, privkey, pubkey); + + for (const auto& r : recipients) + if (r.size() != 32) + throw std::logic_error{"encrypt_for_multiple requires 32-byte recipients pubkeys"}; + if (messages.size() != 1 && messages.size() != recipients.size()) + throw std::logic_error{ + "encrypt_for_multiple requires either 1 or recipients.size() messages"}; + + size_t max_msg_size = 0; + for (const auto& m : messages) + if (auto sz = m.size(); sz > max_msg_size) + max_msg_size = sz; + + std::vector encrypted; + encrypted.reserve(max_msg_size + encrypt_multiple_message_overhead); + + sodium_cleared> key; + auto msg_it = messages.begin(); + for (const auto& r : recipients) { + const auto& m = *msg_it; + if (messages.size() > 1) + ++msg_it; + try { + detail::encrypt_multi_key(key, privkey.data(), pubkey.data(), r.data(), true, domain); + } catch (const std::exception&) { + if (ignore_invalid_recipient) + continue; + else + throw; + } + detail::encrypt_multi_impl(encrypted, m, key.data(), nonce.data()); + call(to_span(encrypted)); + } +} + +/// Wrapper for passing a single message for all recipients; all arguments other than the first are +/// identical. +template +void encrypt_for_multiple(std::span message, Args&&... args) { + return encrypt_for_multiple( + to_view_vector(&message, &message + 1), std::forward(args)...); +} +template +void encrypt_for_multiple(std::string_view message, Args&&... args) { + return encrypt_for_multiple(to_span(message), std::forward(args)...); +} +template +void encrypt_for_multiple(std::span message, Args&&... args) { + return encrypt_for_multiple(to_span(message), std::forward(args)...); +} + +/// API: crypto/decrypt_for_multiple +/// +/// Decryption via a lambda: we call the lambda (which must return a std::optional>) repeatedly until we get back a nullopt, and attempt to decrypt each returned +/// value. When decryption succeeds, we return the plaintext to the caller. If none of the fed-in +/// values can be decrypt, we return std::nullopt. +/// +/// Inputs: +/// - `ciphertext` -- callback that returns a std::optional> or +/// std::optional> +/// when called, containing the next ciphertext; should return std::nullopt when finished. +/// - `nonce` -- the nonce used for encryption/decryption (which must have been provided by the +/// sender alongside the encrypted messages, and is the same as the `nonce` value given to +/// `encrypt_for_multiple`) +/// - `privkey` -- the private X25519 key of the recipient. +/// - `pubkey` -- the public X25519 key of the recipient (for a successful decryption, this will be +/// one of the pubkeys given to `encrypt_for_multiple`. +/// - `sender_pubkey` -- the public X25519 key of the sender (this is the `pubkey` passed into +/// `encrypt_for_multiple`). +/// - `domain` -- the encryption domain; this is typically a hard-coded string, and must be the same +/// as the one used for encryption. +template < + typename NextCiphertext, + typename = std::enable_if_t< + std::is_invocable_r_v< + std::optional>, + NextCiphertext> || + std::is_invocable_r_v>, NextCiphertext> || + std::is_invocable_r_v, NextCiphertext> || + std::is_invocable_r_v, NextCiphertext> || + std::is_invocable_r_v>, NextCiphertext> || + std::is_invocable_r_v>, NextCiphertext>>> +std::optional> decrypt_for_multiple( + NextCiphertext next_ciphertext, + std::span nonce, + std::span privkey, + std::span pubkey, + std::span sender_pubkey, + std::string_view domain) { + + detail::validate_multi_fields(nonce, privkey, pubkey); + if (sender_pubkey.size() != 32) + throw std::logic_error{"pubkey requires a 32-byte pubkey"}; + + sodium_cleared> key; + detail::encrypt_multi_key( + key, privkey.data(), pubkey.data(), sender_pubkey.data(), false, domain); + + auto decrypted = std::make_optional>(); + + for (auto ciphertext = next_ciphertext(); ciphertext; ciphertext = next_ciphertext()) + if (detail::decrypt_multi_impl(*decrypted, *ciphertext, key.data(), nonce.data())) + return decrypted; + + decrypted.reset(); + return decrypted; +} + +/// API: crypto/decrypt_for_multiple +/// +/// Attempts to decrypt any of the messages produced by `encrypt_for_multiple`. As soon as one +/// decrypts successfully it is returned. If non decrypt you get back std::nullopt. +/// +/// Inputs: +/// - `ciphertexts` -- the encrypted values +/// - `nonce` -- the nonce used for encryption/decryption (which must have been provided by the +/// sender alongside the encrypted messages, and is the same as the `nonce` value given to +/// `encrypt_for_multiple`) +/// - `privkey` -- the private X25519 key of the recipient. +/// - `pubkey` -- the public X25519 key of the recipient (for a successful decryption, this will be +/// one of the pubkeys given to `encrypt_for_multiple`. +/// - `sender_pubkey` -- the public X25519 key of the sender (this is the `pubkey` passed into +/// `encrypt_for_multiple`). +/// - `domain` -- the encryption domain; this is typically a hard-coded string, and must be the same +/// as the one used for encryption. +/// +std::optional> decrypt_for_multiple( + const std::vector>& ciphertexts, + std::span nonce, + std::span privkey, + std::span pubkey, + std::span sender_pubkey, + std::string_view domain); + +/// API: crypto/encrypt_for_multiple_simple +/// +/// This function performs 1-to-N or N-to-N encryptions (i.e. N encrypted payloads of either the +/// same value, or N separate values) using a random nonce and encodes the resulting encrypted data +/// in a self-contained bt-encoded value suitable for decrypting by a recipient via +/// `decrypt_for_multiple_simple`. +/// +/// In contrast to `encrypt_for_multiple`, this function is less flexible, but easier to use when +/// additional flexibility is not required. +/// +/// Inputs: +/// - `messages` -- vector of messages to encrypt. This vector can either be a single message to +/// separately encrypt the same message for each member, or a vector of the same length as +/// recipients to encrypt a different message for each member. If you have these in some other +/// type of container, session/util.hpp's `session::to_view_vector` is a convenient way to convert +/// compatible containers to this view vector. +/// - `recipients` -- vector of X25519 pubkeys of the recipients. (If sending to Session IDs, these +/// are the 32-byte binary keys after removing the 0x05 prefix byte). +/// - `privkey` -- the X25519 private key of the sender (32 bytes). Note that this is *NOT* the +/// Ed25519 secret key; see the alternative version of the function below if you only have an +/// Ed25519 key. +/// - `pubkey` -- the X25519 public key of the sender (32 bytes). This needs to be known by the +/// recipient in order to decrypt the message; unlike session-protocol encryption, the sender +/// identity is not included in the message. +/// - `domain` -- the encryption domain; this is a short string that uniquely identifies the +/// "domain" of encryption, such as "SessionGroupKickedMessage". The value is arbitrary: what +/// matters is that it is unique for different encryption types, and that both the sender and +/// recipient use the same value. Max length is 64 bytes. Using a domain is encouraged so that +/// the resulting encryption key between a sender and recipient will be different if the same keys +/// are used for encryption of unrelated data types. +/// - `nonce` -- optional; if omitted or empty a random nonce will be generated. If non-empty this +/// should be a 24-byte value; this can be used with a cryptographically secure hash function to +/// construct a deterministic encrypted value. If you don't need that, omit it to use a random +/// one. +/// - `pad` -- if given and greater than 1 then junk encrypted values will be added until there are +/// a multiple of this many encrypted values in total. The size of each junk entry will be the +/// same as the (encrypted) size of the first message; this padding is most useful when all +/// messages are the same size (or the same message) as with variable-sized messages the junk +/// entries will be somewhat identifiable. +/// +/// Outputs: +/// std::vector containing bytes that contains the nonce and encoded encrypted +/// messages, suitable for decryption by the recipients with `decrypt_for_multiple_simple`. +std::vector encrypt_for_multiple_simple( + const std::vector>& messages, + const std::vector>& recipients, + std::span privkey, + std::span pubkey, + std::string_view domain, + std::optional> nonce = std::nullopt, + int pad = 0); + +/// API: crypto/encrypt_for_multiple_simple +/// +/// This function is the same as the above, except that instead of taking the sender private and +/// public X25519 keys, it takes the single, 64-byte libsodium Ed25519 secret key (which is then +/// converted into the required X25519 keys). +std::vector encrypt_for_multiple_simple( + const std::vector>& messages, + const std::vector>& recipients, + std::span ed25519_secret_key, + std::string_view domain, + std::span nonce = {}, + int pad = 0); + +/// API: crypto/encrypt_for_multiple_simple +/// +/// Wrapper that takes a *single* message to send to all recipients. This is simply a shortcut for +/// passing a one-element vector into the above versions of the function; all arguments other than +/// the first are identical. +/// +template +std::vector encrypt_for_multiple_simple( + std::span message, Args&&... args) { + return encrypt_for_multiple_simple( + to_view_vector(&message, &message + 1), std::forward(args)...); +} +template +std::vector encrypt_for_multiple_simple(std::string_view message, Args&&... args) { + return encrypt_for_multiple_simple(to_span(message), std::forward(args)...); +} +template +std::vector encrypt_for_multiple_simple( + std::span message, Args&&... args) { + return encrypt_for_multiple_simple(to_span(message), std::forward(args)...); +} + +/// API: crypto/decrypt_for_multiple_simple +/// +/// This function attempts to decrypt a message produced by `encrypt_for_multiple_simple`; if +/// encryption (of any of the contained messages) succeeds you get back the message, otherwise if +/// the message failed to parse or decryption of all parts fails, you get back std::nullopt. +/// +/// Inputs: +/// - `encoded` -- the incoming message, produced by encrypt_for_multiple_simple +/// - `privkey` -- the X25519 private key of the receiver (32 bytes). Note that this is *NOT* the +/// Ed25519 secret key; see the alternative version of the function below if you only have an +/// Ed25519 key. +/// - `pubkey` -- the X25519 public key of the receiver (32 bytes). +/// - `sender_pubkey` -- the X25519 public key of the sender (32 bytes). Note that unlike session +/// encryption, the sender's identify is not available in the encrypted message itself. +/// - `domain` -- the encryption domain, which must be the same as the value used in +/// `encrypt_for_multiple_simple`. +/// +/// Outputs: +/// If decryption succeeds, returns a std::vector containing the decrypted message, +/// in bytes. If parsing or decryption fails, returns std::nullopt. +std::optional> decrypt_for_multiple_simple( + std::span encoded, + std::span privkey, + std::span pubkey, + std::span sender_pubkey, + std::string_view domain); + +/// API: crypto/decrypt_for_multiple_simple +/// +/// This is the same as the above, except that instead of taking an X25519 private and public key +/// arguments, it takes a single, 64-byte Ed25519 secret key and converts it to X25519 to perform +/// the decryption. +/// +/// Note that `sender_pubkey` is still an X25519 pubkey for this version of the function. +std::optional> decrypt_for_multiple_simple( + std::span encoded, + std::span ed25519_secret_key, + std::span sender_pubkey, + std::string_view domain); + +/// API: crypto/decrypt_for_multiple_simple_ed25519 +/// +/// This is the same as the above, except that it takes both the sender and recipient as Ed25519 +/// keys, converting them on the fly to attempt the decryption. +std::optional> decrypt_for_multiple_simple_ed25519( + std::span encoded, + std::span ed25519_secret_key, + std::span sender_ed25519_pubkey, + std::string_view domain); + +} // namespace session diff --git a/include/session/onionreq/builder.h b/include/session/onionreq/builder.h new file mode 100644 index 00000000..4320276e --- /dev/null +++ b/include/session/onionreq/builder.h @@ -0,0 +1,171 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "../export.h" + +typedef enum ENCRYPT_TYPE { + ENCRYPT_TYPE_AES_GCM = 0, + ENCRYPT_TYPE_X_CHA_CHA_20 = 1, +} ENCRYPT_TYPE; + +typedef struct onion_request_builder_object { + // Internal opaque object pointer; calling code should leave this alone. + void* internals; + + ENCRYPT_TYPE enc_type; +} onion_request_builder_object; + +/// API: groups/onion_request_builder_init +/// +/// Constructs an onion request builder and sets a pointer to it in `builder`. +/// +/// When done with the object the `builder` must be destroyed by passing the pointer to +/// onion_request_builder_free(). +/// +/// Inputs: +/// - `builder` -- [out] Pointer to the builder object +LIBSESSION_EXPORT void onion_request_builder_init(onion_request_builder_object** builder); + +/// API: groups/onion_request_builder_free +/// +/// Properly destroys an onion request builder instance. +/// +/// Inputs: +/// - `builder` -- [out] Pointer to the builder object to be freed +LIBSESSION_EXPORT void onion_request_builder_free(onion_request_builder_object* builder); + +/// API: onion_request_builder_set_enc_type +/// +/// Wrapper around session::onionreq::Builder::onion_request_builder_set_enc_type. +/// +/// Inputs: +/// - `builder` -- [in] Pointer to the builder object +/// - `enc_type` -- [in] The encryption type to use in the onion request +LIBSESSION_EXPORT void onion_request_builder_set_enc_type( + onion_request_builder_object* builder, ENCRYPT_TYPE enc_type); + +/// API: onion_request_builder_set_snode_destination +/// +/// Wrapper around session::onionreq::Builder::set_snode_destination. ed25519_pubkey and +/// x25519_pubkey are both hex strings and must both be exactly 64 characters. +/// +/// Inputs: +/// - `builder` -- [in] Pointer to the builder object +/// - `ip` -- [in] The IP address for the snode destination +/// - `quic_port` -- [in] The Quic port request for the snode destination +/// - `ed25519_pubkey` -- [in] The ed25519 public key for the snode destination +LIBSESSION_EXPORT void onion_request_builder_set_snode_destination( + onion_request_builder_object* builder, + const uint8_t ip[4], + const uint16_t quic_port, + const char* ed25519_pubkey); + +/// API: onion_request_builder_set_server_destination +/// +/// Wrapper around session::onionreq::Builder::set_server_destination. x25519_pubkey +/// is a hex string and must both be exactly 64 characters. +/// +/// Inputs: +/// - `builder` -- [in] Pointer to the builder object +/// - `protocol` -- [in] The protocol to use +/// - `host` -- [in] The server host +/// - `endpoint` -- [in] The endpoint to call +/// - `method` -- [in] The HTTP method to use +/// - `port` -- [in] The port to use +/// - `x25519_pubkey` -- [in] The x25519 public key for server +LIBSESSION_EXPORT void onion_request_builder_set_server_destination( + onion_request_builder_object* builder, + const char* protocol, + const char* host, + const char* endpoint, + const char* method, + uint16_t port, + const char* x25519_pubkey); + +/// API: onion_request_builder_set_destination_pubkey +/// +/// Wrapper around session::onionreq::Builder::set_destination_pubkey. +/// +/// Inputs: +/// - `builder` -- [in] Pointer to the builder object +/// - `x25519_pubkey` -- [in] The x25519 public key for server (Hex string of exactly 64 +/// characters). +LIBSESSION_EXPORT void onion_request_builder_set_destination_pubkey( + onion_request_builder_object* builder, const char* x25519_pubkey); + +/// API: onion_request_builder_add_hop +/// +/// Wrapper around session::onionreq::Builder::add_hop. ed25519_pubkey and +/// x25519_pubkey are both hex strings and must both be exactly 64 characters. +/// +/// Declaration: +/// ```cpp +/// void onion_request_builder_add_hop( +/// [in] onion_request_builder_object* builder +/// [in] const char* ed25519_pubkey, +/// [in] const char* x25519_pubkey +/// ); +/// ``` +/// +/// Inputs: +/// - `builder` -- [in] Pointer to the builder object +/// - `ed25519_pubkey` -- [in] The ed25519 public key for the snode hop +/// - `x25519_pubkey` -- [in] The x25519 public key for the snode hop +LIBSESSION_EXPORT void onion_request_builder_add_hop( + onion_request_builder_object* builder, + const char* ed25519_pubkey, + const char* x25519_pubkey); + +/// API: onion_request_builder_build +/// +/// Wrapper around session::onionreq::Builder::build. payload_in is binary: payload_in +/// has the length provided, destination_ed25519_pubkey and destination_x25519_pubkey +/// are both hex strings and must both be exactly 64 characters. Returns a flag indicating +/// success or failure. +/// +/// Declaration: +/// ```cpp +/// bool onion_request_builder_build( +/// [in] onion_request_builder_object* builder +/// [in] const unsigned char* payload_in, +/// [in] size_t payload_in_len, +/// [out] unsigned char** payload_out, +/// [out] size_t* payload_out_len, +/// [out] unsigned char* final_x25519_pubkey_out, +/// [out] unsigned char* final_x25519_seckey_out +/// ); +/// ``` +/// +/// Inputs: +/// - `builder` -- [in] Pointer to the builder object +/// - `payload_in` -- [in] The payload to be sent in the onion request +/// - `payload_in_len` -- [in] The length of the payload_in +/// - `payload_out` -- [out] payload to be sent through the network, will be nullptr on error +/// - `payload_out_len` -- [out] length of payload_out if not null +/// - `final_x25519_pubkey_out` -- [out] pointer to a buffer of exactly 32 bytes where the final +/// x25519 public key used for the onion request will be written if successful +/// - `final_x25519_seckey_out` -- [out] pointer to a buffer of exactly 32 bytes where the final +/// x25519 secret key used for the onion request will be written if successful +/// +/// Outputs: +/// - `bool` -- True if the onion request payload was successfully constructed, false if it failed. +/// If (and only if) true is returned then `payload_out` must be freed when done with it. +LIBSESSION_EXPORT bool onion_request_builder_build( + onion_request_builder_object* builder, + const unsigned char* payload_in, + size_t payload_in_len, + unsigned char** payload_out, + size_t* payload_out_len, + unsigned char* final_x25519_pubkey_out, + unsigned char* final_x25519_seckey_out); + +#ifdef __cplusplus +} +#endif diff --git a/include/session/onionreq/builder.hpp b/include/session/onionreq/builder.hpp new file mode 100644 index 00000000..fe87ec51 --- /dev/null +++ b/include/session/onionreq/builder.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include +#include + +#include "key_types.hpp" + +namespace session::network { +struct service_node; +struct request_info; +} // namespace session::network + +namespace session::onionreq { + +struct ServerDestination { + std::string protocol; + std::string host; + std::string endpoint; + session::onionreq::x25519_pubkey x25519_pubkey; + std::optional port; + std::optional>> headers; + std::string method; + + ServerDestination( + std::string protocol, + std::string host, + std::string endpoint, + session::onionreq::x25519_pubkey x25519_pubkey, + std::optional port = std::nullopt, + std::optional>> headers = std::nullopt, + std::string method = "GET") : + protocol{std::move(protocol)}, + host{std::move(host)}, + endpoint{std::move(endpoint)}, + x25519_pubkey{std::move(x25519_pubkey)}, + port{std::move(port)}, + headers{std::move(headers)}, + method{std::move(method)} {} +}; + +using network_destination = std::variant; + +namespace detail { + + session::onionreq::x25519_pubkey pubkey_for_destination(network_destination destination); +} + +enum class EncryptType { + aes_gcm, + xchacha20, +}; + +// Takes the encryption type as a string, returns the EncryptType value (or throws if invalid). +// Supported values: aes-gcm and xchacha20. gcm is accepted as an aliases for aes-gcm. +EncryptType parse_enc_type(std::string_view enc_type); + +inline constexpr std::string_view to_string(EncryptType type) { + switch (type) { + case EncryptType::xchacha20: return "xchacha20"sv; + case EncryptType::aes_gcm: return "aes-gcm"sv; + } + return ""sv; +} + +// Builder class for preparing onion request payloads. +class Builder { + Builder(const network_destination& destination, + const std::vector& nodes, + const EncryptType enc_type_); + + public: + static Builder make( + const network_destination& destination, + const std::vector& nodes, + const EncryptType enc_type_ = EncryptType::xchacha20); + + EncryptType enc_type; + std::optional destination_x25519_public_key = std::nullopt; + std::optional final_hop_x25519_keypair = std::nullopt; + + Builder(EncryptType enc_type_ = EncryptType::xchacha20) : enc_type{enc_type_} {} + + void set_enc_type(EncryptType enc_type_) { enc_type = enc_type_; } + + void set_destination(network_destination destination); + void set_destination_pubkey(session::onionreq::x25519_pubkey x25519_pubkey); + void add_hop(std::span remote_key); + void add_hop(std::pair keys) { hops_.push_back(keys); } + + void generate(network::request_info& info); + std::vector build(std::vector payload); + + private: + std::vector> hops_ = {}; + + // Snode request values + + std::optional ed25519_public_key_ = std::nullopt; + + // Proxied request values + + std::optional host_ = std::nullopt; + std::optional endpoint_ = std::nullopt; + std::optional protocol_ = std::nullopt; + std::optional method_ = std::nullopt; + std::optional port_ = std::nullopt; + std::optional>> headers_ = std::nullopt; + std::optional>> query_params_ = std::nullopt; + + std::vector _generate_payload( + std::optional> body) const; +}; + +} // namespace session::onionreq diff --git a/include/session/onionreq/hop_encryption.hpp b/include/session/onionreq/hop_encryption.hpp new file mode 100644 index 00000000..a5e937de --- /dev/null +++ b/include/session/onionreq/hop_encryption.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#include "builder.hpp" +#include "key_types.hpp" + +namespace session::onionreq { + +// Encryption/decription class for encryption/decrypting outgoing/incoming messages. +class HopEncryption { + public: + HopEncryption(x25519_seckey private_key, x25519_pubkey public_key, bool server = true) : + private_key_{std::move(private_key)}, + public_key_{std::move(public_key)}, + server_{server} {} + + // Returns true if the response is long enough to be a valid response. + static bool response_long_enough(EncryptType type, size_t response_size); + + // Encrypts `plaintext` message using encryption `type`. `pubkey` is the recipients public key. + // `reply` should be false for a client-to-snode message, and true on a returning + // snode-to-client message. + std::vector encrypt( + EncryptType type, + std::vector plaintext, + const x25519_pubkey& pubkey) const; + std::vector decrypt( + EncryptType type, + std::vector ciphertext, + const x25519_pubkey& pubkey) const; + + // AES-GCM encryption. + std::vector encrypt_aesgcm( + std::vector plainText, const x25519_pubkey& pubKey) const; + std::vector decrypt_aesgcm( + std::vector cipherText, const x25519_pubkey& pubKey) const; + + // xchacha20-poly1305 encryption; for a message sent from client Alice to server Bob we use a + // shared key of a Blake2B 32-byte (i.e. crypto_aead_xchacha20poly1305_ietf_KEYBYTES) hash of + // H(aB || A || B), which Bob can compute when receiving as H(bA || A || B). The returned value + // always has the crypto_aead_xchacha20poly1305_ietf_NPUBBYTES nonce prepended to the beginning. + // + // When Bob (the server) encrypts a method for Alice (the client), he uses shared key + // H(bA || A || B) (note that this is *different* that what would result if Bob was a client + // sending to Alice the client). + std::vector encrypt_xchacha20( + std::vector plaintext, const x25519_pubkey& pubKey) const; + std::vector decrypt_xchacha20( + std::vector ciphertext, const x25519_pubkey& pubKey) const; + + private: + const x25519_seckey private_key_; + const x25519_pubkey public_key_; + bool server_; // True if we are the server (i.e. the snode). +}; + +} // namespace session::onionreq diff --git a/include/session/onionreq/key_types.hpp b/include/session/onionreq/key_types.hpp new file mode 100644 index 00000000..5f22c71c --- /dev/null +++ b/include/session/onionreq/key_types.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../types.hpp" +#include "../util.hpp" + +namespace session::onionreq { + +using namespace std::literals; + +namespace detail { + template + inline constexpr std::array null_bytes = {0}; + + void load_from_hex(void* buffer, size_t length, std::string_view hex); + void load_from_bytes(void* buffer, size_t length, std::string_view bytes); + +} // namespace detail + +template +struct alignas(size_t) key_base : std::array { + std::string_view view() const { + return {reinterpret_cast(this->data()), KeyLength}; + } + std::string hex() const { return oxenc::to_hex(view()); } + explicit operator bool() const { return *this != detail::null_bytes; } + + // Loads the key from a hex string; throws if the hex is the wrong size or not hex. + static Derived from_hex(std::string_view hex) { + Derived d; + detail::load_from_hex(d.data(), d.size(), hex); + return d; + } + // Same as above, but returns nullopt if invalid instead of throwing + static std::optional maybe_from_hex(std::string_view hex) { + try { + return from_hex(hex); + } catch (...) { + } + return std::nullopt; + } + // Loads the key from a byte string; throws if the wrong size. + static Derived from_bytes(std::string_view bytes) { + Derived d; + detail::load_from_bytes(d.data(), d.size(), bytes); + return d; + } + static Derived from_bytes(std::vector bytes) { + return from_bytes(to_string(bytes)); + } + static Derived from_bytes(std::span bytes) { + return from_bytes(to_string(bytes)); + } +}; + +template +struct pubkey_base : key_base { + using PubKeyBase = pubkey_base; +}; + +struct legacy_pubkey : pubkey_base {}; +struct x25519_pubkey : pubkey_base {}; +struct ed25519_pubkey : pubkey_base { + // Returns the {base32z}.snode representation of this pubkey + std::string snode_address() const; +}; + +template +struct seckey_base : key_base {}; + +struct legacy_seckey : seckey_base { + legacy_pubkey pubkey() const; +}; +struct ed25519_seckey : seckey_base { + ed25519_pubkey pubkey() const; +}; +struct x25519_seckey : seckey_base { + x25519_pubkey pubkey() const; +}; + +using legacy_keypair = std::pair; +using ed25519_keypair = std::pair; +using x25519_keypair = std::pair; + +/// Parse a pubkey string value encoded in any of base32z, b64, hex, or raw bytes, based on the +/// length of the value. Returns a null pk (i.e. operator bool() returns false) and warns on +/// invalid input (i.e. wrong length or invalid encoding). +legacy_pubkey parse_legacy_pubkey(std::string_view pubkey_in); +ed25519_pubkey parse_ed25519_pubkey(std::string_view pubkey_in); +x25519_pubkey parse_x25519_pubkey(std::string_view pubkey_in); +x25519_pubkey compute_x25519_pubkey(std::span ed25519_pk); + +} // namespace session::onionreq + +namespace std { +template +struct hash> { + size_t operator()(const session::onionreq::pubkey_base& pk) const { + // pubkeys are already random enough to use the first bytes directly as a good (and fast) + // hash value + static_assert(alignof(decltype(pk)) >= alignof(size_t)); + return *reinterpret_cast(pk.data()); + } +}; + +template <> +struct hash : hash { +}; +template <> +struct hash : hash { +}; +template <> +struct hash + : hash {}; + +} // namespace std diff --git a/include/session/onionreq/parser.hpp b/include/session/onionreq/parser.hpp new file mode 100644 index 00000000..233a97e0 --- /dev/null +++ b/include/session/onionreq/parser.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include "hop_encryption.hpp" + +namespace session::onionreq { + +/// The default maximum size of an onion request accepted by the OnionReqParser constructor. +constexpr size_t DEFAULT_MAX_SIZE = 10'485'760; // 10 MiB + +class OnionReqParser { + private: + x25519_keypair keys; + HopEncryption enc; + EncryptType enc_type = EncryptType::aes_gcm; + x25519_pubkey remote_pk; + std::vector payload_; + + public: + /// Constructs a parser, parsing the given request sent to us. Throws if parsing or decryption + /// fails. + OnionReqParser( + std::span x25519_pubkey, + std::span x25519_privkey, + std::span req, + size_t max_size = DEFAULT_MAX_SIZE); + + /// plaintext payload, decrypted from the incoming request during construction. + std::span payload() const { return to_span(payload_); } + + /// Extracts payload from this object (via a std::move); after the call the object's payload + /// will be empty. + std::vector move_payload() { + std::vector ret{std::move(payload_)}; + payload_.clear(); // Guarantee empty, even if SSO active + return ret; + } + + std::span remote_pubkey() const { return to_span(remote_pk.view()); } + + /// Encrypts a reply using the appropriate encryption as determined when parsing the + /// request. + std::vector encrypt_reply(std::span reply) const; +}; + +} // namespace session::onionreq diff --git a/include/session/onionreq/response_parser.h b/include/session/onionreq/response_parser.h new file mode 100644 index 00000000..77237d37 --- /dev/null +++ b/include/session/onionreq/response_parser.h @@ -0,0 +1,61 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include "../export.h" +#include "builder.h" + +/// API: onion_request_decrypt +/// +/// Wrapper around session::onionreq::ResponseParser. ciphertext_in is binary. +/// enc_type should be set to ENCRYPT_TYPE::ENCRYPT_TYPE_X_CHA_CHA_20 if it's not +/// set when creating the builder destination_x25519_pubkey, final_x25519_pubkey +/// and final_x25519_seckey should be in bytes and be exactly 32 bytes. Returns a +/// flag indicating success or failure. +/// +/// Declaration: +/// ```cpp +/// bool onion_request_decrypt( +/// [in] const unsigned char* ciphertext, +/// [in] size_t ciphertext_len, +/// [in] ENCRYPT_TYPE enc_type_, +/// [in] const char* destination_x25519_pubkey, +/// [in] const char* final_x25519_pubkey, +/// [in] const char* final_x25519_seckey, +/// [out] unsigned char** plaintext_out, +/// [out] size_t* plaintext_out_len +/// ); +/// ``` +/// +/// Inputs: +/// - `ciphertext` -- [in] The onion request response data +/// - `ciphertext_len` -- [in] The length of ciphertext +/// - `enc_type_` -- [in] The encryption type which was used for the onion request +/// - `destination_x25519_pubkey` -- [in] The x25519 public key for the server destination +/// - `final_x25519_pubkey` -- [in] The final x25519 public key used for the onion request +/// - `final_x25519_seckey` -- [in] The final x25519 secret key used for the onion request +/// - `plaintext_out` -- [out] decrypted content contained within ciphertext, will be nullptr on +/// error +/// - `plaintext_out_len` -- [out] length of plaintext_out if not null +/// +/// Outputs: +/// - `bool` -- True if the onion request was successfully constructed, false if it failed. +/// If (and only if) true is returned then `plaintext_out` must be freed when done with it. +LIBSESSION_EXPORT bool onion_request_decrypt( + const unsigned char* ciphertext, + size_t ciphertext_len, + ENCRYPT_TYPE enc_type_, + unsigned char* destination_x25519_pubkey, + unsigned char* final_x25519_pubkey, + unsigned char* final_x25519_seckey, + unsigned char** plaintext_out, + size_t* plaintext_out_len); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/session/onionreq/response_parser.hpp b/include/session/onionreq/response_parser.hpp new file mode 100644 index 00000000..46f76a43 --- /dev/null +++ b/include/session/onionreq/response_parser.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "hop_encryption.hpp" +#include "key_types.hpp" + +namespace session::onionreq { + +constexpr auto decryption_failed_error = + "Decryption failed (both XChaCha20-Poly1305 and AES256-GCM)"sv; + +class ResponseParser { + public: + /// Constructs a parser, parsing the given request sent to us. Throws if parsing or decryption + /// fails. + ResponseParser(session::onionreq::Builder builder); + ResponseParser( + x25519_pubkey destination_x25519_public_key, + x25519_keypair x25519_keypair, + EncryptType enc_type = EncryptType::xchacha20) : + destination_x25519_public_key_{std::move(destination_x25519_public_key)}, + x25519_keypair_{std::move(x25519_keypair)}, + enc_type_{enc_type} {} + + static bool response_long_enough(EncryptType enc_type, size_t response_size); + + std::vector decrypt(std::vector ciphertext) const; + + private: + x25519_pubkey destination_x25519_public_key_; + x25519_keypair x25519_keypair_; + EncryptType enc_type_; +}; + +} // namespace session::onionreq diff --git a/include/session/platform.h b/include/session/platform.h new file mode 100644 index 00000000..3c89614b --- /dev/null +++ b/include/session/platform.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum CLIENT_PLATFORM { + CLIENT_PLATFORM_ANDROID = 0, + CLIENT_PLATFORM_DESKTOP = 1, + CLIENT_PLATFORM_IOS = 2, +} CLIENT_PLATFORM; + +#ifdef __cplusplus +} +#endif diff --git a/include/session/platform.hpp b/include/session/platform.hpp new file mode 100644 index 00000000..46463f74 --- /dev/null +++ b/include/session/platform.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace session { + +enum class Platform { + android, + desktop, + ios, +}; + +} // namespace session diff --git a/include/session/random.h b/include/session/random.h new file mode 100644 index 00000000..70136315 --- /dev/null +++ b/include/session/random.h @@ -0,0 +1,25 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "export.h" + +/// API: crypto/session_random +/// +/// Wrapper around the randombytes_buf function. +/// +/// Inputs: +/// - `size` -- [in] number of bytes to be generated. +/// +/// Outputs: +/// - `unsigned char*` -- pointer to random bytes of `size` bytes. The caller is responsible for +/// freeing the data when done! +LIBSESSION_EXPORT unsigned char* session_random(size_t size); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/session/random.hpp b/include/session/random.hpp new file mode 100644 index 00000000..54b33fba --- /dev/null +++ b/include/session/random.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include + +#include "util.hpp" + +namespace session { +/// rng type that uses llarp::randint(), which is cryptographically secure +struct CSRNG { + using result_type = uint64_t; + + static constexpr uint64_t min() { return std::numeric_limits::min(); }; + + static constexpr uint64_t max() { return std::numeric_limits::max(); }; + + uint64_t operator()() { + uint64_t i; + randombytes((uint8_t*)&i, sizeof(i)); + return i; + }; +}; + +extern CSRNG csrng; + +} // namespace session + +namespace session::random { + +/// API: random/random +/// +/// Wrapper around the randombytes_buf function. +/// +/// Inputs: +/// - `size` -- the number of random bytes to be generated. +/// +/// Outputs: +/// - random bytes of the specified length. +std::vector random(size_t size); + +/// API: random/random_base32 +/// +/// Return a random base32 string with the given length. +/// +/// Inputs: +/// - `size` -- the number of characters to be generated. +/// +/// Outputs: +/// - random base32 string of the specified length. +std::string random_base32(size_t size); + +} // namespace session::random diff --git a/include/session/session_encrypt.h b/include/session/session_encrypt.h new file mode 100644 index 00000000..f5a1c2b6 --- /dev/null +++ b/include/session/session_encrypt.h @@ -0,0 +1,282 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include "export.h" + +/// API: crypto/session_encrypt_for_recipient_deterministic +/// +/// This function attempts to encrypt a message using the SessionProtocol. +/// +/// Inputs: +/// - `plaintext_in` -- [in] Pointer to a data buffer containing the encrypted data. +/// - `plaintext_len` -- [in] Length of `plaintext_in` +/// - `ed25519_privkey` -- [in] the Ed25519 private key of the sender (64 bytes). +/// - `recipient_pubkey` -- [in] the x25519 public key of the recipient (32 bytes). +/// - `ciphertext_out` -- [out] Pointer-pointer to an output buffer; a new buffer is allocated, the +/// encrypted data written to it, and then the pointer to that buffer is stored here. +/// This buffer must be `free()`d by the caller when done with it *unless* the function returns +/// false, in which case the buffer pointer will not be set. +/// - `ciphertext_len` -- [out] Pointer to a size_t where the length of `ciphertext_out` is stored. +/// Not touched if the function returns false. +/// +/// Outputs: +/// - `bool` -- True if the message was successfully decrypted, false if decryption failed. If +/// (and only if) true is returned then `plaintext_out` must be freed when done with it. +LIBSESSION_EXPORT bool session_encrypt_for_recipient_deterministic( + const unsigned char* plaintext_in, + size_t plaintext_len, + const unsigned char* ed25519_privkey, /* 64 bytes */ + const unsigned char* recipient_pubkey, /* 32 bytes */ + unsigned char** ciphertext_out, + size_t* ciphertext_len); + +/// API: crypto/session_encrypt_for_blinded_recipient +/// +/// This function attempts to encrypt a message using the SessionBlindingProtocol. +/// +/// Inputs: +/// - `plaintext_in` -- [in] Pointer to a data buffer containing the encrypted data. +/// - `plaintext_len` -- [in] Length of `plaintext_in` +/// - `ed25519_privkey` -- [in] the Ed25519 private key of the sender (64 bytes). +/// - `open_group_pubkey` -- [in] the public key of the open group server to route +/// the blinded message through (32 bytes). +/// - `recipient_blinded_id` -- [in] the blinded id of the recipient including the blinding +/// prefix (33 bytes), 'blind15' or 'blind25' encryption will be chosed based on this value. +/// - `ciphertext_out` -- [out] Pointer-pointer to an output buffer; a new buffer is allocated, the +/// encrypted data written to it, and then the pointer to that buffer is stored here. +/// This buffer must be `free()`d by the caller when done with it *unless* the function returns +/// false, in which case the buffer pointer will not be set. +/// - `ciphertext_len` -- [out] Pointer to a size_t where the length of `ciphertext_out` is stored. +/// Not touched if the function returns false. +/// +/// Outputs: +/// - `bool` -- True if the message was successfully decrypted, false if decryption failed. If +/// (and only if) true is returned then `plaintext_out` must be freed when done with it. +LIBSESSION_EXPORT bool session_encrypt_for_blinded_recipient( + const unsigned char* plaintext_in, + size_t plaintext_len, + const unsigned char* ed25519_privkey, /* 64 bytes */ + const unsigned char* open_group_pubkey, /* 32 bytes */ + const unsigned char* recipient_blinded_id, /* 33 bytes */ + unsigned char** ciphertext_out, + size_t* ciphertext_len); + +/// API: crypto/session_decrypt_incoming +/// +/// This function attempts to decrypt a message using the SessionProtocol. +/// +/// Inputs: +/// - `ciphertext_in` -- [in] Pointer to a data buffer containing the encrypted data. +/// - `ciphertext_len` -- [in] Length of `ciphertext_in` +/// - `ed25519_privkey` -- [in] the Ed25519 private key of the receiver (64 bytes). +/// - `session_id_out` -- [out] pointer to a buffer of at least 67 bytes where the null-terminated, +/// hex-encoded session_id of the message's author will be written if decryption/verification was +/// successful. +/// - `plaintext_out` -- [out] Pointer-pointer to an output buffer; a new buffer is allocated, the +/// decrypted data written to it, and then the pointer to that buffer is stored here. +/// This buffer must be `free()`d by the caller when done with it *unless* the function returns +/// false, in which case the buffer pointer will not be set. +/// - `plaintext_len` -- [out] Pointer to a size_t where the length of `plaintext_out` is stored. +/// Not touched if the function returns false. +/// +/// Outputs: +/// - `bool` -- True if the message was successfully decrypted, false if decryption failed. If +/// (and only if) true is returned then `plaintext_out` must be freed when done with it. +LIBSESSION_EXPORT bool session_decrypt_incoming( + const unsigned char* ciphertext_in, + size_t ciphertext_len, + const unsigned char* ed25519_privkey, /* 64 bytes */ + char* session_id_out, /* 67 byte output buffer */ + unsigned char** plaintext_out, + size_t* plaintext_len); + +/// API: crypto/session_decrypt_incoming_legacy_group +/// +/// This function attempts to decrypt a message using the SessionProtocol. +/// +/// Inputs: +/// - `ciphertext_in` -- [in] Pointer to a data buffer containing the encrypted data. +/// - `ciphertext_len` -- [in] Length of `ciphertext_in` +/// - `x25519_pubkey` -- [in] the x25519 public key of the receiver (32 bytes). +/// - `x25519_seckey` -- [in] the x25519 secret key of the receiver (32 bytes). +/// - `session_id_out` -- [out] pointer to a buffer of at least 67 bytes where the null-terminated, +/// hex-encoded session_id of the message's author will be written if decryption/verification was +/// successful. +/// - `plaintext_out` -- [out] Pointer-pointer to an output buffer; a new buffer is allocated, the +/// decrypted data written to it, and then the pointer to that buffer is stored here. +/// This buffer must be `free()`d by the caller when done with it *unless* the function returns +/// false, in which case the buffer pointer will not be set. +/// - `plaintext_len` -- [out] Pointer to a size_t where the length of `plaintext_out` is stored. +/// Not touched if the function returns false. +/// +/// Outputs: +/// - `bool` -- True if the message was successfully decrypted, false if decryption failed. If +/// (and only if) true is returned then `plaintext_out` must be freed when done with it. +LIBSESSION_EXPORT bool session_decrypt_incoming_legacy_group( + const unsigned char* ciphertext_in, + size_t ciphertext_len, + const unsigned char* x25519_pubkey, /* 32 bytes */ + const unsigned char* x25519_seckey, /* 32 bytes */ + char* session_id_out, /* 67 byte output buffer */ + unsigned char** plaintext_out, + size_t* plaintext_len); + +/// API: crypto/session_decrypt_for_blinded_recipient +/// +/// This function attempts to decrypt a message using the SessionBlindingProtocol. +/// +/// Inputs: +/// - `ciphertext_in` -- [in] Pointer to a data buffer containing the encrypted data. +/// - `ciphertext_len` -- [in] Length of `ciphertext_in` +/// - `ed25519_privkey` -- [in] the Ed25519 private key of the receiver (64 bytes). +/// - `open_group_pubkey` -- [in] the public key of the open group server to route +/// the blinded message through (32 bytes). +/// - `sender_id` -- [in] the blinded id of the sender including the blinding prefix (33 bytes), +/// 'blind15' or 'blind25' decryption will be chosed based on this value. +/// - `recipient_id` -- [in] the blinded id of the recipient including the blinding prefix (33 +/// bytes), +/// must match the same 'blind15' or 'blind25' type of the `sender_id`. +/// - `session_id_out` -- [out] pointer to a buffer of at least 67 bytes where the null-terminated, +/// hex-encoded session_id of the message's author will be written if decryption/verification was +/// successful. +/// - `plaintext_out` -- [out] Pointer-pointer to an output buffer; a new buffer is allocated, the +/// decrypted data written to it, and then the pointer to that buffer is stored here. +/// This buffer must be `free()`d by the caller when done with it *unless* the function returns +/// false, in which case the buffer pointer will not be set. +/// - `plaintext_len` -- [out] Pointer to a size_t where the length of `plaintext_out` is stored. +/// Not touched if the function returns false. +/// +/// Outputs: +/// - `bool` -- True if the message was successfully decrypted, false if decryption failed. If +/// (and only if) true is returned then `plaintext_out` must be freed when done with it. +LIBSESSION_EXPORT bool session_decrypt_for_blinded_recipient( + const unsigned char* ciphertext_in, + size_t ciphertext_len, + const unsigned char* ed25519_privkey, /* 64 bytes */ + const unsigned char* open_group_pubkey, /* 32 bytes */ + const unsigned char* sender_id, /* 33 bytes */ + const unsigned char* recipient_id, /* 33 bytes */ + char* session_id_out, /* 67 byte output buffer */ + unsigned char** plaintext_out, + size_t* plaintext_len); + +/// API: crypto/session_decrypt_ons_response +/// +/// This function attempts to decrypt an ONS response. +/// +/// Inputs: +/// - `lowercase_name_in` -- [in] Pointer to a NULL-terminated buffer containing the lowercase name +/// used to trigger the response. +/// - `ciphertext_in` -- [in] Pointer to a data buffer containing the encrypted data. +/// - `ciphertext_len` -- [in] Length of `ciphertext_in`. +/// - `nonce_in` -- [in, optional] Pointer to a data buffer containing the nonce (24 bytes) or NULL. +/// - `session_id_out` -- [out] pointer to a buffer of at least 67 bytes where the null-terminated, +/// hex-encoded session_id will be written if decryption was successful. +/// +/// Outputs: +/// - `bool` -- True if the session ID was successfully decrypted, false if decryption failed. +LIBSESSION_EXPORT bool session_decrypt_ons_response( + const char* lowercase_name_in, + const unsigned char* ciphertext_in, + size_t ciphertext_len, + const unsigned char* nonce_in, /* 24 bytes or NULL */ + char* session_id_out /* 67 byte output buffer */); + +/// API: crypto/session_decrypt_push_notification +/// +/// Decrypts a push notification payload. +/// +/// Inputs: +/// - `payload_in` -- [in] the payload included in the push notification. +/// - `payload_len` -- [in] Length of `payload_in`. +/// - `enc_key_in` -- [in] the device encryption key used when subscribing for push notifications +/// (32 bytes). +/// - `plaintext_out` -- [out] Pointer-pointer to an output buffer; a new buffer is allocated, the +/// decrypted data written to it, and then the pointer to that buffer is stored here. +/// This buffer must be `free()`d by the caller when done with it *unless* the function returns +/// false, in which case the buffer pointer will not be set. +/// - `plaintext_len` -- [out] Pointer to a size_t where the length of `plaintext_out` is stored. +/// Not touched if the function returns false. +/// +/// Outputs: +/// - `bool` -- True if the decryption was successful, false if decryption failed. +LIBSESSION_EXPORT bool session_decrypt_push_notification( + const unsigned char* payload_in, + size_t payload_len, + const unsigned char* enc_key_in, /* 32 bytes */ + unsigned char** plaintext_out, + size_t* plaintext_len); + +/// API: crypto/compute_message_hash +/// +/// Computes the hash for a message. +/// +/// Inputs: +/// - `pubkey_hex_in` -- the pubkey as a 67 character hex string that the message will be stored in. +/// NULL terminated. +/// - `ns` -- the namespace that the message will be stored in. +/// - `data` -- the base64 encoded message data that will be stored for the message. NULL +/// terminated. +/// +/// Outputs: +/// - `std::string` -- a deterministic hash for the message. +LIBSESSION_EXPORT bool session_compute_message_hash( + const char* pubkey_hex_in, int16_t ns, const char* base64_data_in, char* hash_out); + +/// API: crypto/session_encrypt_xchacha20 +/// +/// Encrypts a value with a given key using xchacha20. +/// +/// Inputs: +/// - `plaintext_in` -- [in] the data to encrypt. +/// - `plaintext_len` -- [in] the length of `plaintext_in`. +/// - `enc_key_in` -- [in] the key to use for encryption (32 bytes). +/// - `ciphertext_out` -- [out] Pointer-pointer to an output buffer; a new buffer is allocated, the +/// encrypted data written to it, and then the pointer to that buffer is stored here. +/// This buffer must be `free()`d by the caller when done with it *unless* the function returns +/// false, in which case the buffer pointer will not be set. +/// - `ciphertext_len` -- [out] Pointer to a size_t where the length of `ciphertext_out` is stored. +/// Not touched if the function returns false. +/// +/// Outputs: +/// - `bool` -- True if the encryption was successful, false if encryption failed. +LIBSESSION_EXPORT bool session_encrypt_xchacha20( + const unsigned char* plaintext_in, + size_t plaintext_len, + const unsigned char* enc_key_in, /* 32 bytes */ + unsigned char** ciphertext_out, + size_t* ciphertext_len); + +/// API: crypto/session_decrypt_xchacha20 +/// +/// Decrypts a value that was encrypted with the `encrypt_xchacha20` function. +/// +/// Inputs: +/// - `ciphertext_in` -- [in] the data to decrypt. +/// - `ciphertext_len` -- [in] the length of `ciphertext_in`. +/// - `enc_key_in` -- [in] the key to use for decryption (32 bytes). +/// - `plaintext_out` -- [out] Pointer-pointer to an output buffer; a new buffer is allocated, the +/// decrypted data written to it, and then the pointer to that buffer is stored here. +/// This buffer must be `free()`d by the caller when done with it *unless* the function returns +/// false, in which case the buffer pointer will not be set. +/// - `plaintext_len` -- [out] Pointer to a size_t where the length of `plaintext_out` is stored. +/// Not touched if the function returns false. +/// +/// Outputs: +/// - `bool` -- True if the decryption was successful, false if decryption failed. +LIBSESSION_EXPORT bool session_decrypt_xchacha20( + const unsigned char* ciphertext_in, + size_t ciphertext_len, + const unsigned char* enc_key_in, /* 32 bytes */ + unsigned char** plaintext_out, + size_t* plaintext_len); + +#ifdef __cplusplus +} +#endif diff --git a/include/session/session_encrypt.hpp b/include/session/session_encrypt.hpp new file mode 100644 index 00000000..c357a2f0 --- /dev/null +++ b/include/session/session_encrypt.hpp @@ -0,0 +1,324 @@ +#pragma once + +#include +#include +#include + +#include "types.hpp" + +// Helper functions for the "Session Protocol" encryption mechanism. This is the encryption used +// for DMs sent from one Session user to another. +// +// Suppose Alice with Ed25519 keys `a`/`A` and derived x25519 keys `x`/`X`, wants to send a mesage +// `M` to Brandy with Ed25519 keys `b`/`B` and derived x25519 keys `y`/`Y` (note that the x25519 +// pubkeys in hex form are session ids, but without the `05` prefix). +// +// First she signs the message, her own *Ed25519* (not X) pubkey, and the recipients pubkey (X, not +// Ed): +// +// SIG = Ed25519-sign(M || A || Y) +// +// Next a data message is composed of `M || A || SIG`, then encrypted for Brandy using: +// +// CIPHERTEXT = crypto_box_seal(M || A || SIG) +// +// (see libsodium for details, but is effectively generating an ephemeral X25519 keypair, making a +// shared secret of that and the recipient key, then encrypting using XSalsa20-Poly1305 from the +// shared secret). +// +// On the decryption side, we do this in reverse and verify via the signature both the sender and +// intended (inner) recipient. First, Brandy opens the ciphertext and extract the message, sender +// Ed pubkey, and signature: +// +// M || A || SIG = crypto_box_seal_open(CIPHERTEXT) +// +// then constructs and verifies the expected signature DATA (recall Y = Brandy's X25519 pubkey): +// +// Ed25519-verify(M || A || Y) +// +// Assuming this passes, we now know that `A` sent the message, and can convert this to a X25519 +// pubkey to work out the Session ID: +// +// X = Ed25519-pubkey-to-curve25519(A) +// +// SENDER = '05' + hex(X) +// +// and thus Brandy now has decrypted, verified data sent by Alice. + +namespace session { + +/// API: crypto/encrypt_for_recipient +/// +/// Performs session protocol encryption, typically for a DM sent between Session users. +/// +/// Inputs: +/// - `ed25519_privkey` -- the libsodium-style secret key of the sender, 64 bytes. Can also be +/// passed as a 32-byte seed, but the 64-byte value is preferrable (to avoid needing to +/// recompute the public key from the seed). +/// - `recipient_pubkey` -- the recipient X25519 pubkey, either as a 0x05-prefixed session ID +/// (33 bytes) or an unprefixed pubkey (32 bytes). +/// - `message` -- the message to encrypt for the recipient. +/// +/// Outputs: +/// - The encrypted ciphertext to send. +/// - Throw if encryption fails or (which typically means invalid keys provided) +std::vector encrypt_for_recipient( + std::span ed25519_privkey, + std::span recipient_pubkey, + std::span message); + +/// API: crypto/encrypt_for_recipient_deterministic +/// +/// Performs session protocol encryption, but using a deterministic version of crypto_box_seal. +/// +/// Warning: this determinism completely undermines the point of crypto_box_seal (compared to a +/// regular encrypted crypto_box): someone with the same sender Ed25519 keys and message could later +/// regenerate the same ephemeral key and nonce which would allow them to decrypt the sent message, +/// which is intentionally impossible with a crypto_box_seal. This function is thus only +/// recommended for backwards compatibility with decryption mechanisms using that scheme where this +/// specific property is not needed, such as self-directed config messages. +/// +/// Inputs: +/// Identical to `encrypt_for_recipient`. +/// +/// Outputs: +/// Identical to `encrypt_for_recipient`. +std::vector encrypt_for_recipient_deterministic( + std::span ed25519_privkey, + std::span recipient_pubkey, + std::span message); + +/// API: crypto/session_encrypt_for_blinded_recipient +/// +/// This function attempts to encrypt a message using the SessionBlindingProtocol. +/// +/// Inputs: +/// - `ed25519_privkey` -- the libsodium-style secret key of the sender, 64 bytes. Can also be +/// passed as a 32-byte seed, but the 64-byte value is preferrable (to avoid needing to +/// recompute the public key from the seed). +/// - `recipient_pubkey` -- the recipient blinded id, either 0x15-prefixed or 0x25-prefixed +/// (33 bytes). +/// - `message` -- the message to encrypt for the recipient. +/// +/// Outputs: +/// - The encrypted ciphertext to send. +/// - Throw if encryption fails or (which typically means invalid keys provided) +std::vector encrypt_for_blinded_recipient( + std::span ed25519_privkey, + std::span server_pk, + std::span recipient_blinded_id, + std::span message); + +/// API: crypto/sign_for_recipient +/// +/// Performs the signing steps for session protocol encryption. This is responsible for producing +/// a packed authored, signed message of: +/// +/// MESSAGE || SENDER_ED25519_PUBKEY || SIG +/// +/// where SIG is the signed value of: +/// +/// MESSAGE || SENDER_ED25519_PUBKEY || RECIPIENT_X25519_PUBKEY +/// +/// thus allowing both sender identification, recipient verification, and authentication. +/// +/// This function is mostly for internal use, but is exposed for debugging purposes: it is typically +/// not called directly but rather used by `encrypt_for_recipient` or +/// `encrypt_for_recipient_deterministic`, both of which call this function to construct the inner +/// signed message. +/// +/// Inputs: +/// - `ed25519_privkey` -- the seed (32 bytes) or secret key (64 bytes) of the sender +/// - `recipient_pubkey` -- the recipient X25519 pubkey, which may or may not be prefixed with the +/// 0x05 session id prefix (33 bytes if prefixed, 32 if not prefixed). +/// - `message` -- the message to embed and sign. +std::vector sign_for_recipient( + std::span ed25519_privkey, + std::span recipient_pubkey, + std::span message); + +/// API: crypto/decrypt_incoming +/// +/// Inverse of `encrypt_for_recipient`: this decrypts the message, extracts the sender Ed25519 +/// pubkey, and verifies that the sender Ed25519 signature on the message. +/// +/// Inputs: +/// - `ed25519_privkey` -- the private key of the recipient. Can be a 32-byte seed, or a 64-byte +/// libsodium secret key. The latter is a bit faster as it doesn't have to re-compute the pubkey +/// from the seed. +/// - `ciphertext` -- the encrypted data +/// +/// Outputs: +/// - `std::pair, std::vector>` -- the plaintext binary +/// data that was encrypted and the +/// sender's ED25519 pubkey, *if* the message decrypted and validated successfully. Throws on +/// error. +std::pair, std::vector> decrypt_incoming( + std::span ed25519_privkey, std::span ciphertext); + +/// API: crypto/decrypt_incoming +/// +/// Inverse of `encrypt_for_recipient`: this decrypts the message, extracts the sender Ed25519 +/// pubkey, and verifies that the sender Ed25519 signature on the message. This function is used +/// for decrypting legacy group messages which only have an x25519 key pair, the Ed25519 version +/// of this function should be preferred where possible. +/// +/// Inputs: +/// - `ed25519_privkey` -- the private key of the recipient. Can be a 32-byte seed, or a 64-byte +/// libsodium secret key. The latter is a bit faster as it doesn't have to re-compute the pubkey +/// from the seed. +/// - `ciphertext` -- the encrypted data +/// +/// Outputs: +/// - `std::pair, std::vector>` -- the plaintext binary +/// data that was encrypted and the +/// sender's ED25519 pubkey, *if* the message decrypted and validated successfully. Throws on +/// error. +std::pair, std::vector> decrypt_incoming( + std::span x25519_pubkey, + std::span x25519_seckey, + std::span ciphertext); + +/// API: crypto/decrypt_incoming +/// +/// Inverse of `encrypt_for_recipient`: this decrypts the message, verifies that the sender Ed25519 +/// signature on the message and converts the extracted sender's Ed25519 pubkey into a session ID. +/// +/// Inputs: +/// - `ed25519_privkey` -- the private key of the recipient. Can be a 32-byte seed, or a 64-byte +/// libsodium secret key. The latter is a bit faster as it doesn't have to re-compute the pubkey +/// from the seed. +/// - `ciphertext` -- the encrypted data +/// +/// Outputs: +/// - `std::pair, std::string>` -- the plaintext binary data that was +/// encrypted and the +/// session ID (in hex), *if* the message decrypted and validated successfully. Throws on error. +std::pair, std::string> decrypt_incoming_session_id( + std::span ed25519_privkey, std::span ciphertext); + +/// API: crypto/decrypt_incoming +/// +/// Inverse of `encrypt_for_recipient`: this decrypts the message, verifies that the sender Ed25519 +/// signature on the message and converts the extracted sender's Ed25519 pubkey into a session ID. +/// This function is used for decrypting legacy group messages which only have an x25519 key pair, +/// the Ed25519 version of this function should be preferred where possible. +/// +/// Inputs: +/// - `x25519_pubkey` -- the 32 byte x25519 public key of the recipient. +/// - `x25519_seckey` -- the 32 byte x25519 private key of the recipient. +/// - `ciphertext` -- the encrypted data +/// +/// Outputs: +/// - `std::pair, std::string>` -- the plaintext binary data that was +/// encrypted and the +/// session ID (in hex), *if* the message decrypted and validated successfully. Throws on error. +std::pair, std::string> decrypt_incoming_session_id( + std::span x25519_pubkey, + std::span x25519_seckey, + std::span ciphertext); + +/// API: crypto/decrypt_from_blinded_recipient +/// +/// This function attempts to decrypt a message using the SessionBlindingProtocol. If the +/// `sender_id` matches the `blinded_id` generated from the `ed25519_privkey` this function assumes +/// the `ciphertext` is an outgoing message and decrypts it as such. +/// +/// Inputs: +/// - `ed25519_privkey` -- the Ed25519 private key of the receiver. Can be a 32-byte seed, or a +/// 64-byte +/// libsodium secret key. The latter is a bit faster as it doesn't have to re-compute the pubkey +/// from the seed. +/// - `server_pk` -- the public key of the open group server to route the blinded message through +/// (32 bytes). +/// - `sender_id` -- the blinded id of the sender including the blinding prefix (33 bytes), +/// 'blind15' or 'blind25' decryption will be chosed based on this value. +/// - `recipient_id` -- the blinded id of the recipient including the blinding prefix (33 bytes), +/// must match the same 'blind15' or 'blind25' type of the `sender_id`. +/// - `ciphertext` -- Pointer to a data buffer containing the encrypted data. +/// +/// Outputs: +/// - `std::pair, std::string>` -- the plaintext binary data that was +/// encrypted and the +/// session ID (in hex), *if* the message decrypted and validated successfully. Throws on error. +std::pair, std::string> decrypt_from_blinded_recipient( + std::span ed25519_privkey, + std::span server_pk, + std::span sender_id, + std::span recipient_id, + std::span ciphertext); + +/// API: crypto/decrypt_ons_response +/// +/// Decrypts the response of an ONS lookup. +/// +/// Inputs: +/// - `lowercase_name` -- the lowercase name which was looked to up to retrieve this response. +/// - `ciphertext` -- ciphertext returned from the server. +/// - `nonce` -- the nonce returned from the server if provided. +/// +/// Outputs: +/// - `std::string` -- the session ID (in hex) returned from the server, *if* the server returned +/// a session ID. Throws on error/failure. +std::string decrypt_ons_response( + std::string_view lowercase_name, + std::span ciphertext, + std::optional> nonce); + +/// API: crypto/decrypt_push_notification +/// +/// Decrypts a push notification payload. +/// +/// Inputs: +/// - `payload` -- the payload included in the push notification. +/// - `enc_key` -- the device encryption key used when subscribing for push notifications (32 +/// bytes). +/// +/// Outputs: +/// - `std::vector` -- the decrypted push notification payload, *if* the decryption +/// was +/// successful. Throws on error/failure. +std::vector decrypt_push_notification( + std::span payload, std::span enc_key); + +/// API: crypto/compute_message_hash +/// +/// Computes the hash for a message. +/// +/// Inputs: +/// - `pubkey_hex` -- the pubkey as a 66 character hex string that the message will be stored in. +/// - `ns` -- the namespace that the message will be stored in. +/// - `data` -- the base64 encoded message data that will be stored for the message. +/// +/// Outputs: +/// - `std::string` -- a deterministic hash for the message. +std::string compute_message_hash( + const std::string_view pubkey_hex, int16_t ns, std::string_view data); + +/// API: crypto/encrypt_xchacha20 +/// +/// Encrypts a value with a given key using xchacha20. +/// +/// Inputs: +/// - `plaintext` -- the data to encrypt. +/// - `enc_key` -- the key to use for encryption (32 bytes). +/// +/// Outputs: +/// - `std::vector` -- the resulting ciphertext. +std::vector encrypt_xchacha20( + std::span plaintext, std::span enc_key); + +/// API: crypto/decrypt_xchacha20 +/// +/// Decrypts a value that was encrypted with the `encrypt_xchacha20` function. +/// +/// Inputs: +/// - `ciphertext` -- the data to decrypt. +/// - `enc_key` -- the key to use for decryption (32 bytes). +/// +/// Outputs: +/// - `std::vector` -- the resulting plaintext. +std::vector decrypt_xchacha20( + std::span ciphertext, std::span enc_key); + +} // namespace session diff --git a/include/session/session_network.h b/include/session/session_network.h new file mode 100644 index 00000000..534d93b2 --- /dev/null +++ b/include/session/session_network.h @@ -0,0 +1,351 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include "export.h" +#include "log_level.h" +#include "onionreq/builder.h" +#include "platform.h" + +typedef enum CONNECTION_STATUS { + CONNECTION_STATUS_UNKNOWN = 0, + CONNECTION_STATUS_CONNECTING = 1, + CONNECTION_STATUS_CONNECTED = 2, + CONNECTION_STATUS_DISCONNECTED = 3, +} CONNECTION_STATUS; + +typedef struct network_object { + // Internal opaque object pointer; calling code should leave this alone. + void* internals; +} network_object; + +typedef struct network_service_node { + uint8_t ip[4]; + uint16_t quic_port; + char ed25519_pubkey_hex[65]; // The 64-byte ed25519 pubkey in hex + null terminator. +} network_service_node; + +typedef struct network_server_destination { + const char* method; + const char* protocol; + const char* host; + const char* endpoint; + uint16_t port; + const char* x25519_pubkey; + const char* const* headers; + const char* const* header_values; + size_t headers_size; +} network_server_destination; + +typedef struct onion_request_path { + const network_service_node* nodes; + const size_t nodes_count; +} onion_request_path; + +/// API: network/network_init +/// +/// Constructs a new network object. +/// +/// When done with the object the `network_object` must be destroyed by passing the pointer to +/// network_free(). +/// +/// Inputs: +/// - `network` -- [out] Pointer to the network object +/// - `cache_path` -- [in] Path where the snode cache files should be stored. Should be +/// NULL-terminated. +/// - `use_testnet` -- [in] Flag indicating whether the network should connect to testnet or +/// mainnet. +/// - `single_path_mode` -- [in] Flag indicating whether the network should be in "single path mode" +/// (ie. use a single path for everything - this is useful for iOS App Extensions which perform a +/// single action and then close so we don't waste time building other paths). +/// - `pre_build_paths` -- [in] Flag indicating whether the network should pre-build it's paths. +/// - `error` -- [out] the pointer to a buffer in which we will write an error string if an error +/// occurs; error messages are discarded if this is given as NULL. If non-NULL this must be a +/// buffer of at least 256 bytes. +/// +/// Outputs: +/// - `bool` -- Returns true on success; returns false and write the exception message as a C-string +/// into `error` (if not NULL) on failure. +LIBSESSION_EXPORT bool network_init( + network_object** network, + const char* cache_path, + bool use_testnet, + bool single_path_mode, + bool pre_build_paths, + char* error) LIBSESSION_WARN_UNUSED; + +/// API: network/network_free +/// +/// Frees a network object. +/// +/// Inputs: +/// - `network` -- [in] Pointer to network_object object +LIBSESSION_EXPORT void network_free(network_object* network); + +/// API: network/network_suspend +/// +/// Suspends the network preventing any further requests from creating new connections and paths. +/// This function also calls the `close_connections` function. +LIBSESSION_EXPORT void network_suspend(network_object* network); + +/// API: network/network_resume +/// +/// Resumes the network allowing new requests to creating new connections and paths. +LIBSESSION_EXPORT void network_resume(network_object* network); + +/// API: network/network_close_connections +/// +/// Closes any currently active connections. +LIBSESSION_EXPORT void network_close_connections(network_object* network); + +/// API: network/network_clear_cache +/// +/// Clears the cached from memory and from disk (if a cache path was provided during +/// initialization). +LIBSESSION_EXPORT void network_clear_cache(network_object* network); + +/// API: network/network_get_cache_size +/// +/// Retrieves the current size of the snode cache from memory (if a cache doesn't exist or +/// hasn't been loaded then this will return 0). +LIBSESSION_EXPORT size_t network_get_snode_cache_size(network_object* network); + +/// API: network/network_set_status_changed_callback +/// +/// Registers a callback to be called whenever the network connection status changes. +/// +/// Inputs: +/// - `network` -- [in] Pointer to the network object +/// - `callback` -- [in] callback to be called when the network connection status changes. +/// - `ctx` -- [in, optional] Pointer to an optional context. Set to NULL if unused. +LIBSESSION_EXPORT void network_set_status_changed_callback( + network_object* network, void (*callback)(CONNECTION_STATUS status, void* ctx), void* ctx); + +/// API: network/network_set_paths_changed_callback +/// +/// Registers a callback to be called whenever the onion request paths are updated. +/// +/// The pointer provided to the callback belongs to the caller and must be freed via `free()` when +/// done with it. +/// +/// Inputs: +/// - `network` -- [in] Pointer to the network object +/// - `callback` -- [in] callback to be called when the onion request paths are updated. +/// - `ctx` -- [in, optional] Pointer to an optional context. Set to NULL if unused. +LIBSESSION_EXPORT void network_set_paths_changed_callback( + network_object* network, + void (*callback)(onion_request_path* paths, size_t paths_len, void* ctx), + void* ctx); + +/// API: network/network_get_swarm +/// +/// Retrieves the swarm for the given pubkey. If there is already an entry in the cache for the +/// swarm then that will be returned, otherwise a network request will be made to retrieve the +/// swarm and save it to the cache. +/// +/// Inputs: +/// - `network` -- [in] Pointer to the network object +/// - 'swarm_pubkey_hex' - [in] x25519 pubkey for the swarm in hex (64 characters). +/// - 'callback' - [in] callback to be called with the retrieved swarm (in the case of an error +/// the callback will be called with an empty list). +/// - `ctx` -- [in, optional] Pointer to an optional context. Set to NULL if unused. +LIBSESSION_EXPORT void network_get_swarm( + network_object* network, + const char* swarm_pubkey_hex, + void (*callback)(network_service_node* nodes, size_t nodes_len, void*), + void* ctx); + +/// API: network/network_get_random_nodes +/// +/// Retrieves a number of random nodes from the snode pool. If the are no nodes in the pool a +/// new pool will be populated and the nodes will be retrieved from that. +/// +/// Inputs: +/// - `network` -- [in] Pointer to the network object +/// - 'count' - [in] the number of nodes to retrieve. +/// - 'callback' - [in] callback to be called with the retrieved nodes (in the case of an error +/// the callback will be called with an empty list). +/// - `ctx` -- [in, optional] Pointer to an optional context. Set to NULL if unused. +LIBSESSION_EXPORT void network_get_random_nodes( + network_object* network, + uint16_t count, + void (*callback)(network_service_node*, size_t, void*), + void* ctx); + +/// API: network/network_onion_response_callback_t +/// +/// Function pointer typedef for the callback function pointer given to +/// network_send_onion_request_to_snode_destination and +/// network_send_onion_request_to_server_destination. +/// +/// Fields: +/// - `success` -- true if the request was successful, false if it failed. +/// - `timeout` -- true if the request failed because of a timeout +/// - `status_code` -- the HTTP numeric status code of the request, e.g. 200 for OK +/// - `headers` -- the response headers, array of null-terminated C strings +/// - `header_values` -- the response header values, array of null-terminated C strings +/// - `headers_size` -- the number of `headers`/`header_values` +/// - `response` -- pointer to the beginning of the response body +/// - `response_size` -- length of the response body +/// - `ctx` -- the context pointer passed to the function that initiated the request. +typedef void (*network_onion_response_callback_t)( + bool success, + bool timeout, + int16_t status_code, + const char* const* headers, + const char* const* header_values, + size_t headers_size, + const char* response, + size_t response_size, + void* ctx); + +/// API: network/network_send_onion_request_to_snode_destination +/// +/// Sends a request via onion routing to the provided service node. +/// +/// Inputs: +/// - `network` -- [in] Pointer to the network object. +/// - `node` -- [in] address information about the service node the request should be sent to. +/// - `body` -- [in] data to send to the specified node. +/// - `body_size` -- [in] size of the `body`. +/// - `request_timeout_ms` -- [in] timeout in milliseconds to use for the request. This won't take +/// the path build into account so if the path build takes forever then this request will never +/// timeout. +/// - `request_and_path_build_timeout_ms` -- [in] timeout in milliseconds to use for the request and +/// path build (if required). This value takes presedence over `request_timeout_ms` if provided, +/// the request itself will be given a timeout of this value subtracting however long it took to +/// build the path. A value of `0` will be ignored and `request_timeout_ms` will be used instead. +/// - `callback` -- [in] callback to be called with the result of the request. +/// - `ctx` -- [in, optional] Pointer to an optional context to pass through to the callback. Set to +/// NULL if unused. +LIBSESSION_EXPORT void network_send_onion_request_to_snode_destination( + network_object* network, + const network_service_node node, + const unsigned char* body, + size_t body_size, + const char* swarm_pubkey_hex, + int64_t request_timeout_ms, + int64_t request_and_path_build_timeout_ms, + network_onion_response_callback_t callback, + void* ctx); + +/// API: network/network_send_onion_request_to_server_destination +/// +/// Sends a request via onion routing to the provided server. +/// +/// Inputs: +/// - `network` -- [in] Pointer to the network object. +/// - `server` -- [in] struct containing information about the server the request should be sent to. +/// - `body` -- [in] data to send to the specified endpoint. +/// - `body_size` -- [in] size of the `body`. +/// - `request_timeout_ms` -- [in] timeout in milliseconds to use for the request. This won't take +/// the path build into account so if the path build takes forever then this request will never +/// timeout. +/// - `request_and_path_build_timeout_ms` -- [in] timeout in milliseconds to use for the request and +/// path build (if required). This value takes presedence over `request_timeout_ms` if provided, +/// the request itself will be given a timeout of this value subtracting however long it took to +/// build the path. A value of `0` will be ignored and `request_timeout_ms` will be used instead. +/// - `callback` -- [in] callback to be called with the result of the request. +/// - `ctx` -- [in, optional] Pointer to an optional context to pass through to the callback. Set +/// to NULL if unused. +LIBSESSION_EXPORT void network_send_onion_request_to_server_destination( + network_object* network, + const network_server_destination server, + const unsigned char* body, + size_t body_size, + int64_t request_timeout_ms, + int64_t request_and_path_build_timeout_ms, + network_onion_response_callback_t callback, + void* ctx); + +/// API: network/network_upload_to_server +/// +/// Uploads a file to a server. +/// +/// Inputs: +/// - `network` -- [in] Pointer to the network object. +/// - `server` -- [in] struct containing information about the server the request should be sent to. +/// - `data` -- [in] data to upload to the file server. +/// - `data_len` -- [in] size of the `data`. +/// - `file_name` -- [in, optional] name of the file being uploaded. MUST be null terminated. +/// - `request_timeout_ms` -- [in] timeout in milliseconds to use for the request. This won't take +/// the path build into account so if the path build takes forever then this request will never +/// timeout. +/// - `request_and_path_build_timeout_ms` -- [in] timeout in milliseconds to use for the request and +/// path build (if required). This value takes presedence over `request_timeout_ms` if provided, +/// the request itself will be given a timeout of this value subtracting however long it took to +/// build the path. A value of `0` will be ignored and `request_timeout_ms` will be used instead. +/// - `callback` -- [in] callback to be called with the result of the request. +/// - `ctx` -- [in, optional] Pointer to an optional context to pass through to the callback. Set +/// to NULL if unused. +LIBSESSION_EXPORT void network_upload_to_server( + network_object* network, + const network_server_destination server, + const unsigned char* data, + size_t data_len, + const char* file_name, + int64_t request_timeout_ms, + int64_t request_and_path_build_timeout_ms, + network_onion_response_callback_t callback, + void* ctx); + +/// API: network/network_download_from_server +/// +/// Downloads a file from a server. +/// +/// Inputs: +/// - `network` -- [in] Pointer to the network object. +/// - `server` -- [in] struct containing information about file to be downloaded. +/// - `request_timeout_ms` -- [in] timeout in milliseconds to use for the request. This won't take +/// the path build into account so if the path build takes forever then this request will never +/// timeout. +/// - `request_and_path_build_timeout_ms` -- [in] timeout in milliseconds to use for the request and +/// path build (if required). This value takes presedence over `request_timeout_ms` if provided, +/// the request itself will be given a timeout of this value subtracting however long it took to +/// build the path. A value of `0` will be ignored and `request_timeout_ms` will be used instead. +/// - `callback` -- [in] callback to be called with the result of the request. +/// - `ctx` -- [in, optional] Pointer to an optional context to pass through to the callback. Set +/// to NULL if unused. +LIBSESSION_EXPORT void network_download_from_server( + network_object* network, + const network_server_destination server, + int64_t request_timeout_ms, + int64_t request_and_path_build_timeout_ms, + network_onion_response_callback_t callback, + void* ctx); + +/// API: network/network_get_client_version +/// +/// Retrieves the version information for the given platform. +/// +/// Inputs: +/// - `network` -- [in] Pointer to the network object. +/// - `platform` -- [in] the platform to retrieve the client version for. +/// - `ed25519_secret` -- [in] the users ed25519 secret key (used for blinded auth - 64 bytes). +/// - `request_timeout_ms` -- [in] timeout in milliseconds to use for the request. This won't take +/// the path build into account so if the path build takes forever then this request will never +/// timeout. +/// - `request_and_path_build_timeout_ms` -- [in] timeout in milliseconds to use for the request and +/// path build (if required). This value takes presedence over `request_timeout_ms` if provided, +/// the request itself will be given a timeout of this value subtracting however long it took to +/// build the path. A value of `0` will be ignored and `request_timeout_ms` will be used instead. +/// - `callback` -- [in] callback to be called with the result of the request. +/// - `ctx` -- [in, optional] Pointer to an optional context to pass through to the callback. Set +/// to NULL if unused. +LIBSESSION_EXPORT void network_get_client_version( + network_object* network, + CLIENT_PLATFORM platform, + const unsigned char* ed25519_secret, /* 64 bytes */ + int64_t request_timeout_ms, + int64_t request_and_path_build_timeout_ms, + network_onion_response_callback_t callback, + void* ctx); + +#ifdef __cplusplus +} +#endif diff --git a/include/session/session_network.hpp b/include/session/session_network.hpp new file mode 100644 index 00000000..f526ee21 --- /dev/null +++ b/include/session/session_network.hpp @@ -0,0 +1,755 @@ +#pragma once + +#include +#include +#include + +#include "onionreq/builder.hpp" +#include "onionreq/key_types.hpp" +#include "platform.hpp" +#include "session/random.hpp" +#include "types.hpp" + +namespace session::network { + +namespace fs = std::filesystem; + +using network_response_callback_t = std::function> headers, + std::optional response)>; + +enum class ConnectionStatus { + unknown, + connecting, + connected, + disconnected, +}; + +enum class PathType { + standard, + upload, + download, +}; + +using swarm_id_t = uint64_t; +constexpr swarm_id_t INVALID_SWARM_ID = std::numeric_limits::max(); + +struct service_node : public oxen::quic::RemoteAddress { + public: + std::vector storage_server_version; + swarm_id_t swarm_id; + + service_node() = delete; + + template + service_node( + std::string_view remote_pk, + std::vector storage_server_version, + swarm_id_t swarm_id, + Opt&&... opts) : + oxen::quic::RemoteAddress{remote_pk, std::forward(opts)...}, + storage_server_version{storage_server_version}, + swarm_id{swarm_id} {} + + template + service_node( + std::span remote_pk, + std::vector storage_server_version, + swarm_id_t swarm_id, + Opt&&... opts) : + oxen::quic::RemoteAddress{remote_pk, std::forward(opts)...}, + storage_server_version{storage_server_version}, + swarm_id{swarm_id} {} + + service_node(const service_node& obj) : + oxen::quic::RemoteAddress{obj}, + storage_server_version{obj.storage_server_version}, + swarm_id{obj.swarm_id} {} + service_node& operator=(const service_node& obj) { + storage_server_version = obj.storage_server_version; + swarm_id = obj.swarm_id; + oxen::quic::RemoteAddress::operator=(obj); + _copy_internals(obj); + return *this; + } + + auto operator<=>(const service_node& other) const = delete; + bool operator==(const service_node& other) const { + return RemoteAddress::operator==(other) && + storage_server_version == other.storage_server_version && swarm_id == other.swarm_id; + } +}; + +struct connection_info { + service_node node; + std::shared_ptr pending_requests; + std::shared_ptr conn; + std::shared_ptr stream; + + bool is_valid() const { return conn && stream && !stream->is_closing(); }; + bool has_pending_requests() const { return (pending_requests && (*pending_requests) > 0); }; + + void add_pending_request() { + if (!pending_requests) + pending_requests = std::make_shared(0); + (*pending_requests)++; + }; + + // This is weird but since we are modifying the shared_ptr we aren't mutating + // the object so it can be a const function + void remove_pending_request() const { + if (!pending_requests) + return; + (*pending_requests)--; + }; +}; + +struct onion_path { + std::string id; + connection_info conn_info; + std::vector nodes; + uint8_t failure_count; + + bool is_valid() const { return !nodes.empty() && conn_info.is_valid(); }; + bool has_pending_requests() const { return conn_info.has_pending_requests(); } + size_t num_pending_requests() const { + if (!conn_info.pending_requests) + return 0; + return (*conn_info.pending_requests); + } + + std::string to_string() const; + + bool contains_node(const service_node& sn) const; + + bool operator==(const onion_path& other) const { + // The `conn_info` and failure/timeout counts can be reset for a path in a number + // of situations so just use the nodes to determine if the paths match + return nodes == other.nodes; + } +}; + +namespace detail { + swarm_id_t pubkey_to_swarm_space(const session::onionreq::x25519_pubkey& pk); + std::vector>> generate_swarms( + std::vector nodes); + + std::optional node_for_destination(onionreq::network_destination destination); + + session::onionreq::x25519_pubkey pubkey_for_destination( + onionreq::network_destination destination); + +} // namespace detail + +struct request_info { + static request_info make( + onionreq::network_destination _dest, + std::optional> _original_body, + std::optional _swarm_pk, + std::chrono::milliseconds _request_timeout, + std::optional _request_and_path_build_timeout = std::nullopt, + PathType _type = PathType::standard, + std::optional _req_id = std::nullopt, + std::optional endpoint = "onion_req", + std::optional> _body = std::nullopt); + + enum class RetryReason { + none, + decryption_failure, + redirect, + redirect_swarm_refresh, + }; + + std::string request_id; + session::onionreq::network_destination destination; + std::string endpoint; + std::optional> body; + std::optional> original_body; + std::optional swarm_pubkey; + PathType path_type; + std::chrono::milliseconds request_timeout; + std::optional request_and_path_build_timeout; + std::chrono::system_clock::time_point creation_time = std::chrono::system_clock::now(); + + /// The reason we are retrying the request (if it's a retry). Generally only used for internal + /// purposes (like receiving a `421`) in order to prevent subsequent retries. + std::optional retry_reason{}; + + bool node_destination{detail::node_for_destination(destination).has_value()}; +}; + +class Network { + private: + const bool use_testnet; + const bool should_cache_to_disk; + const bool single_path_mode; + const fs::path cache_path; + + // Disk thread state + std::mutex snode_cache_mutex; // This guards all the below: + std::condition_variable snode_cache_cv; + bool has_pending_disk_write = false; + bool shut_down_disk_thread = false; + bool need_write = false; + bool need_clear_cache = false; + + // Values persisted to disk + std::optional seed_node_cache_size; + std::vector snode_cache; + std::chrono::system_clock::time_point last_snode_cache_update{}; + + std::thread disk_write_thread; + + // General values + bool suspended = false; + ConnectionStatus status; + + std::shared_ptr loop; + std::shared_ptr endpoint; + std::unordered_map> paths; + std::vector> paths_pending_drop; + std::vector unused_nodes; + std::unordered_map snode_failure_counts; + std::vector>> all_swarms; + std::unordered_map>> swarm_cache; + + // Snode refresh state + int snode_cache_refresh_failure_count = 0; + int in_progress_snode_cache_refresh_count = 0; + std::optional current_snode_cache_refresh_request_id; + std::vector> after_snode_cache_refresh; + std::optional> unused_snode_refresh_nodes; + std::shared_ptr>> snode_refresh_results; + + // First hop state + int connection_failures = 0; + std::deque unused_connections; + std::unordered_map in_progress_connections; + + // Path build state + int path_build_failures = 0; + std::deque path_build_queue; + std::unordered_map in_progress_path_builds; + + // Request state + bool has_scheduled_resume_queues = false; + std::optional request_timeout_id; + std::chrono::system_clock::time_point last_resume_queues_timestamp{}; + std::unordered_map>> + request_queue; + + public: + friend class TestNetwork; + friend class TestNetworkWrapper; + + // Hook to be notified whenever the network connection status changes. + std::function status_changed; + + // Hook to be notified whenever the onion request paths are updated. + std::function> paths)> paths_changed; + + // Constructs a new network with the given cache path and a flag indicating whether it should + // use testnet or mainnet, all requests should be made via a single Network instance. + Network(std::optional cache_path, + bool use_testnet, + bool single_path_mode, + bool pre_build_paths); + virtual ~Network(); + + /// API: network/suspend + /// + /// Suspends the network preventing any further requests from creating new connections and + /// paths. This function also calls the `close_connections` function. + void suspend(); + + /// API: network/resume + /// + /// Resumes the network allowing new requests to creating new connections and paths. + void resume(); + + /// API: network/close_connections + /// + /// Closes any currently active connections. + void close_connections(); + + /// API: network/clear_cache + /// + /// Clears the cached from memory and from disk (if a cache path was provided during + /// initialization). + void clear_cache(); + + /// API: network/snode_cache_size + /// + /// Retrieves the current size of the snode cache from memory (if a cache doesn't exist or + /// hasn't been loaded then this will return 0). + size_t snode_cache_size(); + + /// API: network/get_swarm + /// + /// Retrieves the swarm for the given pubkey. If there is already an entry in the cache for the + /// swarm then that will be returned, otherwise a network request will be made to retrieve the + /// swarm and save it to the cache. + /// + /// Inputs: + /// - 'swarm_pubkey' - [in] public key for the swarm. + /// - 'callback' - [in] callback to be called with the retrieved swarm (in the case of an error + /// the callback will be called with an empty list). + void get_swarm( + session::onionreq::x25519_pubkey swarm_pubkey, + std::function swarm)> callback); + + /// API: network/get_random_nodes + /// + /// Retrieves a number of random nodes from the snode pool. If the are no nodes in the pool a + /// new pool will be populated and the nodes will be retrieved from that. + /// + /// Inputs: + /// - 'count' - [in] the number of nodes to retrieve. + /// - 'callback' - [in] callback to be called with the retrieved nodes (in the case of an error + /// the callback will be called with an empty list). + void get_random_nodes( + uint16_t count, std::function nodes)> callback); + + /// API: network/send_onion_request + /// + /// Sends a request via onion routing to the provided service node or server destination. + /// + /// Inputs: + /// - `destination` -- [in] service node or server destination information. + /// - `body` -- [in] data to send to the specified destination. + /// - `swarm_pubkey` -- [in, optional] pubkey for the swarm the request is associated with. + /// Should be NULL if the request is not associated with a swarm. + /// - `handle_response` -- [in] callback to be called with the result of the request. + /// - `request_timeout` -- [in] timeout in milliseconds to use for the request. This won't take + /// the path build into account so if the path build takes forever then this request will never + /// timeout. + /// - `request_and_path_build_timeout` -- [in] timeout in milliseconds to use for the request + /// and path build (if required). This value takes presedence over `request_timeout` if + /// provided, the request itself will be given a timeout of this value subtracting however long + /// it took to build the path. + /// - 'type' - [in] the type of paths to send the request across. + void send_onion_request( + onionreq::network_destination destination, + std::optional> body, + std::optional swarm_pubkey, + network_response_callback_t handle_response, + std::chrono::milliseconds request_timeout, + std::optional request_and_path_build_timeout = std::nullopt, + PathType type = PathType::standard); + + /// API: network/upload_file_to_server + /// + /// Uploads a file to a given server destination. + /// + /// Inputs: + /// - 'data' - [in] the data to be uploaded to a server. + /// - `server` -- [in] the server destination to upload the file to. + /// - `file_name` -- [in, optional] optional name to use for the file. + /// - `request_timeout` -- [in] timeout in milliseconds to use for the request. This won't take + /// the path build into account so if the path build takes forever then this request will never + /// timeout. + /// - `request_and_path_build_timeout` -- [in] timeout in milliseconds to use for the request + /// and path build (if required). This value takes presedence over `request_timeout` if + /// provided, the request itself will be given a timeout of this value subtracting however long + /// it took to build the path. + /// - `handle_response` -- [in] callback to be called with the result of the request. + void upload_file_to_server( + std::vector data, + onionreq::ServerDestination server, + std::optional file_name, + network_response_callback_t handle_response, + std::chrono::milliseconds request_timeout, + std::optional request_and_path_build_timeout = std::nullopt); + + /// API: network/download_file + /// + /// Download a file from a given server destination. + /// + /// Inputs: + /// - `server` -- [in] the server destination to download the file from. + /// - `request_timeout` -- [in] timeout in milliseconds to use for the request. This won't take + /// the path build into account so if the path build takes forever then this request will never + /// timeout. + /// - `request_and_path_build_timeout` -- [in] timeout in milliseconds to use for the request + /// and path build (if required). This value takes presedence over `request_timeout` if + /// provided, the request itself will be given a timeout of this value subtracting however long + /// it took to build the path. + /// - `handle_response` -- [in] callback to be called with the result of the request. + void download_file( + onionreq::ServerDestination server, + network_response_callback_t handle_response, + std::chrono::milliseconds request_timeout, + std::optional request_and_path_build_timeout = std::nullopt); + + /// API: network/download_file + /// + /// Convenience function to download a file from a given url and x25519 pubkey combination. + /// Calls through to the above `download_file` function after constructing a server destination + /// from the provided values. + /// + /// Inputs: + /// - `download_url` -- [in] the url to download the file from. + /// - `x25519_pubkey` -- [in] the server destination to download the file from. + /// - `timeout` -- [in] timeout in milliseconds to use for the request. + /// - `request_timeout` -- [in] timeout in milliseconds to use for the request. This won't take + /// the path build into account so if the path build takes forever then this request will never + /// timeout. + /// - `request_and_path_build_timeout` -- [in] timeout in milliseconds to use for the request + /// and path build (if required). This value takes presedence over `request_timeout` if + /// provided, the request itself will be given a timeout of this value subtracting however long + /// it took to build the path. + /// - `handle_response` -- [in] callback to be called with the result of the request. + void download_file( + std::string_view download_url, + onionreq::x25519_pubkey x25519_pubkey, + network_response_callback_t handle_response, + std::chrono::milliseconds request_timeout, + std::optional request_and_path_build_timeout = std::nullopt); + + /// API: network/get_client_version + /// + /// Retrieves the version information for the given platform. + /// + /// Inputs: + /// - `platform` -- [in] the platform to retrieve the client version for. + /// - `seckey` -- [in] the users ed25519 secret key (to generated blinded auth). + /// - `request_timeout` -- [in] timeout in milliseconds to use for the request. This won't take + /// the path build into account so if the path build takes forever then this request will never + /// timeout. + /// - `request_and_path_build_timeout` -- [in] timeout in milliseconds to use for the request + /// and path build (if required). This value takes presedence over `request_timeout` if + /// provided, the request itself will be given a timeout of this value subtracting however long + /// it took to build the path. + /// - `handle_response` -- [in] callback to be called with the result of the request. + void get_client_version( + Platform platform, + onionreq::ed25519_seckey seckey, + network_response_callback_t handle_response, + std::chrono::milliseconds request_timeout, + std::optional request_and_path_build_timeout = std::nullopt); + + private: + /// API: network/all_path_ips + /// + /// Internal function to retrieve all of the node ips current used in paths + std::vector all_path_ips() const { + std::vector result; + + for (const auto& [path_type, paths_for_type] : paths) + for (const auto& path : paths_for_type) + for (const auto& node : path.nodes) + result.emplace_back(node.to_ipv4()); + + return result; + }; + + /// API: network/update_disk_cache_throttled + /// + /// Function which can be used to notify the disk write thread that a write can be performed. + /// This function has a very basic throttling mechanism where it triggers the write a small + /// delay after it is called, any subsequent calls to the function within the same period will + /// be ignored. This is done to avoid excessive disk writes which probably aren't needed for + /// the cached network data. + virtual void update_disk_cache_throttled(bool force_immediate_write = false); + + /// API: network/disk_write_thread_loop + /// + /// Body of the disk writer which runs until signalled to stop. This is intended to run in its + /// own thread. The thread monitors a number of private variables and persists the snode pool + /// and swarm caches to disk if a `cache_path` was provided during initialization. + void disk_write_thread_loop(); + + /// API: network/load_cache_from_disk + /// + /// Loads the snode pool and swarm caches from disk if a `cache_path` was provided and cached + /// data exists. + void load_cache_from_disk(); + + /// API: network/_close_connections + /// + /// Triggered via the close_connections function but actually contains the logic to clear out + /// paths, requests and connections. This function is not thread safe so should should be + /// called with that in mind. + void _close_connections(); + + /// API: network/update_status + /// + /// Internal function to update the connection status and trigger the `status_changed` hook if + /// provided, this method ignores invalid or unchanged status changes. + /// + /// Inputs: + /// - 'updated_status' - [in] the updated connection status. + void update_status(ConnectionStatus updated_status); + + /// API: network/retry_delay + /// + /// A function which generates an exponential delay to wait before retrying a request/action + /// based on the provided failure count. + /// + /// Inputs: + /// - 'num_failures' - [in] the number of times the request has already failed. + /// - 'max_delay' - [in] the maximum amount of time to delay for. + virtual std::chrono::milliseconds retry_delay( + int num_failures, + std::chrono::milliseconds max_delay = std::chrono::milliseconds{5000}); + + /// API: network/get_endpoint + /// + /// Retrieves or creates a new endpoint pointer. + std::shared_ptr get_endpoint(); + + /// API: network/min_snode_cache_size + /// + /// When talking to testnet it's occassionally possible for the cache size to be smaller than + /// the `min_snode_cache_count` value (which would result in an endless loop re-fetching the + /// node cache) so instead this function will return the smaller of the two if we've done a + /// fetch from a seed node. + size_t min_snode_cache_size() const; + + /// API: network/get_unused_nodes + /// + /// Retrieves a list of all nodes in the cache which are currently unused (ie. not present in an + /// exising or pending path, connection or request). + /// + /// Outputs: + /// - The list of unused nodes. + std::vector get_unused_nodes(); + + /// API: network/establish_connection + /// + /// Establishes a connection to the target node and triggers the callback once the connection is + /// established (or closed in case it fails). + /// + /// Inputs: + /// - 'id' - [in] id for the request or path build which triggered the call. + /// - `target` -- [in] the target service node to connect to. + /// - `timeout` -- [in, optional] optional timeout for the request, if NULL the + /// `quic::DEFAULT_HANDSHAKE_TIMEOUT` will be used. + /// - `callback` -- [in] callback to be called with connection info once the connection is + /// established or fails. + void establish_connection( + std::string id, + service_node target, + std::optional timeout, + std::function error)> callback); + + /// API: network/establish_and_store_connection + /// + /// Establishes a connection to a random unused node and stores it in the `unused_connections` + /// list. + /// + /// Inputs: + /// - 'path_id' - [in] id for the path build which triggered the call. + virtual void establish_and_store_connection(std::string path_id); + + /// API: network/refresh_snode_cache_complete + /// + /// This function will be called from either `refresh_snode_cache` or + /// `refresh_snode_cache_from_seed_nodes` and will actually update the state and persist the + /// updated cache to disk. + /// + /// Inputs: + /// - 'nodes' - [in] the nodes to use as the updated cache. + void refresh_snode_cache_complete(std::vector nodes); + + /// API: network/refresh_snode_cache_from_seed_nodes + /// + /// This function refreshes the snode cache for a random seed node. Unlike the + /// `refresh_snode_cache` function this will update the cache with the response from a single + /// seed node since it's a trusted source. + /// + /// Inputs: + /// - 'request_id' - [in] id for an existing refresh_snode_cache request. + /// - 'reset_unused_nodes' - [in] flag to indicate whether this should reset the unused nodes + /// before kicking off the request. + virtual void refresh_snode_cache_from_seed_nodes( + std::string request_id, bool reset_unused_nodes); + + /// API: network/refresh_snode_cache + /// + /// This function refreshes the snode cache. If the current cache is to small (or not present) + /// this will trigger the above `refresh_snode_cache_from_seed_nodes` function, otherwise it + /// will randomly pick a number of nodes from the existing cache and refresh the cache from the + /// intersection of the results. + /// + /// Inputs: + /// - 'existing_request_id' - [in, optional] id for an existing refresh_snode_cache request. + virtual void refresh_snode_cache(std::optional existing_request_id = std::nullopt); + + /// API: network/build_path + /// + /// Build a new onion request path for the specified type. If there are no existing connections + /// this will open a new connection to a random service nodes in the snode cache. + /// + /// Inputs: + /// - 'path_id' - [in] id for the new path. + /// - `path_type` -- [in] the type of path to build. + virtual void build_path(std::string path_id, PathType path_type); + + /// API: network/find_valid_path + /// + /// Find a random path from the provided paths which is valid for the provided request. Note: + /// if the Network is setup in `single_path_mode` then the path returned may include the + /// destination for the request. + /// + /// Inputs: + /// - `info` -- [in] request to select a path for. + /// - `paths` -- [in] paths to select from. + /// + /// Outputs: + /// - The possible path, if found. + virtual std::optional find_valid_path( + const request_info info, const std::vector paths); + + /// API: network/build_path_if_needed + /// + /// Triggers a path build for the specified type if the total current or pending paths is below + /// the minimum threshold for the given type. Note: This may result in more paths than the + /// minimum threshold being built in order to avoid a situation where a request may never get + /// sent due to it's destination being present in the existing path(s) for the type. + /// + /// Inputs: + /// - `path_type` -- [in] the type of path to be built. + /// - `found_path` -- [in] flag indicating whether a valid path was found by calling + /// `find_valid_path` above. + virtual void build_path_if_needed(PathType path_type, bool found_valid_path); + + /// API: network/get_service_nodes + /// + /// Retrieves all or a random subset of service nodes from the given node. + /// + /// Inputs: + /// - 'request_id' - [in] id for the request which triggered the call. + /// - `conn_info` -- [in] the connection info to retrieve service nodes from. + /// - `limit` -- [in, optional] the number of service nodes to retrieve. + /// - `callback` -- [in] callback to be triggered once we receive nodes. NOTE: If an error + /// occurs an empty list and an error will be provided. + void get_service_nodes( + std::string request_id, + connection_info conn_info, + std::optional limit, + std::function nodes, std::optional error)> + callback); + + /// API: network/check_request_queue_timeouts + /// + /// Checks if any of the requests in the request queue have timed out (and fails them if so). + /// + /// Inputs: + /// - 'request_timeout_id' - [in] id for the timeout loop to prevent multiple loops from being + /// scheduled. + virtual void check_request_queue_timeouts( + std::optional request_timeout_id = std::nullopt); + + /// API: network/send_request + /// + /// Send a request via the network. + /// + /// Inputs: + /// - `info` -- [in] wrapper around all of the information required to send a request. + /// - `conn` -- [in] connection information used to send the request. + /// - `handle_response` -- [in] callback to be called with the result of the request. + void send_request( + request_info info, connection_info conn, network_response_callback_t handle_response); + + /// API: network/_send_onion_request + /// + /// Internal function invoked by ::send_onion_request after request_info construction + virtual void _send_onion_request( + request_info info, network_response_callback_t handle_response); + + /// API: network/process_v3_onion_response + /// + /// Processes a v3 onion request response. + /// + /// Inputs: + /// - `builder` -- [in] the builder that was used to build the onion request. + /// - `response` -- [in] the response data returned from the destination. + /// + /// Outputs: + /// - A tuple containing the status code, headers and body of the decrypted onion request + /// response. + virtual std::tuple< + int16_t, + std::vector>, + std::optional> + process_v3_onion_response(session::onionreq::Builder builder, std::string response); + + /// API: network/process_v4_onion_response + /// + /// Processes a v4 onion request response. + /// + /// Inputs: + /// - `builder` -- [in] the builder that was used to build the onion request. + /// - `response` -- [in] the response data returned from the destination. + /// + /// Outputs: + /// - A tuple containing the status code, headers and body of the decrypted onion request + /// response. + virtual std::tuple< + int16_t, + std::vector>, + std::optional> + process_v4_onion_response(session::onionreq::Builder builder, std::string response); + + /// API: network/validate_response + /// + /// Processes a quic response to extract the status code and body or throw if it errored or + /// received a non-successful status code. + /// + /// Inputs: + /// - `resp` -- [in] the quic response. + /// - `is_bencoded` -- [in] flag indicating whether the response will be bencoded or JSON. + /// + /// Returns: + /// - `std::pair` -- the status code and response body (for a bencoded + /// response this is just the direct response body from quic as it simplifies consuming the + /// response elsewhere). + std::pair validate_response(oxen::quic::message resp, bool is_bencoded); + + /// API: network/drop_path_when_empty + /// + /// Flags a path to be dropped once all pending requests have finished. + /// + /// Inputs: + /// - `id` -- [in] id the request or path which triggered the path drop (if the id is a path_id + /// then the drop was triggered by the connection being dropped). + /// - `path_type` -- [in] the type of path to build. + /// - `path` -- [in] the path to be dropped. + void drop_path_when_empty(std::string id, PathType path_type, onion_path path); + + /// API: network/clear_empty_pending_path_drops + /// + /// Iterates through all paths flagged to be dropped and actually drops any which are no longer + /// valid or have no more pending requests. + void clear_empty_pending_path_drops(); + + /// API: network/handle_errors + /// + /// Processes a non-success response to automatically perform any standard operations based on + /// the errors returned from the service node network (ie. updating the service node cache, + /// dropping nodes and/or onion request paths). + /// + /// Inputs: + /// - `info` -- [in] the information for the request that was made. + /// - `conn_info` -- [in] the connection info for the request that failed. + /// - `timeout` -- [in, optional] flag indicating whether the request timed out. + /// - `status_code` -- [in] the status code returned from the network. + /// - `headers` -- [in] the response headers returned from the network. + /// - `response` -- [in, optional] response data returned from the network. + /// - `handle_response` -- [in, optional] callback to be called with updated response + /// information after processing the error. + virtual void handle_errors( + request_info info, + connection_info conn_info, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response, + std::optional handle_response); +}; + +} // namespace session::network diff --git a/include/session/sodium_array.hpp b/include/session/sodium_array.hpp new file mode 100644 index 00000000..a0a52de0 --- /dev/null +++ b/include/session/sodium_array.hpp @@ -0,0 +1,231 @@ +#pragma once + +#include "util.hpp" + +namespace session { + +// Calls sodium_malloc for secure allocation; throws a std::bad_alloc on allocation failure +void* sodium_buffer_allocate(size_t size); +// Frees a pointer constructed with sodium_buffer_allocate. Does nothing if `p` is nullptr. +void sodium_buffer_deallocate(void* p); +// Calls sodium_memzero to zero a buffer +void sodium_zero_buffer(void* ptr, size_t size); + +// Works similarly to a unique_ptr, but allocations and free go via libsodium (which is slower, but +// more secure for sensitive data). +template +struct sodium_ptr { + private: + T* x; + + public: + sodium_ptr() : x{nullptr} {} + sodium_ptr(std::nullptr_t) : sodium_ptr{} {} + ~sodium_ptr() { reset(x); } + + // Allocates and constructs a new `T` in-place, forwarding any given arguments to the `T` + // constructor. If this sodium_ptr already has an object, `reset()` is first called implicitly + // to destruct and deallocate the existing object. + template + T& emplace(Args&&... args) { + if (x) + reset(); + x = static_cast(sodium_buffer_allocate(sizeof(T))); + new (x) T(std::forward(args)...); + return *x; + } + + void reset() { + if (x) { + x->~T(); + sodium_buffer_deallocate(x); + x = nullptr; + } + } + void operator=(std::nullptr_t) { reset(); } + + T& operator*() { return *x; } + const T& operator*() const { return *x; } + + T* operator->() { return x; } + const T* operator->() const { return x; } + + explicit operator bool() const { return x != nullptr; } +}; + +// Wrapper around a type that uses `sodium_memzero` to zero the container on destruction; may only +// be used with trivially destructible types. +template >> +struct sodium_cleared : T { + using T::T; + + ~sodium_cleared() { sodium_zero_buffer(this, sizeof(*this)); } +}; + +template +using cleared_array = sodium_cleared>; + +using cleared_uc32 = cleared_array<32>; +using cleared_uc64 = cleared_array<64>; + +// This is an optional (i.e. can be empty) fixed-size (at construction) buffer that does allocation +// and freeing via libsodium. It is slower and heavier than a regular allocation type but takes +// extra precautions, intended for storing sensitive values. +template +struct sodium_array { + private: + T* buf; + size_t len; + + public: + // Default constructor: makes an empty object (that is, has no buffer and has `.size()` of 0). + sodium_array() : buf{nullptr}, len{0} {} + + // Constructs an array with a given size, default-constructing the individual elements. + template >> + explicit sodium_array(size_t length) : + buf{length == 0 ? nullptr + : static_cast(sodium_buffer_allocate(length * sizeof(T)))}, + len{0} { + + if (length > 0) { + if constexpr (std::is_trivial_v) { + std::memset(buf, 0, length * sizeof(T)); + len = length; + } else if constexpr (std::is_nothrow_default_constructible_v) { + for (; len < length; len++) + new (buf[len]) T(); + } else { + try { + for (; len < length; len++) + new (buf[len]) T(); + } catch (...) { + reset(); + throw; + } + } + } + } + + ~sodium_array() { reset(); } + + // Moveable: ownership is transferred to the new object and the old object becomes empty. + sodium_array(sodium_array&& other) : buf{other.buf}, len{other.len} { + other.buf = nullptr; + other.len = 0; + } + sodium_array& operator=(sodium_array&& other) { + sodium_buffer_deallocate(buf); + buf = other.buf; + len = other.len; + other.buf = nullptr; + other.len = 0; + } + + // Non-copyable + sodium_array(const sodium_array&) = delete; + sodium_array& operator=(const sodium_array&) = delete; + + // Destroys the held array; after destroying elements the allocated space is overwritten with + // 0s before being deallocated. + void reset() { + if (buf) { + if constexpr (!std::is_trivially_destructible_v) + while (len > 0) + buf[--len].~T(); + + sodium_buffer_deallocate(buf); + } + buf = nullptr; + len = 0; + } + + // Calls reset() to destroy the current value (if any) and then allocates a new + // default-constructed one of the given size. + template >> + void reset(size_t length) { + reset(); + if (length > 0) { + buf = static_cast(sodium_buffer_allocate(length * sizeof(T))); + if constexpr (std::is_trivial_v) { + std::memset(buf, 0, length * sizeof(T)); + len = length; + } else { + for (; len < length; len++) + new (buf[len]) T(); + } + } + } + + // Loads the array from a pointer and size; this first resets a value (if present), allocates a + // new array of the given size, the copies the given value(s) into the new buffer. T must be + // copyable. This is *not* safe to use if `buf` points into the currently allocated data. + template >> + void load(const T* data, size_t length) { + reset(length); + if (length == 0) + return; + + if constexpr (std::is_trivially_copyable_v) + std::memcpy(buf, data, sizeof(T) * length); + else + for (; len < length; len++) + new (buf[len]) T(data[len]); + } + + const T& operator[](size_t i) const { + assert(i < len); + return buf[i]; + } + T& operator[](size_t i) { + assert(i < len); + return buf[i]; + } + + T* data() { return buf; } + const T* data() const { return buf; } + + size_t size() const { return len; } + bool empty() const { return len == 0; } + explicit operator bool() const { return !empty(); } + + T* begin() { return buf; } + const T* begin() const { return buf; } + T* end() { return buf + len; } + const T* end() const { return buf + len; } + + using difference_type = ptrdiff_t; + using value_type = T; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::random_access_iterator_tag; +}; + +// sodium Allocator wrapper; this allocates/frees via libsodium, which is designed for dealing with +// sensitive data. It is as a result slower and has more overhead than a standard allocator and +// intended for use with a container (such as std::vector) when storing keys. +template +struct sodium_allocator { + using value_type = T; + + [[nodiscard]] static T* allocate(std::size_t n) { + return static_cast(sodium_buffer_allocate(n * sizeof(T))); + } + + static void deallocate(T* p, std::size_t) { sodium_buffer_deallocate(p); } + + template + bool operator==(const sodium_allocator&) const noexcept { + return true; + } + template + bool operator!=(const sodium_allocator&) const noexcept { + return false; + } +}; + +/// Vector that uses sodium's secure (but heavy) memory allocations +template +using sodium_vector = std::vector>; + +} // namespace session diff --git a/include/session/types.hpp b/include/session/types.hpp index d63fe470..2f361c49 100644 --- a/include/session/types.hpp +++ b/include/session/types.hpp @@ -1,18 +1,13 @@ #pragma once #include +#include #include #include +#include -namespace session { - -using ustring = std::basic_string; -using ustring_view = std::basic_string_view; - -namespace config { +namespace session { namespace config { using seqno_t = std::int64_t; -} // namespace config - -} // namespace session +}} // namespace session::config diff --git a/include/session/util.hpp b/include/session/util.hpp index 6cfa77e2..2cd85840 100644 --- a/include/session/util.hpp +++ b/include/session/util.hpp @@ -1,27 +1,121 @@ #pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "types.hpp" namespace session { +using namespace oxenc; + +// Helper functions to convert to/from spans +template +inline std::span as_span(const std::span& sp) { + return {reinterpret_cast(sp.data()), sp.size()}; +} + +template +inline std::span to_span(const T& c) { + return {reinterpret_cast(c.data()), c.size()}; +} + +template +inline std::span to_span(const char (&literal)[N]) { + return {reinterpret_cast(literal), N - 1}; +} + +template + requires(!oxenc::bt_input_string) +inline std::span to_span(const Container& c) { + return {reinterpret_cast(c.data()), c.size()}; +} + +// Helper functions to convert container types +template +inline OutContainer convert(const InContainer& in) { + using out_value_type = typename OutContainer::value_type; + auto begin = reinterpret_cast(in.data()); + return OutContainer(begin, begin + in.size()); +} + +template +inline std::vector to_vector(std::span sp) { + return convert>(sp); +} + +template +inline std::vector to_vector(const T& c) { + return convert>(to_span(c)); +} + +template +inline std::vector to_vector(const std::array& arr) { + return convert>(arr); +} + +template + requires(!oxenc::bt_input_string) +inline std::vector to_vector(const Container& c) { + return convert>(to_span(c)); +} + +template +inline std::array to_array(std::span sp) { + std::array result{}; + std::copy_n( + reinterpret_cast(sp.data()), + std::min(N, sp.size()), + result.begin()); + return result; +} + +template +inline std::string to_string(const Container& c) { + return convert(c); +} + +template +inline std::string_view to_string_view(const Container& c) { + return {reinterpret_cast(c.data()), c.size()}; +} + // Helper function to go to/from char pointers to unsigned char pointers: -inline const unsigned char* to_unsigned(const char* x) { +template +inline const unsigned char* to_unsigned(const Char* x) { return reinterpret_cast(x); } -inline unsigned char* to_unsigned(char* x) { +template +inline unsigned char* to_unsigned(Char* x) { return reinterpret_cast(x); } -inline const char* from_unsigned(const unsigned char* x) { - return reinterpret_cast(x); +inline const unsigned char* to_unsigned(const std::byte* x) { + return reinterpret_cast(x); +} +inline unsigned char* to_unsigned(std::byte* x) { + return reinterpret_cast(x); } -inline char* from_unsigned(unsigned char* x) { - return reinterpret_cast(x); +// These do nothing, but having them makes template metaprogramming easier: +inline const unsigned char* to_unsigned(const unsigned char* x) { + return x; } -// Helper function to switch between string_view and ustring_view -inline ustring_view to_unsigned_sv(std::string_view v) { - return {to_unsigned(v.data()), v.size()}; +inline unsigned char* to_unsigned(unsigned char* x) { + return x; } -inline std::string_view from_unsigned_sv(ustring_view v) { - return {from_unsigned(v.data()), v.size()}; + +inline uint64_t get_timestamp() { + return std::chrono::steady_clock::now().time_since_epoch().count(); } /// Returns true if the first string is equal to the second string, compared case-insensitively. @@ -32,13 +126,135 @@ inline bool string_iequal(std::string_view s1, std::string_view s2) { }); } -// C++20 starts_/ends_with backport -inline constexpr bool starts_with(std::string_view str, std::string_view prefix) { - return str.size() >= prefix.size() && str.substr(prefix.size()) == prefix; +using uc32 = std::array; +using uc33 = std::array; +using uc64 = std::array; + +/// Takes a container of string-like binary values and returns a vector of unsigned char spans +/// viewing those values. This can be used on a container of any type with a `.data()` and a +/// `.size()` where `.data()` is a one-byte value pointer; std::string, std::string_view, +/// std::vector, std::span, etc. apply, as does std::array +/// of 1-byte char types. +/// +/// This is useful in various libsession functions that require such a vector. Note that the +/// returned vector's views are valid only as the original container remains alive; this is +/// typically used inline rather than stored, such as: +/// +/// session::function_taking_a_view_vector(session::to_view_vector(mydata)); +/// +/// There are two versions of this: the first takes a generic iterator pair; the second takes a +/// single container. +template +std::vector> to_view_vector(It begin, It end) { + std::vector> vec; + vec.reserve(std::distance(begin, end)); + for (; begin != end; ++begin) { + if constexpr (std::is_same_v, char*>) // C strings + vec.emplace_back(*begin); + else { + static_assert( + sizeof(*begin->data()) == 1, + "to_view_vector can only be used with containers of string-like types of " + "1-byte characters"); + vec.emplace_back(reinterpret_cast(begin->data()), begin->size()); + } + } + return vec; +} + +template +std::vector> to_view_vector(const Container& c) { + return to_view_vector(c.begin(), c.end()); +} + +/// Splits a string on some delimiter string and returns a vector of string_view's pointing into the +/// pieces of the original string. The pieces are valid only as long as the original string remains +/// valid. Leading and trailing empty substrings are not removed. If delim is empty you get back a +/// vector of string_views each viewing one character. If `trim` is true then leading and trailing +/// empty values will be suppressed. +/// +/// auto v = split("ab--c----de", "--"); // v is {"ab", "c", "", "de"} +/// auto v = split("abc", ""); // v is {"a", "b", "c"} +/// auto v = split("abc", "c"); // v is {"ab", ""} +/// auto v = split("abc", "c", true); // v is {"ab"} +/// auto v = split("-a--b--", "-"); // v is {"", "a", "", "b", "", ""} +/// auto v = split("-a--b--", "-", true); // v is {"a", "", "b"} +/// +std::vector split( + std::string_view str, std::string_view delim, bool trim = false); + +/// Returns protocol, host, port, path. Port can be empty; throws on unparseable values. protocol +/// and host get normalized to lower-case. Port will be null if not present in the URL, or if set +/// to the default for the protocol. Path can be empty (a single optional `/` after the domain will +/// be ignored). +std::tuple, std::optional> parse_url( + std::string_view url); + +/// Truncates a utf-8 encoded string to at most `n` bytes long, but with care as to not truncate in +/// the middle of a unicode codepoint. If the `n` length would shorten the string such that it +/// terminates in the middle of a utf-8 encoded unicode codepoint then the string is shortened +/// further to not include the sliced unicode codepoint. +/// +/// For example, "happy 🎂🎂🎂!!" in utf8 encoding is 20 bytes long: +/// "happy \xf0\x9f\x8e\x82\xf0\x9f\x8e\x82\xf0\x9f\x8e\x82!!", that is: +/// - "happy " (6 bytes) +/// - 🎂 = 0xf0 0x9f 0x8e 0x82 (12 bytes = 3 × 4 bytes each) +/// - "!!" (2 bytes) +/// Truncating this to different lengths results in: +/// - 20, 21, or higher - the 20-byte full string +/// - 19: "happy 🎂🎂🎂!" +/// - 18: "happy 🎂🎂🎂" +/// - 17: "happy 🎂🎂" (14 bytes) +/// - 16, 15, 14: same result as 17 +/// - 13, 12, 11, 10: "happy 🎂" +/// - 9, 8, 7, 6: "happy " +/// - 5: "happy" +/// - 4: "happ" +/// - 3: "hap" +/// - 2: "ha" +/// - 1: "a" +/// - 0: "" +/// +/// This function is *not* (currently) aware of unicode "characters", but merely codepoints (because +/// grapheme clusters get incredibly complicated). This is only designed to prevent invalid utf8 +/// encodings. For example, the pair 🇦🇺 (REGIONAL INDICATOR SYMBOL LETTER A, REGIONAL INDICATOR +/// SYMBOL LETTER U) is often rendered as a single Australian flag, but could get chopped here into +/// just 🇦 (REGIONAL INDICATOR SYMBOL LETTER A) rather than removing the getting split in the middle +/// of the pair, which would show up as a decorated A rather than an Australian flag. Another +/// example, é (LATIN SMALL LETTER E, COMBINING ACUTE ACCENT) could get chopped between the e and +/// the accent modifier, and end up as just "e" in the truncated string. +/// +inline std::string utf8_truncate(std::string val, size_t n) { + if (val.size() <= n) + return val; + // The *first* char in a utf8 sequence is either: + // 0b0....... -- single byte encoding, for values up to 0x7f (ascii) + // 0b11...... -- multi-byte encoding for values >= 0x80; the number of sequential high bit 1's + // in the first character indicate the sequence length (e.g. 0b1110.... starts a 3-byte + // sequence). In our birthday cake encoding, the first byte is \xf0 == 0b11110000, and so it is + // a 4-byte sequence. + // + // That leaves 0x10...... bytes as continuation bytes, each one holding 6 bits of the unicode + // codepoint, in big endian order, so our birthday cake (in bits): 0b11110000 0b10011111 + // 0b10001110 0b10000010 is the unicode value 0b000 011111 001110 000010 == 0x1f382 == U+1F382: + // BIRTHDAY CAKE). + // + // To prevent slicing, then, we just have to ensure the the first byte after the slice point is + // *not* a continuation byte (and therefore is either a plain ascii character codepoint, or is + // the start of a multi-character codepoint). + while (n > 0 && (val[n] & 0b1100'0000) == 0b1000'0000) + --n; + + val.resize(n); + return val; } -inline constexpr bool end_with(std::string_view str, std::string_view suffix) { - return str.size() >= suffix.size() && str.substr(str.size() - suffix.size()) == suffix; +// Helper function to transform a timestamp provided in seconds, milliseconds or microseconds to +// seconds +inline int64_t to_epoch_seconds(int64_t timestamp) { + return timestamp > 9'000'000'000'000 ? timestamp / 1'000'000 + : timestamp > 9'000'000'000 ? timestamp / 1'000 + : timestamp; } } // namespace session diff --git a/include/session/xed25519.h b/include/session/xed25519.h index 5348dafa..4963ce89 100644 --- a/include/session/xed25519.h +++ b/include/session/xed25519.h @@ -4,28 +4,32 @@ extern "C" { #endif +#include + +#include "export.h" + /// XEd25519-signed a message given a curve25519 privkey and message. Writes the 64-byte signature /// to `sig` on success and returns 0. Returns non-zero on failure. -__attribute__((warn_unused_result)) int session_xed25519_sign( +LIBSESSION_EXPORT bool session_xed25519_sign( unsigned char* signature /* 64 byte buffer */, const unsigned char* curve25519_privkey /* 32 bytes */, const unsigned char* msg, - const unsigned int msg_len); + size_t msg_len); /// Verifies an XEd25519-signed message given a 64-byte signature, 32-byte curve25519 pubkey, and /// message. Returns 0 if the signature verifies successfully, non-zero on failure. -__attribute__((warn_unused_result)) int session_xed25519_verify( +LIBSESSION_EXPORT bool session_xed25519_verify( const unsigned char* signature /* 64 bytes */, const unsigned char* pubkey /* 32-bytes */, const unsigned char* msg, - const unsigned int msg_len); + size_t msg_len); /// Given a curve25519 pubkey, this writes the associated XEd25519-derived Ed25519 pubkey into /// ed25519_pubkey. Note, however, that there are *two* possible Ed25519 pubkeys that could result /// in a given curve25519 pubkey: this always returns the positive value. You can get the other /// possibility (the negative) by flipping the sign bit, i.e. `returned_pubkey[31] |= 0x80`. /// Returns 0 on success, non-0 on failure. -__attribute__((warn_unused_result)) int session_xed25519_pubkey( +LIBSESSION_EXPORT bool session_xed25519_pubkey( unsigned char* ed25519_pubkey /* 32-byte output buffer */, const unsigned char* curve25519_pubkey /* 32 bytes */); diff --git a/include/session/xed25519.hpp b/include/session/xed25519.hpp index 9889113c..1cedd9ca 100644 --- a/include/session/xed25519.hpp +++ b/include/session/xed25519.hpp @@ -1,24 +1,26 @@ #pragma once + #include +#include #include #include +#include namespace session::xed25519 { -using ustring_view = std::basic_string_view; - /// XEd25519-signs a message given the curve25519 privkey and message. std::array sign( - ustring_view curve25519_privkey /* 32 bytes */, ustring_view msg); + std::span curve25519_privkey /* 32 bytes */, + std::span msg); /// "Softer" version that takes and returns strings of regular chars std::string sign(std::string_view curve25519_privkey /* 32 bytes */, std::string_view msg); /// Verifies a curve25519 message allegedly signed by the given curve25519 pubkey [[nodiscard]] bool verify( - ustring_view signature /* 64 bytes */, - ustring_view curve25519_pubkey /* 32 bytes */, - ustring_view msg); + std::span signature /* 64 bytes */, + std::span curve25519_pubkey /* 32 bytes */, + std::span msg); /// "Softer" version that takes strings of regular chars [[nodiscard]] bool verify( @@ -29,10 +31,24 @@ std::string sign(std::string_view curve25519_privkey /* 32 bytes */, std::string /// Given a curve25519 pubkey, this returns the associated XEd25519-derived Ed25519 pubkey. Note, /// however, that there are *two* possible Ed25519 pubkeys that could result in a given curve25519 /// pubkey: this always returns the positive value. You can get the other possibility (the -/// negative) by flipping the sign bit, i.e. `returned_pubkey[31] |= 0x80`. -std::array pubkey(ustring_view curve25519_pubkey); +/// negative) by setting the sign bit, i.e. `returned_pubkey[31] |= 0x80`. +std::array pubkey(std::span curve25519_pubkey); /// "Softer" version that takes/returns strings of regular chars std::string pubkey(std::string_view curve25519_pubkey); +/// Utility function that provides a constant-time `if (b) f = g;` implementation for byte arrays. +template +void constant_time_conditional_assign( + std::array& f, const std::array& g, bool b) { + std::array x; + for (size_t i = 0; i < x.size(); i++) + x[i] = f[i] ^ g[i]; + unsigned char mask = (unsigned char)(-(signed char)b); + for (size_t i = 0; i < x.size(); i++) + x[i] &= mask; + for (size_t i = 0; i < x.size(); i++) + f[i] ^= x[i]; +} + } // namespace session::xed25519 diff --git a/libsession-util.pc.in b/libsession-util.pc.in new file mode 100644 index 00000000..c59b05c5 --- /dev/null +++ b/libsession-util.pc.in @@ -0,0 +1,14 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: libsession-util +Description: Session utility libraries +Version: @PROJECT_VERSION@ + +Libs: -L${libdir} @libsession_target_links@ +Libs.private: -lprotobuf-lite -lnettle +Requires: liboxenc +Requires.private: nettle protobuf-lite +Cflags: -I${includedir} diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt new file mode 100644 index 00000000..324b116b --- /dev/null +++ b/proto/CMakeLists.txt @@ -0,0 +1,38 @@ + +libsession_static_bundle(protobuf::libprotobuf-lite) + + +add_library(protos + SessionProtos.pb.cc + WebSocketResources.pb.cc) +target_include_directories(protos PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(protos PUBLIC protobuf::libprotobuf-lite) +set_target_properties( + protos PROPERTIES + OUTPUT_NAME session-protos + SOVERSION ${LIBSESSION_LIBVERSION}) + +# -Wunused-parameter triggers in the protobuf dependency +message(STATUS "Disabling -Werror for proto unused-parameter") + +if (MSVC) +else() + target_compile_options(protos PUBLIC -Wno-error=unused-parameter) +endif() + +libsession_static_bundle(protos) + +add_library(libsession::protos ALIAS protos) +export( + TARGETS protos + NAMESPACE libsession:: + FILE libsessionTargets.cmake +) +list(APPEND libsession_export_targets protos) +set(libsession_export_targets "${libsession_export_targets}" PARENT_SCOPE) + + +add_custom_target(regen-protobuf + protoc --cpp_out=. SessionProtos.proto WebSocketResources.proto + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" +) diff --git a/proto/SessionProtos.pb.cc b/proto/SessionProtos.pb.cc new file mode 100644 index 00000000..cd353a0f --- /dev/null +++ b/proto/SessionProtos.pb.cc @@ -0,0 +1,10111 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: SessionProtos.proto + +#include "SessionProtos.pb.h" + +#include + +#include +#include +#include +#include +// @@protoc_insertion_point(includes) +#include + +PROTOBUF_PRAGMA_INIT_SEG + +namespace _pb = ::PROTOBUF_NAMESPACE_ID; +namespace _pbi = _pb::internal; + +namespace SessionProtos { +PROTOBUF_CONSTEXPR Envelope::Envelope( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.source_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.content_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.servertimestamp_)*/uint64_t{0u} + , /*decltype(_impl_.sourcedevice_)*/0u + , /*decltype(_impl_.type_)*/6} {} +struct EnvelopeDefaultTypeInternal { + PROTOBUF_CONSTEXPR EnvelopeDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~EnvelopeDefaultTypeInternal() {} + union { + Envelope _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 EnvelopeDefaultTypeInternal _Envelope_default_instance_; +PROTOBUF_CONSTEXPR TypingMessage::TypingMessage( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.action_)*/0} {} +struct TypingMessageDefaultTypeInternal { + PROTOBUF_CONSTEXPR TypingMessageDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~TypingMessageDefaultTypeInternal() {} + union { + TypingMessage _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 TypingMessageDefaultTypeInternal _TypingMessage_default_instance_; +PROTOBUF_CONSTEXPR UnsendRequest::UnsendRequest( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.author_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.timestamp_)*/uint64_t{0u}} {} +struct UnsendRequestDefaultTypeInternal { + PROTOBUF_CONSTEXPR UnsendRequestDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~UnsendRequestDefaultTypeInternal() {} + union { + UnsendRequest _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 UnsendRequestDefaultTypeInternal _UnsendRequest_default_instance_; +PROTOBUF_CONSTEXPR MessageRequestResponse::MessageRequestResponse( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.profilekey_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.profile_)*/nullptr + , /*decltype(_impl_.isapproved_)*/false} {} +struct MessageRequestResponseDefaultTypeInternal { + PROTOBUF_CONSTEXPR MessageRequestResponseDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~MessageRequestResponseDefaultTypeInternal() {} + union { + MessageRequestResponse _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 MessageRequestResponseDefaultTypeInternal _MessageRequestResponse_default_instance_; +PROTOBUF_CONSTEXPR Content::Content( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.datamessage_)*/nullptr + , /*decltype(_impl_.callmessage_)*/nullptr + , /*decltype(_impl_.receiptmessage_)*/nullptr + , /*decltype(_impl_.typingmessage_)*/nullptr + , /*decltype(_impl_.configurationmessage_)*/nullptr + , /*decltype(_impl_.dataextractionnotification_)*/nullptr + , /*decltype(_impl_.unsendrequest_)*/nullptr + , /*decltype(_impl_.messagerequestresponse_)*/nullptr + , /*decltype(_impl_.sharedconfigmessage_)*/nullptr} {} +struct ContentDefaultTypeInternal { + PROTOBUF_CONSTEXPR ContentDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ContentDefaultTypeInternal() {} + union { + Content _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ContentDefaultTypeInternal _Content_default_instance_; +PROTOBUF_CONSTEXPR CallMessage::CallMessage( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.sdps_)*/{} + , /*decltype(_impl_.sdpmlineindexes_)*/{} + , /*decltype(_impl_.sdpmids_)*/{} + , /*decltype(_impl_.uuid_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.type_)*/6} {} +struct CallMessageDefaultTypeInternal { + PROTOBUF_CONSTEXPR CallMessageDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~CallMessageDefaultTypeInternal() {} + union { + CallMessage _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 CallMessageDefaultTypeInternal _CallMessage_default_instance_; +PROTOBUF_CONSTEXPR KeyPair::KeyPair( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.publickey_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.privatekey_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}} {} +struct KeyPairDefaultTypeInternal { + PROTOBUF_CONSTEXPR KeyPairDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~KeyPairDefaultTypeInternal() {} + union { + KeyPair _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 KeyPairDefaultTypeInternal _KeyPair_default_instance_; +PROTOBUF_CONSTEXPR DataExtractionNotification::DataExtractionNotification( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.type_)*/1} {} +struct DataExtractionNotificationDefaultTypeInternal { + PROTOBUF_CONSTEXPR DataExtractionNotificationDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~DataExtractionNotificationDefaultTypeInternal() {} + union { + DataExtractionNotification _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 DataExtractionNotificationDefaultTypeInternal _DataExtractionNotification_default_instance_; +PROTOBUF_CONSTEXPR LokiProfile::LokiProfile( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.displayname_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.profilepicture_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}} {} +struct LokiProfileDefaultTypeInternal { + PROTOBUF_CONSTEXPR LokiProfileDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~LokiProfileDefaultTypeInternal() {} + union { + LokiProfile _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 LokiProfileDefaultTypeInternal _LokiProfile_default_instance_; +PROTOBUF_CONSTEXPR DataMessage_Quote_QuotedAttachment::DataMessage_Quote_QuotedAttachment( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.contenttype_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.filename_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.thumbnail_)*/nullptr + , /*decltype(_impl_.flags_)*/0u} {} +struct DataMessage_Quote_QuotedAttachmentDefaultTypeInternal { + PROTOBUF_CONSTEXPR DataMessage_Quote_QuotedAttachmentDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~DataMessage_Quote_QuotedAttachmentDefaultTypeInternal() {} + union { + DataMessage_Quote_QuotedAttachment _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 DataMessage_Quote_QuotedAttachmentDefaultTypeInternal _DataMessage_Quote_QuotedAttachment_default_instance_; +PROTOBUF_CONSTEXPR DataMessage_Quote::DataMessage_Quote( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.attachments_)*/{} + , /*decltype(_impl_.author_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.text_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.id_)*/uint64_t{0u}} {} +struct DataMessage_QuoteDefaultTypeInternal { + PROTOBUF_CONSTEXPR DataMessage_QuoteDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~DataMessage_QuoteDefaultTypeInternal() {} + union { + DataMessage_Quote _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 DataMessage_QuoteDefaultTypeInternal _DataMessage_Quote_default_instance_; +PROTOBUF_CONSTEXPR DataMessage_Preview::DataMessage_Preview( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.url_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.title_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.image_)*/nullptr} {} +struct DataMessage_PreviewDefaultTypeInternal { + PROTOBUF_CONSTEXPR DataMessage_PreviewDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~DataMessage_PreviewDefaultTypeInternal() {} + union { + DataMessage_Preview _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 DataMessage_PreviewDefaultTypeInternal _DataMessage_Preview_default_instance_; +PROTOBUF_CONSTEXPR DataMessage_Reaction::DataMessage_Reaction( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.author_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.emoji_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.id_)*/uint64_t{0u} + , /*decltype(_impl_.action_)*/0} {} +struct DataMessage_ReactionDefaultTypeInternal { + PROTOBUF_CONSTEXPR DataMessage_ReactionDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~DataMessage_ReactionDefaultTypeInternal() {} + union { + DataMessage_Reaction _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 DataMessage_ReactionDefaultTypeInternal _DataMessage_Reaction_default_instance_; +PROTOBUF_CONSTEXPR DataMessage_OpenGroupInvitation::DataMessage_OpenGroupInvitation( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.url_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.name_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}} {} +struct DataMessage_OpenGroupInvitationDefaultTypeInternal { + PROTOBUF_CONSTEXPR DataMessage_OpenGroupInvitationDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~DataMessage_OpenGroupInvitationDefaultTypeInternal() {} + union { + DataMessage_OpenGroupInvitation _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 DataMessage_OpenGroupInvitationDefaultTypeInternal _DataMessage_OpenGroupInvitation_default_instance_; +PROTOBUF_CONSTEXPR DataMessage_ClosedGroupControlMessage_KeyPairWrapper::DataMessage_ClosedGroupControlMessage_KeyPairWrapper( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.publickey_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.encryptedkeypair_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}} {} +struct DataMessage_ClosedGroupControlMessage_KeyPairWrapperDefaultTypeInternal { + PROTOBUF_CONSTEXPR DataMessage_ClosedGroupControlMessage_KeyPairWrapperDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~DataMessage_ClosedGroupControlMessage_KeyPairWrapperDefaultTypeInternal() {} + union { + DataMessage_ClosedGroupControlMessage_KeyPairWrapper _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 DataMessage_ClosedGroupControlMessage_KeyPairWrapperDefaultTypeInternal _DataMessage_ClosedGroupControlMessage_KeyPairWrapper_default_instance_; +PROTOBUF_CONSTEXPR DataMessage_ClosedGroupControlMessage::DataMessage_ClosedGroupControlMessage( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.members_)*/{} + , /*decltype(_impl_.admins_)*/{} + , /*decltype(_impl_.wrappers_)*/{} + , /*decltype(_impl_.publickey_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.name_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.encryptionkeypair_)*/nullptr + , /*decltype(_impl_.expirationtimer_)*/0u + , /*decltype(_impl_.type_)*/1} {} +struct DataMessage_ClosedGroupControlMessageDefaultTypeInternal { + PROTOBUF_CONSTEXPR DataMessage_ClosedGroupControlMessageDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~DataMessage_ClosedGroupControlMessageDefaultTypeInternal() {} + union { + DataMessage_ClosedGroupControlMessage _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 DataMessage_ClosedGroupControlMessageDefaultTypeInternal _DataMessage_ClosedGroupControlMessage_default_instance_; +PROTOBUF_CONSTEXPR DataMessage::DataMessage( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.attachments_)*/{} + , /*decltype(_impl_.preview_)*/{} + , /*decltype(_impl_.body_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.profilekey_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.synctarget_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.quote_)*/nullptr + , /*decltype(_impl_.reaction_)*/nullptr + , /*decltype(_impl_.profile_)*/nullptr + , /*decltype(_impl_.opengroupinvitation_)*/nullptr + , /*decltype(_impl_.closedgroupcontrolmessage_)*/nullptr + , /*decltype(_impl_.flags_)*/0u + , /*decltype(_impl_.expiretimer_)*/0u + , /*decltype(_impl_.timestamp_)*/uint64_t{0u} + , /*decltype(_impl_.blockscommunitymessagerequests_)*/false} {} +struct DataMessageDefaultTypeInternal { + PROTOBUF_CONSTEXPR DataMessageDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~DataMessageDefaultTypeInternal() {} + union { + DataMessage _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 DataMessageDefaultTypeInternal _DataMessage_default_instance_; +PROTOBUF_CONSTEXPR ConfigurationMessage_ClosedGroup::ConfigurationMessage_ClosedGroup( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.members_)*/{} + , /*decltype(_impl_.admins_)*/{} + , /*decltype(_impl_.publickey_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.name_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.encryptionkeypair_)*/nullptr + , /*decltype(_impl_.expirationtimer_)*/0u} {} +struct ConfigurationMessage_ClosedGroupDefaultTypeInternal { + PROTOBUF_CONSTEXPR ConfigurationMessage_ClosedGroupDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ConfigurationMessage_ClosedGroupDefaultTypeInternal() {} + union { + ConfigurationMessage_ClosedGroup _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ConfigurationMessage_ClosedGroupDefaultTypeInternal _ConfigurationMessage_ClosedGroup_default_instance_; +PROTOBUF_CONSTEXPR ConfigurationMessage_Contact::ConfigurationMessage_Contact( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.publickey_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.name_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.profilepicture_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.profilekey_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.isapproved_)*/false + , /*decltype(_impl_.isblocked_)*/false + , /*decltype(_impl_.didapproveme_)*/false} {} +struct ConfigurationMessage_ContactDefaultTypeInternal { + PROTOBUF_CONSTEXPR ConfigurationMessage_ContactDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ConfigurationMessage_ContactDefaultTypeInternal() {} + union { + ConfigurationMessage_Contact _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ConfigurationMessage_ContactDefaultTypeInternal _ConfigurationMessage_Contact_default_instance_; +PROTOBUF_CONSTEXPR ConfigurationMessage::ConfigurationMessage( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.closedgroups_)*/{} + , /*decltype(_impl_.opengroups_)*/{} + , /*decltype(_impl_.contacts_)*/{} + , /*decltype(_impl_.displayname_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.profilepicture_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.profilekey_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}} {} +struct ConfigurationMessageDefaultTypeInternal { + PROTOBUF_CONSTEXPR ConfigurationMessageDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ConfigurationMessageDefaultTypeInternal() {} + union { + ConfigurationMessage _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ConfigurationMessageDefaultTypeInternal _ConfigurationMessage_default_instance_; +PROTOBUF_CONSTEXPR ReceiptMessage::ReceiptMessage( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.timestamp_)*/{} + , /*decltype(_impl_.type_)*/0} {} +struct ReceiptMessageDefaultTypeInternal { + PROTOBUF_CONSTEXPR ReceiptMessageDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ReceiptMessageDefaultTypeInternal() {} + union { + ReceiptMessage _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ReceiptMessageDefaultTypeInternal _ReceiptMessage_default_instance_; +PROTOBUF_CONSTEXPR AttachmentPointer::AttachmentPointer( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.contenttype_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.key_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.thumbnail_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.digest_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.filename_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.caption_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.url_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.id_)*/uint64_t{0u} + , /*decltype(_impl_.size_)*/0u + , /*decltype(_impl_.flags_)*/0u + , /*decltype(_impl_.width_)*/0u + , /*decltype(_impl_.height_)*/0u} {} +struct AttachmentPointerDefaultTypeInternal { + PROTOBUF_CONSTEXPR AttachmentPointerDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~AttachmentPointerDefaultTypeInternal() {} + union { + AttachmentPointer _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 AttachmentPointerDefaultTypeInternal _AttachmentPointer_default_instance_; +PROTOBUF_CONSTEXPR SharedConfigMessage::SharedConfigMessage( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.data_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.seqno_)*/int64_t{0} + , /*decltype(_impl_.kind_)*/1} {} +struct SharedConfigMessageDefaultTypeInternal { + PROTOBUF_CONSTEXPR SharedConfigMessageDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~SharedConfigMessageDefaultTypeInternal() {} + union { + SharedConfigMessage _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 SharedConfigMessageDefaultTypeInternal _SharedConfigMessage_default_instance_; +} // namespace SessionProtos +namespace SessionProtos { +bool Envelope_Type_IsValid(int value) { + switch (value) { + case 6: + case 7: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed Envelope_Type_strings[2] = {}; + +static const char Envelope_Type_names[] = + "CLOSED_GROUP_MESSAGE" + "SESSION_MESSAGE"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry Envelope_Type_entries[] = { + { {Envelope_Type_names + 0, 20}, 7 }, + { {Envelope_Type_names + 20, 15}, 6 }, +}; + +static const int Envelope_Type_entries_by_number[] = { + 1, // 6 -> SESSION_MESSAGE + 0, // 7 -> CLOSED_GROUP_MESSAGE +}; + +const std::string& Envelope_Type_Name( + Envelope_Type value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + Envelope_Type_entries, + Envelope_Type_entries_by_number, + 2, Envelope_Type_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + Envelope_Type_entries, + Envelope_Type_entries_by_number, + 2, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + Envelope_Type_strings[idx].get(); +} +bool Envelope_Type_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, Envelope_Type* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + Envelope_Type_entries, 2, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr Envelope_Type Envelope::SESSION_MESSAGE; +constexpr Envelope_Type Envelope::CLOSED_GROUP_MESSAGE; +constexpr Envelope_Type Envelope::Type_MIN; +constexpr Envelope_Type Envelope::Type_MAX; +constexpr int Envelope::Type_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool TypingMessage_Action_IsValid(int value) { + switch (value) { + case 0: + case 1: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed TypingMessage_Action_strings[2] = {}; + +static const char TypingMessage_Action_names[] = + "STARTED" + "STOPPED"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry TypingMessage_Action_entries[] = { + { {TypingMessage_Action_names + 0, 7}, 0 }, + { {TypingMessage_Action_names + 7, 7}, 1 }, +}; + +static const int TypingMessage_Action_entries_by_number[] = { + 0, // 0 -> STARTED + 1, // 1 -> STOPPED +}; + +const std::string& TypingMessage_Action_Name( + TypingMessage_Action value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + TypingMessage_Action_entries, + TypingMessage_Action_entries_by_number, + 2, TypingMessage_Action_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + TypingMessage_Action_entries, + TypingMessage_Action_entries_by_number, + 2, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + TypingMessage_Action_strings[idx].get(); +} +bool TypingMessage_Action_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, TypingMessage_Action* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + TypingMessage_Action_entries, 2, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr TypingMessage_Action TypingMessage::STARTED; +constexpr TypingMessage_Action TypingMessage::STOPPED; +constexpr TypingMessage_Action TypingMessage::Action_MIN; +constexpr TypingMessage_Action TypingMessage::Action_MAX; +constexpr int TypingMessage::Action_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool CallMessage_Type_IsValid(int value) { + switch (value) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed CallMessage_Type_strings[6] = {}; + +static const char CallMessage_Type_names[] = + "ANSWER" + "END_CALL" + "ICE_CANDIDATES" + "OFFER" + "PRE_OFFER" + "PROVISIONAL_ANSWER"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry CallMessage_Type_entries[] = { + { {CallMessage_Type_names + 0, 6}, 2 }, + { {CallMessage_Type_names + 6, 8}, 5 }, + { {CallMessage_Type_names + 14, 14}, 4 }, + { {CallMessage_Type_names + 28, 5}, 1 }, + { {CallMessage_Type_names + 33, 9}, 6 }, + { {CallMessage_Type_names + 42, 18}, 3 }, +}; + +static const int CallMessage_Type_entries_by_number[] = { + 3, // 1 -> OFFER + 0, // 2 -> ANSWER + 5, // 3 -> PROVISIONAL_ANSWER + 2, // 4 -> ICE_CANDIDATES + 1, // 5 -> END_CALL + 4, // 6 -> PRE_OFFER +}; + +const std::string& CallMessage_Type_Name( + CallMessage_Type value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + CallMessage_Type_entries, + CallMessage_Type_entries_by_number, + 6, CallMessage_Type_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + CallMessage_Type_entries, + CallMessage_Type_entries_by_number, + 6, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + CallMessage_Type_strings[idx].get(); +} +bool CallMessage_Type_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, CallMessage_Type* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + CallMessage_Type_entries, 6, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr CallMessage_Type CallMessage::PRE_OFFER; +constexpr CallMessage_Type CallMessage::OFFER; +constexpr CallMessage_Type CallMessage::ANSWER; +constexpr CallMessage_Type CallMessage::PROVISIONAL_ANSWER; +constexpr CallMessage_Type CallMessage::ICE_CANDIDATES; +constexpr CallMessage_Type CallMessage::END_CALL; +constexpr CallMessage_Type CallMessage::Type_MIN; +constexpr CallMessage_Type CallMessage::Type_MAX; +constexpr int CallMessage::Type_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool DataExtractionNotification_Type_IsValid(int value) { + switch (value) { + case 1: + case 2: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed DataExtractionNotification_Type_strings[2] = {}; + +static const char DataExtractionNotification_Type_names[] = + "MEDIA_SAVED" + "SCREENSHOT"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry DataExtractionNotification_Type_entries[] = { + { {DataExtractionNotification_Type_names + 0, 11}, 2 }, + { {DataExtractionNotification_Type_names + 11, 10}, 1 }, +}; + +static const int DataExtractionNotification_Type_entries_by_number[] = { + 1, // 1 -> SCREENSHOT + 0, // 2 -> MEDIA_SAVED +}; + +const std::string& DataExtractionNotification_Type_Name( + DataExtractionNotification_Type value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + DataExtractionNotification_Type_entries, + DataExtractionNotification_Type_entries_by_number, + 2, DataExtractionNotification_Type_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + DataExtractionNotification_Type_entries, + DataExtractionNotification_Type_entries_by_number, + 2, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + DataExtractionNotification_Type_strings[idx].get(); +} +bool DataExtractionNotification_Type_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, DataExtractionNotification_Type* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + DataExtractionNotification_Type_entries, 2, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr DataExtractionNotification_Type DataExtractionNotification::SCREENSHOT; +constexpr DataExtractionNotification_Type DataExtractionNotification::MEDIA_SAVED; +constexpr DataExtractionNotification_Type DataExtractionNotification::Type_MIN; +constexpr DataExtractionNotification_Type DataExtractionNotification::Type_MAX; +constexpr int DataExtractionNotification::Type_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool DataMessage_Quote_QuotedAttachment_Flags_IsValid(int value) { + switch (value) { + case 1: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed DataMessage_Quote_QuotedAttachment_Flags_strings[1] = {}; + +static const char DataMessage_Quote_QuotedAttachment_Flags_names[] = + "VOICE_MESSAGE"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry DataMessage_Quote_QuotedAttachment_Flags_entries[] = { + { {DataMessage_Quote_QuotedAttachment_Flags_names + 0, 13}, 1 }, +}; + +static const int DataMessage_Quote_QuotedAttachment_Flags_entries_by_number[] = { + 0, // 1 -> VOICE_MESSAGE +}; + +const std::string& DataMessage_Quote_QuotedAttachment_Flags_Name( + DataMessage_Quote_QuotedAttachment_Flags value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + DataMessage_Quote_QuotedAttachment_Flags_entries, + DataMessage_Quote_QuotedAttachment_Flags_entries_by_number, + 1, DataMessage_Quote_QuotedAttachment_Flags_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + DataMessage_Quote_QuotedAttachment_Flags_entries, + DataMessage_Quote_QuotedAttachment_Flags_entries_by_number, + 1, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + DataMessage_Quote_QuotedAttachment_Flags_strings[idx].get(); +} +bool DataMessage_Quote_QuotedAttachment_Flags_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, DataMessage_Quote_QuotedAttachment_Flags* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + DataMessage_Quote_QuotedAttachment_Flags_entries, 1, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr DataMessage_Quote_QuotedAttachment_Flags DataMessage_Quote_QuotedAttachment::VOICE_MESSAGE; +constexpr DataMessage_Quote_QuotedAttachment_Flags DataMessage_Quote_QuotedAttachment::Flags_MIN; +constexpr DataMessage_Quote_QuotedAttachment_Flags DataMessage_Quote_QuotedAttachment::Flags_MAX; +constexpr int DataMessage_Quote_QuotedAttachment::Flags_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool DataMessage_Reaction_Action_IsValid(int value) { + switch (value) { + case 0: + case 1: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed DataMessage_Reaction_Action_strings[2] = {}; + +static const char DataMessage_Reaction_Action_names[] = + "REACT" + "REMOVE"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry DataMessage_Reaction_Action_entries[] = { + { {DataMessage_Reaction_Action_names + 0, 5}, 0 }, + { {DataMessage_Reaction_Action_names + 5, 6}, 1 }, +}; + +static const int DataMessage_Reaction_Action_entries_by_number[] = { + 0, // 0 -> REACT + 1, // 1 -> REMOVE +}; + +const std::string& DataMessage_Reaction_Action_Name( + DataMessage_Reaction_Action value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + DataMessage_Reaction_Action_entries, + DataMessage_Reaction_Action_entries_by_number, + 2, DataMessage_Reaction_Action_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + DataMessage_Reaction_Action_entries, + DataMessage_Reaction_Action_entries_by_number, + 2, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + DataMessage_Reaction_Action_strings[idx].get(); +} +bool DataMessage_Reaction_Action_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, DataMessage_Reaction_Action* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + DataMessage_Reaction_Action_entries, 2, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr DataMessage_Reaction_Action DataMessage_Reaction::REACT; +constexpr DataMessage_Reaction_Action DataMessage_Reaction::REMOVE; +constexpr DataMessage_Reaction_Action DataMessage_Reaction::Action_MIN; +constexpr DataMessage_Reaction_Action DataMessage_Reaction::Action_MAX; +constexpr int DataMessage_Reaction::Action_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool DataMessage_ClosedGroupControlMessage_Type_IsValid(int value) { + switch (value) { + case 1: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed DataMessage_ClosedGroupControlMessage_Type_strings[7] = {}; + +static const char DataMessage_ClosedGroupControlMessage_Type_names[] = + "ENCRYPTION_KEY_PAIR" + "ENCRYPTION_KEY_PAIR_REQUEST" + "MEMBERS_ADDED" + "MEMBERS_REMOVED" + "MEMBER_LEFT" + "NAME_CHANGE" + "NEW"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry DataMessage_ClosedGroupControlMessage_Type_entries[] = { + { {DataMessage_ClosedGroupControlMessage_Type_names + 0, 19}, 3 }, + { {DataMessage_ClosedGroupControlMessage_Type_names + 19, 27}, 8 }, + { {DataMessage_ClosedGroupControlMessage_Type_names + 46, 13}, 5 }, + { {DataMessage_ClosedGroupControlMessage_Type_names + 59, 15}, 6 }, + { {DataMessage_ClosedGroupControlMessage_Type_names + 74, 11}, 7 }, + { {DataMessage_ClosedGroupControlMessage_Type_names + 85, 11}, 4 }, + { {DataMessage_ClosedGroupControlMessage_Type_names + 96, 3}, 1 }, +}; + +static const int DataMessage_ClosedGroupControlMessage_Type_entries_by_number[] = { + 6, // 1 -> NEW + 0, // 3 -> ENCRYPTION_KEY_PAIR + 5, // 4 -> NAME_CHANGE + 2, // 5 -> MEMBERS_ADDED + 3, // 6 -> MEMBERS_REMOVED + 4, // 7 -> MEMBER_LEFT + 1, // 8 -> ENCRYPTION_KEY_PAIR_REQUEST +}; + +const std::string& DataMessage_ClosedGroupControlMessage_Type_Name( + DataMessage_ClosedGroupControlMessage_Type value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + DataMessage_ClosedGroupControlMessage_Type_entries, + DataMessage_ClosedGroupControlMessage_Type_entries_by_number, + 7, DataMessage_ClosedGroupControlMessage_Type_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + DataMessage_ClosedGroupControlMessage_Type_entries, + DataMessage_ClosedGroupControlMessage_Type_entries_by_number, + 7, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + DataMessage_ClosedGroupControlMessage_Type_strings[idx].get(); +} +bool DataMessage_ClosedGroupControlMessage_Type_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, DataMessage_ClosedGroupControlMessage_Type* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + DataMessage_ClosedGroupControlMessage_Type_entries, 7, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage::NEW; +constexpr DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage::ENCRYPTION_KEY_PAIR; +constexpr DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage::NAME_CHANGE; +constexpr DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage::MEMBERS_ADDED; +constexpr DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage::MEMBERS_REMOVED; +constexpr DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage::MEMBER_LEFT; +constexpr DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage::ENCRYPTION_KEY_PAIR_REQUEST; +constexpr DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage::Type_MIN; +constexpr DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage::Type_MAX; +constexpr int DataMessage_ClosedGroupControlMessage::Type_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool DataMessage_Flags_IsValid(int value) { + switch (value) { + case 2: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed DataMessage_Flags_strings[1] = {}; + +static const char DataMessage_Flags_names[] = + "EXPIRATION_TIMER_UPDATE"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry DataMessage_Flags_entries[] = { + { {DataMessage_Flags_names + 0, 23}, 2 }, +}; + +static const int DataMessage_Flags_entries_by_number[] = { + 0, // 2 -> EXPIRATION_TIMER_UPDATE +}; + +const std::string& DataMessage_Flags_Name( + DataMessage_Flags value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + DataMessage_Flags_entries, + DataMessage_Flags_entries_by_number, + 1, DataMessage_Flags_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + DataMessage_Flags_entries, + DataMessage_Flags_entries_by_number, + 1, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + DataMessage_Flags_strings[idx].get(); +} +bool DataMessage_Flags_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, DataMessage_Flags* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + DataMessage_Flags_entries, 1, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr DataMessage_Flags DataMessage::EXPIRATION_TIMER_UPDATE; +constexpr DataMessage_Flags DataMessage::Flags_MIN; +constexpr DataMessage_Flags DataMessage::Flags_MAX; +constexpr int DataMessage::Flags_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool ReceiptMessage_Type_IsValid(int value) { + switch (value) { + case 0: + case 1: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed ReceiptMessage_Type_strings[2] = {}; + +static const char ReceiptMessage_Type_names[] = + "DELIVERY" + "READ"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry ReceiptMessage_Type_entries[] = { + { {ReceiptMessage_Type_names + 0, 8}, 0 }, + { {ReceiptMessage_Type_names + 8, 4}, 1 }, +}; + +static const int ReceiptMessage_Type_entries_by_number[] = { + 0, // 0 -> DELIVERY + 1, // 1 -> READ +}; + +const std::string& ReceiptMessage_Type_Name( + ReceiptMessage_Type value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + ReceiptMessage_Type_entries, + ReceiptMessage_Type_entries_by_number, + 2, ReceiptMessage_Type_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + ReceiptMessage_Type_entries, + ReceiptMessage_Type_entries_by_number, + 2, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + ReceiptMessage_Type_strings[idx].get(); +} +bool ReceiptMessage_Type_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ReceiptMessage_Type* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + ReceiptMessage_Type_entries, 2, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr ReceiptMessage_Type ReceiptMessage::DELIVERY; +constexpr ReceiptMessage_Type ReceiptMessage::READ; +constexpr ReceiptMessage_Type ReceiptMessage::Type_MIN; +constexpr ReceiptMessage_Type ReceiptMessage::Type_MAX; +constexpr int ReceiptMessage::Type_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool AttachmentPointer_Flags_IsValid(int value) { + switch (value) { + case 1: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed AttachmentPointer_Flags_strings[1] = {}; + +static const char AttachmentPointer_Flags_names[] = + "VOICE_MESSAGE"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry AttachmentPointer_Flags_entries[] = { + { {AttachmentPointer_Flags_names + 0, 13}, 1 }, +}; + +static const int AttachmentPointer_Flags_entries_by_number[] = { + 0, // 1 -> VOICE_MESSAGE +}; + +const std::string& AttachmentPointer_Flags_Name( + AttachmentPointer_Flags value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + AttachmentPointer_Flags_entries, + AttachmentPointer_Flags_entries_by_number, + 1, AttachmentPointer_Flags_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + AttachmentPointer_Flags_entries, + AttachmentPointer_Flags_entries_by_number, + 1, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + AttachmentPointer_Flags_strings[idx].get(); +} +bool AttachmentPointer_Flags_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, AttachmentPointer_Flags* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + AttachmentPointer_Flags_entries, 1, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr AttachmentPointer_Flags AttachmentPointer::VOICE_MESSAGE; +constexpr AttachmentPointer_Flags AttachmentPointer::Flags_MIN; +constexpr AttachmentPointer_Flags AttachmentPointer::Flags_MAX; +constexpr int AttachmentPointer::Flags_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +bool SharedConfigMessage_Kind_IsValid(int value) { + switch (value) { + case 1: + case 2: + case 3: + case 4: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed SharedConfigMessage_Kind_strings[4] = {}; + +static const char SharedConfigMessage_Kind_names[] = + "CONTACTS" + "CONVO_INFO_VOLATILE" + "USER_GROUPS" + "USER_PROFILE"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry SharedConfigMessage_Kind_entries[] = { + { {SharedConfigMessage_Kind_names + 0, 8}, 2 }, + { {SharedConfigMessage_Kind_names + 8, 19}, 3 }, + { {SharedConfigMessage_Kind_names + 27, 11}, 4 }, + { {SharedConfigMessage_Kind_names + 38, 12}, 1 }, +}; + +static const int SharedConfigMessage_Kind_entries_by_number[] = { + 3, // 1 -> USER_PROFILE + 0, // 2 -> CONTACTS + 1, // 3 -> CONVO_INFO_VOLATILE + 2, // 4 -> USER_GROUPS +}; + +const std::string& SharedConfigMessage_Kind_Name( + SharedConfigMessage_Kind value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + SharedConfigMessage_Kind_entries, + SharedConfigMessage_Kind_entries_by_number, + 4, SharedConfigMessage_Kind_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + SharedConfigMessage_Kind_entries, + SharedConfigMessage_Kind_entries_by_number, + 4, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + SharedConfigMessage_Kind_strings[idx].get(); +} +bool SharedConfigMessage_Kind_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, SharedConfigMessage_Kind* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + SharedConfigMessage_Kind_entries, 4, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr SharedConfigMessage_Kind SharedConfigMessage::USER_PROFILE; +constexpr SharedConfigMessage_Kind SharedConfigMessage::CONTACTS; +constexpr SharedConfigMessage_Kind SharedConfigMessage::CONVO_INFO_VOLATILE; +constexpr SharedConfigMessage_Kind SharedConfigMessage::USER_GROUPS; +constexpr SharedConfigMessage_Kind SharedConfigMessage::Kind_MIN; +constexpr SharedConfigMessage_Kind SharedConfigMessage::Kind_MAX; +constexpr int SharedConfigMessage::Kind_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) + +// =================================================================== + +class Envelope::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_type(HasBits* has_bits) { + (*has_bits)[0] |= 32u; + } + static void set_has_source(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_sourcedevice(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static void set_has_timestamp(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_content(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_servertimestamp(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000024) ^ 0x00000024) != 0; + } +}; + +Envelope::Envelope(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.Envelope) +} +Envelope::Envelope(const Envelope& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + Envelope* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.source_){} + , decltype(_impl_.content_){} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.servertimestamp_){} + , decltype(_impl_.sourcedevice_){} + , decltype(_impl_.type_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.source_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.source_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_source()) { + _this->_impl_.source_.Set(from._internal_source(), + _this->GetArenaForAllocation()); + } + _impl_.content_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.content_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_content()) { + _this->_impl_.content_.Set(from._internal_content(), + _this->GetArenaForAllocation()); + } + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.type_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.type_)); + // @@protoc_insertion_point(copy_constructor:SessionProtos.Envelope) +} + +inline void Envelope::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.source_){} + , decltype(_impl_.content_){} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.servertimestamp_){uint64_t{0u}} + , decltype(_impl_.sourcedevice_){0u} + , decltype(_impl_.type_){6} + }; + _impl_.source_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.source_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.content_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.content_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +Envelope::~Envelope() { + // @@protoc_insertion_point(destructor:SessionProtos.Envelope) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void Envelope::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.source_.Destroy(); + _impl_.content_.Destroy(); +} + +void Envelope::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void Envelope::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.Envelope) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.source_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.content_.ClearNonDefaultToEmpty(); + } + } + if (cached_has_bits & 0x0000003cu) { + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.sourcedevice_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.sourcedevice_)); + _impl_.type_ = 6; + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* Envelope::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required .SessionProtos.Envelope.Type type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::SessionProtos::Envelope_Type_IsValid(val))) { + _internal_set_type(static_cast<::SessionProtos::Envelope_Type>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional string source = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_source(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // required uint64 timestamp = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _Internal::set_has_timestamp(&has_bits); + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional uint32 sourceDevice = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 56)) { + _Internal::set_has_sourcedevice(&has_bits); + _impl_.sourcedevice_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes content = 8; + case 8: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 66)) { + auto str = _internal_mutable_content(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional uint64 serverTimestamp = 10; + case 10: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 80)) { + _Internal::set_has_servertimestamp(&has_bits); + _impl_.servertimestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* Envelope::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.Envelope) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required .SessionProtos.Envelope.Type type = 1; + if (cached_has_bits & 0x00000020u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_type(), target); + } + + // optional string source = 2; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_source(), target); + } + + // required uint64 timestamp = 5; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(5, this->_internal_timestamp(), target); + } + + // optional uint32 sourceDevice = 7; + if (cached_has_bits & 0x00000010u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(7, this->_internal_sourcedevice(), target); + } + + // optional bytes content = 8; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteBytesMaybeAliased( + 8, this->_internal_content(), target); + } + + // optional uint64 serverTimestamp = 10; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(10, this->_internal_servertimestamp(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.Envelope) + return target; +} + +size_t Envelope::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:SessionProtos.Envelope) + size_t total_size = 0; + + if (_internal_has_timestamp()) { + // required uint64 timestamp = 5; + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + if (_internal_has_type()) { + // required .SessionProtos.Envelope.Type type = 1; + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_type()); + } + + return total_size; +} +size_t Envelope::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.Envelope) + size_t total_size = 0; + + if (((_impl_._has_bits_[0] & 0x00000024) ^ 0x00000024) == 0) { // All required fields are present. + // required uint64 timestamp = 5; + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + + // required .SessionProtos.Envelope.Type type = 1; + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_type()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional string source = 2; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_source()); + } + + // optional bytes content = 8; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_content()); + } + + } + if (cached_has_bits & 0x00000018u) { + // optional uint64 serverTimestamp = 10; + if (cached_has_bits & 0x00000008u) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_servertimestamp()); + } + + // optional uint32 sourceDevice = 7; + if (cached_has_bits & 0x00000010u) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_sourcedevice()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void Envelope::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void Envelope::MergeFrom(const Envelope& from) { + Envelope* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.Envelope) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000003fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_source(from._internal_source()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_content(from._internal_content()); + } + if (cached_has_bits & 0x00000004u) { + _this->_impl_.timestamp_ = from._impl_.timestamp_; + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.servertimestamp_ = from._impl_.servertimestamp_; + } + if (cached_has_bits & 0x00000010u) { + _this->_impl_.sourcedevice_ = from._impl_.sourcedevice_; + } + if (cached_has_bits & 0x00000020u) { + _this->_impl_.type_ = from._impl_.type_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void Envelope::CopyFrom(const Envelope& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.Envelope) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Envelope::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void Envelope::InternalSwap(Envelope* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.source_, lhs_arena, + &other->_impl_.source_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.content_, lhs_arena, + &other->_impl_.content_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(Envelope, _impl_.sourcedevice_) + + sizeof(Envelope::_impl_.sourcedevice_) + - PROTOBUF_FIELD_OFFSET(Envelope, _impl_.timestamp_)>( + reinterpret_cast(&_impl_.timestamp_), + reinterpret_cast(&other->_impl_.timestamp_)); + swap(_impl_.type_, other->_impl_.type_); +} + +std::string Envelope::GetTypeName() const { + return "SessionProtos.Envelope"; +} + + +// =================================================================== + +class TypingMessage::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_timestamp(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_action(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000003) ^ 0x00000003) != 0; + } +}; + +TypingMessage::TypingMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.TypingMessage) +} +TypingMessage::TypingMessage(const TypingMessage& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + TypingMessage* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.action_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.action_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.action_)); + // @@protoc_insertion_point(copy_constructor:SessionProtos.TypingMessage) +} + +inline void TypingMessage::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.action_){0} + }; +} + +TypingMessage::~TypingMessage() { + // @@protoc_insertion_point(destructor:SessionProtos.TypingMessage) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void TypingMessage::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); +} + +void TypingMessage::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void TypingMessage::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.TypingMessage) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + ::memset(&_impl_.timestamp_, 0, static_cast( + reinterpret_cast(&_impl_.action_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.action_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* TypingMessage::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _Internal::set_has_timestamp(&has_bits); + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // required .SessionProtos.TypingMessage.Action action = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 16)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::SessionProtos::TypingMessage_Action_IsValid(val))) { + _internal_set_action(static_cast<::SessionProtos::TypingMessage_Action>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(2, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* TypingMessage::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.TypingMessage) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required uint64 timestamp = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // required .SessionProtos.TypingMessage.Action action = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 2, this->_internal_action(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.TypingMessage) + return target; +} + +size_t TypingMessage::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:SessionProtos.TypingMessage) + size_t total_size = 0; + + if (_internal_has_timestamp()) { + // required uint64 timestamp = 1; + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + if (_internal_has_action()) { + // required .SessionProtos.TypingMessage.Action action = 2; + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_action()); + } + + return total_size; +} +size_t TypingMessage::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.TypingMessage) + size_t total_size = 0; + + if (((_impl_._has_bits_[0] & 0x00000003) ^ 0x00000003) == 0) { // All required fields are present. + // required uint64 timestamp = 1; + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + + // required .SessionProtos.TypingMessage.Action action = 2; + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_action()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void TypingMessage::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void TypingMessage::MergeFrom(const TypingMessage& from) { + TypingMessage* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.TypingMessage) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_impl_.timestamp_ = from._impl_.timestamp_; + } + if (cached_has_bits & 0x00000002u) { + _this->_impl_.action_ = from._impl_.action_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void TypingMessage::CopyFrom(const TypingMessage& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.TypingMessage) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool TypingMessage::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void TypingMessage::InternalSwap(TypingMessage* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(TypingMessage, _impl_.action_) + + sizeof(TypingMessage::_impl_.action_) + - PROTOBUF_FIELD_OFFSET(TypingMessage, _impl_.timestamp_)>( + reinterpret_cast(&_impl_.timestamp_), + reinterpret_cast(&other->_impl_.timestamp_)); +} + +std::string TypingMessage::GetTypeName() const { + return "SessionProtos.TypingMessage"; +} + + +// =================================================================== + +class UnsendRequest::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_timestamp(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_author(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000003) ^ 0x00000003) != 0; + } +}; + +UnsendRequest::UnsendRequest(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.UnsendRequest) +} +UnsendRequest::UnsendRequest(const UnsendRequest& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + UnsendRequest* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.author_){} + , decltype(_impl_.timestamp_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.author_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.author_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_author()) { + _this->_impl_.author_.Set(from._internal_author(), + _this->GetArenaForAllocation()); + } + _this->_impl_.timestamp_ = from._impl_.timestamp_; + // @@protoc_insertion_point(copy_constructor:SessionProtos.UnsendRequest) +} + +inline void UnsendRequest::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.author_){} + , decltype(_impl_.timestamp_){uint64_t{0u}} + }; + _impl_.author_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.author_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +UnsendRequest::~UnsendRequest() { + // @@protoc_insertion_point(destructor:SessionProtos.UnsendRequest) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void UnsendRequest::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.author_.Destroy(); +} + +void UnsendRequest::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void UnsendRequest::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.UnsendRequest) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000001u) { + _impl_.author_.ClearNonDefaultToEmpty(); + } + _impl_.timestamp_ = uint64_t{0u}; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* UnsendRequest::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required uint64 timestamp = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _Internal::set_has_timestamp(&has_bits); + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // required string author = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_author(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* UnsendRequest::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.UnsendRequest) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required uint64 timestamp = 1; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target); + } + + // required string author = 2; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_author(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.UnsendRequest) + return target; +} + +size_t UnsendRequest::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:SessionProtos.UnsendRequest) + size_t total_size = 0; + + if (_internal_has_author()) { + // required string author = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_author()); + } + + if (_internal_has_timestamp()) { + // required uint64 timestamp = 1; + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + return total_size; +} +size_t UnsendRequest::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.UnsendRequest) + size_t total_size = 0; + + if (((_impl_._has_bits_[0] & 0x00000003) ^ 0x00000003) == 0) { // All required fields are present. + // required string author = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_author()); + + // required uint64 timestamp = 1; + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void UnsendRequest::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void UnsendRequest::MergeFrom(const UnsendRequest& from) { + UnsendRequest* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.UnsendRequest) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_author(from._internal_author()); + } + if (cached_has_bits & 0x00000002u) { + _this->_impl_.timestamp_ = from._impl_.timestamp_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void UnsendRequest::CopyFrom(const UnsendRequest& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.UnsendRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool UnsendRequest::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void UnsendRequest::InternalSwap(UnsendRequest* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.author_, lhs_arena, + &other->_impl_.author_, rhs_arena + ); + swap(_impl_.timestamp_, other->_impl_.timestamp_); +} + +std::string UnsendRequest::GetTypeName() const { + return "SessionProtos.UnsendRequest"; +} + + +// =================================================================== + +class MessageRequestResponse::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_isapproved(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_profilekey(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::SessionProtos::LokiProfile& profile(const MessageRequestResponse* msg); + static void set_has_profile(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000004) ^ 0x00000004) != 0; + } +}; + +const ::SessionProtos::LokiProfile& +MessageRequestResponse::_Internal::profile(const MessageRequestResponse* msg) { + return *msg->_impl_.profile_; +} +MessageRequestResponse::MessageRequestResponse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.MessageRequestResponse) +} +MessageRequestResponse::MessageRequestResponse(const MessageRequestResponse& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + MessageRequestResponse* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.profilekey_){} + , decltype(_impl_.profile_){nullptr} + , decltype(_impl_.isapproved_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.profilekey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilekey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_profilekey()) { + _this->_impl_.profilekey_.Set(from._internal_profilekey(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_profile()) { + _this->_impl_.profile_ = new ::SessionProtos::LokiProfile(*from._impl_.profile_); + } + _this->_impl_.isapproved_ = from._impl_.isapproved_; + // @@protoc_insertion_point(copy_constructor:SessionProtos.MessageRequestResponse) +} + +inline void MessageRequestResponse::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.profilekey_){} + , decltype(_impl_.profile_){nullptr} + , decltype(_impl_.isapproved_){false} + }; + _impl_.profilekey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilekey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +MessageRequestResponse::~MessageRequestResponse() { + // @@protoc_insertion_point(destructor:SessionProtos.MessageRequestResponse) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void MessageRequestResponse::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.profilekey_.Destroy(); + if (this != internal_default_instance()) delete _impl_.profile_; +} + +void MessageRequestResponse::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void MessageRequestResponse::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.MessageRequestResponse) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.profilekey_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(_impl_.profile_ != nullptr); + _impl_.profile_->Clear(); + } + } + _impl_.isapproved_ = false; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* MessageRequestResponse::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required bool isApproved = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _Internal::set_has_isapproved(&has_bits); + _impl_.isapproved_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes profileKey = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_profilekey(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.LokiProfile profile = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_profile(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* MessageRequestResponse::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.MessageRequestResponse) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required bool isApproved = 1; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(1, this->_internal_isapproved(), target); + } + + // optional bytes profileKey = 2; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 2, this->_internal_profilekey(), target); + } + + // optional .SessionProtos.LokiProfile profile = 3; + if (cached_has_bits & 0x00000002u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::profile(this), + _Internal::profile(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.MessageRequestResponse) + return target; +} + +size_t MessageRequestResponse::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.MessageRequestResponse) + size_t total_size = 0; + + // required bool isApproved = 1; + if (_internal_has_isapproved()) { + total_size += 1 + 1; + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional bytes profileKey = 2; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_profilekey()); + } + + // optional .SessionProtos.LokiProfile profile = 3; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.profile_); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void MessageRequestResponse::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void MessageRequestResponse::MergeFrom(const MessageRequestResponse& from) { + MessageRequestResponse* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.MessageRequestResponse) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_profilekey(from._internal_profilekey()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_mutable_profile()->::SessionProtos::LokiProfile::MergeFrom( + from._internal_profile()); + } + if (cached_has_bits & 0x00000004u) { + _this->_impl_.isapproved_ = from._impl_.isapproved_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void MessageRequestResponse::CopyFrom(const MessageRequestResponse& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.MessageRequestResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool MessageRequestResponse::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void MessageRequestResponse::InternalSwap(MessageRequestResponse* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.profilekey_, lhs_arena, + &other->_impl_.profilekey_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(MessageRequestResponse, _impl_.isapproved_) + + sizeof(MessageRequestResponse::_impl_.isapproved_) + - PROTOBUF_FIELD_OFFSET(MessageRequestResponse, _impl_.profile_)>( + reinterpret_cast(&_impl_.profile_), + reinterpret_cast(&other->_impl_.profile_)); +} + +std::string MessageRequestResponse::GetTypeName() const { + return "SessionProtos.MessageRequestResponse"; +} + + +// =================================================================== + +class Content::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static const ::SessionProtos::DataMessage& datamessage(const Content* msg); + static void set_has_datamessage(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::SessionProtos::CallMessage& callmessage(const Content* msg); + static void set_has_callmessage(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static const ::SessionProtos::ReceiptMessage& receiptmessage(const Content* msg); + static void set_has_receiptmessage(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static const ::SessionProtos::TypingMessage& typingmessage(const Content* msg); + static void set_has_typingmessage(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static const ::SessionProtos::ConfigurationMessage& configurationmessage(const Content* msg); + static void set_has_configurationmessage(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static const ::SessionProtos::DataExtractionNotification& dataextractionnotification(const Content* msg); + static void set_has_dataextractionnotification(HasBits* has_bits) { + (*has_bits)[0] |= 32u; + } + static const ::SessionProtos::UnsendRequest& unsendrequest(const Content* msg); + static void set_has_unsendrequest(HasBits* has_bits) { + (*has_bits)[0] |= 64u; + } + static const ::SessionProtos::MessageRequestResponse& messagerequestresponse(const Content* msg); + static void set_has_messagerequestresponse(HasBits* has_bits) { + (*has_bits)[0] |= 128u; + } + static const ::SessionProtos::SharedConfigMessage& sharedconfigmessage(const Content* msg); + static void set_has_sharedconfigmessage(HasBits* has_bits) { + (*has_bits)[0] |= 256u; + } +}; + +const ::SessionProtos::DataMessage& +Content::_Internal::datamessage(const Content* msg) { + return *msg->_impl_.datamessage_; +} +const ::SessionProtos::CallMessage& +Content::_Internal::callmessage(const Content* msg) { + return *msg->_impl_.callmessage_; +} +const ::SessionProtos::ReceiptMessage& +Content::_Internal::receiptmessage(const Content* msg) { + return *msg->_impl_.receiptmessage_; +} +const ::SessionProtos::TypingMessage& +Content::_Internal::typingmessage(const Content* msg) { + return *msg->_impl_.typingmessage_; +} +const ::SessionProtos::ConfigurationMessage& +Content::_Internal::configurationmessage(const Content* msg) { + return *msg->_impl_.configurationmessage_; +} +const ::SessionProtos::DataExtractionNotification& +Content::_Internal::dataextractionnotification(const Content* msg) { + return *msg->_impl_.dataextractionnotification_; +} +const ::SessionProtos::UnsendRequest& +Content::_Internal::unsendrequest(const Content* msg) { + return *msg->_impl_.unsendrequest_; +} +const ::SessionProtos::MessageRequestResponse& +Content::_Internal::messagerequestresponse(const Content* msg) { + return *msg->_impl_.messagerequestresponse_; +} +const ::SessionProtos::SharedConfigMessage& +Content::_Internal::sharedconfigmessage(const Content* msg) { + return *msg->_impl_.sharedconfigmessage_; +} +Content::Content(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.Content) +} +Content::Content(const Content& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + Content* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.datamessage_){nullptr} + , decltype(_impl_.callmessage_){nullptr} + , decltype(_impl_.receiptmessage_){nullptr} + , decltype(_impl_.typingmessage_){nullptr} + , decltype(_impl_.configurationmessage_){nullptr} + , decltype(_impl_.dataextractionnotification_){nullptr} + , decltype(_impl_.unsendrequest_){nullptr} + , decltype(_impl_.messagerequestresponse_){nullptr} + , decltype(_impl_.sharedconfigmessage_){nullptr}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + if (from._internal_has_datamessage()) { + _this->_impl_.datamessage_ = new ::SessionProtos::DataMessage(*from._impl_.datamessage_); + } + if (from._internal_has_callmessage()) { + _this->_impl_.callmessage_ = new ::SessionProtos::CallMessage(*from._impl_.callmessage_); + } + if (from._internal_has_receiptmessage()) { + _this->_impl_.receiptmessage_ = new ::SessionProtos::ReceiptMessage(*from._impl_.receiptmessage_); + } + if (from._internal_has_typingmessage()) { + _this->_impl_.typingmessage_ = new ::SessionProtos::TypingMessage(*from._impl_.typingmessage_); + } + if (from._internal_has_configurationmessage()) { + _this->_impl_.configurationmessage_ = new ::SessionProtos::ConfigurationMessage(*from._impl_.configurationmessage_); + } + if (from._internal_has_dataextractionnotification()) { + _this->_impl_.dataextractionnotification_ = new ::SessionProtos::DataExtractionNotification(*from._impl_.dataextractionnotification_); + } + if (from._internal_has_unsendrequest()) { + _this->_impl_.unsendrequest_ = new ::SessionProtos::UnsendRequest(*from._impl_.unsendrequest_); + } + if (from._internal_has_messagerequestresponse()) { + _this->_impl_.messagerequestresponse_ = new ::SessionProtos::MessageRequestResponse(*from._impl_.messagerequestresponse_); + } + if (from._internal_has_sharedconfigmessage()) { + _this->_impl_.sharedconfigmessage_ = new ::SessionProtos::SharedConfigMessage(*from._impl_.sharedconfigmessage_); + } + // @@protoc_insertion_point(copy_constructor:SessionProtos.Content) +} + +inline void Content::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.datamessage_){nullptr} + , decltype(_impl_.callmessage_){nullptr} + , decltype(_impl_.receiptmessage_){nullptr} + , decltype(_impl_.typingmessage_){nullptr} + , decltype(_impl_.configurationmessage_){nullptr} + , decltype(_impl_.dataextractionnotification_){nullptr} + , decltype(_impl_.unsendrequest_){nullptr} + , decltype(_impl_.messagerequestresponse_){nullptr} + , decltype(_impl_.sharedconfigmessage_){nullptr} + }; +} + +Content::~Content() { + // @@protoc_insertion_point(destructor:SessionProtos.Content) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void Content::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + if (this != internal_default_instance()) delete _impl_.datamessage_; + if (this != internal_default_instance()) delete _impl_.callmessage_; + if (this != internal_default_instance()) delete _impl_.receiptmessage_; + if (this != internal_default_instance()) delete _impl_.typingmessage_; + if (this != internal_default_instance()) delete _impl_.configurationmessage_; + if (this != internal_default_instance()) delete _impl_.dataextractionnotification_; + if (this != internal_default_instance()) delete _impl_.unsendrequest_; + if (this != internal_default_instance()) delete _impl_.messagerequestresponse_; + if (this != internal_default_instance()) delete _impl_.sharedconfigmessage_; +} + +void Content::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void Content::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.Content) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x000000ffu) { + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(_impl_.datamessage_ != nullptr); + _impl_.datamessage_->Clear(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(_impl_.callmessage_ != nullptr); + _impl_.callmessage_->Clear(); + } + if (cached_has_bits & 0x00000004u) { + GOOGLE_DCHECK(_impl_.receiptmessage_ != nullptr); + _impl_.receiptmessage_->Clear(); + } + if (cached_has_bits & 0x00000008u) { + GOOGLE_DCHECK(_impl_.typingmessage_ != nullptr); + _impl_.typingmessage_->Clear(); + } + if (cached_has_bits & 0x00000010u) { + GOOGLE_DCHECK(_impl_.configurationmessage_ != nullptr); + _impl_.configurationmessage_->Clear(); + } + if (cached_has_bits & 0x00000020u) { + GOOGLE_DCHECK(_impl_.dataextractionnotification_ != nullptr); + _impl_.dataextractionnotification_->Clear(); + } + if (cached_has_bits & 0x00000040u) { + GOOGLE_DCHECK(_impl_.unsendrequest_ != nullptr); + _impl_.unsendrequest_->Clear(); + } + if (cached_has_bits & 0x00000080u) { + GOOGLE_DCHECK(_impl_.messagerequestresponse_ != nullptr); + _impl_.messagerequestresponse_->Clear(); + } + } + if (cached_has_bits & 0x00000100u) { + GOOGLE_DCHECK(_impl_.sharedconfigmessage_ != nullptr); + _impl_.sharedconfigmessage_->Clear(); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* Content::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional .SessionProtos.DataMessage dataMessage = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + ptr = ctx->ParseMessage(_internal_mutable_datamessage(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.CallMessage callMessage = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_callmessage(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.ReceiptMessage receiptMessage = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + ptr = ctx->ParseMessage(_internal_mutable_receiptmessage(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.TypingMessage typingMessage = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 50)) { + ptr = ctx->ParseMessage(_internal_mutable_typingmessage(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.ConfigurationMessage configurationMessage = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 58)) { + ptr = ctx->ParseMessage(_internal_mutable_configurationmessage(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.DataExtractionNotification dataExtractionNotification = 8; + case 8: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 66)) { + ptr = ctx->ParseMessage(_internal_mutable_dataextractionnotification(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.UnsendRequest unsendRequest = 9; + case 9: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 74)) { + ptr = ctx->ParseMessage(_internal_mutable_unsendrequest(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.MessageRequestResponse messageRequestResponse = 10; + case 10: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 82)) { + ptr = ctx->ParseMessage(_internal_mutable_messagerequestresponse(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.SharedConfigMessage sharedConfigMessage = 11; + case 11: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 90)) { + ptr = ctx->ParseMessage(_internal_mutable_sharedconfigmessage(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* Content::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.Content) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional .SessionProtos.DataMessage dataMessage = 1; + if (cached_has_bits & 0x00000001u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, _Internal::datamessage(this), + _Internal::datamessage(this).GetCachedSize(), target, stream); + } + + // optional .SessionProtos.CallMessage callMessage = 3; + if (cached_has_bits & 0x00000002u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::callmessage(this), + _Internal::callmessage(this).GetCachedSize(), target, stream); + } + + // optional .SessionProtos.ReceiptMessage receiptMessage = 5; + if (cached_has_bits & 0x00000004u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(5, _Internal::receiptmessage(this), + _Internal::receiptmessage(this).GetCachedSize(), target, stream); + } + + // optional .SessionProtos.TypingMessage typingMessage = 6; + if (cached_has_bits & 0x00000008u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(6, _Internal::typingmessage(this), + _Internal::typingmessage(this).GetCachedSize(), target, stream); + } + + // optional .SessionProtos.ConfigurationMessage configurationMessage = 7; + if (cached_has_bits & 0x00000010u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(7, _Internal::configurationmessage(this), + _Internal::configurationmessage(this).GetCachedSize(), target, stream); + } + + // optional .SessionProtos.DataExtractionNotification dataExtractionNotification = 8; + if (cached_has_bits & 0x00000020u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(8, _Internal::dataextractionnotification(this), + _Internal::dataextractionnotification(this).GetCachedSize(), target, stream); + } + + // optional .SessionProtos.UnsendRequest unsendRequest = 9; + if (cached_has_bits & 0x00000040u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(9, _Internal::unsendrequest(this), + _Internal::unsendrequest(this).GetCachedSize(), target, stream); + } + + // optional .SessionProtos.MessageRequestResponse messageRequestResponse = 10; + if (cached_has_bits & 0x00000080u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(10, _Internal::messagerequestresponse(this), + _Internal::messagerequestresponse(this).GetCachedSize(), target, stream); + } + + // optional .SessionProtos.SharedConfigMessage sharedConfigMessage = 11; + if (cached_has_bits & 0x00000100u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(11, _Internal::sharedconfigmessage(this), + _Internal::sharedconfigmessage(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.Content) + return target; +} + +size_t Content::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.Content) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x000000ffu) { + // optional .SessionProtos.DataMessage dataMessage = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.datamessage_); + } + + // optional .SessionProtos.CallMessage callMessage = 3; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.callmessage_); + } + + // optional .SessionProtos.ReceiptMessage receiptMessage = 5; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.receiptmessage_); + } + + // optional .SessionProtos.TypingMessage typingMessage = 6; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.typingmessage_); + } + + // optional .SessionProtos.ConfigurationMessage configurationMessage = 7; + if (cached_has_bits & 0x00000010u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.configurationmessage_); + } + + // optional .SessionProtos.DataExtractionNotification dataExtractionNotification = 8; + if (cached_has_bits & 0x00000020u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.dataextractionnotification_); + } + + // optional .SessionProtos.UnsendRequest unsendRequest = 9; + if (cached_has_bits & 0x00000040u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.unsendrequest_); + } + + // optional .SessionProtos.MessageRequestResponse messageRequestResponse = 10; + if (cached_has_bits & 0x00000080u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.messagerequestresponse_); + } + + } + // optional .SessionProtos.SharedConfigMessage sharedConfigMessage = 11; + if (cached_has_bits & 0x00000100u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.sharedconfigmessage_); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void Content::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void Content::MergeFrom(const Content& from) { + Content* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.Content) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x000000ffu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_mutable_datamessage()->::SessionProtos::DataMessage::MergeFrom( + from._internal_datamessage()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_mutable_callmessage()->::SessionProtos::CallMessage::MergeFrom( + from._internal_callmessage()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_mutable_receiptmessage()->::SessionProtos::ReceiptMessage::MergeFrom( + from._internal_receiptmessage()); + } + if (cached_has_bits & 0x00000008u) { + _this->_internal_mutable_typingmessage()->::SessionProtos::TypingMessage::MergeFrom( + from._internal_typingmessage()); + } + if (cached_has_bits & 0x00000010u) { + _this->_internal_mutable_configurationmessage()->::SessionProtos::ConfigurationMessage::MergeFrom( + from._internal_configurationmessage()); + } + if (cached_has_bits & 0x00000020u) { + _this->_internal_mutable_dataextractionnotification()->::SessionProtos::DataExtractionNotification::MergeFrom( + from._internal_dataextractionnotification()); + } + if (cached_has_bits & 0x00000040u) { + _this->_internal_mutable_unsendrequest()->::SessionProtos::UnsendRequest::MergeFrom( + from._internal_unsendrequest()); + } + if (cached_has_bits & 0x00000080u) { + _this->_internal_mutable_messagerequestresponse()->::SessionProtos::MessageRequestResponse::MergeFrom( + from._internal_messagerequestresponse()); + } + } + if (cached_has_bits & 0x00000100u) { + _this->_internal_mutable_sharedconfigmessage()->::SessionProtos::SharedConfigMessage::MergeFrom( + from._internal_sharedconfigmessage()); + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void Content::CopyFrom(const Content& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.Content) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Content::IsInitialized() const { + if (_internal_has_datamessage()) { + if (!_impl_.datamessage_->IsInitialized()) return false; + } + if (_internal_has_callmessage()) { + if (!_impl_.callmessage_->IsInitialized()) return false; + } + if (_internal_has_receiptmessage()) { + if (!_impl_.receiptmessage_->IsInitialized()) return false; + } + if (_internal_has_typingmessage()) { + if (!_impl_.typingmessage_->IsInitialized()) return false; + } + if (_internal_has_configurationmessage()) { + if (!_impl_.configurationmessage_->IsInitialized()) return false; + } + if (_internal_has_dataextractionnotification()) { + if (!_impl_.dataextractionnotification_->IsInitialized()) return false; + } + if (_internal_has_unsendrequest()) { + if (!_impl_.unsendrequest_->IsInitialized()) return false; + } + if (_internal_has_messagerequestresponse()) { + if (!_impl_.messagerequestresponse_->IsInitialized()) return false; + } + if (_internal_has_sharedconfigmessage()) { + if (!_impl_.sharedconfigmessage_->IsInitialized()) return false; + } + return true; +} + +void Content::InternalSwap(Content* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(Content, _impl_.sharedconfigmessage_) + + sizeof(Content::_impl_.sharedconfigmessage_) + - PROTOBUF_FIELD_OFFSET(Content, _impl_.datamessage_)>( + reinterpret_cast(&_impl_.datamessage_), + reinterpret_cast(&other->_impl_.datamessage_)); +} + +std::string Content::GetTypeName() const { + return "SessionProtos.Content"; +} + + +// =================================================================== + +class CallMessage::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_type(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_uuid(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000003) ^ 0x00000003) != 0; + } +}; + +CallMessage::CallMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.CallMessage) +} +CallMessage::CallMessage(const CallMessage& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + CallMessage* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.sdps_){from._impl_.sdps_} + , decltype(_impl_.sdpmlineindexes_){from._impl_.sdpmlineindexes_} + , decltype(_impl_.sdpmids_){from._impl_.sdpmids_} + , decltype(_impl_.uuid_){} + , decltype(_impl_.type_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.uuid_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.uuid_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_uuid()) { + _this->_impl_.uuid_.Set(from._internal_uuid(), + _this->GetArenaForAllocation()); + } + _this->_impl_.type_ = from._impl_.type_; + // @@protoc_insertion_point(copy_constructor:SessionProtos.CallMessage) +} + +inline void CallMessage::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.sdps_){arena} + , decltype(_impl_.sdpmlineindexes_){arena} + , decltype(_impl_.sdpmids_){arena} + , decltype(_impl_.uuid_){} + , decltype(_impl_.type_){6} + }; + _impl_.uuid_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.uuid_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +CallMessage::~CallMessage() { + // @@protoc_insertion_point(destructor:SessionProtos.CallMessage) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void CallMessage::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.sdps_.~RepeatedPtrField(); + _impl_.sdpmlineindexes_.~RepeatedField(); + _impl_.sdpmids_.~RepeatedPtrField(); + _impl_.uuid_.Destroy(); +} + +void CallMessage::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void CallMessage::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.CallMessage) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.sdps_.Clear(); + _impl_.sdpmlineindexes_.Clear(); + _impl_.sdpmids_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.uuid_.ClearNonDefaultToEmpty(); + } + _impl_.type_ = 6; + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* CallMessage::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required .SessionProtos.CallMessage.Type type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::SessionProtos::CallMessage_Type_IsValid(val))) { + _internal_set_type(static_cast<::SessionProtos::CallMessage_Type>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // repeated string sdps = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + ptr -= 1; + do { + ptr += 1; + auto str = _internal_add_sdps(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<18>(ptr)); + } else + goto handle_unusual; + continue; + // repeated uint32 sdpMLineIndexes = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 24)) { + ptr -= 1; + do { + ptr += 1; + _internal_add_sdpmlineindexes(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr)); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<24>(ptr)); + } else if (static_cast(tag) == 26) { + ptr = ::PROTOBUF_NAMESPACE_ID::internal::PackedUInt32Parser(_internal_mutable_sdpmlineindexes(), ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated string sdpMids = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr -= 1; + do { + ptr += 1; + auto str = _internal_add_sdpmids(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<34>(ptr)); + } else + goto handle_unusual; + continue; + // required string uuid = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + auto str = _internal_mutable_uuid(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* CallMessage::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.CallMessage) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required .SessionProtos.CallMessage.Type type = 1; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_type(), target); + } + + // repeated string sdps = 2; + for (int i = 0, n = this->_internal_sdps_size(); i < n; i++) { + const auto& s = this->_internal_sdps(i); + target = stream->WriteString(2, s, target); + } + + // repeated uint32 sdpMLineIndexes = 3; + for (int i = 0, n = this->_internal_sdpmlineindexes_size(); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(3, this->_internal_sdpmlineindexes(i), target); + } + + // repeated string sdpMids = 4; + for (int i = 0, n = this->_internal_sdpmids_size(); i < n; i++) { + const auto& s = this->_internal_sdpmids(i); + target = stream->WriteString(4, s, target); + } + + // required string uuid = 5; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 5, this->_internal_uuid(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.CallMessage) + return target; +} + +size_t CallMessage::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:SessionProtos.CallMessage) + size_t total_size = 0; + + if (_internal_has_uuid()) { + // required string uuid = 5; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_uuid()); + } + + if (_internal_has_type()) { + // required .SessionProtos.CallMessage.Type type = 1; + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_type()); + } + + return total_size; +} +size_t CallMessage::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.CallMessage) + size_t total_size = 0; + + if (((_impl_._has_bits_[0] & 0x00000003) ^ 0x00000003) == 0) { // All required fields are present. + // required string uuid = 5; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_uuid()); + + // required .SessionProtos.CallMessage.Type type = 1; + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_type()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated string sdps = 2; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(_impl_.sdps_.size()); + for (int i = 0, n = _impl_.sdps_.size(); i < n; i++) { + total_size += ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + _impl_.sdps_.Get(i)); + } + + // repeated uint32 sdpMLineIndexes = 3; + { + size_t data_size = ::_pbi::WireFormatLite:: + UInt32Size(this->_impl_.sdpmlineindexes_); + total_size += 1 * + ::_pbi::FromIntSize(this->_internal_sdpmlineindexes_size()); + total_size += data_size; + } + + // repeated string sdpMids = 4; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(_impl_.sdpmids_.size()); + for (int i = 0, n = _impl_.sdpmids_.size(); i < n; i++) { + total_size += ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + _impl_.sdpmids_.Get(i)); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void CallMessage::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void CallMessage::MergeFrom(const CallMessage& from) { + CallMessage* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.CallMessage) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.sdps_.MergeFrom(from._impl_.sdps_); + _this->_impl_.sdpmlineindexes_.MergeFrom(from._impl_.sdpmlineindexes_); + _this->_impl_.sdpmids_.MergeFrom(from._impl_.sdpmids_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_uuid(from._internal_uuid()); + } + if (cached_has_bits & 0x00000002u) { + _this->_impl_.type_ = from._impl_.type_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void CallMessage::CopyFrom(const CallMessage& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.CallMessage) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CallMessage::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void CallMessage::InternalSwap(CallMessage* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.sdps_.InternalSwap(&other->_impl_.sdps_); + _impl_.sdpmlineindexes_.InternalSwap(&other->_impl_.sdpmlineindexes_); + _impl_.sdpmids_.InternalSwap(&other->_impl_.sdpmids_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.uuid_, lhs_arena, + &other->_impl_.uuid_, rhs_arena + ); + swap(_impl_.type_, other->_impl_.type_); +} + +std::string CallMessage::GetTypeName() const { + return "SessionProtos.CallMessage"; +} + + +// =================================================================== + +class KeyPair::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_publickey(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_privatekey(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000003) ^ 0x00000003) != 0; + } +}; + +KeyPair::KeyPair(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.KeyPair) +} +KeyPair::KeyPair(const KeyPair& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + KeyPair* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.publickey_){} + , decltype(_impl_.privatekey_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.publickey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.publickey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_publickey()) { + _this->_impl_.publickey_.Set(from._internal_publickey(), + _this->GetArenaForAllocation()); + } + _impl_.privatekey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.privatekey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_privatekey()) { + _this->_impl_.privatekey_.Set(from._internal_privatekey(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:SessionProtos.KeyPair) +} + +inline void KeyPair::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.publickey_){} + , decltype(_impl_.privatekey_){} + }; + _impl_.publickey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.publickey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.privatekey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.privatekey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +KeyPair::~KeyPair() { + // @@protoc_insertion_point(destructor:SessionProtos.KeyPair) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void KeyPair::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.publickey_.Destroy(); + _impl_.privatekey_.Destroy(); +} + +void KeyPair::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void KeyPair::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.KeyPair) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.publickey_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.privatekey_.ClearNonDefaultToEmpty(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* KeyPair::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required bytes publicKey = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_publickey(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // required bytes privateKey = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_privatekey(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* KeyPair::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.KeyPair) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required bytes publicKey = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 1, this->_internal_publickey(), target); + } + + // required bytes privateKey = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteBytesMaybeAliased( + 2, this->_internal_privatekey(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.KeyPair) + return target; +} + +size_t KeyPair::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:SessionProtos.KeyPair) + size_t total_size = 0; + + if (_internal_has_publickey()) { + // required bytes publicKey = 1; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_publickey()); + } + + if (_internal_has_privatekey()) { + // required bytes privateKey = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_privatekey()); + } + + return total_size; +} +size_t KeyPair::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.KeyPair) + size_t total_size = 0; + + if (((_impl_._has_bits_[0] & 0x00000003) ^ 0x00000003) == 0) { // All required fields are present. + // required bytes publicKey = 1; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_publickey()); + + // required bytes privateKey = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_privatekey()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void KeyPair::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void KeyPair::MergeFrom(const KeyPair& from) { + KeyPair* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.KeyPair) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_publickey(from._internal_publickey()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_privatekey(from._internal_privatekey()); + } + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void KeyPair::CopyFrom(const KeyPair& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.KeyPair) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool KeyPair::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void KeyPair::InternalSwap(KeyPair* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.publickey_, lhs_arena, + &other->_impl_.publickey_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.privatekey_, lhs_arena, + &other->_impl_.privatekey_, rhs_arena + ); +} + +std::string KeyPair::GetTypeName() const { + return "SessionProtos.KeyPair"; +} + + +// =================================================================== + +class DataExtractionNotification::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_type(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_timestamp(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000002) ^ 0x00000002) != 0; + } +}; + +DataExtractionNotification::DataExtractionNotification(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.DataExtractionNotification) +} +DataExtractionNotification::DataExtractionNotification(const DataExtractionNotification& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + DataExtractionNotification* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.type_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&_impl_.timestamp_, &from._impl_.timestamp_, + static_cast(reinterpret_cast(&_impl_.type_) - + reinterpret_cast(&_impl_.timestamp_)) + sizeof(_impl_.type_)); + // @@protoc_insertion_point(copy_constructor:SessionProtos.DataExtractionNotification) +} + +inline void DataExtractionNotification::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.type_){1} + }; +} + +DataExtractionNotification::~DataExtractionNotification() { + // @@protoc_insertion_point(destructor:SessionProtos.DataExtractionNotification) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void DataExtractionNotification::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); +} + +void DataExtractionNotification::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void DataExtractionNotification::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.DataExtractionNotification) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + _impl_.timestamp_ = uint64_t{0u}; + _impl_.type_ = 1; + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* DataExtractionNotification::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required .SessionProtos.DataExtractionNotification.Type type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::SessionProtos::DataExtractionNotification_Type_IsValid(val))) { + _internal_set_type(static_cast<::SessionProtos::DataExtractionNotification_Type>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional uint64 timestamp = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 16)) { + _Internal::set_has_timestamp(&has_bits); + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* DataExtractionNotification::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.DataExtractionNotification) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required .SessionProtos.DataExtractionNotification.Type type = 1; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_type(), target); + } + + // optional uint64 timestamp = 2; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(2, this->_internal_timestamp(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.DataExtractionNotification) + return target; +} + +size_t DataExtractionNotification::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.DataExtractionNotification) + size_t total_size = 0; + + // required .SessionProtos.DataExtractionNotification.Type type = 1; + if (_internal_has_type()) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_type()); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // optional uint64 timestamp = 2; + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000001u) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void DataExtractionNotification::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void DataExtractionNotification::MergeFrom(const DataExtractionNotification& from) { + DataExtractionNotification* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.DataExtractionNotification) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_impl_.timestamp_ = from._impl_.timestamp_; + } + if (cached_has_bits & 0x00000002u) { + _this->_impl_.type_ = from._impl_.type_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void DataExtractionNotification::CopyFrom(const DataExtractionNotification& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.DataExtractionNotification) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool DataExtractionNotification::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void DataExtractionNotification::InternalSwap(DataExtractionNotification* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + swap(_impl_.timestamp_, other->_impl_.timestamp_); + swap(_impl_.type_, other->_impl_.type_); +} + +std::string DataExtractionNotification::GetTypeName() const { + return "SessionProtos.DataExtractionNotification"; +} + + +// =================================================================== + +class LokiProfile::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_displayname(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_profilepicture(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +LokiProfile::LokiProfile(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.LokiProfile) +} +LokiProfile::LokiProfile(const LokiProfile& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + LokiProfile* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.displayname_){} + , decltype(_impl_.profilepicture_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.displayname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.displayname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_displayname()) { + _this->_impl_.displayname_.Set(from._internal_displayname(), + _this->GetArenaForAllocation()); + } + _impl_.profilepicture_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilepicture_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_profilepicture()) { + _this->_impl_.profilepicture_.Set(from._internal_profilepicture(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:SessionProtos.LokiProfile) +} + +inline void LokiProfile::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.displayname_){} + , decltype(_impl_.profilepicture_){} + }; + _impl_.displayname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.displayname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilepicture_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilepicture_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +LokiProfile::~LokiProfile() { + // @@protoc_insertion_point(destructor:SessionProtos.LokiProfile) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void LokiProfile::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.displayname_.Destroy(); + _impl_.profilepicture_.Destroy(); +} + +void LokiProfile::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void LokiProfile::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.LokiProfile) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.displayname_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.profilepicture_.ClearNonDefaultToEmpty(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* LokiProfile::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional string displayName = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_displayname(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string profilePicture = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_profilepicture(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* LokiProfile::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.LokiProfile) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional string displayName = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 1, this->_internal_displayname(), target); + } + + // optional string profilePicture = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_profilepicture(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.LokiProfile) + return target; +} + +size_t LokiProfile::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.LokiProfile) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional string displayName = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_displayname()); + } + + // optional string profilePicture = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_profilepicture()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void LokiProfile::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void LokiProfile::MergeFrom(const LokiProfile& from) { + LokiProfile* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.LokiProfile) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_displayname(from._internal_displayname()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_profilepicture(from._internal_profilepicture()); + } + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void LokiProfile::CopyFrom(const LokiProfile& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.LokiProfile) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool LokiProfile::IsInitialized() const { + return true; +} + +void LokiProfile::InternalSwap(LokiProfile* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.displayname_, lhs_arena, + &other->_impl_.displayname_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.profilepicture_, lhs_arena, + &other->_impl_.profilepicture_, rhs_arena + ); +} + +std::string LokiProfile::GetTypeName() const { + return "SessionProtos.LokiProfile"; +} + + +// =================================================================== + +class DataMessage_Quote_QuotedAttachment::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_contenttype(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_filename(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static const ::SessionProtos::AttachmentPointer& thumbnail(const DataMessage_Quote_QuotedAttachment* msg); + static void set_has_thumbnail(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_flags(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } +}; + +const ::SessionProtos::AttachmentPointer& +DataMessage_Quote_QuotedAttachment::_Internal::thumbnail(const DataMessage_Quote_QuotedAttachment* msg) { + return *msg->_impl_.thumbnail_; +} +DataMessage_Quote_QuotedAttachment::DataMessage_Quote_QuotedAttachment(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.DataMessage.Quote.QuotedAttachment) +} +DataMessage_Quote_QuotedAttachment::DataMessage_Quote_QuotedAttachment(const DataMessage_Quote_QuotedAttachment& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + DataMessage_Quote_QuotedAttachment* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.contenttype_){} + , decltype(_impl_.filename_){} + , decltype(_impl_.thumbnail_){nullptr} + , decltype(_impl_.flags_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.contenttype_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.contenttype_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_contenttype()) { + _this->_impl_.contenttype_.Set(from._internal_contenttype(), + _this->GetArenaForAllocation()); + } + _impl_.filename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.filename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_filename()) { + _this->_impl_.filename_.Set(from._internal_filename(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_thumbnail()) { + _this->_impl_.thumbnail_ = new ::SessionProtos::AttachmentPointer(*from._impl_.thumbnail_); + } + _this->_impl_.flags_ = from._impl_.flags_; + // @@protoc_insertion_point(copy_constructor:SessionProtos.DataMessage.Quote.QuotedAttachment) +} + +inline void DataMessage_Quote_QuotedAttachment::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.contenttype_){} + , decltype(_impl_.filename_){} + , decltype(_impl_.thumbnail_){nullptr} + , decltype(_impl_.flags_){0u} + }; + _impl_.contenttype_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.contenttype_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.filename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.filename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +DataMessage_Quote_QuotedAttachment::~DataMessage_Quote_QuotedAttachment() { + // @@protoc_insertion_point(destructor:SessionProtos.DataMessage.Quote.QuotedAttachment) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void DataMessage_Quote_QuotedAttachment::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.contenttype_.Destroy(); + _impl_.filename_.Destroy(); + if (this != internal_default_instance()) delete _impl_.thumbnail_; +} + +void DataMessage_Quote_QuotedAttachment::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void DataMessage_Quote_QuotedAttachment::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.DataMessage.Quote.QuotedAttachment) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _impl_.contenttype_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.filename_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000004u) { + GOOGLE_DCHECK(_impl_.thumbnail_ != nullptr); + _impl_.thumbnail_->Clear(); + } + } + _impl_.flags_ = 0u; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* DataMessage_Quote_QuotedAttachment::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional string contentType = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_contenttype(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string fileName = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_filename(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.AttachmentPointer thumbnail = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_thumbnail(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional uint32 flags = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 32)) { + _Internal::set_has_flags(&has_bits); + _impl_.flags_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* DataMessage_Quote_QuotedAttachment::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.DataMessage.Quote.QuotedAttachment) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional string contentType = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 1, this->_internal_contenttype(), target); + } + + // optional string fileName = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_filename(), target); + } + + // optional .SessionProtos.AttachmentPointer thumbnail = 3; + if (cached_has_bits & 0x00000004u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::thumbnail(this), + _Internal::thumbnail(this).GetCachedSize(), target, stream); + } + + // optional uint32 flags = 4; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(4, this->_internal_flags(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.DataMessage.Quote.QuotedAttachment) + return target; +} + +size_t DataMessage_Quote_QuotedAttachment::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.DataMessage.Quote.QuotedAttachment) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + // optional string contentType = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_contenttype()); + } + + // optional string fileName = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_filename()); + } + + // optional .SessionProtos.AttachmentPointer thumbnail = 3; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.thumbnail_); + } + + // optional uint32 flags = 4; + if (cached_has_bits & 0x00000008u) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_flags()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void DataMessage_Quote_QuotedAttachment::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void DataMessage_Quote_QuotedAttachment::MergeFrom(const DataMessage_Quote_QuotedAttachment& from) { + DataMessage_Quote_QuotedAttachment* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.DataMessage.Quote.QuotedAttachment) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_contenttype(from._internal_contenttype()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_filename(from._internal_filename()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_mutable_thumbnail()->::SessionProtos::AttachmentPointer::MergeFrom( + from._internal_thumbnail()); + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.flags_ = from._impl_.flags_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void DataMessage_Quote_QuotedAttachment::CopyFrom(const DataMessage_Quote_QuotedAttachment& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.DataMessage.Quote.QuotedAttachment) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool DataMessage_Quote_QuotedAttachment::IsInitialized() const { + if (_internal_has_thumbnail()) { + if (!_impl_.thumbnail_->IsInitialized()) return false; + } + return true; +} + +void DataMessage_Quote_QuotedAttachment::InternalSwap(DataMessage_Quote_QuotedAttachment* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.contenttype_, lhs_arena, + &other->_impl_.contenttype_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.filename_, lhs_arena, + &other->_impl_.filename_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(DataMessage_Quote_QuotedAttachment, _impl_.flags_) + + sizeof(DataMessage_Quote_QuotedAttachment::_impl_.flags_) + - PROTOBUF_FIELD_OFFSET(DataMessage_Quote_QuotedAttachment, _impl_.thumbnail_)>( + reinterpret_cast(&_impl_.thumbnail_), + reinterpret_cast(&other->_impl_.thumbnail_)); +} + +std::string DataMessage_Quote_QuotedAttachment::GetTypeName() const { + return "SessionProtos.DataMessage.Quote.QuotedAttachment"; +} + + +// =================================================================== + +class DataMessage_Quote::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_id(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_author(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_text(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000005) ^ 0x00000005) != 0; + } +}; + +DataMessage_Quote::DataMessage_Quote(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.DataMessage.Quote) +} +DataMessage_Quote::DataMessage_Quote(const DataMessage_Quote& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + DataMessage_Quote* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.attachments_){from._impl_.attachments_} + , decltype(_impl_.author_){} + , decltype(_impl_.text_){} + , decltype(_impl_.id_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.author_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.author_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_author()) { + _this->_impl_.author_.Set(from._internal_author(), + _this->GetArenaForAllocation()); + } + _impl_.text_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.text_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_text()) { + _this->_impl_.text_.Set(from._internal_text(), + _this->GetArenaForAllocation()); + } + _this->_impl_.id_ = from._impl_.id_; + // @@protoc_insertion_point(copy_constructor:SessionProtos.DataMessage.Quote) +} + +inline void DataMessage_Quote::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.attachments_){arena} + , decltype(_impl_.author_){} + , decltype(_impl_.text_){} + , decltype(_impl_.id_){uint64_t{0u}} + }; + _impl_.author_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.author_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.text_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.text_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +DataMessage_Quote::~DataMessage_Quote() { + // @@protoc_insertion_point(destructor:SessionProtos.DataMessage.Quote) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void DataMessage_Quote::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.attachments_.~RepeatedPtrField(); + _impl_.author_.Destroy(); + _impl_.text_.Destroy(); +} + +void DataMessage_Quote::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void DataMessage_Quote::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.DataMessage.Quote) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.attachments_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.author_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.text_.ClearNonDefaultToEmpty(); + } + } + _impl_.id_ = uint64_t{0u}; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* DataMessage_Quote::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required uint64 id = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _Internal::set_has_id(&has_bits); + _impl_.id_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // required string author = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_author(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string text = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_text(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated .SessionProtos.DataMessage.Quote.QuotedAttachment attachments = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_attachments(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<34>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* DataMessage_Quote::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.DataMessage.Quote) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required uint64 id = 1; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_id(), target); + } + + // required string author = 2; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_author(), target); + } + + // optional string text = 3; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 3, this->_internal_text(), target); + } + + // repeated .SessionProtos.DataMessage.Quote.QuotedAttachment attachments = 4; + for (unsigned i = 0, + n = static_cast(this->_internal_attachments_size()); i < n; i++) { + const auto& repfield = this->_internal_attachments(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, repfield, repfield.GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.DataMessage.Quote) + return target; +} + +size_t DataMessage_Quote::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:SessionProtos.DataMessage.Quote) + size_t total_size = 0; + + if (_internal_has_author()) { + // required string author = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_author()); + } + + if (_internal_has_id()) { + // required uint64 id = 1; + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_id()); + } + + return total_size; +} +size_t DataMessage_Quote::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.DataMessage.Quote) + size_t total_size = 0; + + if (((_impl_._has_bits_[0] & 0x00000005) ^ 0x00000005) == 0) { // All required fields are present. + // required string author = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_author()); + + // required uint64 id = 1; + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_id()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .SessionProtos.DataMessage.Quote.QuotedAttachment attachments = 4; + total_size += 1UL * this->_internal_attachments_size(); + for (const auto& msg : this->_impl_.attachments_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // optional string text = 3; + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_text()); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void DataMessage_Quote::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void DataMessage_Quote::MergeFrom(const DataMessage_Quote& from) { + DataMessage_Quote* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.DataMessage.Quote) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.attachments_.MergeFrom(from._impl_.attachments_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_author(from._internal_author()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_text(from._internal_text()); + } + if (cached_has_bits & 0x00000004u) { + _this->_impl_.id_ = from._impl_.id_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void DataMessage_Quote::CopyFrom(const DataMessage_Quote& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.DataMessage.Quote) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool DataMessage_Quote::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + if (!::PROTOBUF_NAMESPACE_ID::internal::AllAreInitialized(_impl_.attachments_)) + return false; + return true; +} + +void DataMessage_Quote::InternalSwap(DataMessage_Quote* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.attachments_.InternalSwap(&other->_impl_.attachments_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.author_, lhs_arena, + &other->_impl_.author_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.text_, lhs_arena, + &other->_impl_.text_, rhs_arena + ); + swap(_impl_.id_, other->_impl_.id_); +} + +std::string DataMessage_Quote::GetTypeName() const { + return "SessionProtos.DataMessage.Quote"; +} + + +// =================================================================== + +class DataMessage_Preview::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_url(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_title(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static const ::SessionProtos::AttachmentPointer& image(const DataMessage_Preview* msg); + static void set_has_image(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000001) ^ 0x00000001) != 0; + } +}; + +const ::SessionProtos::AttachmentPointer& +DataMessage_Preview::_Internal::image(const DataMessage_Preview* msg) { + return *msg->_impl_.image_; +} +DataMessage_Preview::DataMessage_Preview(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.DataMessage.Preview) +} +DataMessage_Preview::DataMessage_Preview(const DataMessage_Preview& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + DataMessage_Preview* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.url_){} + , decltype(_impl_.title_){} + , decltype(_impl_.image_){nullptr}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.url_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.url_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_url()) { + _this->_impl_.url_.Set(from._internal_url(), + _this->GetArenaForAllocation()); + } + _impl_.title_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.title_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_title()) { + _this->_impl_.title_.Set(from._internal_title(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_image()) { + _this->_impl_.image_ = new ::SessionProtos::AttachmentPointer(*from._impl_.image_); + } + // @@protoc_insertion_point(copy_constructor:SessionProtos.DataMessage.Preview) +} + +inline void DataMessage_Preview::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.url_){} + , decltype(_impl_.title_){} + , decltype(_impl_.image_){nullptr} + }; + _impl_.url_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.url_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.title_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.title_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +DataMessage_Preview::~DataMessage_Preview() { + // @@protoc_insertion_point(destructor:SessionProtos.DataMessage.Preview) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void DataMessage_Preview::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.url_.Destroy(); + _impl_.title_.Destroy(); + if (this != internal_default_instance()) delete _impl_.image_; +} + +void DataMessage_Preview::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void DataMessage_Preview::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.DataMessage.Preview) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _impl_.url_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.title_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000004u) { + GOOGLE_DCHECK(_impl_.image_ != nullptr); + _impl_.image_->Clear(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* DataMessage_Preview::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required string url = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_url(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string title = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_title(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.AttachmentPointer image = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_image(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* DataMessage_Preview::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.DataMessage.Preview) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required string url = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 1, this->_internal_url(), target); + } + + // optional string title = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_title(), target); + } + + // optional .SessionProtos.AttachmentPointer image = 3; + if (cached_has_bits & 0x00000004u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::image(this), + _Internal::image(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.DataMessage.Preview) + return target; +} + +size_t DataMessage_Preview::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.DataMessage.Preview) + size_t total_size = 0; + + // required string url = 1; + if (_internal_has_url()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_url()); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000006u) { + // optional string title = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_title()); + } + + // optional .SessionProtos.AttachmentPointer image = 3; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.image_); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void DataMessage_Preview::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void DataMessage_Preview::MergeFrom(const DataMessage_Preview& from) { + DataMessage_Preview* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.DataMessage.Preview) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_url(from._internal_url()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_title(from._internal_title()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_mutable_image()->::SessionProtos::AttachmentPointer::MergeFrom( + from._internal_image()); + } + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void DataMessage_Preview::CopyFrom(const DataMessage_Preview& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.DataMessage.Preview) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool DataMessage_Preview::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + if (_internal_has_image()) { + if (!_impl_.image_->IsInitialized()) return false; + } + return true; +} + +void DataMessage_Preview::InternalSwap(DataMessage_Preview* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.url_, lhs_arena, + &other->_impl_.url_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.title_, lhs_arena, + &other->_impl_.title_, rhs_arena + ); + swap(_impl_.image_, other->_impl_.image_); +} + +std::string DataMessage_Preview::GetTypeName() const { + return "SessionProtos.DataMessage.Preview"; +} + + +// =================================================================== + +class DataMessage_Reaction::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_id(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_author(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_emoji(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_action(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x0000000d) ^ 0x0000000d) != 0; + } +}; + +DataMessage_Reaction::DataMessage_Reaction(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.DataMessage.Reaction) +} +DataMessage_Reaction::DataMessage_Reaction(const DataMessage_Reaction& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + DataMessage_Reaction* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.author_){} + , decltype(_impl_.emoji_){} + , decltype(_impl_.id_){} + , decltype(_impl_.action_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.author_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.author_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_author()) { + _this->_impl_.author_.Set(from._internal_author(), + _this->GetArenaForAllocation()); + } + _impl_.emoji_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.emoji_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_emoji()) { + _this->_impl_.emoji_.Set(from._internal_emoji(), + _this->GetArenaForAllocation()); + } + ::memcpy(&_impl_.id_, &from._impl_.id_, + static_cast(reinterpret_cast(&_impl_.action_) - + reinterpret_cast(&_impl_.id_)) + sizeof(_impl_.action_)); + // @@protoc_insertion_point(copy_constructor:SessionProtos.DataMessage.Reaction) +} + +inline void DataMessage_Reaction::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.author_){} + , decltype(_impl_.emoji_){} + , decltype(_impl_.id_){uint64_t{0u}} + , decltype(_impl_.action_){0} + }; + _impl_.author_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.author_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.emoji_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.emoji_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +DataMessage_Reaction::~DataMessage_Reaction() { + // @@protoc_insertion_point(destructor:SessionProtos.DataMessage.Reaction) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void DataMessage_Reaction::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.author_.Destroy(); + _impl_.emoji_.Destroy(); +} + +void DataMessage_Reaction::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void DataMessage_Reaction::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.DataMessage.Reaction) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.author_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.emoji_.ClearNonDefaultToEmpty(); + } + } + if (cached_has_bits & 0x0000000cu) { + ::memset(&_impl_.id_, 0, static_cast( + reinterpret_cast(&_impl_.action_) - + reinterpret_cast(&_impl_.id_)) + sizeof(_impl_.action_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* DataMessage_Reaction::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required uint64 id = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _Internal::set_has_id(&has_bits); + _impl_.id_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // required string author = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_author(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string emoji = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_emoji(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // required .SessionProtos.DataMessage.Reaction.Action action = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 32)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::SessionProtos::DataMessage_Reaction_Action_IsValid(val))) { + _internal_set_action(static_cast<::SessionProtos::DataMessage_Reaction_Action>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(4, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* DataMessage_Reaction::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.DataMessage.Reaction) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required uint64 id = 1; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_id(), target); + } + + // required string author = 2; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_author(), target); + } + + // optional string emoji = 3; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 3, this->_internal_emoji(), target); + } + + // required .SessionProtos.DataMessage.Reaction.Action action = 4; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 4, this->_internal_action(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.DataMessage.Reaction) + return target; +} + +size_t DataMessage_Reaction::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:SessionProtos.DataMessage.Reaction) + size_t total_size = 0; + + if (_internal_has_author()) { + // required string author = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_author()); + } + + if (_internal_has_id()) { + // required uint64 id = 1; + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_id()); + } + + if (_internal_has_action()) { + // required .SessionProtos.DataMessage.Reaction.Action action = 4; + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_action()); + } + + return total_size; +} +size_t DataMessage_Reaction::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.DataMessage.Reaction) + size_t total_size = 0; + + if (((_impl_._has_bits_[0] & 0x0000000d) ^ 0x0000000d) == 0) { // All required fields are present. + // required string author = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_author()); + + // required uint64 id = 1; + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_id()); + + // required .SessionProtos.DataMessage.Reaction.Action action = 4; + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_action()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // optional string emoji = 3; + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_emoji()); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void DataMessage_Reaction::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void DataMessage_Reaction::MergeFrom(const DataMessage_Reaction& from) { + DataMessage_Reaction* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.DataMessage.Reaction) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_author(from._internal_author()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_emoji(from._internal_emoji()); + } + if (cached_has_bits & 0x00000004u) { + _this->_impl_.id_ = from._impl_.id_; + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.action_ = from._impl_.action_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void DataMessage_Reaction::CopyFrom(const DataMessage_Reaction& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.DataMessage.Reaction) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool DataMessage_Reaction::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void DataMessage_Reaction::InternalSwap(DataMessage_Reaction* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.author_, lhs_arena, + &other->_impl_.author_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.emoji_, lhs_arena, + &other->_impl_.emoji_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(DataMessage_Reaction, _impl_.action_) + + sizeof(DataMessage_Reaction::_impl_.action_) + - PROTOBUF_FIELD_OFFSET(DataMessage_Reaction, _impl_.id_)>( + reinterpret_cast(&_impl_.id_), + reinterpret_cast(&other->_impl_.id_)); +} + +std::string DataMessage_Reaction::GetTypeName() const { + return "SessionProtos.DataMessage.Reaction"; +} + + +// =================================================================== + +class DataMessage_OpenGroupInvitation::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_url(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_name(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000003) ^ 0x00000003) != 0; + } +}; + +DataMessage_OpenGroupInvitation::DataMessage_OpenGroupInvitation(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.DataMessage.OpenGroupInvitation) +} +DataMessage_OpenGroupInvitation::DataMessage_OpenGroupInvitation(const DataMessage_OpenGroupInvitation& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + DataMessage_OpenGroupInvitation* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.url_){} + , decltype(_impl_.name_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.url_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.url_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_url()) { + _this->_impl_.url_.Set(from._internal_url(), + _this->GetArenaForAllocation()); + } + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_name()) { + _this->_impl_.name_.Set(from._internal_name(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:SessionProtos.DataMessage.OpenGroupInvitation) +} + +inline void DataMessage_OpenGroupInvitation::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.url_){} + , decltype(_impl_.name_){} + }; + _impl_.url_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.url_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +DataMessage_OpenGroupInvitation::~DataMessage_OpenGroupInvitation() { + // @@protoc_insertion_point(destructor:SessionProtos.DataMessage.OpenGroupInvitation) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void DataMessage_OpenGroupInvitation::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.url_.Destroy(); + _impl_.name_.Destroy(); +} + +void DataMessage_OpenGroupInvitation::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void DataMessage_OpenGroupInvitation::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.DataMessage.OpenGroupInvitation) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.url_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.name_.ClearNonDefaultToEmpty(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* DataMessage_OpenGroupInvitation::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required string url = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_url(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // required string name = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_name(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* DataMessage_OpenGroupInvitation::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.DataMessage.OpenGroupInvitation) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required string url = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 1, this->_internal_url(), target); + } + + // required string name = 3; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 3, this->_internal_name(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.DataMessage.OpenGroupInvitation) + return target; +} + +size_t DataMessage_OpenGroupInvitation::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:SessionProtos.DataMessage.OpenGroupInvitation) + size_t total_size = 0; + + if (_internal_has_url()) { + // required string url = 1; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_url()); + } + + if (_internal_has_name()) { + // required string name = 3; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_name()); + } + + return total_size; +} +size_t DataMessage_OpenGroupInvitation::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.DataMessage.OpenGroupInvitation) + size_t total_size = 0; + + if (((_impl_._has_bits_[0] & 0x00000003) ^ 0x00000003) == 0) { // All required fields are present. + // required string url = 1; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_url()); + + // required string name = 3; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_name()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void DataMessage_OpenGroupInvitation::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void DataMessage_OpenGroupInvitation::MergeFrom(const DataMessage_OpenGroupInvitation& from) { + DataMessage_OpenGroupInvitation* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.DataMessage.OpenGroupInvitation) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_url(from._internal_url()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_name(from._internal_name()); + } + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void DataMessage_OpenGroupInvitation::CopyFrom(const DataMessage_OpenGroupInvitation& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.DataMessage.OpenGroupInvitation) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool DataMessage_OpenGroupInvitation::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void DataMessage_OpenGroupInvitation::InternalSwap(DataMessage_OpenGroupInvitation* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.url_, lhs_arena, + &other->_impl_.url_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.name_, lhs_arena, + &other->_impl_.name_, rhs_arena + ); +} + +std::string DataMessage_OpenGroupInvitation::GetTypeName() const { + return "SessionProtos.DataMessage.OpenGroupInvitation"; +} + + +// =================================================================== + +class DataMessage_ClosedGroupControlMessage_KeyPairWrapper::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_publickey(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_encryptedkeypair(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000003) ^ 0x00000003) != 0; + } +}; + +DataMessage_ClosedGroupControlMessage_KeyPairWrapper::DataMessage_ClosedGroupControlMessage_KeyPairWrapper(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) +} +DataMessage_ClosedGroupControlMessage_KeyPairWrapper::DataMessage_ClosedGroupControlMessage_KeyPairWrapper(const DataMessage_ClosedGroupControlMessage_KeyPairWrapper& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + DataMessage_ClosedGroupControlMessage_KeyPairWrapper* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.publickey_){} + , decltype(_impl_.encryptedkeypair_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.publickey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.publickey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_publickey()) { + _this->_impl_.publickey_.Set(from._internal_publickey(), + _this->GetArenaForAllocation()); + } + _impl_.encryptedkeypair_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.encryptedkeypair_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_encryptedkeypair()) { + _this->_impl_.encryptedkeypair_.Set(from._internal_encryptedkeypair(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) +} + +inline void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.publickey_){} + , decltype(_impl_.encryptedkeypair_){} + }; + _impl_.publickey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.publickey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.encryptedkeypair_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.encryptedkeypair_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +DataMessage_ClosedGroupControlMessage_KeyPairWrapper::~DataMessage_ClosedGroupControlMessage_KeyPairWrapper() { + // @@protoc_insertion_point(destructor:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.publickey_.Destroy(); + _impl_.encryptedkeypair_.Destroy(); +} + +void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.publickey_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.encryptedkeypair_.ClearNonDefaultToEmpty(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* DataMessage_ClosedGroupControlMessage_KeyPairWrapper::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required bytes publicKey = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_publickey(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // required bytes encryptedKeyPair = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_encryptedkeypair(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* DataMessage_ClosedGroupControlMessage_KeyPairWrapper::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required bytes publicKey = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 1, this->_internal_publickey(), target); + } + + // required bytes encryptedKeyPair = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteBytesMaybeAliased( + 2, this->_internal_encryptedkeypair(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) + return target; +} + +size_t DataMessage_ClosedGroupControlMessage_KeyPairWrapper::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) + size_t total_size = 0; + + if (_internal_has_publickey()) { + // required bytes publicKey = 1; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_publickey()); + } + + if (_internal_has_encryptedkeypair()) { + // required bytes encryptedKeyPair = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_encryptedkeypair()); + } + + return total_size; +} +size_t DataMessage_ClosedGroupControlMessage_KeyPairWrapper::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) + size_t total_size = 0; + + if (((_impl_._has_bits_[0] & 0x00000003) ^ 0x00000003) == 0) { // All required fields are present. + // required bytes publicKey = 1; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_publickey()); + + // required bytes encryptedKeyPair = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_encryptedkeypair()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::MergeFrom(const DataMessage_ClosedGroupControlMessage_KeyPairWrapper& from) { + DataMessage_ClosedGroupControlMessage_KeyPairWrapper* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_publickey(from._internal_publickey()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_encryptedkeypair(from._internal_encryptedkeypair()); + } + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::CopyFrom(const DataMessage_ClosedGroupControlMessage_KeyPairWrapper& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool DataMessage_ClosedGroupControlMessage_KeyPairWrapper::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::InternalSwap(DataMessage_ClosedGroupControlMessage_KeyPairWrapper* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.publickey_, lhs_arena, + &other->_impl_.publickey_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.encryptedkeypair_, lhs_arena, + &other->_impl_.encryptedkeypair_, rhs_arena + ); +} + +std::string DataMessage_ClosedGroupControlMessage_KeyPairWrapper::GetTypeName() const { + return "SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper"; +} + + +// =================================================================== + +class DataMessage_ClosedGroupControlMessage::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_type(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static void set_has_publickey(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_name(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static const ::SessionProtos::KeyPair& encryptionkeypair(const DataMessage_ClosedGroupControlMessage* msg); + static void set_has_encryptionkeypair(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_expirationtimer(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000010) ^ 0x00000010) != 0; + } +}; + +const ::SessionProtos::KeyPair& +DataMessage_ClosedGroupControlMessage::_Internal::encryptionkeypair(const DataMessage_ClosedGroupControlMessage* msg) { + return *msg->_impl_.encryptionkeypair_; +} +DataMessage_ClosedGroupControlMessage::DataMessage_ClosedGroupControlMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.DataMessage.ClosedGroupControlMessage) +} +DataMessage_ClosedGroupControlMessage::DataMessage_ClosedGroupControlMessage(const DataMessage_ClosedGroupControlMessage& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + DataMessage_ClosedGroupControlMessage* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.members_){from._impl_.members_} + , decltype(_impl_.admins_){from._impl_.admins_} + , decltype(_impl_.wrappers_){from._impl_.wrappers_} + , decltype(_impl_.publickey_){} + , decltype(_impl_.name_){} + , decltype(_impl_.encryptionkeypair_){nullptr} + , decltype(_impl_.expirationtimer_){} + , decltype(_impl_.type_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.publickey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.publickey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_publickey()) { + _this->_impl_.publickey_.Set(from._internal_publickey(), + _this->GetArenaForAllocation()); + } + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_name()) { + _this->_impl_.name_.Set(from._internal_name(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_encryptionkeypair()) { + _this->_impl_.encryptionkeypair_ = new ::SessionProtos::KeyPair(*from._impl_.encryptionkeypair_); + } + ::memcpy(&_impl_.expirationtimer_, &from._impl_.expirationtimer_, + static_cast(reinterpret_cast(&_impl_.type_) - + reinterpret_cast(&_impl_.expirationtimer_)) + sizeof(_impl_.type_)); + // @@protoc_insertion_point(copy_constructor:SessionProtos.DataMessage.ClosedGroupControlMessage) +} + +inline void DataMessage_ClosedGroupControlMessage::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.members_){arena} + , decltype(_impl_.admins_){arena} + , decltype(_impl_.wrappers_){arena} + , decltype(_impl_.publickey_){} + , decltype(_impl_.name_){} + , decltype(_impl_.encryptionkeypair_){nullptr} + , decltype(_impl_.expirationtimer_){0u} + , decltype(_impl_.type_){1} + }; + _impl_.publickey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.publickey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +DataMessage_ClosedGroupControlMessage::~DataMessage_ClosedGroupControlMessage() { + // @@protoc_insertion_point(destructor:SessionProtos.DataMessage.ClosedGroupControlMessage) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void DataMessage_ClosedGroupControlMessage::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.members_.~RepeatedPtrField(); + _impl_.admins_.~RepeatedPtrField(); + _impl_.wrappers_.~RepeatedPtrField(); + _impl_.publickey_.Destroy(); + _impl_.name_.Destroy(); + if (this != internal_default_instance()) delete _impl_.encryptionkeypair_; +} + +void DataMessage_ClosedGroupControlMessage::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void DataMessage_ClosedGroupControlMessage::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.DataMessage.ClosedGroupControlMessage) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.members_.Clear(); + _impl_.admins_.Clear(); + _impl_.wrappers_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _impl_.publickey_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.name_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000004u) { + GOOGLE_DCHECK(_impl_.encryptionkeypair_ != nullptr); + _impl_.encryptionkeypair_->Clear(); + } + } + if (cached_has_bits & 0x00000018u) { + _impl_.expirationtimer_ = 0u; + _impl_.type_ = 1; + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* DataMessage_ClosedGroupControlMessage::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required .SessionProtos.DataMessage.ClosedGroupControlMessage.Type type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::SessionProtos::DataMessage_ClosedGroupControlMessage_Type_IsValid(val))) { + _internal_set_type(static_cast<::SessionProtos::DataMessage_ClosedGroupControlMessage_Type>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional bytes publicKey = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_publickey(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string name = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_name(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.KeyPair encryptionKeyPair = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_encryptionkeypair(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated bytes members = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + ptr -= 1; + do { + ptr += 1; + auto str = _internal_add_members(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<42>(ptr)); + } else + goto handle_unusual; + continue; + // repeated bytes admins = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 50)) { + ptr -= 1; + do { + ptr += 1; + auto str = _internal_add_admins(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<50>(ptr)); + } else + goto handle_unusual; + continue; + // repeated .SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 58)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_wrappers(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<58>(ptr)); + } else + goto handle_unusual; + continue; + // optional uint32 expirationTimer = 8; + case 8: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 64)) { + _Internal::set_has_expirationtimer(&has_bits); + _impl_.expirationtimer_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* DataMessage_ClosedGroupControlMessage::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.DataMessage.ClosedGroupControlMessage) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required .SessionProtos.DataMessage.ClosedGroupControlMessage.Type type = 1; + if (cached_has_bits & 0x00000010u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_type(), target); + } + + // optional bytes publicKey = 2; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 2, this->_internal_publickey(), target); + } + + // optional string name = 3; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 3, this->_internal_name(), target); + } + + // optional .SessionProtos.KeyPair encryptionKeyPair = 4; + if (cached_has_bits & 0x00000004u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(4, _Internal::encryptionkeypair(this), + _Internal::encryptionkeypair(this).GetCachedSize(), target, stream); + } + + // repeated bytes members = 5; + for (int i = 0, n = this->_internal_members_size(); i < n; i++) { + const auto& s = this->_internal_members(i); + target = stream->WriteBytes(5, s, target); + } + + // repeated bytes admins = 6; + for (int i = 0, n = this->_internal_admins_size(); i < n; i++) { + const auto& s = this->_internal_admins(i); + target = stream->WriteBytes(6, s, target); + } + + // repeated .SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; + for (unsigned i = 0, + n = static_cast(this->_internal_wrappers_size()); i < n; i++) { + const auto& repfield = this->_internal_wrappers(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(7, repfield, repfield.GetCachedSize(), target, stream); + } + + // optional uint32 expirationTimer = 8; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(8, this->_internal_expirationtimer(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.DataMessage.ClosedGroupControlMessage) + return target; +} + +size_t DataMessage_ClosedGroupControlMessage::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.DataMessage.ClosedGroupControlMessage) + size_t total_size = 0; + + // required .SessionProtos.DataMessage.ClosedGroupControlMessage.Type type = 1; + if (_internal_has_type()) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_type()); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated bytes members = 5; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(_impl_.members_.size()); + for (int i = 0, n = _impl_.members_.size(); i < n; i++) { + total_size += ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + _impl_.members_.Get(i)); + } + + // repeated bytes admins = 6; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(_impl_.admins_.size()); + for (int i = 0, n = _impl_.admins_.size(); i < n; i++) { + total_size += ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + _impl_.admins_.Get(i)); + } + + // repeated .SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; + total_size += 1UL * this->_internal_wrappers_size(); + for (const auto& msg : this->_impl_.wrappers_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + // optional bytes publicKey = 2; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_publickey()); + } + + // optional string name = 3; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_name()); + } + + // optional .SessionProtos.KeyPair encryptionKeyPair = 4; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.encryptionkeypair_); + } + + // optional uint32 expirationTimer = 8; + if (cached_has_bits & 0x00000008u) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_expirationtimer()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void DataMessage_ClosedGroupControlMessage::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void DataMessage_ClosedGroupControlMessage::MergeFrom(const DataMessage_ClosedGroupControlMessage& from) { + DataMessage_ClosedGroupControlMessage* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.DataMessage.ClosedGroupControlMessage) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.members_.MergeFrom(from._impl_.members_); + _this->_impl_.admins_.MergeFrom(from._impl_.admins_); + _this->_impl_.wrappers_.MergeFrom(from._impl_.wrappers_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000001fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_publickey(from._internal_publickey()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_name(from._internal_name()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_mutable_encryptionkeypair()->::SessionProtos::KeyPair::MergeFrom( + from._internal_encryptionkeypair()); + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.expirationtimer_ = from._impl_.expirationtimer_; + } + if (cached_has_bits & 0x00000010u) { + _this->_impl_.type_ = from._impl_.type_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void DataMessage_ClosedGroupControlMessage::CopyFrom(const DataMessage_ClosedGroupControlMessage& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.DataMessage.ClosedGroupControlMessage) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool DataMessage_ClosedGroupControlMessage::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + if (!::PROTOBUF_NAMESPACE_ID::internal::AllAreInitialized(_impl_.wrappers_)) + return false; + if (_internal_has_encryptionkeypair()) { + if (!_impl_.encryptionkeypair_->IsInitialized()) return false; + } + return true; +} + +void DataMessage_ClosedGroupControlMessage::InternalSwap(DataMessage_ClosedGroupControlMessage* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.members_.InternalSwap(&other->_impl_.members_); + _impl_.admins_.InternalSwap(&other->_impl_.admins_); + _impl_.wrappers_.InternalSwap(&other->_impl_.wrappers_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.publickey_, lhs_arena, + &other->_impl_.publickey_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.name_, lhs_arena, + &other->_impl_.name_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(DataMessage_ClosedGroupControlMessage, _impl_.expirationtimer_) + + sizeof(DataMessage_ClosedGroupControlMessage::_impl_.expirationtimer_) + - PROTOBUF_FIELD_OFFSET(DataMessage_ClosedGroupControlMessage, _impl_.encryptionkeypair_)>( + reinterpret_cast(&_impl_.encryptionkeypair_), + reinterpret_cast(&other->_impl_.encryptionkeypair_)); + swap(_impl_.type_, other->_impl_.type_); +} + +std::string DataMessage_ClosedGroupControlMessage::GetTypeName() const { + return "SessionProtos.DataMessage.ClosedGroupControlMessage"; +} + + +// =================================================================== + +class DataMessage::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_body(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_flags(HasBits* has_bits) { + (*has_bits)[0] |= 256u; + } + static void set_has_expiretimer(HasBits* has_bits) { + (*has_bits)[0] |= 512u; + } + static void set_has_profilekey(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_timestamp(HasBits* has_bits) { + (*has_bits)[0] |= 1024u; + } + static const ::SessionProtos::DataMessage_Quote& quote(const DataMessage* msg); + static void set_has_quote(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static const ::SessionProtos::DataMessage_Reaction& reaction(const DataMessage* msg); + static void set_has_reaction(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static const ::SessionProtos::LokiProfile& profile(const DataMessage* msg); + static void set_has_profile(HasBits* has_bits) { + (*has_bits)[0] |= 32u; + } + static const ::SessionProtos::DataMessage_OpenGroupInvitation& opengroupinvitation(const DataMessage* msg); + static void set_has_opengroupinvitation(HasBits* has_bits) { + (*has_bits)[0] |= 64u; + } + static const ::SessionProtos::DataMessage_ClosedGroupControlMessage& closedgroupcontrolmessage(const DataMessage* msg); + static void set_has_closedgroupcontrolmessage(HasBits* has_bits) { + (*has_bits)[0] |= 128u; + } + static void set_has_synctarget(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_blockscommunitymessagerequests(HasBits* has_bits) { + (*has_bits)[0] |= 2048u; + } +}; + +const ::SessionProtos::DataMessage_Quote& +DataMessage::_Internal::quote(const DataMessage* msg) { + return *msg->_impl_.quote_; +} +const ::SessionProtos::DataMessage_Reaction& +DataMessage::_Internal::reaction(const DataMessage* msg) { + return *msg->_impl_.reaction_; +} +const ::SessionProtos::LokiProfile& +DataMessage::_Internal::profile(const DataMessage* msg) { + return *msg->_impl_.profile_; +} +const ::SessionProtos::DataMessage_OpenGroupInvitation& +DataMessage::_Internal::opengroupinvitation(const DataMessage* msg) { + return *msg->_impl_.opengroupinvitation_; +} +const ::SessionProtos::DataMessage_ClosedGroupControlMessage& +DataMessage::_Internal::closedgroupcontrolmessage(const DataMessage* msg) { + return *msg->_impl_.closedgroupcontrolmessage_; +} +DataMessage::DataMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.DataMessage) +} +DataMessage::DataMessage(const DataMessage& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + DataMessage* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.attachments_){from._impl_.attachments_} + , decltype(_impl_.preview_){from._impl_.preview_} + , decltype(_impl_.body_){} + , decltype(_impl_.profilekey_){} + , decltype(_impl_.synctarget_){} + , decltype(_impl_.quote_){nullptr} + , decltype(_impl_.reaction_){nullptr} + , decltype(_impl_.profile_){nullptr} + , decltype(_impl_.opengroupinvitation_){nullptr} + , decltype(_impl_.closedgroupcontrolmessage_){nullptr} + , decltype(_impl_.flags_){} + , decltype(_impl_.expiretimer_){} + , decltype(_impl_.timestamp_){} + , decltype(_impl_.blockscommunitymessagerequests_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.body_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.body_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_body()) { + _this->_impl_.body_.Set(from._internal_body(), + _this->GetArenaForAllocation()); + } + _impl_.profilekey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilekey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_profilekey()) { + _this->_impl_.profilekey_.Set(from._internal_profilekey(), + _this->GetArenaForAllocation()); + } + _impl_.synctarget_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.synctarget_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_synctarget()) { + _this->_impl_.synctarget_.Set(from._internal_synctarget(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_quote()) { + _this->_impl_.quote_ = new ::SessionProtos::DataMessage_Quote(*from._impl_.quote_); + } + if (from._internal_has_reaction()) { + _this->_impl_.reaction_ = new ::SessionProtos::DataMessage_Reaction(*from._impl_.reaction_); + } + if (from._internal_has_profile()) { + _this->_impl_.profile_ = new ::SessionProtos::LokiProfile(*from._impl_.profile_); + } + if (from._internal_has_opengroupinvitation()) { + _this->_impl_.opengroupinvitation_ = new ::SessionProtos::DataMessage_OpenGroupInvitation(*from._impl_.opengroupinvitation_); + } + if (from._internal_has_closedgroupcontrolmessage()) { + _this->_impl_.closedgroupcontrolmessage_ = new ::SessionProtos::DataMessage_ClosedGroupControlMessage(*from._impl_.closedgroupcontrolmessage_); + } + ::memcpy(&_impl_.flags_, &from._impl_.flags_, + static_cast(reinterpret_cast(&_impl_.blockscommunitymessagerequests_) - + reinterpret_cast(&_impl_.flags_)) + sizeof(_impl_.blockscommunitymessagerequests_)); + // @@protoc_insertion_point(copy_constructor:SessionProtos.DataMessage) +} + +inline void DataMessage::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.attachments_){arena} + , decltype(_impl_.preview_){arena} + , decltype(_impl_.body_){} + , decltype(_impl_.profilekey_){} + , decltype(_impl_.synctarget_){} + , decltype(_impl_.quote_){nullptr} + , decltype(_impl_.reaction_){nullptr} + , decltype(_impl_.profile_){nullptr} + , decltype(_impl_.opengroupinvitation_){nullptr} + , decltype(_impl_.closedgroupcontrolmessage_){nullptr} + , decltype(_impl_.flags_){0u} + , decltype(_impl_.expiretimer_){0u} + , decltype(_impl_.timestamp_){uint64_t{0u}} + , decltype(_impl_.blockscommunitymessagerequests_){false} + }; + _impl_.body_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.body_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilekey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilekey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.synctarget_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.synctarget_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +DataMessage::~DataMessage() { + // @@protoc_insertion_point(destructor:SessionProtos.DataMessage) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void DataMessage::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.attachments_.~RepeatedPtrField(); + _impl_.preview_.~RepeatedPtrField(); + _impl_.body_.Destroy(); + _impl_.profilekey_.Destroy(); + _impl_.synctarget_.Destroy(); + if (this != internal_default_instance()) delete _impl_.quote_; + if (this != internal_default_instance()) delete _impl_.reaction_; + if (this != internal_default_instance()) delete _impl_.profile_; + if (this != internal_default_instance()) delete _impl_.opengroupinvitation_; + if (this != internal_default_instance()) delete _impl_.closedgroupcontrolmessage_; +} + +void DataMessage::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void DataMessage::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.DataMessage) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.attachments_.Clear(); + _impl_.preview_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x000000ffu) { + if (cached_has_bits & 0x00000001u) { + _impl_.body_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.profilekey_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000004u) { + _impl_.synctarget_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000008u) { + GOOGLE_DCHECK(_impl_.quote_ != nullptr); + _impl_.quote_->Clear(); + } + if (cached_has_bits & 0x00000010u) { + GOOGLE_DCHECK(_impl_.reaction_ != nullptr); + _impl_.reaction_->Clear(); + } + if (cached_has_bits & 0x00000020u) { + GOOGLE_DCHECK(_impl_.profile_ != nullptr); + _impl_.profile_->Clear(); + } + if (cached_has_bits & 0x00000040u) { + GOOGLE_DCHECK(_impl_.opengroupinvitation_ != nullptr); + _impl_.opengroupinvitation_->Clear(); + } + if (cached_has_bits & 0x00000080u) { + GOOGLE_DCHECK(_impl_.closedgroupcontrolmessage_ != nullptr); + _impl_.closedgroupcontrolmessage_->Clear(); + } + } + if (cached_has_bits & 0x00000f00u) { + ::memset(&_impl_.flags_, 0, static_cast( + reinterpret_cast(&_impl_.blockscommunitymessagerequests_) - + reinterpret_cast(&_impl_.flags_)) + sizeof(_impl_.blockscommunitymessagerequests_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* DataMessage::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional string body = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_body(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated .SessionProtos.AttachmentPointer attachments = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_attachments(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<18>(ptr)); + } else + goto handle_unusual; + continue; + // optional uint32 flags = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 32)) { + _Internal::set_has_flags(&has_bits); + _impl_.flags_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional uint32 expireTimer = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _Internal::set_has_expiretimer(&has_bits); + _impl_.expiretimer_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes profileKey = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 50)) { + auto str = _internal_mutable_profilekey(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional uint64 timestamp = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 56)) { + _Internal::set_has_timestamp(&has_bits); + _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.DataMessage.Quote quote = 8; + case 8: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 66)) { + ptr = ctx->ParseMessage(_internal_mutable_quote(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated .SessionProtos.DataMessage.Preview preview = 10; + case 10: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 82)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_preview(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<82>(ptr)); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.DataMessage.Reaction reaction = 11; + case 11: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 90)) { + ptr = ctx->ParseMessage(_internal_mutable_reaction(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.LokiProfile profile = 101; + case 101: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + ptr = ctx->ParseMessage(_internal_mutable_profile(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.DataMessage.OpenGroupInvitation openGroupInvitation = 102; + case 102: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 50)) { + ptr = ctx->ParseMessage(_internal_mutable_opengroupinvitation(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; + case 104: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 66)) { + ptr = ctx->ParseMessage(_internal_mutable_closedgroupcontrolmessage(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string syncTarget = 105; + case 105: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 74)) { + auto str = _internal_mutable_synctarget(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bool blocksCommunityMessageRequests = 106; + case 106: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 80)) { + _Internal::set_has_blockscommunitymessagerequests(&has_bits); + _impl_.blockscommunitymessagerequests_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* DataMessage::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.DataMessage) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional string body = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 1, this->_internal_body(), target); + } + + // repeated .SessionProtos.AttachmentPointer attachments = 2; + for (unsigned i = 0, + n = static_cast(this->_internal_attachments_size()); i < n; i++) { + const auto& repfield = this->_internal_attachments(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(2, repfield, repfield.GetCachedSize(), target, stream); + } + + // optional uint32 flags = 4; + if (cached_has_bits & 0x00000100u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(4, this->_internal_flags(), target); + } + + // optional uint32 expireTimer = 5; + if (cached_has_bits & 0x00000200u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(5, this->_internal_expiretimer(), target); + } + + // optional bytes profileKey = 6; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteBytesMaybeAliased( + 6, this->_internal_profilekey(), target); + } + + // optional uint64 timestamp = 7; + if (cached_has_bits & 0x00000400u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(7, this->_internal_timestamp(), target); + } + + // optional .SessionProtos.DataMessage.Quote quote = 8; + if (cached_has_bits & 0x00000008u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(8, _Internal::quote(this), + _Internal::quote(this).GetCachedSize(), target, stream); + } + + // repeated .SessionProtos.DataMessage.Preview preview = 10; + for (unsigned i = 0, + n = static_cast(this->_internal_preview_size()); i < n; i++) { + const auto& repfield = this->_internal_preview(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(10, repfield, repfield.GetCachedSize(), target, stream); + } + + // optional .SessionProtos.DataMessage.Reaction reaction = 11; + if (cached_has_bits & 0x00000010u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(11, _Internal::reaction(this), + _Internal::reaction(this).GetCachedSize(), target, stream); + } + + // optional .SessionProtos.LokiProfile profile = 101; + if (cached_has_bits & 0x00000020u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(101, _Internal::profile(this), + _Internal::profile(this).GetCachedSize(), target, stream); + } + + // optional .SessionProtos.DataMessage.OpenGroupInvitation openGroupInvitation = 102; + if (cached_has_bits & 0x00000040u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(102, _Internal::opengroupinvitation(this), + _Internal::opengroupinvitation(this).GetCachedSize(), target, stream); + } + + // optional .SessionProtos.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; + if (cached_has_bits & 0x00000080u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(104, _Internal::closedgroupcontrolmessage(this), + _Internal::closedgroupcontrolmessage(this).GetCachedSize(), target, stream); + } + + // optional string syncTarget = 105; + if (cached_has_bits & 0x00000004u) { + target = stream->WriteStringMaybeAliased( + 105, this->_internal_synctarget(), target); + } + + // optional bool blocksCommunityMessageRequests = 106; + if (cached_has_bits & 0x00000800u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(106, this->_internal_blockscommunitymessagerequests(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.DataMessage) + return target; +} + +size_t DataMessage::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.DataMessage) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .SessionProtos.AttachmentPointer attachments = 2; + total_size += 1UL * this->_internal_attachments_size(); + for (const auto& msg : this->_impl_.attachments_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // repeated .SessionProtos.DataMessage.Preview preview = 10; + total_size += 1UL * this->_internal_preview_size(); + for (const auto& msg : this->_impl_.preview_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x000000ffu) { + // optional string body = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_body()); + } + + // optional bytes profileKey = 6; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_profilekey()); + } + + // optional string syncTarget = 105; + if (cached_has_bits & 0x00000004u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_synctarget()); + } + + // optional .SessionProtos.DataMessage.Quote quote = 8; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.quote_); + } + + // optional .SessionProtos.DataMessage.Reaction reaction = 11; + if (cached_has_bits & 0x00000010u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.reaction_); + } + + // optional .SessionProtos.LokiProfile profile = 101; + if (cached_has_bits & 0x00000020u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.profile_); + } + + // optional .SessionProtos.DataMessage.OpenGroupInvitation openGroupInvitation = 102; + if (cached_has_bits & 0x00000040u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.opengroupinvitation_); + } + + // optional .SessionProtos.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; + if (cached_has_bits & 0x00000080u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.closedgroupcontrolmessage_); + } + + } + if (cached_has_bits & 0x00000f00u) { + // optional uint32 flags = 4; + if (cached_has_bits & 0x00000100u) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_flags()); + } + + // optional uint32 expireTimer = 5; + if (cached_has_bits & 0x00000200u) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_expiretimer()); + } + + // optional uint64 timestamp = 7; + if (cached_has_bits & 0x00000400u) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp()); + } + + // optional bool blocksCommunityMessageRequests = 106; + if (cached_has_bits & 0x00000800u) { + total_size += 2 + 1; + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void DataMessage::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void DataMessage::MergeFrom(const DataMessage& from) { + DataMessage* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.DataMessage) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.attachments_.MergeFrom(from._impl_.attachments_); + _this->_impl_.preview_.MergeFrom(from._impl_.preview_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x000000ffu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_body(from._internal_body()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_profilekey(from._internal_profilekey()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_set_synctarget(from._internal_synctarget()); + } + if (cached_has_bits & 0x00000008u) { + _this->_internal_mutable_quote()->::SessionProtos::DataMessage_Quote::MergeFrom( + from._internal_quote()); + } + if (cached_has_bits & 0x00000010u) { + _this->_internal_mutable_reaction()->::SessionProtos::DataMessage_Reaction::MergeFrom( + from._internal_reaction()); + } + if (cached_has_bits & 0x00000020u) { + _this->_internal_mutable_profile()->::SessionProtos::LokiProfile::MergeFrom( + from._internal_profile()); + } + if (cached_has_bits & 0x00000040u) { + _this->_internal_mutable_opengroupinvitation()->::SessionProtos::DataMessage_OpenGroupInvitation::MergeFrom( + from._internal_opengroupinvitation()); + } + if (cached_has_bits & 0x00000080u) { + _this->_internal_mutable_closedgroupcontrolmessage()->::SessionProtos::DataMessage_ClosedGroupControlMessage::MergeFrom( + from._internal_closedgroupcontrolmessage()); + } + } + if (cached_has_bits & 0x00000f00u) { + if (cached_has_bits & 0x00000100u) { + _this->_impl_.flags_ = from._impl_.flags_; + } + if (cached_has_bits & 0x00000200u) { + _this->_impl_.expiretimer_ = from._impl_.expiretimer_; + } + if (cached_has_bits & 0x00000400u) { + _this->_impl_.timestamp_ = from._impl_.timestamp_; + } + if (cached_has_bits & 0x00000800u) { + _this->_impl_.blockscommunitymessagerequests_ = from._impl_.blockscommunitymessagerequests_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void DataMessage::CopyFrom(const DataMessage& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.DataMessage) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool DataMessage::IsInitialized() const { + if (!::PROTOBUF_NAMESPACE_ID::internal::AllAreInitialized(_impl_.attachments_)) + return false; + if (!::PROTOBUF_NAMESPACE_ID::internal::AllAreInitialized(_impl_.preview_)) + return false; + if (_internal_has_quote()) { + if (!_impl_.quote_->IsInitialized()) return false; + } + if (_internal_has_reaction()) { + if (!_impl_.reaction_->IsInitialized()) return false; + } + if (_internal_has_opengroupinvitation()) { + if (!_impl_.opengroupinvitation_->IsInitialized()) return false; + } + if (_internal_has_closedgroupcontrolmessage()) { + if (!_impl_.closedgroupcontrolmessage_->IsInitialized()) return false; + } + return true; +} + +void DataMessage::InternalSwap(DataMessage* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.attachments_.InternalSwap(&other->_impl_.attachments_); + _impl_.preview_.InternalSwap(&other->_impl_.preview_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.body_, lhs_arena, + &other->_impl_.body_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.profilekey_, lhs_arena, + &other->_impl_.profilekey_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.synctarget_, lhs_arena, + &other->_impl_.synctarget_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(DataMessage, _impl_.blockscommunitymessagerequests_) + + sizeof(DataMessage::_impl_.blockscommunitymessagerequests_) + - PROTOBUF_FIELD_OFFSET(DataMessage, _impl_.quote_)>( + reinterpret_cast(&_impl_.quote_), + reinterpret_cast(&other->_impl_.quote_)); +} + +std::string DataMessage::GetTypeName() const { + return "SessionProtos.DataMessage"; +} + + +// =================================================================== + +class ConfigurationMessage_ClosedGroup::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_publickey(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_name(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static const ::SessionProtos::KeyPair& encryptionkeypair(const ConfigurationMessage_ClosedGroup* msg); + static void set_has_encryptionkeypair(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_expirationtimer(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } +}; + +const ::SessionProtos::KeyPair& +ConfigurationMessage_ClosedGroup::_Internal::encryptionkeypair(const ConfigurationMessage_ClosedGroup* msg) { + return *msg->_impl_.encryptionkeypair_; +} +ConfigurationMessage_ClosedGroup::ConfigurationMessage_ClosedGroup(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.ConfigurationMessage.ClosedGroup) +} +ConfigurationMessage_ClosedGroup::ConfigurationMessage_ClosedGroup(const ConfigurationMessage_ClosedGroup& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ConfigurationMessage_ClosedGroup* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.members_){from._impl_.members_} + , decltype(_impl_.admins_){from._impl_.admins_} + , decltype(_impl_.publickey_){} + , decltype(_impl_.name_){} + , decltype(_impl_.encryptionkeypair_){nullptr} + , decltype(_impl_.expirationtimer_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.publickey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.publickey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_publickey()) { + _this->_impl_.publickey_.Set(from._internal_publickey(), + _this->GetArenaForAllocation()); + } + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_name()) { + _this->_impl_.name_.Set(from._internal_name(), + _this->GetArenaForAllocation()); + } + if (from._internal_has_encryptionkeypair()) { + _this->_impl_.encryptionkeypair_ = new ::SessionProtos::KeyPair(*from._impl_.encryptionkeypair_); + } + _this->_impl_.expirationtimer_ = from._impl_.expirationtimer_; + // @@protoc_insertion_point(copy_constructor:SessionProtos.ConfigurationMessage.ClosedGroup) +} + +inline void ConfigurationMessage_ClosedGroup::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.members_){arena} + , decltype(_impl_.admins_){arena} + , decltype(_impl_.publickey_){} + , decltype(_impl_.name_){} + , decltype(_impl_.encryptionkeypair_){nullptr} + , decltype(_impl_.expirationtimer_){0u} + }; + _impl_.publickey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.publickey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ConfigurationMessage_ClosedGroup::~ConfigurationMessage_ClosedGroup() { + // @@protoc_insertion_point(destructor:SessionProtos.ConfigurationMessage.ClosedGroup) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ConfigurationMessage_ClosedGroup::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.members_.~RepeatedPtrField(); + _impl_.admins_.~RepeatedPtrField(); + _impl_.publickey_.Destroy(); + _impl_.name_.Destroy(); + if (this != internal_default_instance()) delete _impl_.encryptionkeypair_; +} + +void ConfigurationMessage_ClosedGroup::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ConfigurationMessage_ClosedGroup::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.ConfigurationMessage.ClosedGroup) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.members_.Clear(); + _impl_.admins_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _impl_.publickey_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.name_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000004u) { + GOOGLE_DCHECK(_impl_.encryptionkeypair_ != nullptr); + _impl_.encryptionkeypair_->Clear(); + } + } + _impl_.expirationtimer_ = 0u; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* ConfigurationMessage_ClosedGroup::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional bytes publicKey = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_publickey(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string name = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_name(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .SessionProtos.KeyPair encryptionKeyPair = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_encryptionkeypair(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated bytes members = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + ptr -= 1; + do { + ptr += 1; + auto str = _internal_add_members(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<34>(ptr)); + } else + goto handle_unusual; + continue; + // repeated bytes admins = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + ptr -= 1; + do { + ptr += 1; + auto str = _internal_add_admins(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<42>(ptr)); + } else + goto handle_unusual; + continue; + // optional uint32 expirationTimer = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 48)) { + _Internal::set_has_expirationtimer(&has_bits); + _impl_.expirationtimer_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ConfigurationMessage_ClosedGroup::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.ConfigurationMessage.ClosedGroup) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional bytes publicKey = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 1, this->_internal_publickey(), target); + } + + // optional string name = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_name(), target); + } + + // optional .SessionProtos.KeyPair encryptionKeyPair = 3; + if (cached_has_bits & 0x00000004u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::encryptionkeypair(this), + _Internal::encryptionkeypair(this).GetCachedSize(), target, stream); + } + + // repeated bytes members = 4; + for (int i = 0, n = this->_internal_members_size(); i < n; i++) { + const auto& s = this->_internal_members(i); + target = stream->WriteBytes(4, s, target); + } + + // repeated bytes admins = 5; + for (int i = 0, n = this->_internal_admins_size(); i < n; i++) { + const auto& s = this->_internal_admins(i); + target = stream->WriteBytes(5, s, target); + } + + // optional uint32 expirationTimer = 6; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(6, this->_internal_expirationtimer(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.ConfigurationMessage.ClosedGroup) + return target; +} + +size_t ConfigurationMessage_ClosedGroup::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.ConfigurationMessage.ClosedGroup) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated bytes members = 4; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(_impl_.members_.size()); + for (int i = 0, n = _impl_.members_.size(); i < n; i++) { + total_size += ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + _impl_.members_.Get(i)); + } + + // repeated bytes admins = 5; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(_impl_.admins_.size()); + for (int i = 0, n = _impl_.admins_.size(); i < n; i++) { + total_size += ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + _impl_.admins_.Get(i)); + } + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + // optional bytes publicKey = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_publickey()); + } + + // optional string name = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_name()); + } + + // optional .SessionProtos.KeyPair encryptionKeyPair = 3; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.encryptionkeypair_); + } + + // optional uint32 expirationTimer = 6; + if (cached_has_bits & 0x00000008u) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_expirationtimer()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ConfigurationMessage_ClosedGroup::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void ConfigurationMessage_ClosedGroup::MergeFrom(const ConfigurationMessage_ClosedGroup& from) { + ConfigurationMessage_ClosedGroup* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.ConfigurationMessage.ClosedGroup) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.members_.MergeFrom(from._impl_.members_); + _this->_impl_.admins_.MergeFrom(from._impl_.admins_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_publickey(from._internal_publickey()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_name(from._internal_name()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_mutable_encryptionkeypair()->::SessionProtos::KeyPair::MergeFrom( + from._internal_encryptionkeypair()); + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.expirationtimer_ = from._impl_.expirationtimer_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void ConfigurationMessage_ClosedGroup::CopyFrom(const ConfigurationMessage_ClosedGroup& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.ConfigurationMessage.ClosedGroup) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ConfigurationMessage_ClosedGroup::IsInitialized() const { + if (_internal_has_encryptionkeypair()) { + if (!_impl_.encryptionkeypair_->IsInitialized()) return false; + } + return true; +} + +void ConfigurationMessage_ClosedGroup::InternalSwap(ConfigurationMessage_ClosedGroup* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.members_.InternalSwap(&other->_impl_.members_); + _impl_.admins_.InternalSwap(&other->_impl_.admins_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.publickey_, lhs_arena, + &other->_impl_.publickey_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.name_, lhs_arena, + &other->_impl_.name_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(ConfigurationMessage_ClosedGroup, _impl_.expirationtimer_) + + sizeof(ConfigurationMessage_ClosedGroup::_impl_.expirationtimer_) + - PROTOBUF_FIELD_OFFSET(ConfigurationMessage_ClosedGroup, _impl_.encryptionkeypair_)>( + reinterpret_cast(&_impl_.encryptionkeypair_), + reinterpret_cast(&other->_impl_.encryptionkeypair_)); +} + +std::string ConfigurationMessage_ClosedGroup::GetTypeName() const { + return "SessionProtos.ConfigurationMessage.ClosedGroup"; +} + + +// =================================================================== + +class ConfigurationMessage_Contact::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_publickey(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_name(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_profilepicture(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_profilekey(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static void set_has_isapproved(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static void set_has_isblocked(HasBits* has_bits) { + (*has_bits)[0] |= 32u; + } + static void set_has_didapproveme(HasBits* has_bits) { + (*has_bits)[0] |= 64u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000003) ^ 0x00000003) != 0; + } +}; + +ConfigurationMessage_Contact::ConfigurationMessage_Contact(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.ConfigurationMessage.Contact) +} +ConfigurationMessage_Contact::ConfigurationMessage_Contact(const ConfigurationMessage_Contact& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ConfigurationMessage_Contact* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.publickey_){} + , decltype(_impl_.name_){} + , decltype(_impl_.profilepicture_){} + , decltype(_impl_.profilekey_){} + , decltype(_impl_.isapproved_){} + , decltype(_impl_.isblocked_){} + , decltype(_impl_.didapproveme_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.publickey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.publickey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_publickey()) { + _this->_impl_.publickey_.Set(from._internal_publickey(), + _this->GetArenaForAllocation()); + } + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_name()) { + _this->_impl_.name_.Set(from._internal_name(), + _this->GetArenaForAllocation()); + } + _impl_.profilepicture_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilepicture_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_profilepicture()) { + _this->_impl_.profilepicture_.Set(from._internal_profilepicture(), + _this->GetArenaForAllocation()); + } + _impl_.profilekey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilekey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_profilekey()) { + _this->_impl_.profilekey_.Set(from._internal_profilekey(), + _this->GetArenaForAllocation()); + } + ::memcpy(&_impl_.isapproved_, &from._impl_.isapproved_, + static_cast(reinterpret_cast(&_impl_.didapproveme_) - + reinterpret_cast(&_impl_.isapproved_)) + sizeof(_impl_.didapproveme_)); + // @@protoc_insertion_point(copy_constructor:SessionProtos.ConfigurationMessage.Contact) +} + +inline void ConfigurationMessage_Contact::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.publickey_){} + , decltype(_impl_.name_){} + , decltype(_impl_.profilepicture_){} + , decltype(_impl_.profilekey_){} + , decltype(_impl_.isapproved_){false} + , decltype(_impl_.isblocked_){false} + , decltype(_impl_.didapproveme_){false} + }; + _impl_.publickey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.publickey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.name_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilepicture_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilepicture_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilekey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilekey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ConfigurationMessage_Contact::~ConfigurationMessage_Contact() { + // @@protoc_insertion_point(destructor:SessionProtos.ConfigurationMessage.Contact) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ConfigurationMessage_Contact::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.publickey_.Destroy(); + _impl_.name_.Destroy(); + _impl_.profilepicture_.Destroy(); + _impl_.profilekey_.Destroy(); +} + +void ConfigurationMessage_Contact::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ConfigurationMessage_Contact::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.ConfigurationMessage.Contact) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + _impl_.publickey_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.name_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000004u) { + _impl_.profilepicture_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000008u) { + _impl_.profilekey_.ClearNonDefaultToEmpty(); + } + } + ::memset(&_impl_.isapproved_, 0, static_cast( + reinterpret_cast(&_impl_.didapproveme_) - + reinterpret_cast(&_impl_.isapproved_)) + sizeof(_impl_.didapproveme_)); + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* ConfigurationMessage_Contact::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required bytes publicKey = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_publickey(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // required string name = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_name(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string profilePicture = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_profilepicture(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes profileKey = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_profilekey(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bool isApproved = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 40)) { + _Internal::set_has_isapproved(&has_bits); + _impl_.isapproved_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bool isBlocked = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 48)) { + _Internal::set_has_isblocked(&has_bits); + _impl_.isblocked_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bool didApproveMe = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 56)) { + _Internal::set_has_didapproveme(&has_bits); + _impl_.didapproveme_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ConfigurationMessage_Contact::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.ConfigurationMessage.Contact) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required bytes publicKey = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 1, this->_internal_publickey(), target); + } + + // required string name = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_name(), target); + } + + // optional string profilePicture = 3; + if (cached_has_bits & 0x00000004u) { + target = stream->WriteStringMaybeAliased( + 3, this->_internal_profilepicture(), target); + } + + // optional bytes profileKey = 4; + if (cached_has_bits & 0x00000008u) { + target = stream->WriteBytesMaybeAliased( + 4, this->_internal_profilekey(), target); + } + + // optional bool isApproved = 5; + if (cached_has_bits & 0x00000010u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(5, this->_internal_isapproved(), target); + } + + // optional bool isBlocked = 6; + if (cached_has_bits & 0x00000020u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(6, this->_internal_isblocked(), target); + } + + // optional bool didApproveMe = 7; + if (cached_has_bits & 0x00000040u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray(7, this->_internal_didapproveme(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.ConfigurationMessage.Contact) + return target; +} + +size_t ConfigurationMessage_Contact::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:SessionProtos.ConfigurationMessage.Contact) + size_t total_size = 0; + + if (_internal_has_publickey()) { + // required bytes publicKey = 1; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_publickey()); + } + + if (_internal_has_name()) { + // required string name = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_name()); + } + + return total_size; +} +size_t ConfigurationMessage_Contact::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.ConfigurationMessage.Contact) + size_t total_size = 0; + + if (((_impl_._has_bits_[0] & 0x00000003) ^ 0x00000003) == 0) { // All required fields are present. + // required bytes publicKey = 1; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_publickey()); + + // required string name = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_name()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000007cu) { + // optional string profilePicture = 3; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_profilepicture()); + } + + // optional bytes profileKey = 4; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_profilekey()); + } + + // optional bool isApproved = 5; + if (cached_has_bits & 0x00000010u) { + total_size += 1 + 1; + } + + // optional bool isBlocked = 6; + if (cached_has_bits & 0x00000020u) { + total_size += 1 + 1; + } + + // optional bool didApproveMe = 7; + if (cached_has_bits & 0x00000040u) { + total_size += 1 + 1; + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ConfigurationMessage_Contact::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void ConfigurationMessage_Contact::MergeFrom(const ConfigurationMessage_Contact& from) { + ConfigurationMessage_Contact* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.ConfigurationMessage.Contact) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000007fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_publickey(from._internal_publickey()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_name(from._internal_name()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_set_profilepicture(from._internal_profilepicture()); + } + if (cached_has_bits & 0x00000008u) { + _this->_internal_set_profilekey(from._internal_profilekey()); + } + if (cached_has_bits & 0x00000010u) { + _this->_impl_.isapproved_ = from._impl_.isapproved_; + } + if (cached_has_bits & 0x00000020u) { + _this->_impl_.isblocked_ = from._impl_.isblocked_; + } + if (cached_has_bits & 0x00000040u) { + _this->_impl_.didapproveme_ = from._impl_.didapproveme_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void ConfigurationMessage_Contact::CopyFrom(const ConfigurationMessage_Contact& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.ConfigurationMessage.Contact) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ConfigurationMessage_Contact::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void ConfigurationMessage_Contact::InternalSwap(ConfigurationMessage_Contact* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.publickey_, lhs_arena, + &other->_impl_.publickey_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.name_, lhs_arena, + &other->_impl_.name_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.profilepicture_, lhs_arena, + &other->_impl_.profilepicture_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.profilekey_, lhs_arena, + &other->_impl_.profilekey_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(ConfigurationMessage_Contact, _impl_.didapproveme_) + + sizeof(ConfigurationMessage_Contact::_impl_.didapproveme_) + - PROTOBUF_FIELD_OFFSET(ConfigurationMessage_Contact, _impl_.isapproved_)>( + reinterpret_cast(&_impl_.isapproved_), + reinterpret_cast(&other->_impl_.isapproved_)); +} + +std::string ConfigurationMessage_Contact::GetTypeName() const { + return "SessionProtos.ConfigurationMessage.Contact"; +} + + +// =================================================================== + +class ConfigurationMessage::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_displayname(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_profilepicture(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_profilekey(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } +}; + +ConfigurationMessage::ConfigurationMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.ConfigurationMessage) +} +ConfigurationMessage::ConfigurationMessage(const ConfigurationMessage& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ConfigurationMessage* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.closedgroups_){from._impl_.closedgroups_} + , decltype(_impl_.opengroups_){from._impl_.opengroups_} + , decltype(_impl_.contacts_){from._impl_.contacts_} + , decltype(_impl_.displayname_){} + , decltype(_impl_.profilepicture_){} + , decltype(_impl_.profilekey_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.displayname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.displayname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_displayname()) { + _this->_impl_.displayname_.Set(from._internal_displayname(), + _this->GetArenaForAllocation()); + } + _impl_.profilepicture_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilepicture_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_profilepicture()) { + _this->_impl_.profilepicture_.Set(from._internal_profilepicture(), + _this->GetArenaForAllocation()); + } + _impl_.profilekey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilekey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_profilekey()) { + _this->_impl_.profilekey_.Set(from._internal_profilekey(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:SessionProtos.ConfigurationMessage) +} + +inline void ConfigurationMessage::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.closedgroups_){arena} + , decltype(_impl_.opengroups_){arena} + , decltype(_impl_.contacts_){arena} + , decltype(_impl_.displayname_){} + , decltype(_impl_.profilepicture_){} + , decltype(_impl_.profilekey_){} + }; + _impl_.displayname_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.displayname_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilepicture_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilepicture_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilekey_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.profilekey_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ConfigurationMessage::~ConfigurationMessage() { + // @@protoc_insertion_point(destructor:SessionProtos.ConfigurationMessage) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ConfigurationMessage::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.closedgroups_.~RepeatedPtrField(); + _impl_.opengroups_.~RepeatedPtrField(); + _impl_.contacts_.~RepeatedPtrField(); + _impl_.displayname_.Destroy(); + _impl_.profilepicture_.Destroy(); + _impl_.profilekey_.Destroy(); +} + +void ConfigurationMessage::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ConfigurationMessage::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.ConfigurationMessage) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.closedgroups_.Clear(); + _impl_.opengroups_.Clear(); + _impl_.contacts_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _impl_.displayname_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.profilepicture_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000004u) { + _impl_.profilekey_.ClearNonDefaultToEmpty(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* ConfigurationMessage::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // repeated .SessionProtos.ConfigurationMessage.ClosedGroup closedGroups = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_closedgroups(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<10>(ptr)); + } else + goto handle_unusual; + continue; + // repeated string openGroups = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + ptr -= 1; + do { + ptr += 1; + auto str = _internal_add_opengroups(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<18>(ptr)); + } else + goto handle_unusual; + continue; + // optional string displayName = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_displayname(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string profilePicture = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_profilepicture(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes profileKey = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + auto str = _internal_mutable_profilekey(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated .SessionProtos.ConfigurationMessage.Contact contacts = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 50)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_contacts(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<50>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ConfigurationMessage::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.ConfigurationMessage) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // repeated .SessionProtos.ConfigurationMessage.ClosedGroup closedGroups = 1; + for (unsigned i = 0, + n = static_cast(this->_internal_closedgroups_size()); i < n; i++) { + const auto& repfield = this->_internal_closedgroups(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, repfield, repfield.GetCachedSize(), target, stream); + } + + // repeated string openGroups = 2; + for (int i = 0, n = this->_internal_opengroups_size(); i < n; i++) { + const auto& s = this->_internal_opengroups(i); + target = stream->WriteString(2, s, target); + } + + cached_has_bits = _impl_._has_bits_[0]; + // optional string displayName = 3; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 3, this->_internal_displayname(), target); + } + + // optional string profilePicture = 4; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 4, this->_internal_profilepicture(), target); + } + + // optional bytes profileKey = 5; + if (cached_has_bits & 0x00000004u) { + target = stream->WriteBytesMaybeAliased( + 5, this->_internal_profilekey(), target); + } + + // repeated .SessionProtos.ConfigurationMessage.Contact contacts = 6; + for (unsigned i = 0, + n = static_cast(this->_internal_contacts_size()); i < n; i++) { + const auto& repfield = this->_internal_contacts(i); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(6, repfield, repfield.GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.ConfigurationMessage) + return target; +} + +size_t ConfigurationMessage::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.ConfigurationMessage) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .SessionProtos.ConfigurationMessage.ClosedGroup closedGroups = 1; + total_size += 1UL * this->_internal_closedgroups_size(); + for (const auto& msg : this->_impl_.closedgroups_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // repeated string openGroups = 2; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(_impl_.opengroups_.size()); + for (int i = 0, n = _impl_.opengroups_.size(); i < n; i++) { + total_size += ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + _impl_.opengroups_.Get(i)); + } + + // repeated .SessionProtos.ConfigurationMessage.Contact contacts = 6; + total_size += 1UL * this->_internal_contacts_size(); + for (const auto& msg : this->_impl_.contacts_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + // optional string displayName = 3; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_displayname()); + } + + // optional string profilePicture = 4; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_profilepicture()); + } + + // optional bytes profileKey = 5; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_profilekey()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ConfigurationMessage::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void ConfigurationMessage::MergeFrom(const ConfigurationMessage& from) { + ConfigurationMessage* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.ConfigurationMessage) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.closedgroups_.MergeFrom(from._impl_.closedgroups_); + _this->_impl_.opengroups_.MergeFrom(from._impl_.opengroups_); + _this->_impl_.contacts_.MergeFrom(from._impl_.contacts_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_displayname(from._internal_displayname()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_profilepicture(from._internal_profilepicture()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_set_profilekey(from._internal_profilekey()); + } + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void ConfigurationMessage::CopyFrom(const ConfigurationMessage& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.ConfigurationMessage) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ConfigurationMessage::IsInitialized() const { + if (!::PROTOBUF_NAMESPACE_ID::internal::AllAreInitialized(_impl_.closedgroups_)) + return false; + if (!::PROTOBUF_NAMESPACE_ID::internal::AllAreInitialized(_impl_.contacts_)) + return false; + return true; +} + +void ConfigurationMessage::InternalSwap(ConfigurationMessage* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.closedgroups_.InternalSwap(&other->_impl_.closedgroups_); + _impl_.opengroups_.InternalSwap(&other->_impl_.opengroups_); + _impl_.contacts_.InternalSwap(&other->_impl_.contacts_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.displayname_, lhs_arena, + &other->_impl_.displayname_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.profilepicture_, lhs_arena, + &other->_impl_.profilepicture_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.profilekey_, lhs_arena, + &other->_impl_.profilekey_, rhs_arena + ); +} + +std::string ConfigurationMessage::GetTypeName() const { + return "SessionProtos.ConfigurationMessage"; +} + + +// =================================================================== + +class ReceiptMessage::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_type(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000001) ^ 0x00000001) != 0; + } +}; + +ReceiptMessage::ReceiptMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.ReceiptMessage) +} +ReceiptMessage::ReceiptMessage(const ReceiptMessage& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + ReceiptMessage* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.timestamp_){from._impl_.timestamp_} + , decltype(_impl_.type_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _this->_impl_.type_ = from._impl_.type_; + // @@protoc_insertion_point(copy_constructor:SessionProtos.ReceiptMessage) +} + +inline void ReceiptMessage::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.timestamp_){arena} + , decltype(_impl_.type_){0} + }; +} + +ReceiptMessage::~ReceiptMessage() { + // @@protoc_insertion_point(destructor:SessionProtos.ReceiptMessage) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ReceiptMessage::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.timestamp_.~RepeatedField(); +} + +void ReceiptMessage::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ReceiptMessage::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.ReceiptMessage) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.timestamp_.Clear(); + _impl_.type_ = 0; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* ReceiptMessage::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required .SessionProtos.ReceiptMessage.Type type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::SessionProtos::ReceiptMessage_Type_IsValid(val))) { + _internal_set_type(static_cast<::SessionProtos::ReceiptMessage_Type>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // repeated uint64 timestamp = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 16)) { + ptr -= 1; + do { + ptr += 1; + _internal_add_timestamp(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr)); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<16>(ptr)); + } else if (static_cast(tag) == 18) { + ptr = ::PROTOBUF_NAMESPACE_ID::internal::PackedUInt64Parser(_internal_mutable_timestamp(), ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ReceiptMessage::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.ReceiptMessage) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required .SessionProtos.ReceiptMessage.Type type = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_type(), target); + } + + // repeated uint64 timestamp = 2; + for (int i = 0, n = this->_internal_timestamp_size(); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(2, this->_internal_timestamp(i), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.ReceiptMessage) + return target; +} + +size_t ReceiptMessage::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.ReceiptMessage) + size_t total_size = 0; + + // required .SessionProtos.ReceiptMessage.Type type = 1; + if (_internal_has_type()) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_type()); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated uint64 timestamp = 2; + { + size_t data_size = ::_pbi::WireFormatLite:: + UInt64Size(this->_impl_.timestamp_); + total_size += 1 * + ::_pbi::FromIntSize(this->_internal_timestamp_size()); + total_size += data_size; + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ReceiptMessage::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void ReceiptMessage::MergeFrom(const ReceiptMessage& from) { + ReceiptMessage* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.ReceiptMessage) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.timestamp_.MergeFrom(from._impl_.timestamp_); + if (from._internal_has_type()) { + _this->_internal_set_type(from._internal_type()); + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void ReceiptMessage::CopyFrom(const ReceiptMessage& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.ReceiptMessage) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ReceiptMessage::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void ReceiptMessage::InternalSwap(ReceiptMessage* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.timestamp_.InternalSwap(&other->_impl_.timestamp_); + swap(_impl_.type_, other->_impl_.type_); +} + +std::string ReceiptMessage::GetTypeName() const { + return "SessionProtos.ReceiptMessage"; +} + + +// =================================================================== + +class AttachmentPointer::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_id(HasBits* has_bits) { + (*has_bits)[0] |= 128u; + } + static void set_has_contenttype(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_key(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_size(HasBits* has_bits) { + (*has_bits)[0] |= 256u; + } + static void set_has_thumbnail(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_digest(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static void set_has_filename(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static void set_has_flags(HasBits* has_bits) { + (*has_bits)[0] |= 512u; + } + static void set_has_width(HasBits* has_bits) { + (*has_bits)[0] |= 1024u; + } + static void set_has_height(HasBits* has_bits) { + (*has_bits)[0] |= 2048u; + } + static void set_has_caption(HasBits* has_bits) { + (*has_bits)[0] |= 32u; + } + static void set_has_url(HasBits* has_bits) { + (*has_bits)[0] |= 64u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000080) ^ 0x00000080) != 0; + } +}; + +AttachmentPointer::AttachmentPointer(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.AttachmentPointer) +} +AttachmentPointer::AttachmentPointer(const AttachmentPointer& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + AttachmentPointer* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.contenttype_){} + , decltype(_impl_.key_){} + , decltype(_impl_.thumbnail_){} + , decltype(_impl_.digest_){} + , decltype(_impl_.filename_){} + , decltype(_impl_.caption_){} + , decltype(_impl_.url_){} + , decltype(_impl_.id_){} + , decltype(_impl_.size_){} + , decltype(_impl_.flags_){} + , decltype(_impl_.width_){} + , decltype(_impl_.height_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.contenttype_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.contenttype_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_contenttype()) { + _this->_impl_.contenttype_.Set(from._internal_contenttype(), + _this->GetArenaForAllocation()); + } + _impl_.key_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.key_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_key()) { + _this->_impl_.key_.Set(from._internal_key(), + _this->GetArenaForAllocation()); + } + _impl_.thumbnail_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.thumbnail_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_thumbnail()) { + _this->_impl_.thumbnail_.Set(from._internal_thumbnail(), + _this->GetArenaForAllocation()); + } + _impl_.digest_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.digest_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_digest()) { + _this->_impl_.digest_.Set(from._internal_digest(), + _this->GetArenaForAllocation()); + } + _impl_.filename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.filename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_filename()) { + _this->_impl_.filename_.Set(from._internal_filename(), + _this->GetArenaForAllocation()); + } + _impl_.caption_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.caption_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_caption()) { + _this->_impl_.caption_.Set(from._internal_caption(), + _this->GetArenaForAllocation()); + } + _impl_.url_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.url_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_url()) { + _this->_impl_.url_.Set(from._internal_url(), + _this->GetArenaForAllocation()); + } + ::memcpy(&_impl_.id_, &from._impl_.id_, + static_cast(reinterpret_cast(&_impl_.height_) - + reinterpret_cast(&_impl_.id_)) + sizeof(_impl_.height_)); + // @@protoc_insertion_point(copy_constructor:SessionProtos.AttachmentPointer) +} + +inline void AttachmentPointer::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.contenttype_){} + , decltype(_impl_.key_){} + , decltype(_impl_.thumbnail_){} + , decltype(_impl_.digest_){} + , decltype(_impl_.filename_){} + , decltype(_impl_.caption_){} + , decltype(_impl_.url_){} + , decltype(_impl_.id_){uint64_t{0u}} + , decltype(_impl_.size_){0u} + , decltype(_impl_.flags_){0u} + , decltype(_impl_.width_){0u} + , decltype(_impl_.height_){0u} + }; + _impl_.contenttype_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.contenttype_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.key_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.key_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.thumbnail_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.thumbnail_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.digest_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.digest_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.filename_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.filename_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.caption_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.caption_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.url_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.url_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +AttachmentPointer::~AttachmentPointer() { + // @@protoc_insertion_point(destructor:SessionProtos.AttachmentPointer) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void AttachmentPointer::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.contenttype_.Destroy(); + _impl_.key_.Destroy(); + _impl_.thumbnail_.Destroy(); + _impl_.digest_.Destroy(); + _impl_.filename_.Destroy(); + _impl_.caption_.Destroy(); + _impl_.url_.Destroy(); +} + +void AttachmentPointer::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void AttachmentPointer::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.AttachmentPointer) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000007fu) { + if (cached_has_bits & 0x00000001u) { + _impl_.contenttype_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.key_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000004u) { + _impl_.thumbnail_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000008u) { + _impl_.digest_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000010u) { + _impl_.filename_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000020u) { + _impl_.caption_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000040u) { + _impl_.url_.ClearNonDefaultToEmpty(); + } + } + _impl_.id_ = uint64_t{0u}; + if (cached_has_bits & 0x00000f00u) { + ::memset(&_impl_.size_, 0, static_cast( + reinterpret_cast(&_impl_.height_) - + reinterpret_cast(&_impl_.size_)) + sizeof(_impl_.height_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* AttachmentPointer::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required fixed64 id = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 9)) { + _Internal::set_has_id(&has_bits); + _impl_.id_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(uint64_t); + } else + goto handle_unusual; + continue; + // optional string contentType = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_contenttype(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes key = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_key(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional uint32 size = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 32)) { + _Internal::set_has_size(&has_bits); + _impl_.size_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes thumbnail = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + auto str = _internal_mutable_thumbnail(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes digest = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 50)) { + auto str = _internal_mutable_digest(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string fileName = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 58)) { + auto str = _internal_mutable_filename(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional uint32 flags = 8; + case 8: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 64)) { + _Internal::set_has_flags(&has_bits); + _impl_.flags_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional uint32 width = 9; + case 9: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 72)) { + _Internal::set_has_width(&has_bits); + _impl_.width_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional uint32 height = 10; + case 10: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 80)) { + _Internal::set_has_height(&has_bits); + _impl_.height_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string caption = 11; + case 11: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 90)) { + auto str = _internal_mutable_caption(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string url = 101; + case 101: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + auto str = _internal_mutable_url(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* AttachmentPointer::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.AttachmentPointer) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required fixed64 id = 1; + if (cached_has_bits & 0x00000080u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteFixed64ToArray(1, this->_internal_id(), target); + } + + // optional string contentType = 2; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_contenttype(), target); + } + + // optional bytes key = 3; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteBytesMaybeAliased( + 3, this->_internal_key(), target); + } + + // optional uint32 size = 4; + if (cached_has_bits & 0x00000100u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(4, this->_internal_size(), target); + } + + // optional bytes thumbnail = 5; + if (cached_has_bits & 0x00000004u) { + target = stream->WriteBytesMaybeAliased( + 5, this->_internal_thumbnail(), target); + } + + // optional bytes digest = 6; + if (cached_has_bits & 0x00000008u) { + target = stream->WriteBytesMaybeAliased( + 6, this->_internal_digest(), target); + } + + // optional string fileName = 7; + if (cached_has_bits & 0x00000010u) { + target = stream->WriteStringMaybeAliased( + 7, this->_internal_filename(), target); + } + + // optional uint32 flags = 8; + if (cached_has_bits & 0x00000200u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(8, this->_internal_flags(), target); + } + + // optional uint32 width = 9; + if (cached_has_bits & 0x00000400u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(9, this->_internal_width(), target); + } + + // optional uint32 height = 10; + if (cached_has_bits & 0x00000800u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(10, this->_internal_height(), target); + } + + // optional string caption = 11; + if (cached_has_bits & 0x00000020u) { + target = stream->WriteStringMaybeAliased( + 11, this->_internal_caption(), target); + } + + // optional string url = 101; + if (cached_has_bits & 0x00000040u) { + target = stream->WriteStringMaybeAliased( + 101, this->_internal_url(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.AttachmentPointer) + return target; +} + +size_t AttachmentPointer::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.AttachmentPointer) + size_t total_size = 0; + + // required fixed64 id = 1; + if (_internal_has_id()) { + total_size += 1 + 8; + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000007fu) { + // optional string contentType = 2; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_contenttype()); + } + + // optional bytes key = 3; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_key()); + } + + // optional bytes thumbnail = 5; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_thumbnail()); + } + + // optional bytes digest = 6; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_digest()); + } + + // optional string fileName = 7; + if (cached_has_bits & 0x00000010u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_filename()); + } + + // optional string caption = 11; + if (cached_has_bits & 0x00000020u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_caption()); + } + + // optional string url = 101; + if (cached_has_bits & 0x00000040u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_url()); + } + + } + if (cached_has_bits & 0x00000f00u) { + // optional uint32 size = 4; + if (cached_has_bits & 0x00000100u) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_size()); + } + + // optional uint32 flags = 8; + if (cached_has_bits & 0x00000200u) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_flags()); + } + + // optional uint32 width = 9; + if (cached_has_bits & 0x00000400u) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_width()); + } + + // optional uint32 height = 10; + if (cached_has_bits & 0x00000800u) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_height()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void AttachmentPointer::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void AttachmentPointer::MergeFrom(const AttachmentPointer& from) { + AttachmentPointer* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.AttachmentPointer) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x000000ffu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_contenttype(from._internal_contenttype()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_key(from._internal_key()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_set_thumbnail(from._internal_thumbnail()); + } + if (cached_has_bits & 0x00000008u) { + _this->_internal_set_digest(from._internal_digest()); + } + if (cached_has_bits & 0x00000010u) { + _this->_internal_set_filename(from._internal_filename()); + } + if (cached_has_bits & 0x00000020u) { + _this->_internal_set_caption(from._internal_caption()); + } + if (cached_has_bits & 0x00000040u) { + _this->_internal_set_url(from._internal_url()); + } + if (cached_has_bits & 0x00000080u) { + _this->_impl_.id_ = from._impl_.id_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + if (cached_has_bits & 0x00000f00u) { + if (cached_has_bits & 0x00000100u) { + _this->_impl_.size_ = from._impl_.size_; + } + if (cached_has_bits & 0x00000200u) { + _this->_impl_.flags_ = from._impl_.flags_; + } + if (cached_has_bits & 0x00000400u) { + _this->_impl_.width_ = from._impl_.width_; + } + if (cached_has_bits & 0x00000800u) { + _this->_impl_.height_ = from._impl_.height_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void AttachmentPointer::CopyFrom(const AttachmentPointer& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.AttachmentPointer) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool AttachmentPointer::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void AttachmentPointer::InternalSwap(AttachmentPointer* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.contenttype_, lhs_arena, + &other->_impl_.contenttype_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.key_, lhs_arena, + &other->_impl_.key_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.thumbnail_, lhs_arena, + &other->_impl_.thumbnail_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.digest_, lhs_arena, + &other->_impl_.digest_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.filename_, lhs_arena, + &other->_impl_.filename_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.caption_, lhs_arena, + &other->_impl_.caption_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.url_, lhs_arena, + &other->_impl_.url_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(AttachmentPointer, _impl_.height_) + + sizeof(AttachmentPointer::_impl_.height_) + - PROTOBUF_FIELD_OFFSET(AttachmentPointer, _impl_.id_)>( + reinterpret_cast(&_impl_.id_), + reinterpret_cast(&other->_impl_.id_)); +} + +std::string AttachmentPointer::GetTypeName() const { + return "SessionProtos.AttachmentPointer"; +} + + +// =================================================================== + +class SharedConfigMessage::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_kind(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_seqno(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_data(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static bool MissingRequiredFields(const HasBits& has_bits) { + return ((has_bits[0] & 0x00000007) ^ 0x00000007) != 0; + } +}; + +SharedConfigMessage::SharedConfigMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:SessionProtos.SharedConfigMessage) +} +SharedConfigMessage::SharedConfigMessage(const SharedConfigMessage& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + SharedConfigMessage* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.data_){} + , decltype(_impl_.seqno_){} + , decltype(_impl_.kind_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.data_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.data_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_data()) { + _this->_impl_.data_.Set(from._internal_data(), + _this->GetArenaForAllocation()); + } + ::memcpy(&_impl_.seqno_, &from._impl_.seqno_, + static_cast(reinterpret_cast(&_impl_.kind_) - + reinterpret_cast(&_impl_.seqno_)) + sizeof(_impl_.kind_)); + // @@protoc_insertion_point(copy_constructor:SessionProtos.SharedConfigMessage) +} + +inline void SharedConfigMessage::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.data_){} + , decltype(_impl_.seqno_){int64_t{0}} + , decltype(_impl_.kind_){1} + }; + _impl_.data_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.data_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +SharedConfigMessage::~SharedConfigMessage() { + // @@protoc_insertion_point(destructor:SessionProtos.SharedConfigMessage) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void SharedConfigMessage::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.data_.Destroy(); +} + +void SharedConfigMessage::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void SharedConfigMessage::Clear() { +// @@protoc_insertion_point(message_clear_start:SessionProtos.SharedConfigMessage) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000001u) { + _impl_.data_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000006u) { + _impl_.seqno_ = int64_t{0}; + _impl_.kind_ = 1; + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* SharedConfigMessage::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // required .SessionProtos.SharedConfigMessage.Kind kind = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::SessionProtos::SharedConfigMessage_Kind_IsValid(val))) { + _internal_set_kind(static_cast<::SessionProtos::SharedConfigMessage_Kind>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // required int64 seqno = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 16)) { + _Internal::set_has_seqno(&has_bits); + _impl_.seqno_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // required bytes data = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_data(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* SharedConfigMessage::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:SessionProtos.SharedConfigMessage) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // required .SessionProtos.SharedConfigMessage.Kind kind = 1; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_kind(), target); + } + + // required int64 seqno = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt64ToArray(2, this->_internal_seqno(), target); + } + + // required bytes data = 3; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 3, this->_internal_data(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:SessionProtos.SharedConfigMessage) + return target; +} + +size_t SharedConfigMessage::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:SessionProtos.SharedConfigMessage) + size_t total_size = 0; + + if (_internal_has_data()) { + // required bytes data = 3; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_data()); + } + + if (_internal_has_seqno()) { + // required int64 seqno = 2; + total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_seqno()); + } + + if (_internal_has_kind()) { + // required .SessionProtos.SharedConfigMessage.Kind kind = 1; + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_kind()); + } + + return total_size; +} +size_t SharedConfigMessage::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:SessionProtos.SharedConfigMessage) + size_t total_size = 0; + + if (((_impl_._has_bits_[0] & 0x00000007) ^ 0x00000007) == 0) { // All required fields are present. + // required bytes data = 3; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_data()); + + // required int64 seqno = 2; + total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_seqno()); + + // required .SessionProtos.SharedConfigMessage.Kind kind = 1; + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_kind()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void SharedConfigMessage::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void SharedConfigMessage::MergeFrom(const SharedConfigMessage& from) { + SharedConfigMessage* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:SessionProtos.SharedConfigMessage) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_data(from._internal_data()); + } + if (cached_has_bits & 0x00000002u) { + _this->_impl_.seqno_ = from._impl_.seqno_; + } + if (cached_has_bits & 0x00000004u) { + _this->_impl_.kind_ = from._impl_.kind_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void SharedConfigMessage::CopyFrom(const SharedConfigMessage& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:SessionProtos.SharedConfigMessage) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool SharedConfigMessage::IsInitialized() const { + if (_Internal::MissingRequiredFields(_impl_._has_bits_)) return false; + return true; +} + +void SharedConfigMessage::InternalSwap(SharedConfigMessage* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.data_, lhs_arena, + &other->_impl_.data_, rhs_arena + ); + swap(_impl_.seqno_, other->_impl_.seqno_); + swap(_impl_.kind_, other->_impl_.kind_); +} + +std::string SharedConfigMessage::GetTypeName() const { + return "SessionProtos.SharedConfigMessage"; +} + + +// @@protoc_insertion_point(namespace_scope) +} // namespace SessionProtos +PROTOBUF_NAMESPACE_OPEN +template<> PROTOBUF_NOINLINE ::SessionProtos::Envelope* +Arena::CreateMaybeMessage< ::SessionProtos::Envelope >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::Envelope >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::TypingMessage* +Arena::CreateMaybeMessage< ::SessionProtos::TypingMessage >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::TypingMessage >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::UnsendRequest* +Arena::CreateMaybeMessage< ::SessionProtos::UnsendRequest >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::UnsendRequest >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::MessageRequestResponse* +Arena::CreateMaybeMessage< ::SessionProtos::MessageRequestResponse >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::MessageRequestResponse >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::Content* +Arena::CreateMaybeMessage< ::SessionProtos::Content >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::Content >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::CallMessage* +Arena::CreateMaybeMessage< ::SessionProtos::CallMessage >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::CallMessage >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::KeyPair* +Arena::CreateMaybeMessage< ::SessionProtos::KeyPair >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::KeyPair >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::DataExtractionNotification* +Arena::CreateMaybeMessage< ::SessionProtos::DataExtractionNotification >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::DataExtractionNotification >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::LokiProfile* +Arena::CreateMaybeMessage< ::SessionProtos::LokiProfile >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::LokiProfile >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::DataMessage_Quote_QuotedAttachment* +Arena::CreateMaybeMessage< ::SessionProtos::DataMessage_Quote_QuotedAttachment >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::DataMessage_Quote_QuotedAttachment >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::DataMessage_Quote* +Arena::CreateMaybeMessage< ::SessionProtos::DataMessage_Quote >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::DataMessage_Quote >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::DataMessage_Preview* +Arena::CreateMaybeMessage< ::SessionProtos::DataMessage_Preview >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::DataMessage_Preview >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::DataMessage_Reaction* +Arena::CreateMaybeMessage< ::SessionProtos::DataMessage_Reaction >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::DataMessage_Reaction >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::DataMessage_OpenGroupInvitation* +Arena::CreateMaybeMessage< ::SessionProtos::DataMessage_OpenGroupInvitation >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::DataMessage_OpenGroupInvitation >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper* +Arena::CreateMaybeMessage< ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::DataMessage_ClosedGroupControlMessage* +Arena::CreateMaybeMessage< ::SessionProtos::DataMessage_ClosedGroupControlMessage >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::DataMessage_ClosedGroupControlMessage >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::DataMessage* +Arena::CreateMaybeMessage< ::SessionProtos::DataMessage >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::DataMessage >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::ConfigurationMessage_ClosedGroup* +Arena::CreateMaybeMessage< ::SessionProtos::ConfigurationMessage_ClosedGroup >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::ConfigurationMessage_ClosedGroup >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::ConfigurationMessage_Contact* +Arena::CreateMaybeMessage< ::SessionProtos::ConfigurationMessage_Contact >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::ConfigurationMessage_Contact >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::ConfigurationMessage* +Arena::CreateMaybeMessage< ::SessionProtos::ConfigurationMessage >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::ConfigurationMessage >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::ReceiptMessage* +Arena::CreateMaybeMessage< ::SessionProtos::ReceiptMessage >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::ReceiptMessage >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::AttachmentPointer* +Arena::CreateMaybeMessage< ::SessionProtos::AttachmentPointer >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::AttachmentPointer >(arena); +} +template<> PROTOBUF_NOINLINE ::SessionProtos::SharedConfigMessage* +Arena::CreateMaybeMessage< ::SessionProtos::SharedConfigMessage >(Arena* arena) { + return Arena::CreateMessageInternal< ::SessionProtos::SharedConfigMessage >(arena); +} +PROTOBUF_NAMESPACE_CLOSE + +// @@protoc_insertion_point(global_scope) +#include diff --git a/proto/SessionProtos.pb.h b/proto/SessionProtos.pb.h new file mode 100644 index 00000000..e75a5652 --- /dev/null +++ b/proto/SessionProtos.pb.h @@ -0,0 +1,12309 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: SessionProtos.proto + +#ifndef GOOGLE_PROTOBUF_INCLUDED_SessionProtos_2eproto +#define GOOGLE_PROTOBUF_INCLUDED_SessionProtos_2eproto + +#include +#include + +#include +#if PROTOBUF_VERSION < 3021000 +#error This file was generated by a newer version of protoc which is +#error incompatible with your Protocol Buffer headers. Please update +#error your headers. +#endif +#if 3021012 < PROTOBUF_MIN_PROTOC_VERSION +#error This file was generated by an older version of protoc which is +#error incompatible with your Protocol Buffer headers. Please +#error regenerate this file with a newer version of protoc. +#endif + +#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: export +#include // IWYU pragma: export +#include +// @@protoc_insertion_point(includes) +#include +#define PROTOBUF_INTERNAL_EXPORT_SessionProtos_2eproto +PROTOBUF_NAMESPACE_OPEN +namespace internal { +class AnyMetadata; +} // namespace internal +PROTOBUF_NAMESPACE_CLOSE + +// Internal implementation detail -- do not use these members. +struct TableStruct_SessionProtos_2eproto { + static const uint32_t offsets[]; +}; +namespace SessionProtos { +class AttachmentPointer; +struct AttachmentPointerDefaultTypeInternal; +extern AttachmentPointerDefaultTypeInternal _AttachmentPointer_default_instance_; +class CallMessage; +struct CallMessageDefaultTypeInternal; +extern CallMessageDefaultTypeInternal _CallMessage_default_instance_; +class ConfigurationMessage; +struct ConfigurationMessageDefaultTypeInternal; +extern ConfigurationMessageDefaultTypeInternal _ConfigurationMessage_default_instance_; +class ConfigurationMessage_ClosedGroup; +struct ConfigurationMessage_ClosedGroupDefaultTypeInternal; +extern ConfigurationMessage_ClosedGroupDefaultTypeInternal _ConfigurationMessage_ClosedGroup_default_instance_; +class ConfigurationMessage_Contact; +struct ConfigurationMessage_ContactDefaultTypeInternal; +extern ConfigurationMessage_ContactDefaultTypeInternal _ConfigurationMessage_Contact_default_instance_; +class Content; +struct ContentDefaultTypeInternal; +extern ContentDefaultTypeInternal _Content_default_instance_; +class DataExtractionNotification; +struct DataExtractionNotificationDefaultTypeInternal; +extern DataExtractionNotificationDefaultTypeInternal _DataExtractionNotification_default_instance_; +class DataMessage; +struct DataMessageDefaultTypeInternal; +extern DataMessageDefaultTypeInternal _DataMessage_default_instance_; +class DataMessage_ClosedGroupControlMessage; +struct DataMessage_ClosedGroupControlMessageDefaultTypeInternal; +extern DataMessage_ClosedGroupControlMessageDefaultTypeInternal _DataMessage_ClosedGroupControlMessage_default_instance_; +class DataMessage_ClosedGroupControlMessage_KeyPairWrapper; +struct DataMessage_ClosedGroupControlMessage_KeyPairWrapperDefaultTypeInternal; +extern DataMessage_ClosedGroupControlMessage_KeyPairWrapperDefaultTypeInternal _DataMessage_ClosedGroupControlMessage_KeyPairWrapper_default_instance_; +class DataMessage_OpenGroupInvitation; +struct DataMessage_OpenGroupInvitationDefaultTypeInternal; +extern DataMessage_OpenGroupInvitationDefaultTypeInternal _DataMessage_OpenGroupInvitation_default_instance_; +class DataMessage_Preview; +struct DataMessage_PreviewDefaultTypeInternal; +extern DataMessage_PreviewDefaultTypeInternal _DataMessage_Preview_default_instance_; +class DataMessage_Quote; +struct DataMessage_QuoteDefaultTypeInternal; +extern DataMessage_QuoteDefaultTypeInternal _DataMessage_Quote_default_instance_; +class DataMessage_Quote_QuotedAttachment; +struct DataMessage_Quote_QuotedAttachmentDefaultTypeInternal; +extern DataMessage_Quote_QuotedAttachmentDefaultTypeInternal _DataMessage_Quote_QuotedAttachment_default_instance_; +class DataMessage_Reaction; +struct DataMessage_ReactionDefaultTypeInternal; +extern DataMessage_ReactionDefaultTypeInternal _DataMessage_Reaction_default_instance_; +class Envelope; +struct EnvelopeDefaultTypeInternal; +extern EnvelopeDefaultTypeInternal _Envelope_default_instance_; +class KeyPair; +struct KeyPairDefaultTypeInternal; +extern KeyPairDefaultTypeInternal _KeyPair_default_instance_; +class LokiProfile; +struct LokiProfileDefaultTypeInternal; +extern LokiProfileDefaultTypeInternal _LokiProfile_default_instance_; +class MessageRequestResponse; +struct MessageRequestResponseDefaultTypeInternal; +extern MessageRequestResponseDefaultTypeInternal _MessageRequestResponse_default_instance_; +class ReceiptMessage; +struct ReceiptMessageDefaultTypeInternal; +extern ReceiptMessageDefaultTypeInternal _ReceiptMessage_default_instance_; +class SharedConfigMessage; +struct SharedConfigMessageDefaultTypeInternal; +extern SharedConfigMessageDefaultTypeInternal _SharedConfigMessage_default_instance_; +class TypingMessage; +struct TypingMessageDefaultTypeInternal; +extern TypingMessageDefaultTypeInternal _TypingMessage_default_instance_; +class UnsendRequest; +struct UnsendRequestDefaultTypeInternal; +extern UnsendRequestDefaultTypeInternal _UnsendRequest_default_instance_; +} // namespace SessionProtos +PROTOBUF_NAMESPACE_OPEN +template<> ::SessionProtos::AttachmentPointer* Arena::CreateMaybeMessage<::SessionProtos::AttachmentPointer>(Arena*); +template<> ::SessionProtos::CallMessage* Arena::CreateMaybeMessage<::SessionProtos::CallMessage>(Arena*); +template<> ::SessionProtos::ConfigurationMessage* Arena::CreateMaybeMessage<::SessionProtos::ConfigurationMessage>(Arena*); +template<> ::SessionProtos::ConfigurationMessage_ClosedGroup* Arena::CreateMaybeMessage<::SessionProtos::ConfigurationMessage_ClosedGroup>(Arena*); +template<> ::SessionProtos::ConfigurationMessage_Contact* Arena::CreateMaybeMessage<::SessionProtos::ConfigurationMessage_Contact>(Arena*); +template<> ::SessionProtos::Content* Arena::CreateMaybeMessage<::SessionProtos::Content>(Arena*); +template<> ::SessionProtos::DataExtractionNotification* Arena::CreateMaybeMessage<::SessionProtos::DataExtractionNotification>(Arena*); +template<> ::SessionProtos::DataMessage* Arena::CreateMaybeMessage<::SessionProtos::DataMessage>(Arena*); +template<> ::SessionProtos::DataMessage_ClosedGroupControlMessage* Arena::CreateMaybeMessage<::SessionProtos::DataMessage_ClosedGroupControlMessage>(Arena*); +template<> ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper* Arena::CreateMaybeMessage<::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper>(Arena*); +template<> ::SessionProtos::DataMessage_OpenGroupInvitation* Arena::CreateMaybeMessage<::SessionProtos::DataMessage_OpenGroupInvitation>(Arena*); +template<> ::SessionProtos::DataMessage_Preview* Arena::CreateMaybeMessage<::SessionProtos::DataMessage_Preview>(Arena*); +template<> ::SessionProtos::DataMessage_Quote* Arena::CreateMaybeMessage<::SessionProtos::DataMessage_Quote>(Arena*); +template<> ::SessionProtos::DataMessage_Quote_QuotedAttachment* Arena::CreateMaybeMessage<::SessionProtos::DataMessage_Quote_QuotedAttachment>(Arena*); +template<> ::SessionProtos::DataMessage_Reaction* Arena::CreateMaybeMessage<::SessionProtos::DataMessage_Reaction>(Arena*); +template<> ::SessionProtos::Envelope* Arena::CreateMaybeMessage<::SessionProtos::Envelope>(Arena*); +template<> ::SessionProtos::KeyPair* Arena::CreateMaybeMessage<::SessionProtos::KeyPair>(Arena*); +template<> ::SessionProtos::LokiProfile* Arena::CreateMaybeMessage<::SessionProtos::LokiProfile>(Arena*); +template<> ::SessionProtos::MessageRequestResponse* Arena::CreateMaybeMessage<::SessionProtos::MessageRequestResponse>(Arena*); +template<> ::SessionProtos::ReceiptMessage* Arena::CreateMaybeMessage<::SessionProtos::ReceiptMessage>(Arena*); +template<> ::SessionProtos::SharedConfigMessage* Arena::CreateMaybeMessage<::SessionProtos::SharedConfigMessage>(Arena*); +template<> ::SessionProtos::TypingMessage* Arena::CreateMaybeMessage<::SessionProtos::TypingMessage>(Arena*); +template<> ::SessionProtos::UnsendRequest* Arena::CreateMaybeMessage<::SessionProtos::UnsendRequest>(Arena*); +PROTOBUF_NAMESPACE_CLOSE +namespace SessionProtos { + +enum Envelope_Type : int { + Envelope_Type_SESSION_MESSAGE = 6, + Envelope_Type_CLOSED_GROUP_MESSAGE = 7 +}; +bool Envelope_Type_IsValid(int value); +constexpr Envelope_Type Envelope_Type_Type_MIN = Envelope_Type_SESSION_MESSAGE; +constexpr Envelope_Type Envelope_Type_Type_MAX = Envelope_Type_CLOSED_GROUP_MESSAGE; +constexpr int Envelope_Type_Type_ARRAYSIZE = Envelope_Type_Type_MAX + 1; + +const std::string& Envelope_Type_Name(Envelope_Type value); +template +inline const std::string& Envelope_Type_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Envelope_Type_Name."); + return Envelope_Type_Name(static_cast(enum_t_value)); +} +bool Envelope_Type_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, Envelope_Type* value); +enum TypingMessage_Action : int { + TypingMessage_Action_STARTED = 0, + TypingMessage_Action_STOPPED = 1 +}; +bool TypingMessage_Action_IsValid(int value); +constexpr TypingMessage_Action TypingMessage_Action_Action_MIN = TypingMessage_Action_STARTED; +constexpr TypingMessage_Action TypingMessage_Action_Action_MAX = TypingMessage_Action_STOPPED; +constexpr int TypingMessage_Action_Action_ARRAYSIZE = TypingMessage_Action_Action_MAX + 1; + +const std::string& TypingMessage_Action_Name(TypingMessage_Action value); +template +inline const std::string& TypingMessage_Action_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function TypingMessage_Action_Name."); + return TypingMessage_Action_Name(static_cast(enum_t_value)); +} +bool TypingMessage_Action_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, TypingMessage_Action* value); +enum CallMessage_Type : int { + CallMessage_Type_PRE_OFFER = 6, + CallMessage_Type_OFFER = 1, + CallMessage_Type_ANSWER = 2, + CallMessage_Type_PROVISIONAL_ANSWER = 3, + CallMessage_Type_ICE_CANDIDATES = 4, + CallMessage_Type_END_CALL = 5 +}; +bool CallMessage_Type_IsValid(int value); +constexpr CallMessage_Type CallMessage_Type_Type_MIN = CallMessage_Type_OFFER; +constexpr CallMessage_Type CallMessage_Type_Type_MAX = CallMessage_Type_PRE_OFFER; +constexpr int CallMessage_Type_Type_ARRAYSIZE = CallMessage_Type_Type_MAX + 1; + +const std::string& CallMessage_Type_Name(CallMessage_Type value); +template +inline const std::string& CallMessage_Type_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function CallMessage_Type_Name."); + return CallMessage_Type_Name(static_cast(enum_t_value)); +} +bool CallMessage_Type_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, CallMessage_Type* value); +enum DataExtractionNotification_Type : int { + DataExtractionNotification_Type_SCREENSHOT = 1, + DataExtractionNotification_Type_MEDIA_SAVED = 2 +}; +bool DataExtractionNotification_Type_IsValid(int value); +constexpr DataExtractionNotification_Type DataExtractionNotification_Type_Type_MIN = DataExtractionNotification_Type_SCREENSHOT; +constexpr DataExtractionNotification_Type DataExtractionNotification_Type_Type_MAX = DataExtractionNotification_Type_MEDIA_SAVED; +constexpr int DataExtractionNotification_Type_Type_ARRAYSIZE = DataExtractionNotification_Type_Type_MAX + 1; + +const std::string& DataExtractionNotification_Type_Name(DataExtractionNotification_Type value); +template +inline const std::string& DataExtractionNotification_Type_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function DataExtractionNotification_Type_Name."); + return DataExtractionNotification_Type_Name(static_cast(enum_t_value)); +} +bool DataExtractionNotification_Type_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, DataExtractionNotification_Type* value); +enum DataMessage_Quote_QuotedAttachment_Flags : int { + DataMessage_Quote_QuotedAttachment_Flags_VOICE_MESSAGE = 1 +}; +bool DataMessage_Quote_QuotedAttachment_Flags_IsValid(int value); +constexpr DataMessage_Quote_QuotedAttachment_Flags DataMessage_Quote_QuotedAttachment_Flags_Flags_MIN = DataMessage_Quote_QuotedAttachment_Flags_VOICE_MESSAGE; +constexpr DataMessage_Quote_QuotedAttachment_Flags DataMessage_Quote_QuotedAttachment_Flags_Flags_MAX = DataMessage_Quote_QuotedAttachment_Flags_VOICE_MESSAGE; +constexpr int DataMessage_Quote_QuotedAttachment_Flags_Flags_ARRAYSIZE = DataMessage_Quote_QuotedAttachment_Flags_Flags_MAX + 1; + +const std::string& DataMessage_Quote_QuotedAttachment_Flags_Name(DataMessage_Quote_QuotedAttachment_Flags value); +template +inline const std::string& DataMessage_Quote_QuotedAttachment_Flags_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function DataMessage_Quote_QuotedAttachment_Flags_Name."); + return DataMessage_Quote_QuotedAttachment_Flags_Name(static_cast(enum_t_value)); +} +bool DataMessage_Quote_QuotedAttachment_Flags_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, DataMessage_Quote_QuotedAttachment_Flags* value); +enum DataMessage_Reaction_Action : int { + DataMessage_Reaction_Action_REACT = 0, + DataMessage_Reaction_Action_REMOVE = 1 +}; +bool DataMessage_Reaction_Action_IsValid(int value); +constexpr DataMessage_Reaction_Action DataMessage_Reaction_Action_Action_MIN = DataMessage_Reaction_Action_REACT; +constexpr DataMessage_Reaction_Action DataMessage_Reaction_Action_Action_MAX = DataMessage_Reaction_Action_REMOVE; +constexpr int DataMessage_Reaction_Action_Action_ARRAYSIZE = DataMessage_Reaction_Action_Action_MAX + 1; + +const std::string& DataMessage_Reaction_Action_Name(DataMessage_Reaction_Action value); +template +inline const std::string& DataMessage_Reaction_Action_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function DataMessage_Reaction_Action_Name."); + return DataMessage_Reaction_Action_Name(static_cast(enum_t_value)); +} +bool DataMessage_Reaction_Action_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, DataMessage_Reaction_Action* value); +enum DataMessage_ClosedGroupControlMessage_Type : int { + DataMessage_ClosedGroupControlMessage_Type_NEW = 1, + DataMessage_ClosedGroupControlMessage_Type_ENCRYPTION_KEY_PAIR = 3, + DataMessage_ClosedGroupControlMessage_Type_NAME_CHANGE = 4, + DataMessage_ClosedGroupControlMessage_Type_MEMBERS_ADDED = 5, + DataMessage_ClosedGroupControlMessage_Type_MEMBERS_REMOVED = 6, + DataMessage_ClosedGroupControlMessage_Type_MEMBER_LEFT = 7, + DataMessage_ClosedGroupControlMessage_Type_ENCRYPTION_KEY_PAIR_REQUEST = 8 +}; +bool DataMessage_ClosedGroupControlMessage_Type_IsValid(int value); +constexpr DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage_Type_Type_MIN = DataMessage_ClosedGroupControlMessage_Type_NEW; +constexpr DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage_Type_Type_MAX = DataMessage_ClosedGroupControlMessage_Type_ENCRYPTION_KEY_PAIR_REQUEST; +constexpr int DataMessage_ClosedGroupControlMessage_Type_Type_ARRAYSIZE = DataMessage_ClosedGroupControlMessage_Type_Type_MAX + 1; + +const std::string& DataMessage_ClosedGroupControlMessage_Type_Name(DataMessage_ClosedGroupControlMessage_Type value); +template +inline const std::string& DataMessage_ClosedGroupControlMessage_Type_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function DataMessage_ClosedGroupControlMessage_Type_Name."); + return DataMessage_ClosedGroupControlMessage_Type_Name(static_cast(enum_t_value)); +} +bool DataMessage_ClosedGroupControlMessage_Type_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, DataMessage_ClosedGroupControlMessage_Type* value); +enum DataMessage_Flags : int { + DataMessage_Flags_EXPIRATION_TIMER_UPDATE = 2 +}; +bool DataMessage_Flags_IsValid(int value); +constexpr DataMessage_Flags DataMessage_Flags_Flags_MIN = DataMessage_Flags_EXPIRATION_TIMER_UPDATE; +constexpr DataMessage_Flags DataMessage_Flags_Flags_MAX = DataMessage_Flags_EXPIRATION_TIMER_UPDATE; +constexpr int DataMessage_Flags_Flags_ARRAYSIZE = DataMessage_Flags_Flags_MAX + 1; + +const std::string& DataMessage_Flags_Name(DataMessage_Flags value); +template +inline const std::string& DataMessage_Flags_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function DataMessage_Flags_Name."); + return DataMessage_Flags_Name(static_cast(enum_t_value)); +} +bool DataMessage_Flags_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, DataMessage_Flags* value); +enum ReceiptMessage_Type : int { + ReceiptMessage_Type_DELIVERY = 0, + ReceiptMessage_Type_READ = 1 +}; +bool ReceiptMessage_Type_IsValid(int value); +constexpr ReceiptMessage_Type ReceiptMessage_Type_Type_MIN = ReceiptMessage_Type_DELIVERY; +constexpr ReceiptMessage_Type ReceiptMessage_Type_Type_MAX = ReceiptMessage_Type_READ; +constexpr int ReceiptMessage_Type_Type_ARRAYSIZE = ReceiptMessage_Type_Type_MAX + 1; + +const std::string& ReceiptMessage_Type_Name(ReceiptMessage_Type value); +template +inline const std::string& ReceiptMessage_Type_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function ReceiptMessage_Type_Name."); + return ReceiptMessage_Type_Name(static_cast(enum_t_value)); +} +bool ReceiptMessage_Type_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ReceiptMessage_Type* value); +enum AttachmentPointer_Flags : int { + AttachmentPointer_Flags_VOICE_MESSAGE = 1 +}; +bool AttachmentPointer_Flags_IsValid(int value); +constexpr AttachmentPointer_Flags AttachmentPointer_Flags_Flags_MIN = AttachmentPointer_Flags_VOICE_MESSAGE; +constexpr AttachmentPointer_Flags AttachmentPointer_Flags_Flags_MAX = AttachmentPointer_Flags_VOICE_MESSAGE; +constexpr int AttachmentPointer_Flags_Flags_ARRAYSIZE = AttachmentPointer_Flags_Flags_MAX + 1; + +const std::string& AttachmentPointer_Flags_Name(AttachmentPointer_Flags value); +template +inline const std::string& AttachmentPointer_Flags_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function AttachmentPointer_Flags_Name."); + return AttachmentPointer_Flags_Name(static_cast(enum_t_value)); +} +bool AttachmentPointer_Flags_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, AttachmentPointer_Flags* value); +enum SharedConfigMessage_Kind : int { + SharedConfigMessage_Kind_USER_PROFILE = 1, + SharedConfigMessage_Kind_CONTACTS = 2, + SharedConfigMessage_Kind_CONVO_INFO_VOLATILE = 3, + SharedConfigMessage_Kind_USER_GROUPS = 4 +}; +bool SharedConfigMessage_Kind_IsValid(int value); +constexpr SharedConfigMessage_Kind SharedConfigMessage_Kind_Kind_MIN = SharedConfigMessage_Kind_USER_PROFILE; +constexpr SharedConfigMessage_Kind SharedConfigMessage_Kind_Kind_MAX = SharedConfigMessage_Kind_USER_GROUPS; +constexpr int SharedConfigMessage_Kind_Kind_ARRAYSIZE = SharedConfigMessage_Kind_Kind_MAX + 1; + +const std::string& SharedConfigMessage_Kind_Name(SharedConfigMessage_Kind value); +template +inline const std::string& SharedConfigMessage_Kind_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function SharedConfigMessage_Kind_Name."); + return SharedConfigMessage_Kind_Name(static_cast(enum_t_value)); +} +bool SharedConfigMessage_Kind_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, SharedConfigMessage_Kind* value); +// =================================================================== + +class Envelope final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.Envelope) */ { + public: + inline Envelope() : Envelope(nullptr) {} + ~Envelope() override; + explicit PROTOBUF_CONSTEXPR Envelope(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + Envelope(const Envelope& from); + Envelope(Envelope&& from) noexcept + : Envelope() { + *this = ::std::move(from); + } + + inline Envelope& operator=(const Envelope& from) { + CopyFrom(from); + return *this; + } + inline Envelope& operator=(Envelope&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const Envelope& default_instance() { + return *internal_default_instance(); + } + static inline const Envelope* internal_default_instance() { + return reinterpret_cast( + &_Envelope_default_instance_); + } + static constexpr int kIndexInFileMessages = + 0; + + friend void swap(Envelope& a, Envelope& b) { + a.Swap(&b); + } + inline void Swap(Envelope* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Envelope* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + Envelope* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const Envelope& from); + void MergeFrom(const Envelope& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(Envelope* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.Envelope"; + } + protected: + explicit Envelope(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef Envelope_Type Type; + static constexpr Type SESSION_MESSAGE = + Envelope_Type_SESSION_MESSAGE; + static constexpr Type CLOSED_GROUP_MESSAGE = + Envelope_Type_CLOSED_GROUP_MESSAGE; + static inline bool Type_IsValid(int value) { + return Envelope_Type_IsValid(value); + } + static constexpr Type Type_MIN = + Envelope_Type_Type_MIN; + static constexpr Type Type_MAX = + Envelope_Type_Type_MAX; + static constexpr int Type_ARRAYSIZE = + Envelope_Type_Type_ARRAYSIZE; + template + static inline const std::string& Type_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Type_Name."); + return Envelope_Type_Name(enum_t_value); + } + static inline bool Type_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Type* value) { + return Envelope_Type_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kSourceFieldNumber = 2, + kContentFieldNumber = 8, + kTimestampFieldNumber = 5, + kServerTimestampFieldNumber = 10, + kSourceDeviceFieldNumber = 7, + kTypeFieldNumber = 1, + }; + // optional string source = 2; + bool has_source() const; + private: + bool _internal_has_source() const; + public: + void clear_source(); + const std::string& source() const; + template + void set_source(ArgT0&& arg0, ArgT... args); + std::string* mutable_source(); + PROTOBUF_NODISCARD std::string* release_source(); + void set_allocated_source(std::string* source); + private: + const std::string& _internal_source() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_source(const std::string& value); + std::string* _internal_mutable_source(); + public: + + // optional bytes content = 8; + bool has_content() const; + private: + bool _internal_has_content() const; + public: + void clear_content(); + const std::string& content() const; + template + void set_content(ArgT0&& arg0, ArgT... args); + std::string* mutable_content(); + PROTOBUF_NODISCARD std::string* release_content(); + void set_allocated_content(std::string* content); + private: + const std::string& _internal_content() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_content(const std::string& value); + std::string* _internal_mutable_content(); + public: + + // required uint64 timestamp = 5; + bool has_timestamp() const; + private: + bool _internal_has_timestamp() const; + public: + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // optional uint64 serverTimestamp = 10; + bool has_servertimestamp() const; + private: + bool _internal_has_servertimestamp() const; + public: + void clear_servertimestamp(); + uint64_t servertimestamp() const; + void set_servertimestamp(uint64_t value); + private: + uint64_t _internal_servertimestamp() const; + void _internal_set_servertimestamp(uint64_t value); + public: + + // optional uint32 sourceDevice = 7; + bool has_sourcedevice() const; + private: + bool _internal_has_sourcedevice() const; + public: + void clear_sourcedevice(); + uint32_t sourcedevice() const; + void set_sourcedevice(uint32_t value); + private: + uint32_t _internal_sourcedevice() const; + void _internal_set_sourcedevice(uint32_t value); + public: + + // required .SessionProtos.Envelope.Type type = 1; + bool has_type() const; + private: + bool _internal_has_type() const; + public: + void clear_type(); + ::SessionProtos::Envelope_Type type() const; + void set_type(::SessionProtos::Envelope_Type value); + private: + ::SessionProtos::Envelope_Type _internal_type() const; + void _internal_set_type(::SessionProtos::Envelope_Type value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.Envelope) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr source_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr content_; + uint64_t timestamp_; + uint64_t servertimestamp_; + uint32_t sourcedevice_; + int type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class TypingMessage final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.TypingMessage) */ { + public: + inline TypingMessage() : TypingMessage(nullptr) {} + ~TypingMessage() override; + explicit PROTOBUF_CONSTEXPR TypingMessage(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + TypingMessage(const TypingMessage& from); + TypingMessage(TypingMessage&& from) noexcept + : TypingMessage() { + *this = ::std::move(from); + } + + inline TypingMessage& operator=(const TypingMessage& from) { + CopyFrom(from); + return *this; + } + inline TypingMessage& operator=(TypingMessage&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const TypingMessage& default_instance() { + return *internal_default_instance(); + } + static inline const TypingMessage* internal_default_instance() { + return reinterpret_cast( + &_TypingMessage_default_instance_); + } + static constexpr int kIndexInFileMessages = + 1; + + friend void swap(TypingMessage& a, TypingMessage& b) { + a.Swap(&b); + } + inline void Swap(TypingMessage* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(TypingMessage* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + TypingMessage* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const TypingMessage& from); + void MergeFrom(const TypingMessage& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(TypingMessage* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.TypingMessage"; + } + protected: + explicit TypingMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef TypingMessage_Action Action; + static constexpr Action STARTED = + TypingMessage_Action_STARTED; + static constexpr Action STOPPED = + TypingMessage_Action_STOPPED; + static inline bool Action_IsValid(int value) { + return TypingMessage_Action_IsValid(value); + } + static constexpr Action Action_MIN = + TypingMessage_Action_Action_MIN; + static constexpr Action Action_MAX = + TypingMessage_Action_Action_MAX; + static constexpr int Action_ARRAYSIZE = + TypingMessage_Action_Action_ARRAYSIZE; + template + static inline const std::string& Action_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Action_Name."); + return TypingMessage_Action_Name(enum_t_value); + } + static inline bool Action_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Action* value) { + return TypingMessage_Action_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kTimestampFieldNumber = 1, + kActionFieldNumber = 2, + }; + // required uint64 timestamp = 1; + bool has_timestamp() const; + private: + bool _internal_has_timestamp() const; + public: + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // required .SessionProtos.TypingMessage.Action action = 2; + bool has_action() const; + private: + bool _internal_has_action() const; + public: + void clear_action(); + ::SessionProtos::TypingMessage_Action action() const; + void set_action(::SessionProtos::TypingMessage_Action value); + private: + ::SessionProtos::TypingMessage_Action _internal_action() const; + void _internal_set_action(::SessionProtos::TypingMessage_Action value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.TypingMessage) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + uint64_t timestamp_; + int action_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class UnsendRequest final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.UnsendRequest) */ { + public: + inline UnsendRequest() : UnsendRequest(nullptr) {} + ~UnsendRequest() override; + explicit PROTOBUF_CONSTEXPR UnsendRequest(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + UnsendRequest(const UnsendRequest& from); + UnsendRequest(UnsendRequest&& from) noexcept + : UnsendRequest() { + *this = ::std::move(from); + } + + inline UnsendRequest& operator=(const UnsendRequest& from) { + CopyFrom(from); + return *this; + } + inline UnsendRequest& operator=(UnsendRequest&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const UnsendRequest& default_instance() { + return *internal_default_instance(); + } + static inline const UnsendRequest* internal_default_instance() { + return reinterpret_cast( + &_UnsendRequest_default_instance_); + } + static constexpr int kIndexInFileMessages = + 2; + + friend void swap(UnsendRequest& a, UnsendRequest& b) { + a.Swap(&b); + } + inline void Swap(UnsendRequest* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(UnsendRequest* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + UnsendRequest* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const UnsendRequest& from); + void MergeFrom(const UnsendRequest& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(UnsendRequest* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.UnsendRequest"; + } + protected: + explicit UnsendRequest(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kAuthorFieldNumber = 2, + kTimestampFieldNumber = 1, + }; + // required string author = 2; + bool has_author() const; + private: + bool _internal_has_author() const; + public: + void clear_author(); + const std::string& author() const; + template + void set_author(ArgT0&& arg0, ArgT... args); + std::string* mutable_author(); + PROTOBUF_NODISCARD std::string* release_author(); + void set_allocated_author(std::string* author); + private: + const std::string& _internal_author() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_author(const std::string& value); + std::string* _internal_mutable_author(); + public: + + // required uint64 timestamp = 1; + bool has_timestamp() const; + private: + bool _internal_has_timestamp() const; + public: + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.UnsendRequest) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr author_; + uint64_t timestamp_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class MessageRequestResponse final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.MessageRequestResponse) */ { + public: + inline MessageRequestResponse() : MessageRequestResponse(nullptr) {} + ~MessageRequestResponse() override; + explicit PROTOBUF_CONSTEXPR MessageRequestResponse(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + MessageRequestResponse(const MessageRequestResponse& from); + MessageRequestResponse(MessageRequestResponse&& from) noexcept + : MessageRequestResponse() { + *this = ::std::move(from); + } + + inline MessageRequestResponse& operator=(const MessageRequestResponse& from) { + CopyFrom(from); + return *this; + } + inline MessageRequestResponse& operator=(MessageRequestResponse&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const MessageRequestResponse& default_instance() { + return *internal_default_instance(); + } + static inline const MessageRequestResponse* internal_default_instance() { + return reinterpret_cast( + &_MessageRequestResponse_default_instance_); + } + static constexpr int kIndexInFileMessages = + 3; + + friend void swap(MessageRequestResponse& a, MessageRequestResponse& b) { + a.Swap(&b); + } + inline void Swap(MessageRequestResponse* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(MessageRequestResponse* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + MessageRequestResponse* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const MessageRequestResponse& from); + void MergeFrom(const MessageRequestResponse& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(MessageRequestResponse* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.MessageRequestResponse"; + } + protected: + explicit MessageRequestResponse(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kProfileKeyFieldNumber = 2, + kProfileFieldNumber = 3, + kIsApprovedFieldNumber = 1, + }; + // optional bytes profileKey = 2; + bool has_profilekey() const; + private: + bool _internal_has_profilekey() const; + public: + void clear_profilekey(); + const std::string& profilekey() const; + template + void set_profilekey(ArgT0&& arg0, ArgT... args); + std::string* mutable_profilekey(); + PROTOBUF_NODISCARD std::string* release_profilekey(); + void set_allocated_profilekey(std::string* profilekey); + private: + const std::string& _internal_profilekey() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_profilekey(const std::string& value); + std::string* _internal_mutable_profilekey(); + public: + + // optional .SessionProtos.LokiProfile profile = 3; + bool has_profile() const; + private: + bool _internal_has_profile() const; + public: + void clear_profile(); + const ::SessionProtos::LokiProfile& profile() const; + PROTOBUF_NODISCARD ::SessionProtos::LokiProfile* release_profile(); + ::SessionProtos::LokiProfile* mutable_profile(); + void set_allocated_profile(::SessionProtos::LokiProfile* profile); + private: + const ::SessionProtos::LokiProfile& _internal_profile() const; + ::SessionProtos::LokiProfile* _internal_mutable_profile(); + public: + void unsafe_arena_set_allocated_profile( + ::SessionProtos::LokiProfile* profile); + ::SessionProtos::LokiProfile* unsafe_arena_release_profile(); + + // required bool isApproved = 1; + bool has_isapproved() const; + private: + bool _internal_has_isapproved() const; + public: + void clear_isapproved(); + bool isapproved() const; + void set_isapproved(bool value); + private: + bool _internal_isapproved() const; + void _internal_set_isapproved(bool value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.MessageRequestResponse) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr profilekey_; + ::SessionProtos::LokiProfile* profile_; + bool isapproved_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class Content final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.Content) */ { + public: + inline Content() : Content(nullptr) {} + ~Content() override; + explicit PROTOBUF_CONSTEXPR Content(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + Content(const Content& from); + Content(Content&& from) noexcept + : Content() { + *this = ::std::move(from); + } + + inline Content& operator=(const Content& from) { + CopyFrom(from); + return *this; + } + inline Content& operator=(Content&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const Content& default_instance() { + return *internal_default_instance(); + } + static inline const Content* internal_default_instance() { + return reinterpret_cast( + &_Content_default_instance_); + } + static constexpr int kIndexInFileMessages = + 4; + + friend void swap(Content& a, Content& b) { + a.Swap(&b); + } + inline void Swap(Content* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Content* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + Content* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const Content& from); + void MergeFrom(const Content& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(Content* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.Content"; + } + protected: + explicit Content(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kDataMessageFieldNumber = 1, + kCallMessageFieldNumber = 3, + kReceiptMessageFieldNumber = 5, + kTypingMessageFieldNumber = 6, + kConfigurationMessageFieldNumber = 7, + kDataExtractionNotificationFieldNumber = 8, + kUnsendRequestFieldNumber = 9, + kMessageRequestResponseFieldNumber = 10, + kSharedConfigMessageFieldNumber = 11, + }; + // optional .SessionProtos.DataMessage dataMessage = 1; + bool has_datamessage() const; + private: + bool _internal_has_datamessage() const; + public: + void clear_datamessage(); + const ::SessionProtos::DataMessage& datamessage() const; + PROTOBUF_NODISCARD ::SessionProtos::DataMessage* release_datamessage(); + ::SessionProtos::DataMessage* mutable_datamessage(); + void set_allocated_datamessage(::SessionProtos::DataMessage* datamessage); + private: + const ::SessionProtos::DataMessage& _internal_datamessage() const; + ::SessionProtos::DataMessage* _internal_mutable_datamessage(); + public: + void unsafe_arena_set_allocated_datamessage( + ::SessionProtos::DataMessage* datamessage); + ::SessionProtos::DataMessage* unsafe_arena_release_datamessage(); + + // optional .SessionProtos.CallMessage callMessage = 3; + bool has_callmessage() const; + private: + bool _internal_has_callmessage() const; + public: + void clear_callmessage(); + const ::SessionProtos::CallMessage& callmessage() const; + PROTOBUF_NODISCARD ::SessionProtos::CallMessage* release_callmessage(); + ::SessionProtos::CallMessage* mutable_callmessage(); + void set_allocated_callmessage(::SessionProtos::CallMessage* callmessage); + private: + const ::SessionProtos::CallMessage& _internal_callmessage() const; + ::SessionProtos::CallMessage* _internal_mutable_callmessage(); + public: + void unsafe_arena_set_allocated_callmessage( + ::SessionProtos::CallMessage* callmessage); + ::SessionProtos::CallMessage* unsafe_arena_release_callmessage(); + + // optional .SessionProtos.ReceiptMessage receiptMessage = 5; + bool has_receiptmessage() const; + private: + bool _internal_has_receiptmessage() const; + public: + void clear_receiptmessage(); + const ::SessionProtos::ReceiptMessage& receiptmessage() const; + PROTOBUF_NODISCARD ::SessionProtos::ReceiptMessage* release_receiptmessage(); + ::SessionProtos::ReceiptMessage* mutable_receiptmessage(); + void set_allocated_receiptmessage(::SessionProtos::ReceiptMessage* receiptmessage); + private: + const ::SessionProtos::ReceiptMessage& _internal_receiptmessage() const; + ::SessionProtos::ReceiptMessage* _internal_mutable_receiptmessage(); + public: + void unsafe_arena_set_allocated_receiptmessage( + ::SessionProtos::ReceiptMessage* receiptmessage); + ::SessionProtos::ReceiptMessage* unsafe_arena_release_receiptmessage(); + + // optional .SessionProtos.TypingMessage typingMessage = 6; + bool has_typingmessage() const; + private: + bool _internal_has_typingmessage() const; + public: + void clear_typingmessage(); + const ::SessionProtos::TypingMessage& typingmessage() const; + PROTOBUF_NODISCARD ::SessionProtos::TypingMessage* release_typingmessage(); + ::SessionProtos::TypingMessage* mutable_typingmessage(); + void set_allocated_typingmessage(::SessionProtos::TypingMessage* typingmessage); + private: + const ::SessionProtos::TypingMessage& _internal_typingmessage() const; + ::SessionProtos::TypingMessage* _internal_mutable_typingmessage(); + public: + void unsafe_arena_set_allocated_typingmessage( + ::SessionProtos::TypingMessage* typingmessage); + ::SessionProtos::TypingMessage* unsafe_arena_release_typingmessage(); + + // optional .SessionProtos.ConfigurationMessage configurationMessage = 7; + bool has_configurationmessage() const; + private: + bool _internal_has_configurationmessage() const; + public: + void clear_configurationmessage(); + const ::SessionProtos::ConfigurationMessage& configurationmessage() const; + PROTOBUF_NODISCARD ::SessionProtos::ConfigurationMessage* release_configurationmessage(); + ::SessionProtos::ConfigurationMessage* mutable_configurationmessage(); + void set_allocated_configurationmessage(::SessionProtos::ConfigurationMessage* configurationmessage); + private: + const ::SessionProtos::ConfigurationMessage& _internal_configurationmessage() const; + ::SessionProtos::ConfigurationMessage* _internal_mutable_configurationmessage(); + public: + void unsafe_arena_set_allocated_configurationmessage( + ::SessionProtos::ConfigurationMessage* configurationmessage); + ::SessionProtos::ConfigurationMessage* unsafe_arena_release_configurationmessage(); + + // optional .SessionProtos.DataExtractionNotification dataExtractionNotification = 8; + bool has_dataextractionnotification() const; + private: + bool _internal_has_dataextractionnotification() const; + public: + void clear_dataextractionnotification(); + const ::SessionProtos::DataExtractionNotification& dataextractionnotification() const; + PROTOBUF_NODISCARD ::SessionProtos::DataExtractionNotification* release_dataextractionnotification(); + ::SessionProtos::DataExtractionNotification* mutable_dataextractionnotification(); + void set_allocated_dataextractionnotification(::SessionProtos::DataExtractionNotification* dataextractionnotification); + private: + const ::SessionProtos::DataExtractionNotification& _internal_dataextractionnotification() const; + ::SessionProtos::DataExtractionNotification* _internal_mutable_dataextractionnotification(); + public: + void unsafe_arena_set_allocated_dataextractionnotification( + ::SessionProtos::DataExtractionNotification* dataextractionnotification); + ::SessionProtos::DataExtractionNotification* unsafe_arena_release_dataextractionnotification(); + + // optional .SessionProtos.UnsendRequest unsendRequest = 9; + bool has_unsendrequest() const; + private: + bool _internal_has_unsendrequest() const; + public: + void clear_unsendrequest(); + const ::SessionProtos::UnsendRequest& unsendrequest() const; + PROTOBUF_NODISCARD ::SessionProtos::UnsendRequest* release_unsendrequest(); + ::SessionProtos::UnsendRequest* mutable_unsendrequest(); + void set_allocated_unsendrequest(::SessionProtos::UnsendRequest* unsendrequest); + private: + const ::SessionProtos::UnsendRequest& _internal_unsendrequest() const; + ::SessionProtos::UnsendRequest* _internal_mutable_unsendrequest(); + public: + void unsafe_arena_set_allocated_unsendrequest( + ::SessionProtos::UnsendRequest* unsendrequest); + ::SessionProtos::UnsendRequest* unsafe_arena_release_unsendrequest(); + + // optional .SessionProtos.MessageRequestResponse messageRequestResponse = 10; + bool has_messagerequestresponse() const; + private: + bool _internal_has_messagerequestresponse() const; + public: + void clear_messagerequestresponse(); + const ::SessionProtos::MessageRequestResponse& messagerequestresponse() const; + PROTOBUF_NODISCARD ::SessionProtos::MessageRequestResponse* release_messagerequestresponse(); + ::SessionProtos::MessageRequestResponse* mutable_messagerequestresponse(); + void set_allocated_messagerequestresponse(::SessionProtos::MessageRequestResponse* messagerequestresponse); + private: + const ::SessionProtos::MessageRequestResponse& _internal_messagerequestresponse() const; + ::SessionProtos::MessageRequestResponse* _internal_mutable_messagerequestresponse(); + public: + void unsafe_arena_set_allocated_messagerequestresponse( + ::SessionProtos::MessageRequestResponse* messagerequestresponse); + ::SessionProtos::MessageRequestResponse* unsafe_arena_release_messagerequestresponse(); + + // optional .SessionProtos.SharedConfigMessage sharedConfigMessage = 11; + bool has_sharedconfigmessage() const; + private: + bool _internal_has_sharedconfigmessage() const; + public: + void clear_sharedconfigmessage(); + const ::SessionProtos::SharedConfigMessage& sharedconfigmessage() const; + PROTOBUF_NODISCARD ::SessionProtos::SharedConfigMessage* release_sharedconfigmessage(); + ::SessionProtos::SharedConfigMessage* mutable_sharedconfigmessage(); + void set_allocated_sharedconfigmessage(::SessionProtos::SharedConfigMessage* sharedconfigmessage); + private: + const ::SessionProtos::SharedConfigMessage& _internal_sharedconfigmessage() const; + ::SessionProtos::SharedConfigMessage* _internal_mutable_sharedconfigmessage(); + public: + void unsafe_arena_set_allocated_sharedconfigmessage( + ::SessionProtos::SharedConfigMessage* sharedconfigmessage); + ::SessionProtos::SharedConfigMessage* unsafe_arena_release_sharedconfigmessage(); + + // @@protoc_insertion_point(class_scope:SessionProtos.Content) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::SessionProtos::DataMessage* datamessage_; + ::SessionProtos::CallMessage* callmessage_; + ::SessionProtos::ReceiptMessage* receiptmessage_; + ::SessionProtos::TypingMessage* typingmessage_; + ::SessionProtos::ConfigurationMessage* configurationmessage_; + ::SessionProtos::DataExtractionNotification* dataextractionnotification_; + ::SessionProtos::UnsendRequest* unsendrequest_; + ::SessionProtos::MessageRequestResponse* messagerequestresponse_; + ::SessionProtos::SharedConfigMessage* sharedconfigmessage_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class CallMessage final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.CallMessage) */ { + public: + inline CallMessage() : CallMessage(nullptr) {} + ~CallMessage() override; + explicit PROTOBUF_CONSTEXPR CallMessage(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + CallMessage(const CallMessage& from); + CallMessage(CallMessage&& from) noexcept + : CallMessage() { + *this = ::std::move(from); + } + + inline CallMessage& operator=(const CallMessage& from) { + CopyFrom(from); + return *this; + } + inline CallMessage& operator=(CallMessage&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const CallMessage& default_instance() { + return *internal_default_instance(); + } + static inline const CallMessage* internal_default_instance() { + return reinterpret_cast( + &_CallMessage_default_instance_); + } + static constexpr int kIndexInFileMessages = + 5; + + friend void swap(CallMessage& a, CallMessage& b) { + a.Swap(&b); + } + inline void Swap(CallMessage* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CallMessage* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + CallMessage* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const CallMessage& from); + void MergeFrom(const CallMessage& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(CallMessage* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.CallMessage"; + } + protected: + explicit CallMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef CallMessage_Type Type; + static constexpr Type PRE_OFFER = + CallMessage_Type_PRE_OFFER; + static constexpr Type OFFER = + CallMessage_Type_OFFER; + static constexpr Type ANSWER = + CallMessage_Type_ANSWER; + static constexpr Type PROVISIONAL_ANSWER = + CallMessage_Type_PROVISIONAL_ANSWER; + static constexpr Type ICE_CANDIDATES = + CallMessage_Type_ICE_CANDIDATES; + static constexpr Type END_CALL = + CallMessage_Type_END_CALL; + static inline bool Type_IsValid(int value) { + return CallMessage_Type_IsValid(value); + } + static constexpr Type Type_MIN = + CallMessage_Type_Type_MIN; + static constexpr Type Type_MAX = + CallMessage_Type_Type_MAX; + static constexpr int Type_ARRAYSIZE = + CallMessage_Type_Type_ARRAYSIZE; + template + static inline const std::string& Type_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Type_Name."); + return CallMessage_Type_Name(enum_t_value); + } + static inline bool Type_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Type* value) { + return CallMessage_Type_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kSdpsFieldNumber = 2, + kSdpMLineIndexesFieldNumber = 3, + kSdpMidsFieldNumber = 4, + kUuidFieldNumber = 5, + kTypeFieldNumber = 1, + }; + // repeated string sdps = 2; + int sdps_size() const; + private: + int _internal_sdps_size() const; + public: + void clear_sdps(); + const std::string& sdps(int index) const; + std::string* mutable_sdps(int index); + void set_sdps(int index, const std::string& value); + void set_sdps(int index, std::string&& value); + void set_sdps(int index, const char* value); + void set_sdps(int index, const char* value, size_t size); + std::string* add_sdps(); + void add_sdps(const std::string& value); + void add_sdps(std::string&& value); + void add_sdps(const char* value); + void add_sdps(const char* value, size_t size); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& sdps() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* mutable_sdps(); + private: + const std::string& _internal_sdps(int index) const; + std::string* _internal_add_sdps(); + public: + + // repeated uint32 sdpMLineIndexes = 3; + int sdpmlineindexes_size() const; + private: + int _internal_sdpmlineindexes_size() const; + public: + void clear_sdpmlineindexes(); + private: + uint32_t _internal_sdpmlineindexes(int index) const; + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint32_t >& + _internal_sdpmlineindexes() const; + void _internal_add_sdpmlineindexes(uint32_t value); + ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint32_t >* + _internal_mutable_sdpmlineindexes(); + public: + uint32_t sdpmlineindexes(int index) const; + void set_sdpmlineindexes(int index, uint32_t value); + void add_sdpmlineindexes(uint32_t value); + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint32_t >& + sdpmlineindexes() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint32_t >* + mutable_sdpmlineindexes(); + + // repeated string sdpMids = 4; + int sdpmids_size() const; + private: + int _internal_sdpmids_size() const; + public: + void clear_sdpmids(); + const std::string& sdpmids(int index) const; + std::string* mutable_sdpmids(int index); + void set_sdpmids(int index, const std::string& value); + void set_sdpmids(int index, std::string&& value); + void set_sdpmids(int index, const char* value); + void set_sdpmids(int index, const char* value, size_t size); + std::string* add_sdpmids(); + void add_sdpmids(const std::string& value); + void add_sdpmids(std::string&& value); + void add_sdpmids(const char* value); + void add_sdpmids(const char* value, size_t size); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& sdpmids() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* mutable_sdpmids(); + private: + const std::string& _internal_sdpmids(int index) const; + std::string* _internal_add_sdpmids(); + public: + + // required string uuid = 5; + bool has_uuid() const; + private: + bool _internal_has_uuid() const; + public: + void clear_uuid(); + const std::string& uuid() const; + template + void set_uuid(ArgT0&& arg0, ArgT... args); + std::string* mutable_uuid(); + PROTOBUF_NODISCARD std::string* release_uuid(); + void set_allocated_uuid(std::string* uuid); + private: + const std::string& _internal_uuid() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_uuid(const std::string& value); + std::string* _internal_mutable_uuid(); + public: + + // required .SessionProtos.CallMessage.Type type = 1; + bool has_type() const; + private: + bool _internal_has_type() const; + public: + void clear_type(); + ::SessionProtos::CallMessage_Type type() const; + void set_type(::SessionProtos::CallMessage_Type value); + private: + ::SessionProtos::CallMessage_Type _internal_type() const; + void _internal_set_type(::SessionProtos::CallMessage_Type value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.CallMessage) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField sdps_; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint32_t > sdpmlineindexes_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField sdpmids_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr uuid_; + int type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class KeyPair final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.KeyPair) */ { + public: + inline KeyPair() : KeyPair(nullptr) {} + ~KeyPair() override; + explicit PROTOBUF_CONSTEXPR KeyPair(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + KeyPair(const KeyPair& from); + KeyPair(KeyPair&& from) noexcept + : KeyPair() { + *this = ::std::move(from); + } + + inline KeyPair& operator=(const KeyPair& from) { + CopyFrom(from); + return *this; + } + inline KeyPair& operator=(KeyPair&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const KeyPair& default_instance() { + return *internal_default_instance(); + } + static inline const KeyPair* internal_default_instance() { + return reinterpret_cast( + &_KeyPair_default_instance_); + } + static constexpr int kIndexInFileMessages = + 6; + + friend void swap(KeyPair& a, KeyPair& b) { + a.Swap(&b); + } + inline void Swap(KeyPair* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(KeyPair* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + KeyPair* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const KeyPair& from); + void MergeFrom(const KeyPair& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(KeyPair* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.KeyPair"; + } + protected: + explicit KeyPair(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kPublicKeyFieldNumber = 1, + kPrivateKeyFieldNumber = 2, + }; + // required bytes publicKey = 1; + bool has_publickey() const; + private: + bool _internal_has_publickey() const; + public: + void clear_publickey(); + const std::string& publickey() const; + template + void set_publickey(ArgT0&& arg0, ArgT... args); + std::string* mutable_publickey(); + PROTOBUF_NODISCARD std::string* release_publickey(); + void set_allocated_publickey(std::string* publickey); + private: + const std::string& _internal_publickey() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_publickey(const std::string& value); + std::string* _internal_mutable_publickey(); + public: + + // required bytes privateKey = 2; + bool has_privatekey() const; + private: + bool _internal_has_privatekey() const; + public: + void clear_privatekey(); + const std::string& privatekey() const; + template + void set_privatekey(ArgT0&& arg0, ArgT... args); + std::string* mutable_privatekey(); + PROTOBUF_NODISCARD std::string* release_privatekey(); + void set_allocated_privatekey(std::string* privatekey); + private: + const std::string& _internal_privatekey() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_privatekey(const std::string& value); + std::string* _internal_mutable_privatekey(); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.KeyPair) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr publickey_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr privatekey_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class DataExtractionNotification final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.DataExtractionNotification) */ { + public: + inline DataExtractionNotification() : DataExtractionNotification(nullptr) {} + ~DataExtractionNotification() override; + explicit PROTOBUF_CONSTEXPR DataExtractionNotification(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + DataExtractionNotification(const DataExtractionNotification& from); + DataExtractionNotification(DataExtractionNotification&& from) noexcept + : DataExtractionNotification() { + *this = ::std::move(from); + } + + inline DataExtractionNotification& operator=(const DataExtractionNotification& from) { + CopyFrom(from); + return *this; + } + inline DataExtractionNotification& operator=(DataExtractionNotification&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const DataExtractionNotification& default_instance() { + return *internal_default_instance(); + } + static inline const DataExtractionNotification* internal_default_instance() { + return reinterpret_cast( + &_DataExtractionNotification_default_instance_); + } + static constexpr int kIndexInFileMessages = + 7; + + friend void swap(DataExtractionNotification& a, DataExtractionNotification& b) { + a.Swap(&b); + } + inline void Swap(DataExtractionNotification* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(DataExtractionNotification* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + DataExtractionNotification* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const DataExtractionNotification& from); + void MergeFrom(const DataExtractionNotification& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(DataExtractionNotification* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.DataExtractionNotification"; + } + protected: + explicit DataExtractionNotification(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef DataExtractionNotification_Type Type; + static constexpr Type SCREENSHOT = + DataExtractionNotification_Type_SCREENSHOT; + static constexpr Type MEDIA_SAVED = + DataExtractionNotification_Type_MEDIA_SAVED; + static inline bool Type_IsValid(int value) { + return DataExtractionNotification_Type_IsValid(value); + } + static constexpr Type Type_MIN = + DataExtractionNotification_Type_Type_MIN; + static constexpr Type Type_MAX = + DataExtractionNotification_Type_Type_MAX; + static constexpr int Type_ARRAYSIZE = + DataExtractionNotification_Type_Type_ARRAYSIZE; + template + static inline const std::string& Type_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Type_Name."); + return DataExtractionNotification_Type_Name(enum_t_value); + } + static inline bool Type_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Type* value) { + return DataExtractionNotification_Type_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kTimestampFieldNumber = 2, + kTypeFieldNumber = 1, + }; + // optional uint64 timestamp = 2; + bool has_timestamp() const; + private: + bool _internal_has_timestamp() const; + public: + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // required .SessionProtos.DataExtractionNotification.Type type = 1; + bool has_type() const; + private: + bool _internal_has_type() const; + public: + void clear_type(); + ::SessionProtos::DataExtractionNotification_Type type() const; + void set_type(::SessionProtos::DataExtractionNotification_Type value); + private: + ::SessionProtos::DataExtractionNotification_Type _internal_type() const; + void _internal_set_type(::SessionProtos::DataExtractionNotification_Type value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.DataExtractionNotification) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + uint64_t timestamp_; + int type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class LokiProfile final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.LokiProfile) */ { + public: + inline LokiProfile() : LokiProfile(nullptr) {} + ~LokiProfile() override; + explicit PROTOBUF_CONSTEXPR LokiProfile(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + LokiProfile(const LokiProfile& from); + LokiProfile(LokiProfile&& from) noexcept + : LokiProfile() { + *this = ::std::move(from); + } + + inline LokiProfile& operator=(const LokiProfile& from) { + CopyFrom(from); + return *this; + } + inline LokiProfile& operator=(LokiProfile&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const LokiProfile& default_instance() { + return *internal_default_instance(); + } + static inline const LokiProfile* internal_default_instance() { + return reinterpret_cast( + &_LokiProfile_default_instance_); + } + static constexpr int kIndexInFileMessages = + 8; + + friend void swap(LokiProfile& a, LokiProfile& b) { + a.Swap(&b); + } + inline void Swap(LokiProfile* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(LokiProfile* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + LokiProfile* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const LokiProfile& from); + void MergeFrom(const LokiProfile& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(LokiProfile* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.LokiProfile"; + } + protected: + explicit LokiProfile(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kDisplayNameFieldNumber = 1, + kProfilePictureFieldNumber = 2, + }; + // optional string displayName = 1; + bool has_displayname() const; + private: + bool _internal_has_displayname() const; + public: + void clear_displayname(); + const std::string& displayname() const; + template + void set_displayname(ArgT0&& arg0, ArgT... args); + std::string* mutable_displayname(); + PROTOBUF_NODISCARD std::string* release_displayname(); + void set_allocated_displayname(std::string* displayname); + private: + const std::string& _internal_displayname() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_displayname(const std::string& value); + std::string* _internal_mutable_displayname(); + public: + + // optional string profilePicture = 2; + bool has_profilepicture() const; + private: + bool _internal_has_profilepicture() const; + public: + void clear_profilepicture(); + const std::string& profilepicture() const; + template + void set_profilepicture(ArgT0&& arg0, ArgT... args); + std::string* mutable_profilepicture(); + PROTOBUF_NODISCARD std::string* release_profilepicture(); + void set_allocated_profilepicture(std::string* profilepicture); + private: + const std::string& _internal_profilepicture() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_profilepicture(const std::string& value); + std::string* _internal_mutable_profilepicture(); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.LokiProfile) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr displayname_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr profilepicture_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class DataMessage_Quote_QuotedAttachment final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.DataMessage.Quote.QuotedAttachment) */ { + public: + inline DataMessage_Quote_QuotedAttachment() : DataMessage_Quote_QuotedAttachment(nullptr) {} + ~DataMessage_Quote_QuotedAttachment() override; + explicit PROTOBUF_CONSTEXPR DataMessage_Quote_QuotedAttachment(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + DataMessage_Quote_QuotedAttachment(const DataMessage_Quote_QuotedAttachment& from); + DataMessage_Quote_QuotedAttachment(DataMessage_Quote_QuotedAttachment&& from) noexcept + : DataMessage_Quote_QuotedAttachment() { + *this = ::std::move(from); + } + + inline DataMessage_Quote_QuotedAttachment& operator=(const DataMessage_Quote_QuotedAttachment& from) { + CopyFrom(from); + return *this; + } + inline DataMessage_Quote_QuotedAttachment& operator=(DataMessage_Quote_QuotedAttachment&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const DataMessage_Quote_QuotedAttachment& default_instance() { + return *internal_default_instance(); + } + static inline const DataMessage_Quote_QuotedAttachment* internal_default_instance() { + return reinterpret_cast( + &_DataMessage_Quote_QuotedAttachment_default_instance_); + } + static constexpr int kIndexInFileMessages = + 9; + + friend void swap(DataMessage_Quote_QuotedAttachment& a, DataMessage_Quote_QuotedAttachment& b) { + a.Swap(&b); + } + inline void Swap(DataMessage_Quote_QuotedAttachment* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(DataMessage_Quote_QuotedAttachment* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + DataMessage_Quote_QuotedAttachment* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const DataMessage_Quote_QuotedAttachment& from); + void MergeFrom(const DataMessage_Quote_QuotedAttachment& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(DataMessage_Quote_QuotedAttachment* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.DataMessage.Quote.QuotedAttachment"; + } + protected: + explicit DataMessage_Quote_QuotedAttachment(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef DataMessage_Quote_QuotedAttachment_Flags Flags; + static constexpr Flags VOICE_MESSAGE = + DataMessage_Quote_QuotedAttachment_Flags_VOICE_MESSAGE; + static inline bool Flags_IsValid(int value) { + return DataMessage_Quote_QuotedAttachment_Flags_IsValid(value); + } + static constexpr Flags Flags_MIN = + DataMessage_Quote_QuotedAttachment_Flags_Flags_MIN; + static constexpr Flags Flags_MAX = + DataMessage_Quote_QuotedAttachment_Flags_Flags_MAX; + static constexpr int Flags_ARRAYSIZE = + DataMessage_Quote_QuotedAttachment_Flags_Flags_ARRAYSIZE; + template + static inline const std::string& Flags_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Flags_Name."); + return DataMessage_Quote_QuotedAttachment_Flags_Name(enum_t_value); + } + static inline bool Flags_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Flags* value) { + return DataMessage_Quote_QuotedAttachment_Flags_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kContentTypeFieldNumber = 1, + kFileNameFieldNumber = 2, + kThumbnailFieldNumber = 3, + kFlagsFieldNumber = 4, + }; + // optional string contentType = 1; + bool has_contenttype() const; + private: + bool _internal_has_contenttype() const; + public: + void clear_contenttype(); + const std::string& contenttype() const; + template + void set_contenttype(ArgT0&& arg0, ArgT... args); + std::string* mutable_contenttype(); + PROTOBUF_NODISCARD std::string* release_contenttype(); + void set_allocated_contenttype(std::string* contenttype); + private: + const std::string& _internal_contenttype() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_contenttype(const std::string& value); + std::string* _internal_mutable_contenttype(); + public: + + // optional string fileName = 2; + bool has_filename() const; + private: + bool _internal_has_filename() const; + public: + void clear_filename(); + const std::string& filename() const; + template + void set_filename(ArgT0&& arg0, ArgT... args); + std::string* mutable_filename(); + PROTOBUF_NODISCARD std::string* release_filename(); + void set_allocated_filename(std::string* filename); + private: + const std::string& _internal_filename() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_filename(const std::string& value); + std::string* _internal_mutable_filename(); + public: + + // optional .SessionProtos.AttachmentPointer thumbnail = 3; + bool has_thumbnail() const; + private: + bool _internal_has_thumbnail() const; + public: + void clear_thumbnail(); + const ::SessionProtos::AttachmentPointer& thumbnail() const; + PROTOBUF_NODISCARD ::SessionProtos::AttachmentPointer* release_thumbnail(); + ::SessionProtos::AttachmentPointer* mutable_thumbnail(); + void set_allocated_thumbnail(::SessionProtos::AttachmentPointer* thumbnail); + private: + const ::SessionProtos::AttachmentPointer& _internal_thumbnail() const; + ::SessionProtos::AttachmentPointer* _internal_mutable_thumbnail(); + public: + void unsafe_arena_set_allocated_thumbnail( + ::SessionProtos::AttachmentPointer* thumbnail); + ::SessionProtos::AttachmentPointer* unsafe_arena_release_thumbnail(); + + // optional uint32 flags = 4; + bool has_flags() const; + private: + bool _internal_has_flags() const; + public: + void clear_flags(); + uint32_t flags() const; + void set_flags(uint32_t value); + private: + uint32_t _internal_flags() const; + void _internal_set_flags(uint32_t value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.DataMessage.Quote.QuotedAttachment) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr contenttype_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr filename_; + ::SessionProtos::AttachmentPointer* thumbnail_; + uint32_t flags_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class DataMessage_Quote final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.DataMessage.Quote) */ { + public: + inline DataMessage_Quote() : DataMessage_Quote(nullptr) {} + ~DataMessage_Quote() override; + explicit PROTOBUF_CONSTEXPR DataMessage_Quote(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + DataMessage_Quote(const DataMessage_Quote& from); + DataMessage_Quote(DataMessage_Quote&& from) noexcept + : DataMessage_Quote() { + *this = ::std::move(from); + } + + inline DataMessage_Quote& operator=(const DataMessage_Quote& from) { + CopyFrom(from); + return *this; + } + inline DataMessage_Quote& operator=(DataMessage_Quote&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const DataMessage_Quote& default_instance() { + return *internal_default_instance(); + } + static inline const DataMessage_Quote* internal_default_instance() { + return reinterpret_cast( + &_DataMessage_Quote_default_instance_); + } + static constexpr int kIndexInFileMessages = + 10; + + friend void swap(DataMessage_Quote& a, DataMessage_Quote& b) { + a.Swap(&b); + } + inline void Swap(DataMessage_Quote* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(DataMessage_Quote* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + DataMessage_Quote* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const DataMessage_Quote& from); + void MergeFrom(const DataMessage_Quote& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(DataMessage_Quote* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.DataMessage.Quote"; + } + protected: + explicit DataMessage_Quote(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef DataMessage_Quote_QuotedAttachment QuotedAttachment; + + // accessors ------------------------------------------------------- + + enum : int { + kAttachmentsFieldNumber = 4, + kAuthorFieldNumber = 2, + kTextFieldNumber = 3, + kIdFieldNumber = 1, + }; + // repeated .SessionProtos.DataMessage.Quote.QuotedAttachment attachments = 4; + int attachments_size() const; + private: + int _internal_attachments_size() const; + public: + void clear_attachments(); + ::SessionProtos::DataMessage_Quote_QuotedAttachment* mutable_attachments(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_Quote_QuotedAttachment >* + mutable_attachments(); + private: + const ::SessionProtos::DataMessage_Quote_QuotedAttachment& _internal_attachments(int index) const; + ::SessionProtos::DataMessage_Quote_QuotedAttachment* _internal_add_attachments(); + public: + const ::SessionProtos::DataMessage_Quote_QuotedAttachment& attachments(int index) const; + ::SessionProtos::DataMessage_Quote_QuotedAttachment* add_attachments(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_Quote_QuotedAttachment >& + attachments() const; + + // required string author = 2; + bool has_author() const; + private: + bool _internal_has_author() const; + public: + void clear_author(); + const std::string& author() const; + template + void set_author(ArgT0&& arg0, ArgT... args); + std::string* mutable_author(); + PROTOBUF_NODISCARD std::string* release_author(); + void set_allocated_author(std::string* author); + private: + const std::string& _internal_author() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_author(const std::string& value); + std::string* _internal_mutable_author(); + public: + + // optional string text = 3; + bool has_text() const; + private: + bool _internal_has_text() const; + public: + void clear_text(); + const std::string& text() const; + template + void set_text(ArgT0&& arg0, ArgT... args); + std::string* mutable_text(); + PROTOBUF_NODISCARD std::string* release_text(); + void set_allocated_text(std::string* text); + private: + const std::string& _internal_text() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_text(const std::string& value); + std::string* _internal_mutable_text(); + public: + + // required uint64 id = 1; + bool has_id() const; + private: + bool _internal_has_id() const; + public: + void clear_id(); + uint64_t id() const; + void set_id(uint64_t value); + private: + uint64_t _internal_id() const; + void _internal_set_id(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.DataMessage.Quote) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_Quote_QuotedAttachment > attachments_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr author_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr text_; + uint64_t id_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class DataMessage_Preview final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.DataMessage.Preview) */ { + public: + inline DataMessage_Preview() : DataMessage_Preview(nullptr) {} + ~DataMessage_Preview() override; + explicit PROTOBUF_CONSTEXPR DataMessage_Preview(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + DataMessage_Preview(const DataMessage_Preview& from); + DataMessage_Preview(DataMessage_Preview&& from) noexcept + : DataMessage_Preview() { + *this = ::std::move(from); + } + + inline DataMessage_Preview& operator=(const DataMessage_Preview& from) { + CopyFrom(from); + return *this; + } + inline DataMessage_Preview& operator=(DataMessage_Preview&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const DataMessage_Preview& default_instance() { + return *internal_default_instance(); + } + static inline const DataMessage_Preview* internal_default_instance() { + return reinterpret_cast( + &_DataMessage_Preview_default_instance_); + } + static constexpr int kIndexInFileMessages = + 11; + + friend void swap(DataMessage_Preview& a, DataMessage_Preview& b) { + a.Swap(&b); + } + inline void Swap(DataMessage_Preview* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(DataMessage_Preview* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + DataMessage_Preview* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const DataMessage_Preview& from); + void MergeFrom(const DataMessage_Preview& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(DataMessage_Preview* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.DataMessage.Preview"; + } + protected: + explicit DataMessage_Preview(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kUrlFieldNumber = 1, + kTitleFieldNumber = 2, + kImageFieldNumber = 3, + }; + // required string url = 1; + bool has_url() const; + private: + bool _internal_has_url() const; + public: + void clear_url(); + const std::string& url() const; + template + void set_url(ArgT0&& arg0, ArgT... args); + std::string* mutable_url(); + PROTOBUF_NODISCARD std::string* release_url(); + void set_allocated_url(std::string* url); + private: + const std::string& _internal_url() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_url(const std::string& value); + std::string* _internal_mutable_url(); + public: + + // optional string title = 2; + bool has_title() const; + private: + bool _internal_has_title() const; + public: + void clear_title(); + const std::string& title() const; + template + void set_title(ArgT0&& arg0, ArgT... args); + std::string* mutable_title(); + PROTOBUF_NODISCARD std::string* release_title(); + void set_allocated_title(std::string* title); + private: + const std::string& _internal_title() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_title(const std::string& value); + std::string* _internal_mutable_title(); + public: + + // optional .SessionProtos.AttachmentPointer image = 3; + bool has_image() const; + private: + bool _internal_has_image() const; + public: + void clear_image(); + const ::SessionProtos::AttachmentPointer& image() const; + PROTOBUF_NODISCARD ::SessionProtos::AttachmentPointer* release_image(); + ::SessionProtos::AttachmentPointer* mutable_image(); + void set_allocated_image(::SessionProtos::AttachmentPointer* image); + private: + const ::SessionProtos::AttachmentPointer& _internal_image() const; + ::SessionProtos::AttachmentPointer* _internal_mutable_image(); + public: + void unsafe_arena_set_allocated_image( + ::SessionProtos::AttachmentPointer* image); + ::SessionProtos::AttachmentPointer* unsafe_arena_release_image(); + + // @@protoc_insertion_point(class_scope:SessionProtos.DataMessage.Preview) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr url_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr title_; + ::SessionProtos::AttachmentPointer* image_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class DataMessage_Reaction final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.DataMessage.Reaction) */ { + public: + inline DataMessage_Reaction() : DataMessage_Reaction(nullptr) {} + ~DataMessage_Reaction() override; + explicit PROTOBUF_CONSTEXPR DataMessage_Reaction(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + DataMessage_Reaction(const DataMessage_Reaction& from); + DataMessage_Reaction(DataMessage_Reaction&& from) noexcept + : DataMessage_Reaction() { + *this = ::std::move(from); + } + + inline DataMessage_Reaction& operator=(const DataMessage_Reaction& from) { + CopyFrom(from); + return *this; + } + inline DataMessage_Reaction& operator=(DataMessage_Reaction&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const DataMessage_Reaction& default_instance() { + return *internal_default_instance(); + } + static inline const DataMessage_Reaction* internal_default_instance() { + return reinterpret_cast( + &_DataMessage_Reaction_default_instance_); + } + static constexpr int kIndexInFileMessages = + 12; + + friend void swap(DataMessage_Reaction& a, DataMessage_Reaction& b) { + a.Swap(&b); + } + inline void Swap(DataMessage_Reaction* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(DataMessage_Reaction* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + DataMessage_Reaction* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const DataMessage_Reaction& from); + void MergeFrom(const DataMessage_Reaction& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(DataMessage_Reaction* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.DataMessage.Reaction"; + } + protected: + explicit DataMessage_Reaction(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef DataMessage_Reaction_Action Action; + static constexpr Action REACT = + DataMessage_Reaction_Action_REACT; + static constexpr Action REMOVE = + DataMessage_Reaction_Action_REMOVE; + static inline bool Action_IsValid(int value) { + return DataMessage_Reaction_Action_IsValid(value); + } + static constexpr Action Action_MIN = + DataMessage_Reaction_Action_Action_MIN; + static constexpr Action Action_MAX = + DataMessage_Reaction_Action_Action_MAX; + static constexpr int Action_ARRAYSIZE = + DataMessage_Reaction_Action_Action_ARRAYSIZE; + template + static inline const std::string& Action_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Action_Name."); + return DataMessage_Reaction_Action_Name(enum_t_value); + } + static inline bool Action_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Action* value) { + return DataMessage_Reaction_Action_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kAuthorFieldNumber = 2, + kEmojiFieldNumber = 3, + kIdFieldNumber = 1, + kActionFieldNumber = 4, + }; + // required string author = 2; + bool has_author() const; + private: + bool _internal_has_author() const; + public: + void clear_author(); + const std::string& author() const; + template + void set_author(ArgT0&& arg0, ArgT... args); + std::string* mutable_author(); + PROTOBUF_NODISCARD std::string* release_author(); + void set_allocated_author(std::string* author); + private: + const std::string& _internal_author() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_author(const std::string& value); + std::string* _internal_mutable_author(); + public: + + // optional string emoji = 3; + bool has_emoji() const; + private: + bool _internal_has_emoji() const; + public: + void clear_emoji(); + const std::string& emoji() const; + template + void set_emoji(ArgT0&& arg0, ArgT... args); + std::string* mutable_emoji(); + PROTOBUF_NODISCARD std::string* release_emoji(); + void set_allocated_emoji(std::string* emoji); + private: + const std::string& _internal_emoji() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_emoji(const std::string& value); + std::string* _internal_mutable_emoji(); + public: + + // required uint64 id = 1; + bool has_id() const; + private: + bool _internal_has_id() const; + public: + void clear_id(); + uint64_t id() const; + void set_id(uint64_t value); + private: + uint64_t _internal_id() const; + void _internal_set_id(uint64_t value); + public: + + // required .SessionProtos.DataMessage.Reaction.Action action = 4; + bool has_action() const; + private: + bool _internal_has_action() const; + public: + void clear_action(); + ::SessionProtos::DataMessage_Reaction_Action action() const; + void set_action(::SessionProtos::DataMessage_Reaction_Action value); + private: + ::SessionProtos::DataMessage_Reaction_Action _internal_action() const; + void _internal_set_action(::SessionProtos::DataMessage_Reaction_Action value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.DataMessage.Reaction) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr author_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr emoji_; + uint64_t id_; + int action_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class DataMessage_OpenGroupInvitation final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.DataMessage.OpenGroupInvitation) */ { + public: + inline DataMessage_OpenGroupInvitation() : DataMessage_OpenGroupInvitation(nullptr) {} + ~DataMessage_OpenGroupInvitation() override; + explicit PROTOBUF_CONSTEXPR DataMessage_OpenGroupInvitation(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + DataMessage_OpenGroupInvitation(const DataMessage_OpenGroupInvitation& from); + DataMessage_OpenGroupInvitation(DataMessage_OpenGroupInvitation&& from) noexcept + : DataMessage_OpenGroupInvitation() { + *this = ::std::move(from); + } + + inline DataMessage_OpenGroupInvitation& operator=(const DataMessage_OpenGroupInvitation& from) { + CopyFrom(from); + return *this; + } + inline DataMessage_OpenGroupInvitation& operator=(DataMessage_OpenGroupInvitation&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const DataMessage_OpenGroupInvitation& default_instance() { + return *internal_default_instance(); + } + static inline const DataMessage_OpenGroupInvitation* internal_default_instance() { + return reinterpret_cast( + &_DataMessage_OpenGroupInvitation_default_instance_); + } + static constexpr int kIndexInFileMessages = + 13; + + friend void swap(DataMessage_OpenGroupInvitation& a, DataMessage_OpenGroupInvitation& b) { + a.Swap(&b); + } + inline void Swap(DataMessage_OpenGroupInvitation* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(DataMessage_OpenGroupInvitation* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + DataMessage_OpenGroupInvitation* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const DataMessage_OpenGroupInvitation& from); + void MergeFrom(const DataMessage_OpenGroupInvitation& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(DataMessage_OpenGroupInvitation* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.DataMessage.OpenGroupInvitation"; + } + protected: + explicit DataMessage_OpenGroupInvitation(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kUrlFieldNumber = 1, + kNameFieldNumber = 3, + }; + // required string url = 1; + bool has_url() const; + private: + bool _internal_has_url() const; + public: + void clear_url(); + const std::string& url() const; + template + void set_url(ArgT0&& arg0, ArgT... args); + std::string* mutable_url(); + PROTOBUF_NODISCARD std::string* release_url(); + void set_allocated_url(std::string* url); + private: + const std::string& _internal_url() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_url(const std::string& value); + std::string* _internal_mutable_url(); + public: + + // required string name = 3; + bool has_name() const; + private: + bool _internal_has_name() const; + public: + void clear_name(); + const std::string& name() const; + template + void set_name(ArgT0&& arg0, ArgT... args); + std::string* mutable_name(); + PROTOBUF_NODISCARD std::string* release_name(); + void set_allocated_name(std::string* name); + private: + const std::string& _internal_name() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value); + std::string* _internal_mutable_name(); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.DataMessage.OpenGroupInvitation) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr url_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class DataMessage_ClosedGroupControlMessage_KeyPairWrapper final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) */ { + public: + inline DataMessage_ClosedGroupControlMessage_KeyPairWrapper() : DataMessage_ClosedGroupControlMessage_KeyPairWrapper(nullptr) {} + ~DataMessage_ClosedGroupControlMessage_KeyPairWrapper() override; + explicit PROTOBUF_CONSTEXPR DataMessage_ClosedGroupControlMessage_KeyPairWrapper(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + DataMessage_ClosedGroupControlMessage_KeyPairWrapper(const DataMessage_ClosedGroupControlMessage_KeyPairWrapper& from); + DataMessage_ClosedGroupControlMessage_KeyPairWrapper(DataMessage_ClosedGroupControlMessage_KeyPairWrapper&& from) noexcept + : DataMessage_ClosedGroupControlMessage_KeyPairWrapper() { + *this = ::std::move(from); + } + + inline DataMessage_ClosedGroupControlMessage_KeyPairWrapper& operator=(const DataMessage_ClosedGroupControlMessage_KeyPairWrapper& from) { + CopyFrom(from); + return *this; + } + inline DataMessage_ClosedGroupControlMessage_KeyPairWrapper& operator=(DataMessage_ClosedGroupControlMessage_KeyPairWrapper&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const DataMessage_ClosedGroupControlMessage_KeyPairWrapper& default_instance() { + return *internal_default_instance(); + } + static inline const DataMessage_ClosedGroupControlMessage_KeyPairWrapper* internal_default_instance() { + return reinterpret_cast( + &_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_default_instance_); + } + static constexpr int kIndexInFileMessages = + 14; + + friend void swap(DataMessage_ClosedGroupControlMessage_KeyPairWrapper& a, DataMessage_ClosedGroupControlMessage_KeyPairWrapper& b) { + a.Swap(&b); + } + inline void Swap(DataMessage_ClosedGroupControlMessage_KeyPairWrapper* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(DataMessage_ClosedGroupControlMessage_KeyPairWrapper* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + DataMessage_ClosedGroupControlMessage_KeyPairWrapper* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const DataMessage_ClosedGroupControlMessage_KeyPairWrapper& from); + void MergeFrom(const DataMessage_ClosedGroupControlMessage_KeyPairWrapper& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(DataMessage_ClosedGroupControlMessage_KeyPairWrapper* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper"; + } + protected: + explicit DataMessage_ClosedGroupControlMessage_KeyPairWrapper(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kPublicKeyFieldNumber = 1, + kEncryptedKeyPairFieldNumber = 2, + }; + // required bytes publicKey = 1; + bool has_publickey() const; + private: + bool _internal_has_publickey() const; + public: + void clear_publickey(); + const std::string& publickey() const; + template + void set_publickey(ArgT0&& arg0, ArgT... args); + std::string* mutable_publickey(); + PROTOBUF_NODISCARD std::string* release_publickey(); + void set_allocated_publickey(std::string* publickey); + private: + const std::string& _internal_publickey() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_publickey(const std::string& value); + std::string* _internal_mutable_publickey(); + public: + + // required bytes encryptedKeyPair = 2; + bool has_encryptedkeypair() const; + private: + bool _internal_has_encryptedkeypair() const; + public: + void clear_encryptedkeypair(); + const std::string& encryptedkeypair() const; + template + void set_encryptedkeypair(ArgT0&& arg0, ArgT... args); + std::string* mutable_encryptedkeypair(); + PROTOBUF_NODISCARD std::string* release_encryptedkeypair(); + void set_allocated_encryptedkeypair(std::string* encryptedkeypair); + private: + const std::string& _internal_encryptedkeypair() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_encryptedkeypair(const std::string& value); + std::string* _internal_mutable_encryptedkeypair(); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr publickey_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr encryptedkeypair_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class DataMessage_ClosedGroupControlMessage final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.DataMessage.ClosedGroupControlMessage) */ { + public: + inline DataMessage_ClosedGroupControlMessage() : DataMessage_ClosedGroupControlMessage(nullptr) {} + ~DataMessage_ClosedGroupControlMessage() override; + explicit PROTOBUF_CONSTEXPR DataMessage_ClosedGroupControlMessage(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + DataMessage_ClosedGroupControlMessage(const DataMessage_ClosedGroupControlMessage& from); + DataMessage_ClosedGroupControlMessage(DataMessage_ClosedGroupControlMessage&& from) noexcept + : DataMessage_ClosedGroupControlMessage() { + *this = ::std::move(from); + } + + inline DataMessage_ClosedGroupControlMessage& operator=(const DataMessage_ClosedGroupControlMessage& from) { + CopyFrom(from); + return *this; + } + inline DataMessage_ClosedGroupControlMessage& operator=(DataMessage_ClosedGroupControlMessage&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const DataMessage_ClosedGroupControlMessage& default_instance() { + return *internal_default_instance(); + } + static inline const DataMessage_ClosedGroupControlMessage* internal_default_instance() { + return reinterpret_cast( + &_DataMessage_ClosedGroupControlMessage_default_instance_); + } + static constexpr int kIndexInFileMessages = + 15; + + friend void swap(DataMessage_ClosedGroupControlMessage& a, DataMessage_ClosedGroupControlMessage& b) { + a.Swap(&b); + } + inline void Swap(DataMessage_ClosedGroupControlMessage* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(DataMessage_ClosedGroupControlMessage* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + DataMessage_ClosedGroupControlMessage* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const DataMessage_ClosedGroupControlMessage& from); + void MergeFrom(const DataMessage_ClosedGroupControlMessage& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(DataMessage_ClosedGroupControlMessage* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.DataMessage.ClosedGroupControlMessage"; + } + protected: + explicit DataMessage_ClosedGroupControlMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef DataMessage_ClosedGroupControlMessage_KeyPairWrapper KeyPairWrapper; + + typedef DataMessage_ClosedGroupControlMessage_Type Type; + static constexpr Type NEW = + DataMessage_ClosedGroupControlMessage_Type_NEW; + static constexpr Type ENCRYPTION_KEY_PAIR = + DataMessage_ClosedGroupControlMessage_Type_ENCRYPTION_KEY_PAIR; + static constexpr Type NAME_CHANGE = + DataMessage_ClosedGroupControlMessage_Type_NAME_CHANGE; + static constexpr Type MEMBERS_ADDED = + DataMessage_ClosedGroupControlMessage_Type_MEMBERS_ADDED; + static constexpr Type MEMBERS_REMOVED = + DataMessage_ClosedGroupControlMessage_Type_MEMBERS_REMOVED; + static constexpr Type MEMBER_LEFT = + DataMessage_ClosedGroupControlMessage_Type_MEMBER_LEFT; + static constexpr Type ENCRYPTION_KEY_PAIR_REQUEST = + DataMessage_ClosedGroupControlMessage_Type_ENCRYPTION_KEY_PAIR_REQUEST; + static inline bool Type_IsValid(int value) { + return DataMessage_ClosedGroupControlMessage_Type_IsValid(value); + } + static constexpr Type Type_MIN = + DataMessage_ClosedGroupControlMessage_Type_Type_MIN; + static constexpr Type Type_MAX = + DataMessage_ClosedGroupControlMessage_Type_Type_MAX; + static constexpr int Type_ARRAYSIZE = + DataMessage_ClosedGroupControlMessage_Type_Type_ARRAYSIZE; + template + static inline const std::string& Type_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Type_Name."); + return DataMessage_ClosedGroupControlMessage_Type_Name(enum_t_value); + } + static inline bool Type_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Type* value) { + return DataMessage_ClosedGroupControlMessage_Type_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kMembersFieldNumber = 5, + kAdminsFieldNumber = 6, + kWrappersFieldNumber = 7, + kPublicKeyFieldNumber = 2, + kNameFieldNumber = 3, + kEncryptionKeyPairFieldNumber = 4, + kExpirationTimerFieldNumber = 8, + kTypeFieldNumber = 1, + }; + // repeated bytes members = 5; + int members_size() const; + private: + int _internal_members_size() const; + public: + void clear_members(); + const std::string& members(int index) const; + std::string* mutable_members(int index); + void set_members(int index, const std::string& value); + void set_members(int index, std::string&& value); + void set_members(int index, const char* value); + void set_members(int index, const void* value, size_t size); + std::string* add_members(); + void add_members(const std::string& value); + void add_members(std::string&& value); + void add_members(const char* value); + void add_members(const void* value, size_t size); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& members() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* mutable_members(); + private: + const std::string& _internal_members(int index) const; + std::string* _internal_add_members(); + public: + + // repeated bytes admins = 6; + int admins_size() const; + private: + int _internal_admins_size() const; + public: + void clear_admins(); + const std::string& admins(int index) const; + std::string* mutable_admins(int index); + void set_admins(int index, const std::string& value); + void set_admins(int index, std::string&& value); + void set_admins(int index, const char* value); + void set_admins(int index, const void* value, size_t size); + std::string* add_admins(); + void add_admins(const std::string& value); + void add_admins(std::string&& value); + void add_admins(const char* value); + void add_admins(const void* value, size_t size); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& admins() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* mutable_admins(); + private: + const std::string& _internal_admins(int index) const; + std::string* _internal_add_admins(); + public: + + // repeated .SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; + int wrappers_size() const; + private: + int _internal_wrappers_size() const; + public: + void clear_wrappers(); + ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper* mutable_wrappers(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper >* + mutable_wrappers(); + private: + const ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper& _internal_wrappers(int index) const; + ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper* _internal_add_wrappers(); + public: + const ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper& wrappers(int index) const; + ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper* add_wrappers(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper >& + wrappers() const; + + // optional bytes publicKey = 2; + bool has_publickey() const; + private: + bool _internal_has_publickey() const; + public: + void clear_publickey(); + const std::string& publickey() const; + template + void set_publickey(ArgT0&& arg0, ArgT... args); + std::string* mutable_publickey(); + PROTOBUF_NODISCARD std::string* release_publickey(); + void set_allocated_publickey(std::string* publickey); + private: + const std::string& _internal_publickey() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_publickey(const std::string& value); + std::string* _internal_mutable_publickey(); + public: + + // optional string name = 3; + bool has_name() const; + private: + bool _internal_has_name() const; + public: + void clear_name(); + const std::string& name() const; + template + void set_name(ArgT0&& arg0, ArgT... args); + std::string* mutable_name(); + PROTOBUF_NODISCARD std::string* release_name(); + void set_allocated_name(std::string* name); + private: + const std::string& _internal_name() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value); + std::string* _internal_mutable_name(); + public: + + // optional .SessionProtos.KeyPair encryptionKeyPair = 4; + bool has_encryptionkeypair() const; + private: + bool _internal_has_encryptionkeypair() const; + public: + void clear_encryptionkeypair(); + const ::SessionProtos::KeyPair& encryptionkeypair() const; + PROTOBUF_NODISCARD ::SessionProtos::KeyPair* release_encryptionkeypair(); + ::SessionProtos::KeyPair* mutable_encryptionkeypair(); + void set_allocated_encryptionkeypair(::SessionProtos::KeyPair* encryptionkeypair); + private: + const ::SessionProtos::KeyPair& _internal_encryptionkeypair() const; + ::SessionProtos::KeyPair* _internal_mutable_encryptionkeypair(); + public: + void unsafe_arena_set_allocated_encryptionkeypair( + ::SessionProtos::KeyPair* encryptionkeypair); + ::SessionProtos::KeyPair* unsafe_arena_release_encryptionkeypair(); + + // optional uint32 expirationTimer = 8; + bool has_expirationtimer() const; + private: + bool _internal_has_expirationtimer() const; + public: + void clear_expirationtimer(); + uint32_t expirationtimer() const; + void set_expirationtimer(uint32_t value); + private: + uint32_t _internal_expirationtimer() const; + void _internal_set_expirationtimer(uint32_t value); + public: + + // required .SessionProtos.DataMessage.ClosedGroupControlMessage.Type type = 1; + bool has_type() const; + private: + bool _internal_has_type() const; + public: + void clear_type(); + ::SessionProtos::DataMessage_ClosedGroupControlMessage_Type type() const; + void set_type(::SessionProtos::DataMessage_ClosedGroupControlMessage_Type value); + private: + ::SessionProtos::DataMessage_ClosedGroupControlMessage_Type _internal_type() const; + void _internal_set_type(::SessionProtos::DataMessage_ClosedGroupControlMessage_Type value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.DataMessage.ClosedGroupControlMessage) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField members_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField admins_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper > wrappers_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr publickey_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_; + ::SessionProtos::KeyPair* encryptionkeypair_; + uint32_t expirationtimer_; + int type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class DataMessage final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.DataMessage) */ { + public: + inline DataMessage() : DataMessage(nullptr) {} + ~DataMessage() override; + explicit PROTOBUF_CONSTEXPR DataMessage(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + DataMessage(const DataMessage& from); + DataMessage(DataMessage&& from) noexcept + : DataMessage() { + *this = ::std::move(from); + } + + inline DataMessage& operator=(const DataMessage& from) { + CopyFrom(from); + return *this; + } + inline DataMessage& operator=(DataMessage&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const DataMessage& default_instance() { + return *internal_default_instance(); + } + static inline const DataMessage* internal_default_instance() { + return reinterpret_cast( + &_DataMessage_default_instance_); + } + static constexpr int kIndexInFileMessages = + 16; + + friend void swap(DataMessage& a, DataMessage& b) { + a.Swap(&b); + } + inline void Swap(DataMessage* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(DataMessage* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + DataMessage* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const DataMessage& from); + void MergeFrom(const DataMessage& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(DataMessage* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.DataMessage"; + } + protected: + explicit DataMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef DataMessage_Quote Quote; + typedef DataMessage_Preview Preview; + typedef DataMessage_Reaction Reaction; + typedef DataMessage_OpenGroupInvitation OpenGroupInvitation; + typedef DataMessage_ClosedGroupControlMessage ClosedGroupControlMessage; + + typedef DataMessage_Flags Flags; + static constexpr Flags EXPIRATION_TIMER_UPDATE = + DataMessage_Flags_EXPIRATION_TIMER_UPDATE; + static inline bool Flags_IsValid(int value) { + return DataMessage_Flags_IsValid(value); + } + static constexpr Flags Flags_MIN = + DataMessage_Flags_Flags_MIN; + static constexpr Flags Flags_MAX = + DataMessage_Flags_Flags_MAX; + static constexpr int Flags_ARRAYSIZE = + DataMessage_Flags_Flags_ARRAYSIZE; + template + static inline const std::string& Flags_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Flags_Name."); + return DataMessage_Flags_Name(enum_t_value); + } + static inline bool Flags_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Flags* value) { + return DataMessage_Flags_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kAttachmentsFieldNumber = 2, + kPreviewFieldNumber = 10, + kBodyFieldNumber = 1, + kProfileKeyFieldNumber = 6, + kSyncTargetFieldNumber = 105, + kQuoteFieldNumber = 8, + kReactionFieldNumber = 11, + kProfileFieldNumber = 101, + kOpenGroupInvitationFieldNumber = 102, + kClosedGroupControlMessageFieldNumber = 104, + kFlagsFieldNumber = 4, + kExpireTimerFieldNumber = 5, + kTimestampFieldNumber = 7, + kBlocksCommunityMessageRequestsFieldNumber = 106, + }; + // repeated .SessionProtos.AttachmentPointer attachments = 2; + int attachments_size() const; + private: + int _internal_attachments_size() const; + public: + void clear_attachments(); + ::SessionProtos::AttachmentPointer* mutable_attachments(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::AttachmentPointer >* + mutable_attachments(); + private: + const ::SessionProtos::AttachmentPointer& _internal_attachments(int index) const; + ::SessionProtos::AttachmentPointer* _internal_add_attachments(); + public: + const ::SessionProtos::AttachmentPointer& attachments(int index) const; + ::SessionProtos::AttachmentPointer* add_attachments(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::AttachmentPointer >& + attachments() const; + + // repeated .SessionProtos.DataMessage.Preview preview = 10; + int preview_size() const; + private: + int _internal_preview_size() const; + public: + void clear_preview(); + ::SessionProtos::DataMessage_Preview* mutable_preview(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_Preview >* + mutable_preview(); + private: + const ::SessionProtos::DataMessage_Preview& _internal_preview(int index) const; + ::SessionProtos::DataMessage_Preview* _internal_add_preview(); + public: + const ::SessionProtos::DataMessage_Preview& preview(int index) const; + ::SessionProtos::DataMessage_Preview* add_preview(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_Preview >& + preview() const; + + // optional string body = 1; + bool has_body() const; + private: + bool _internal_has_body() const; + public: + void clear_body(); + const std::string& body() const; + template + void set_body(ArgT0&& arg0, ArgT... args); + std::string* mutable_body(); + PROTOBUF_NODISCARD std::string* release_body(); + void set_allocated_body(std::string* body); + private: + const std::string& _internal_body() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_body(const std::string& value); + std::string* _internal_mutable_body(); + public: + + // optional bytes profileKey = 6; + bool has_profilekey() const; + private: + bool _internal_has_profilekey() const; + public: + void clear_profilekey(); + const std::string& profilekey() const; + template + void set_profilekey(ArgT0&& arg0, ArgT... args); + std::string* mutable_profilekey(); + PROTOBUF_NODISCARD std::string* release_profilekey(); + void set_allocated_profilekey(std::string* profilekey); + private: + const std::string& _internal_profilekey() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_profilekey(const std::string& value); + std::string* _internal_mutable_profilekey(); + public: + + // optional string syncTarget = 105; + bool has_synctarget() const; + private: + bool _internal_has_synctarget() const; + public: + void clear_synctarget(); + const std::string& synctarget() const; + template + void set_synctarget(ArgT0&& arg0, ArgT... args); + std::string* mutable_synctarget(); + PROTOBUF_NODISCARD std::string* release_synctarget(); + void set_allocated_synctarget(std::string* synctarget); + private: + const std::string& _internal_synctarget() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_synctarget(const std::string& value); + std::string* _internal_mutable_synctarget(); + public: + + // optional .SessionProtos.DataMessage.Quote quote = 8; + bool has_quote() const; + private: + bool _internal_has_quote() const; + public: + void clear_quote(); + const ::SessionProtos::DataMessage_Quote& quote() const; + PROTOBUF_NODISCARD ::SessionProtos::DataMessage_Quote* release_quote(); + ::SessionProtos::DataMessage_Quote* mutable_quote(); + void set_allocated_quote(::SessionProtos::DataMessage_Quote* quote); + private: + const ::SessionProtos::DataMessage_Quote& _internal_quote() const; + ::SessionProtos::DataMessage_Quote* _internal_mutable_quote(); + public: + void unsafe_arena_set_allocated_quote( + ::SessionProtos::DataMessage_Quote* quote); + ::SessionProtos::DataMessage_Quote* unsafe_arena_release_quote(); + + // optional .SessionProtos.DataMessage.Reaction reaction = 11; + bool has_reaction() const; + private: + bool _internal_has_reaction() const; + public: + void clear_reaction(); + const ::SessionProtos::DataMessage_Reaction& reaction() const; + PROTOBUF_NODISCARD ::SessionProtos::DataMessage_Reaction* release_reaction(); + ::SessionProtos::DataMessage_Reaction* mutable_reaction(); + void set_allocated_reaction(::SessionProtos::DataMessage_Reaction* reaction); + private: + const ::SessionProtos::DataMessage_Reaction& _internal_reaction() const; + ::SessionProtos::DataMessage_Reaction* _internal_mutable_reaction(); + public: + void unsafe_arena_set_allocated_reaction( + ::SessionProtos::DataMessage_Reaction* reaction); + ::SessionProtos::DataMessage_Reaction* unsafe_arena_release_reaction(); + + // optional .SessionProtos.LokiProfile profile = 101; + bool has_profile() const; + private: + bool _internal_has_profile() const; + public: + void clear_profile(); + const ::SessionProtos::LokiProfile& profile() const; + PROTOBUF_NODISCARD ::SessionProtos::LokiProfile* release_profile(); + ::SessionProtos::LokiProfile* mutable_profile(); + void set_allocated_profile(::SessionProtos::LokiProfile* profile); + private: + const ::SessionProtos::LokiProfile& _internal_profile() const; + ::SessionProtos::LokiProfile* _internal_mutable_profile(); + public: + void unsafe_arena_set_allocated_profile( + ::SessionProtos::LokiProfile* profile); + ::SessionProtos::LokiProfile* unsafe_arena_release_profile(); + + // optional .SessionProtos.DataMessage.OpenGroupInvitation openGroupInvitation = 102; + bool has_opengroupinvitation() const; + private: + bool _internal_has_opengroupinvitation() const; + public: + void clear_opengroupinvitation(); + const ::SessionProtos::DataMessage_OpenGroupInvitation& opengroupinvitation() const; + PROTOBUF_NODISCARD ::SessionProtos::DataMessage_OpenGroupInvitation* release_opengroupinvitation(); + ::SessionProtos::DataMessage_OpenGroupInvitation* mutable_opengroupinvitation(); + void set_allocated_opengroupinvitation(::SessionProtos::DataMessage_OpenGroupInvitation* opengroupinvitation); + private: + const ::SessionProtos::DataMessage_OpenGroupInvitation& _internal_opengroupinvitation() const; + ::SessionProtos::DataMessage_OpenGroupInvitation* _internal_mutable_opengroupinvitation(); + public: + void unsafe_arena_set_allocated_opengroupinvitation( + ::SessionProtos::DataMessage_OpenGroupInvitation* opengroupinvitation); + ::SessionProtos::DataMessage_OpenGroupInvitation* unsafe_arena_release_opengroupinvitation(); + + // optional .SessionProtos.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; + bool has_closedgroupcontrolmessage() const; + private: + bool _internal_has_closedgroupcontrolmessage() const; + public: + void clear_closedgroupcontrolmessage(); + const ::SessionProtos::DataMessage_ClosedGroupControlMessage& closedgroupcontrolmessage() const; + PROTOBUF_NODISCARD ::SessionProtos::DataMessage_ClosedGroupControlMessage* release_closedgroupcontrolmessage(); + ::SessionProtos::DataMessage_ClosedGroupControlMessage* mutable_closedgroupcontrolmessage(); + void set_allocated_closedgroupcontrolmessage(::SessionProtos::DataMessage_ClosedGroupControlMessage* closedgroupcontrolmessage); + private: + const ::SessionProtos::DataMessage_ClosedGroupControlMessage& _internal_closedgroupcontrolmessage() const; + ::SessionProtos::DataMessage_ClosedGroupControlMessage* _internal_mutable_closedgroupcontrolmessage(); + public: + void unsafe_arena_set_allocated_closedgroupcontrolmessage( + ::SessionProtos::DataMessage_ClosedGroupControlMessage* closedgroupcontrolmessage); + ::SessionProtos::DataMessage_ClosedGroupControlMessage* unsafe_arena_release_closedgroupcontrolmessage(); + + // optional uint32 flags = 4; + bool has_flags() const; + private: + bool _internal_has_flags() const; + public: + void clear_flags(); + uint32_t flags() const; + void set_flags(uint32_t value); + private: + uint32_t _internal_flags() const; + void _internal_set_flags(uint32_t value); + public: + + // optional uint32 expireTimer = 5; + bool has_expiretimer() const; + private: + bool _internal_has_expiretimer() const; + public: + void clear_expiretimer(); + uint32_t expiretimer() const; + void set_expiretimer(uint32_t value); + private: + uint32_t _internal_expiretimer() const; + void _internal_set_expiretimer(uint32_t value); + public: + + // optional uint64 timestamp = 7; + bool has_timestamp() const; + private: + bool _internal_has_timestamp() const; + public: + void clear_timestamp(); + uint64_t timestamp() const; + void set_timestamp(uint64_t value); + private: + uint64_t _internal_timestamp() const; + void _internal_set_timestamp(uint64_t value); + public: + + // optional bool blocksCommunityMessageRequests = 106; + bool has_blockscommunitymessagerequests() const; + private: + bool _internal_has_blockscommunitymessagerequests() const; + public: + void clear_blockscommunitymessagerequests(); + bool blockscommunitymessagerequests() const; + void set_blockscommunitymessagerequests(bool value); + private: + bool _internal_blockscommunitymessagerequests() const; + void _internal_set_blockscommunitymessagerequests(bool value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.DataMessage) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::AttachmentPointer > attachments_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_Preview > preview_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr body_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr profilekey_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr synctarget_; + ::SessionProtos::DataMessage_Quote* quote_; + ::SessionProtos::DataMessage_Reaction* reaction_; + ::SessionProtos::LokiProfile* profile_; + ::SessionProtos::DataMessage_OpenGroupInvitation* opengroupinvitation_; + ::SessionProtos::DataMessage_ClosedGroupControlMessage* closedgroupcontrolmessage_; + uint32_t flags_; + uint32_t expiretimer_; + uint64_t timestamp_; + bool blockscommunitymessagerequests_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class ConfigurationMessage_ClosedGroup final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.ConfigurationMessage.ClosedGroup) */ { + public: + inline ConfigurationMessage_ClosedGroup() : ConfigurationMessage_ClosedGroup(nullptr) {} + ~ConfigurationMessage_ClosedGroup() override; + explicit PROTOBUF_CONSTEXPR ConfigurationMessage_ClosedGroup(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ConfigurationMessage_ClosedGroup(const ConfigurationMessage_ClosedGroup& from); + ConfigurationMessage_ClosedGroup(ConfigurationMessage_ClosedGroup&& from) noexcept + : ConfigurationMessage_ClosedGroup() { + *this = ::std::move(from); + } + + inline ConfigurationMessage_ClosedGroup& operator=(const ConfigurationMessage_ClosedGroup& from) { + CopyFrom(from); + return *this; + } + inline ConfigurationMessage_ClosedGroup& operator=(ConfigurationMessage_ClosedGroup&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const ConfigurationMessage_ClosedGroup& default_instance() { + return *internal_default_instance(); + } + static inline const ConfigurationMessage_ClosedGroup* internal_default_instance() { + return reinterpret_cast( + &_ConfigurationMessage_ClosedGroup_default_instance_); + } + static constexpr int kIndexInFileMessages = + 17; + + friend void swap(ConfigurationMessage_ClosedGroup& a, ConfigurationMessage_ClosedGroup& b) { + a.Swap(&b); + } + inline void Swap(ConfigurationMessage_ClosedGroup* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ConfigurationMessage_ClosedGroup* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ConfigurationMessage_ClosedGroup* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ConfigurationMessage_ClosedGroup& from); + void MergeFrom(const ConfigurationMessage_ClosedGroup& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ConfigurationMessage_ClosedGroup* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.ConfigurationMessage.ClosedGroup"; + } + protected: + explicit ConfigurationMessage_ClosedGroup(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kMembersFieldNumber = 4, + kAdminsFieldNumber = 5, + kPublicKeyFieldNumber = 1, + kNameFieldNumber = 2, + kEncryptionKeyPairFieldNumber = 3, + kExpirationTimerFieldNumber = 6, + }; + // repeated bytes members = 4; + int members_size() const; + private: + int _internal_members_size() const; + public: + void clear_members(); + const std::string& members(int index) const; + std::string* mutable_members(int index); + void set_members(int index, const std::string& value); + void set_members(int index, std::string&& value); + void set_members(int index, const char* value); + void set_members(int index, const void* value, size_t size); + std::string* add_members(); + void add_members(const std::string& value); + void add_members(std::string&& value); + void add_members(const char* value); + void add_members(const void* value, size_t size); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& members() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* mutable_members(); + private: + const std::string& _internal_members(int index) const; + std::string* _internal_add_members(); + public: + + // repeated bytes admins = 5; + int admins_size() const; + private: + int _internal_admins_size() const; + public: + void clear_admins(); + const std::string& admins(int index) const; + std::string* mutable_admins(int index); + void set_admins(int index, const std::string& value); + void set_admins(int index, std::string&& value); + void set_admins(int index, const char* value); + void set_admins(int index, const void* value, size_t size); + std::string* add_admins(); + void add_admins(const std::string& value); + void add_admins(std::string&& value); + void add_admins(const char* value); + void add_admins(const void* value, size_t size); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& admins() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* mutable_admins(); + private: + const std::string& _internal_admins(int index) const; + std::string* _internal_add_admins(); + public: + + // optional bytes publicKey = 1; + bool has_publickey() const; + private: + bool _internal_has_publickey() const; + public: + void clear_publickey(); + const std::string& publickey() const; + template + void set_publickey(ArgT0&& arg0, ArgT... args); + std::string* mutable_publickey(); + PROTOBUF_NODISCARD std::string* release_publickey(); + void set_allocated_publickey(std::string* publickey); + private: + const std::string& _internal_publickey() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_publickey(const std::string& value); + std::string* _internal_mutable_publickey(); + public: + + // optional string name = 2; + bool has_name() const; + private: + bool _internal_has_name() const; + public: + void clear_name(); + const std::string& name() const; + template + void set_name(ArgT0&& arg0, ArgT... args); + std::string* mutable_name(); + PROTOBUF_NODISCARD std::string* release_name(); + void set_allocated_name(std::string* name); + private: + const std::string& _internal_name() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value); + std::string* _internal_mutable_name(); + public: + + // optional .SessionProtos.KeyPair encryptionKeyPair = 3; + bool has_encryptionkeypair() const; + private: + bool _internal_has_encryptionkeypair() const; + public: + void clear_encryptionkeypair(); + const ::SessionProtos::KeyPair& encryptionkeypair() const; + PROTOBUF_NODISCARD ::SessionProtos::KeyPair* release_encryptionkeypair(); + ::SessionProtos::KeyPair* mutable_encryptionkeypair(); + void set_allocated_encryptionkeypair(::SessionProtos::KeyPair* encryptionkeypair); + private: + const ::SessionProtos::KeyPair& _internal_encryptionkeypair() const; + ::SessionProtos::KeyPair* _internal_mutable_encryptionkeypair(); + public: + void unsafe_arena_set_allocated_encryptionkeypair( + ::SessionProtos::KeyPair* encryptionkeypair); + ::SessionProtos::KeyPair* unsafe_arena_release_encryptionkeypair(); + + // optional uint32 expirationTimer = 6; + bool has_expirationtimer() const; + private: + bool _internal_has_expirationtimer() const; + public: + void clear_expirationtimer(); + uint32_t expirationtimer() const; + void set_expirationtimer(uint32_t value); + private: + uint32_t _internal_expirationtimer() const; + void _internal_set_expirationtimer(uint32_t value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.ConfigurationMessage.ClosedGroup) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField members_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField admins_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr publickey_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_; + ::SessionProtos::KeyPair* encryptionkeypair_; + uint32_t expirationtimer_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class ConfigurationMessage_Contact final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.ConfigurationMessage.Contact) */ { + public: + inline ConfigurationMessage_Contact() : ConfigurationMessage_Contact(nullptr) {} + ~ConfigurationMessage_Contact() override; + explicit PROTOBUF_CONSTEXPR ConfigurationMessage_Contact(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ConfigurationMessage_Contact(const ConfigurationMessage_Contact& from); + ConfigurationMessage_Contact(ConfigurationMessage_Contact&& from) noexcept + : ConfigurationMessage_Contact() { + *this = ::std::move(from); + } + + inline ConfigurationMessage_Contact& operator=(const ConfigurationMessage_Contact& from) { + CopyFrom(from); + return *this; + } + inline ConfigurationMessage_Contact& operator=(ConfigurationMessage_Contact&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const ConfigurationMessage_Contact& default_instance() { + return *internal_default_instance(); + } + static inline const ConfigurationMessage_Contact* internal_default_instance() { + return reinterpret_cast( + &_ConfigurationMessage_Contact_default_instance_); + } + static constexpr int kIndexInFileMessages = + 18; + + friend void swap(ConfigurationMessage_Contact& a, ConfigurationMessage_Contact& b) { + a.Swap(&b); + } + inline void Swap(ConfigurationMessage_Contact* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ConfigurationMessage_Contact* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ConfigurationMessage_Contact* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ConfigurationMessage_Contact& from); + void MergeFrom(const ConfigurationMessage_Contact& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ConfigurationMessage_Contact* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.ConfigurationMessage.Contact"; + } + protected: + explicit ConfigurationMessage_Contact(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kPublicKeyFieldNumber = 1, + kNameFieldNumber = 2, + kProfilePictureFieldNumber = 3, + kProfileKeyFieldNumber = 4, + kIsApprovedFieldNumber = 5, + kIsBlockedFieldNumber = 6, + kDidApproveMeFieldNumber = 7, + }; + // required bytes publicKey = 1; + bool has_publickey() const; + private: + bool _internal_has_publickey() const; + public: + void clear_publickey(); + const std::string& publickey() const; + template + void set_publickey(ArgT0&& arg0, ArgT... args); + std::string* mutable_publickey(); + PROTOBUF_NODISCARD std::string* release_publickey(); + void set_allocated_publickey(std::string* publickey); + private: + const std::string& _internal_publickey() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_publickey(const std::string& value); + std::string* _internal_mutable_publickey(); + public: + + // required string name = 2; + bool has_name() const; + private: + bool _internal_has_name() const; + public: + void clear_name(); + const std::string& name() const; + template + void set_name(ArgT0&& arg0, ArgT... args); + std::string* mutable_name(); + PROTOBUF_NODISCARD std::string* release_name(); + void set_allocated_name(std::string* name); + private: + const std::string& _internal_name() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value); + std::string* _internal_mutable_name(); + public: + + // optional string profilePicture = 3; + bool has_profilepicture() const; + private: + bool _internal_has_profilepicture() const; + public: + void clear_profilepicture(); + const std::string& profilepicture() const; + template + void set_profilepicture(ArgT0&& arg0, ArgT... args); + std::string* mutable_profilepicture(); + PROTOBUF_NODISCARD std::string* release_profilepicture(); + void set_allocated_profilepicture(std::string* profilepicture); + private: + const std::string& _internal_profilepicture() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_profilepicture(const std::string& value); + std::string* _internal_mutable_profilepicture(); + public: + + // optional bytes profileKey = 4; + bool has_profilekey() const; + private: + bool _internal_has_profilekey() const; + public: + void clear_profilekey(); + const std::string& profilekey() const; + template + void set_profilekey(ArgT0&& arg0, ArgT... args); + std::string* mutable_profilekey(); + PROTOBUF_NODISCARD std::string* release_profilekey(); + void set_allocated_profilekey(std::string* profilekey); + private: + const std::string& _internal_profilekey() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_profilekey(const std::string& value); + std::string* _internal_mutable_profilekey(); + public: + + // optional bool isApproved = 5; + bool has_isapproved() const; + private: + bool _internal_has_isapproved() const; + public: + void clear_isapproved(); + bool isapproved() const; + void set_isapproved(bool value); + private: + bool _internal_isapproved() const; + void _internal_set_isapproved(bool value); + public: + + // optional bool isBlocked = 6; + bool has_isblocked() const; + private: + bool _internal_has_isblocked() const; + public: + void clear_isblocked(); + bool isblocked() const; + void set_isblocked(bool value); + private: + bool _internal_isblocked() const; + void _internal_set_isblocked(bool value); + public: + + // optional bool didApproveMe = 7; + bool has_didapproveme() const; + private: + bool _internal_has_didapproveme() const; + public: + void clear_didapproveme(); + bool didapproveme() const; + void set_didapproveme(bool value); + private: + bool _internal_didapproveme() const; + void _internal_set_didapproveme(bool value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.ConfigurationMessage.Contact) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr publickey_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr profilepicture_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr profilekey_; + bool isapproved_; + bool isblocked_; + bool didapproveme_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class ConfigurationMessage final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.ConfigurationMessage) */ { + public: + inline ConfigurationMessage() : ConfigurationMessage(nullptr) {} + ~ConfigurationMessage() override; + explicit PROTOBUF_CONSTEXPR ConfigurationMessage(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ConfigurationMessage(const ConfigurationMessage& from); + ConfigurationMessage(ConfigurationMessage&& from) noexcept + : ConfigurationMessage() { + *this = ::std::move(from); + } + + inline ConfigurationMessage& operator=(const ConfigurationMessage& from) { + CopyFrom(from); + return *this; + } + inline ConfigurationMessage& operator=(ConfigurationMessage&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const ConfigurationMessage& default_instance() { + return *internal_default_instance(); + } + static inline const ConfigurationMessage* internal_default_instance() { + return reinterpret_cast( + &_ConfigurationMessage_default_instance_); + } + static constexpr int kIndexInFileMessages = + 19; + + friend void swap(ConfigurationMessage& a, ConfigurationMessage& b) { + a.Swap(&b); + } + inline void Swap(ConfigurationMessage* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ConfigurationMessage* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ConfigurationMessage* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ConfigurationMessage& from); + void MergeFrom(const ConfigurationMessage& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ConfigurationMessage* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.ConfigurationMessage"; + } + protected: + explicit ConfigurationMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef ConfigurationMessage_ClosedGroup ClosedGroup; + typedef ConfigurationMessage_Contact Contact; + + // accessors ------------------------------------------------------- + + enum : int { + kClosedGroupsFieldNumber = 1, + kOpenGroupsFieldNumber = 2, + kContactsFieldNumber = 6, + kDisplayNameFieldNumber = 3, + kProfilePictureFieldNumber = 4, + kProfileKeyFieldNumber = 5, + }; + // repeated .SessionProtos.ConfigurationMessage.ClosedGroup closedGroups = 1; + int closedgroups_size() const; + private: + int _internal_closedgroups_size() const; + public: + void clear_closedgroups(); + ::SessionProtos::ConfigurationMessage_ClosedGroup* mutable_closedgroups(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::ConfigurationMessage_ClosedGroup >* + mutable_closedgroups(); + private: + const ::SessionProtos::ConfigurationMessage_ClosedGroup& _internal_closedgroups(int index) const; + ::SessionProtos::ConfigurationMessage_ClosedGroup* _internal_add_closedgroups(); + public: + const ::SessionProtos::ConfigurationMessage_ClosedGroup& closedgroups(int index) const; + ::SessionProtos::ConfigurationMessage_ClosedGroup* add_closedgroups(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::ConfigurationMessage_ClosedGroup >& + closedgroups() const; + + // repeated string openGroups = 2; + int opengroups_size() const; + private: + int _internal_opengroups_size() const; + public: + void clear_opengroups(); + const std::string& opengroups(int index) const; + std::string* mutable_opengroups(int index); + void set_opengroups(int index, const std::string& value); + void set_opengroups(int index, std::string&& value); + void set_opengroups(int index, const char* value); + void set_opengroups(int index, const char* value, size_t size); + std::string* add_opengroups(); + void add_opengroups(const std::string& value); + void add_opengroups(std::string&& value); + void add_opengroups(const char* value); + void add_opengroups(const char* value, size_t size); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& opengroups() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* mutable_opengroups(); + private: + const std::string& _internal_opengroups(int index) const; + std::string* _internal_add_opengroups(); + public: + + // repeated .SessionProtos.ConfigurationMessage.Contact contacts = 6; + int contacts_size() const; + private: + int _internal_contacts_size() const; + public: + void clear_contacts(); + ::SessionProtos::ConfigurationMessage_Contact* mutable_contacts(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::ConfigurationMessage_Contact >* + mutable_contacts(); + private: + const ::SessionProtos::ConfigurationMessage_Contact& _internal_contacts(int index) const; + ::SessionProtos::ConfigurationMessage_Contact* _internal_add_contacts(); + public: + const ::SessionProtos::ConfigurationMessage_Contact& contacts(int index) const; + ::SessionProtos::ConfigurationMessage_Contact* add_contacts(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::ConfigurationMessage_Contact >& + contacts() const; + + // optional string displayName = 3; + bool has_displayname() const; + private: + bool _internal_has_displayname() const; + public: + void clear_displayname(); + const std::string& displayname() const; + template + void set_displayname(ArgT0&& arg0, ArgT... args); + std::string* mutable_displayname(); + PROTOBUF_NODISCARD std::string* release_displayname(); + void set_allocated_displayname(std::string* displayname); + private: + const std::string& _internal_displayname() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_displayname(const std::string& value); + std::string* _internal_mutable_displayname(); + public: + + // optional string profilePicture = 4; + bool has_profilepicture() const; + private: + bool _internal_has_profilepicture() const; + public: + void clear_profilepicture(); + const std::string& profilepicture() const; + template + void set_profilepicture(ArgT0&& arg0, ArgT... args); + std::string* mutable_profilepicture(); + PROTOBUF_NODISCARD std::string* release_profilepicture(); + void set_allocated_profilepicture(std::string* profilepicture); + private: + const std::string& _internal_profilepicture() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_profilepicture(const std::string& value); + std::string* _internal_mutable_profilepicture(); + public: + + // optional bytes profileKey = 5; + bool has_profilekey() const; + private: + bool _internal_has_profilekey() const; + public: + void clear_profilekey(); + const std::string& profilekey() const; + template + void set_profilekey(ArgT0&& arg0, ArgT... args); + std::string* mutable_profilekey(); + PROTOBUF_NODISCARD std::string* release_profilekey(); + void set_allocated_profilekey(std::string* profilekey); + private: + const std::string& _internal_profilekey() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_profilekey(const std::string& value); + std::string* _internal_mutable_profilekey(); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.ConfigurationMessage) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::ConfigurationMessage_ClosedGroup > closedgroups_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField opengroups_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::ConfigurationMessage_Contact > contacts_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr displayname_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr profilepicture_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr profilekey_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class ReceiptMessage final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.ReceiptMessage) */ { + public: + inline ReceiptMessage() : ReceiptMessage(nullptr) {} + ~ReceiptMessage() override; + explicit PROTOBUF_CONSTEXPR ReceiptMessage(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ReceiptMessage(const ReceiptMessage& from); + ReceiptMessage(ReceiptMessage&& from) noexcept + : ReceiptMessage() { + *this = ::std::move(from); + } + + inline ReceiptMessage& operator=(const ReceiptMessage& from) { + CopyFrom(from); + return *this; + } + inline ReceiptMessage& operator=(ReceiptMessage&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const ReceiptMessage& default_instance() { + return *internal_default_instance(); + } + static inline const ReceiptMessage* internal_default_instance() { + return reinterpret_cast( + &_ReceiptMessage_default_instance_); + } + static constexpr int kIndexInFileMessages = + 20; + + friend void swap(ReceiptMessage& a, ReceiptMessage& b) { + a.Swap(&b); + } + inline void Swap(ReceiptMessage* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ReceiptMessage* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ReceiptMessage* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const ReceiptMessage& from); + void MergeFrom(const ReceiptMessage& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ReceiptMessage* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.ReceiptMessage"; + } + protected: + explicit ReceiptMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef ReceiptMessage_Type Type; + static constexpr Type DELIVERY = + ReceiptMessage_Type_DELIVERY; + static constexpr Type READ = + ReceiptMessage_Type_READ; + static inline bool Type_IsValid(int value) { + return ReceiptMessage_Type_IsValid(value); + } + static constexpr Type Type_MIN = + ReceiptMessage_Type_Type_MIN; + static constexpr Type Type_MAX = + ReceiptMessage_Type_Type_MAX; + static constexpr int Type_ARRAYSIZE = + ReceiptMessage_Type_Type_ARRAYSIZE; + template + static inline const std::string& Type_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Type_Name."); + return ReceiptMessage_Type_Name(enum_t_value); + } + static inline bool Type_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Type* value) { + return ReceiptMessage_Type_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kTimestampFieldNumber = 2, + kTypeFieldNumber = 1, + }; + // repeated uint64 timestamp = 2; + int timestamp_size() const; + private: + int _internal_timestamp_size() const; + public: + void clear_timestamp(); + private: + uint64_t _internal_timestamp(int index) const; + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint64_t >& + _internal_timestamp() const; + void _internal_add_timestamp(uint64_t value); + ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint64_t >* + _internal_mutable_timestamp(); + public: + uint64_t timestamp(int index) const; + void set_timestamp(int index, uint64_t value); + void add_timestamp(uint64_t value); + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint64_t >& + timestamp() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint64_t >* + mutable_timestamp(); + + // required .SessionProtos.ReceiptMessage.Type type = 1; + bool has_type() const; + private: + bool _internal_has_type() const; + public: + void clear_type(); + ::SessionProtos::ReceiptMessage_Type type() const; + void set_type(::SessionProtos::ReceiptMessage_Type value); + private: + ::SessionProtos::ReceiptMessage_Type _internal_type() const; + void _internal_set_type(::SessionProtos::ReceiptMessage_Type value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.ReceiptMessage) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint64_t > timestamp_; + int type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class AttachmentPointer final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.AttachmentPointer) */ { + public: + inline AttachmentPointer() : AttachmentPointer(nullptr) {} + ~AttachmentPointer() override; + explicit PROTOBUF_CONSTEXPR AttachmentPointer(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + AttachmentPointer(const AttachmentPointer& from); + AttachmentPointer(AttachmentPointer&& from) noexcept + : AttachmentPointer() { + *this = ::std::move(from); + } + + inline AttachmentPointer& operator=(const AttachmentPointer& from) { + CopyFrom(from); + return *this; + } + inline AttachmentPointer& operator=(AttachmentPointer&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const AttachmentPointer& default_instance() { + return *internal_default_instance(); + } + static inline const AttachmentPointer* internal_default_instance() { + return reinterpret_cast( + &_AttachmentPointer_default_instance_); + } + static constexpr int kIndexInFileMessages = + 21; + + friend void swap(AttachmentPointer& a, AttachmentPointer& b) { + a.Swap(&b); + } + inline void Swap(AttachmentPointer* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(AttachmentPointer* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + AttachmentPointer* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const AttachmentPointer& from); + void MergeFrom(const AttachmentPointer& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(AttachmentPointer* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.AttachmentPointer"; + } + protected: + explicit AttachmentPointer(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef AttachmentPointer_Flags Flags; + static constexpr Flags VOICE_MESSAGE = + AttachmentPointer_Flags_VOICE_MESSAGE; + static inline bool Flags_IsValid(int value) { + return AttachmentPointer_Flags_IsValid(value); + } + static constexpr Flags Flags_MIN = + AttachmentPointer_Flags_Flags_MIN; + static constexpr Flags Flags_MAX = + AttachmentPointer_Flags_Flags_MAX; + static constexpr int Flags_ARRAYSIZE = + AttachmentPointer_Flags_Flags_ARRAYSIZE; + template + static inline const std::string& Flags_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Flags_Name."); + return AttachmentPointer_Flags_Name(enum_t_value); + } + static inline bool Flags_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Flags* value) { + return AttachmentPointer_Flags_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kContentTypeFieldNumber = 2, + kKeyFieldNumber = 3, + kThumbnailFieldNumber = 5, + kDigestFieldNumber = 6, + kFileNameFieldNumber = 7, + kCaptionFieldNumber = 11, + kUrlFieldNumber = 101, + kIdFieldNumber = 1, + kSizeFieldNumber = 4, + kFlagsFieldNumber = 8, + kWidthFieldNumber = 9, + kHeightFieldNumber = 10, + }; + // optional string contentType = 2; + bool has_contenttype() const; + private: + bool _internal_has_contenttype() const; + public: + void clear_contenttype(); + const std::string& contenttype() const; + template + void set_contenttype(ArgT0&& arg0, ArgT... args); + std::string* mutable_contenttype(); + PROTOBUF_NODISCARD std::string* release_contenttype(); + void set_allocated_contenttype(std::string* contenttype); + private: + const std::string& _internal_contenttype() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_contenttype(const std::string& value); + std::string* _internal_mutable_contenttype(); + public: + + // optional bytes key = 3; + bool has_key() const; + private: + bool _internal_has_key() const; + public: + void clear_key(); + const std::string& key() const; + template + void set_key(ArgT0&& arg0, ArgT... args); + std::string* mutable_key(); + PROTOBUF_NODISCARD std::string* release_key(); + void set_allocated_key(std::string* key); + private: + const std::string& _internal_key() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_key(const std::string& value); + std::string* _internal_mutable_key(); + public: + + // optional bytes thumbnail = 5; + bool has_thumbnail() const; + private: + bool _internal_has_thumbnail() const; + public: + void clear_thumbnail(); + const std::string& thumbnail() const; + template + void set_thumbnail(ArgT0&& arg0, ArgT... args); + std::string* mutable_thumbnail(); + PROTOBUF_NODISCARD std::string* release_thumbnail(); + void set_allocated_thumbnail(std::string* thumbnail); + private: + const std::string& _internal_thumbnail() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_thumbnail(const std::string& value); + std::string* _internal_mutable_thumbnail(); + public: + + // optional bytes digest = 6; + bool has_digest() const; + private: + bool _internal_has_digest() const; + public: + void clear_digest(); + const std::string& digest() const; + template + void set_digest(ArgT0&& arg0, ArgT... args); + std::string* mutable_digest(); + PROTOBUF_NODISCARD std::string* release_digest(); + void set_allocated_digest(std::string* digest); + private: + const std::string& _internal_digest() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_digest(const std::string& value); + std::string* _internal_mutable_digest(); + public: + + // optional string fileName = 7; + bool has_filename() const; + private: + bool _internal_has_filename() const; + public: + void clear_filename(); + const std::string& filename() const; + template + void set_filename(ArgT0&& arg0, ArgT... args); + std::string* mutable_filename(); + PROTOBUF_NODISCARD std::string* release_filename(); + void set_allocated_filename(std::string* filename); + private: + const std::string& _internal_filename() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_filename(const std::string& value); + std::string* _internal_mutable_filename(); + public: + + // optional string caption = 11; + bool has_caption() const; + private: + bool _internal_has_caption() const; + public: + void clear_caption(); + const std::string& caption() const; + template + void set_caption(ArgT0&& arg0, ArgT... args); + std::string* mutable_caption(); + PROTOBUF_NODISCARD std::string* release_caption(); + void set_allocated_caption(std::string* caption); + private: + const std::string& _internal_caption() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_caption(const std::string& value); + std::string* _internal_mutable_caption(); + public: + + // optional string url = 101; + bool has_url() const; + private: + bool _internal_has_url() const; + public: + void clear_url(); + const std::string& url() const; + template + void set_url(ArgT0&& arg0, ArgT... args); + std::string* mutable_url(); + PROTOBUF_NODISCARD std::string* release_url(); + void set_allocated_url(std::string* url); + private: + const std::string& _internal_url() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_url(const std::string& value); + std::string* _internal_mutable_url(); + public: + + // required fixed64 id = 1; + bool has_id() const; + private: + bool _internal_has_id() const; + public: + void clear_id(); + uint64_t id() const; + void set_id(uint64_t value); + private: + uint64_t _internal_id() const; + void _internal_set_id(uint64_t value); + public: + + // optional uint32 size = 4; + bool has_size() const; + private: + bool _internal_has_size() const; + public: + void clear_size(); + uint32_t size() const; + void set_size(uint32_t value); + private: + uint32_t _internal_size() const; + void _internal_set_size(uint32_t value); + public: + + // optional uint32 flags = 8; + bool has_flags() const; + private: + bool _internal_has_flags() const; + public: + void clear_flags(); + uint32_t flags() const; + void set_flags(uint32_t value); + private: + uint32_t _internal_flags() const; + void _internal_set_flags(uint32_t value); + public: + + // optional uint32 width = 9; + bool has_width() const; + private: + bool _internal_has_width() const; + public: + void clear_width(); + uint32_t width() const; + void set_width(uint32_t value); + private: + uint32_t _internal_width() const; + void _internal_set_width(uint32_t value); + public: + + // optional uint32 height = 10; + bool has_height() const; + private: + bool _internal_has_height() const; + public: + void clear_height(); + uint32_t height() const; + void set_height(uint32_t value); + private: + uint32_t _internal_height() const; + void _internal_set_height(uint32_t value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.AttachmentPointer) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr contenttype_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr key_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr thumbnail_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr digest_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr filename_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr caption_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr url_; + uint64_t id_; + uint32_t size_; + uint32_t flags_; + uint32_t width_; + uint32_t height_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// ------------------------------------------------------------------- + +class SharedConfigMessage final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:SessionProtos.SharedConfigMessage) */ { + public: + inline SharedConfigMessage() : SharedConfigMessage(nullptr) {} + ~SharedConfigMessage() override; + explicit PROTOBUF_CONSTEXPR SharedConfigMessage(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + SharedConfigMessage(const SharedConfigMessage& from); + SharedConfigMessage(SharedConfigMessage&& from) noexcept + : SharedConfigMessage() { + *this = ::std::move(from); + } + + inline SharedConfigMessage& operator=(const SharedConfigMessage& from) { + CopyFrom(from); + return *this; + } + inline SharedConfigMessage& operator=(SharedConfigMessage&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const SharedConfigMessage& default_instance() { + return *internal_default_instance(); + } + static inline const SharedConfigMessage* internal_default_instance() { + return reinterpret_cast( + &_SharedConfigMessage_default_instance_); + } + static constexpr int kIndexInFileMessages = + 22; + + friend void swap(SharedConfigMessage& a, SharedConfigMessage& b) { + a.Swap(&b); + } + inline void Swap(SharedConfigMessage* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(SharedConfigMessage* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + SharedConfigMessage* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const SharedConfigMessage& from); + void MergeFrom(const SharedConfigMessage& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(SharedConfigMessage* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "SessionProtos.SharedConfigMessage"; + } + protected: + explicit SharedConfigMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef SharedConfigMessage_Kind Kind; + static constexpr Kind USER_PROFILE = + SharedConfigMessage_Kind_USER_PROFILE; + static constexpr Kind CONTACTS = + SharedConfigMessage_Kind_CONTACTS; + static constexpr Kind CONVO_INFO_VOLATILE = + SharedConfigMessage_Kind_CONVO_INFO_VOLATILE; + static constexpr Kind USER_GROUPS = + SharedConfigMessage_Kind_USER_GROUPS; + static inline bool Kind_IsValid(int value) { + return SharedConfigMessage_Kind_IsValid(value); + } + static constexpr Kind Kind_MIN = + SharedConfigMessage_Kind_Kind_MIN; + static constexpr Kind Kind_MAX = + SharedConfigMessage_Kind_Kind_MAX; + static constexpr int Kind_ARRAYSIZE = + SharedConfigMessage_Kind_Kind_ARRAYSIZE; + template + static inline const std::string& Kind_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Kind_Name."); + return SharedConfigMessage_Kind_Name(enum_t_value); + } + static inline bool Kind_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Kind* value) { + return SharedConfigMessage_Kind_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kDataFieldNumber = 3, + kSeqnoFieldNumber = 2, + kKindFieldNumber = 1, + }; + // required bytes data = 3; + bool has_data() const; + private: + bool _internal_has_data() const; + public: + void clear_data(); + const std::string& data() const; + template + void set_data(ArgT0&& arg0, ArgT... args); + std::string* mutable_data(); + PROTOBUF_NODISCARD std::string* release_data(); + void set_allocated_data(std::string* data); + private: + const std::string& _internal_data() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_data(const std::string& value); + std::string* _internal_mutable_data(); + public: + + // required int64 seqno = 2; + bool has_seqno() const; + private: + bool _internal_has_seqno() const; + public: + void clear_seqno(); + int64_t seqno() const; + void set_seqno(int64_t value); + private: + int64_t _internal_seqno() const; + void _internal_set_seqno(int64_t value); + public: + + // required .SessionProtos.SharedConfigMessage.Kind kind = 1; + bool has_kind() const; + private: + bool _internal_has_kind() const; + public: + void clear_kind(); + ::SessionProtos::SharedConfigMessage_Kind kind() const; + void set_kind(::SessionProtos::SharedConfigMessage_Kind value); + private: + ::SessionProtos::SharedConfigMessage_Kind _internal_kind() const; + void _internal_set_kind(::SessionProtos::SharedConfigMessage_Kind value); + public: + + // @@protoc_insertion_point(class_scope:SessionProtos.SharedConfigMessage) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr data_; + int64_t seqno_; + int kind_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_SessionProtos_2eproto; +}; +// =================================================================== + + +// =================================================================== + +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif // __GNUC__ +// Envelope + +// required .SessionProtos.Envelope.Type type = 1; +inline bool Envelope::_internal_has_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000020u) != 0; + return value; +} +inline bool Envelope::has_type() const { + return _internal_has_type(); +} +inline void Envelope::clear_type() { + _impl_.type_ = 6; + _impl_._has_bits_[0] &= ~0x00000020u; +} +inline ::SessionProtos::Envelope_Type Envelope::_internal_type() const { + return static_cast< ::SessionProtos::Envelope_Type >(_impl_.type_); +} +inline ::SessionProtos::Envelope_Type Envelope::type() const { + // @@protoc_insertion_point(field_get:SessionProtos.Envelope.type) + return _internal_type(); +} +inline void Envelope::_internal_set_type(::SessionProtos::Envelope_Type value) { + assert(::SessionProtos::Envelope_Type_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000020u; + _impl_.type_ = value; +} +inline void Envelope::set_type(::SessionProtos::Envelope_Type value) { + _internal_set_type(value); + // @@protoc_insertion_point(field_set:SessionProtos.Envelope.type) +} + +// optional string source = 2; +inline bool Envelope::_internal_has_source() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool Envelope::has_source() const { + return _internal_has_source(); +} +inline void Envelope::clear_source() { + _impl_.source_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& Envelope::source() const { + // @@protoc_insertion_point(field_get:SessionProtos.Envelope.source) + return _internal_source(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Envelope::set_source(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.source_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.Envelope.source) +} +inline std::string* Envelope::mutable_source() { + std::string* _s = _internal_mutable_source(); + // @@protoc_insertion_point(field_mutable:SessionProtos.Envelope.source) + return _s; +} +inline const std::string& Envelope::_internal_source() const { + return _impl_.source_.Get(); +} +inline void Envelope::_internal_set_source(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.source_.Set(value, GetArenaForAllocation()); +} +inline std::string* Envelope::_internal_mutable_source() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.source_.Mutable(GetArenaForAllocation()); +} +inline std::string* Envelope::release_source() { + // @@protoc_insertion_point(field_release:SessionProtos.Envelope.source) + if (!_internal_has_source()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.source_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.source_.IsDefault()) { + _impl_.source_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void Envelope::set_allocated_source(std::string* source) { + if (source != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.source_.SetAllocated(source, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.source_.IsDefault()) { + _impl_.source_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.Envelope.source) +} + +// optional uint32 sourceDevice = 7; +inline bool Envelope::_internal_has_sourcedevice() const { + bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; + return value; +} +inline bool Envelope::has_sourcedevice() const { + return _internal_has_sourcedevice(); +} +inline void Envelope::clear_sourcedevice() { + _impl_.sourcedevice_ = 0u; + _impl_._has_bits_[0] &= ~0x00000010u; +} +inline uint32_t Envelope::_internal_sourcedevice() const { + return _impl_.sourcedevice_; +} +inline uint32_t Envelope::sourcedevice() const { + // @@protoc_insertion_point(field_get:SessionProtos.Envelope.sourceDevice) + return _internal_sourcedevice(); +} +inline void Envelope::_internal_set_sourcedevice(uint32_t value) { + _impl_._has_bits_[0] |= 0x00000010u; + _impl_.sourcedevice_ = value; +} +inline void Envelope::set_sourcedevice(uint32_t value) { + _internal_set_sourcedevice(value); + // @@protoc_insertion_point(field_set:SessionProtos.Envelope.sourceDevice) +} + +// required uint64 timestamp = 5; +inline bool Envelope::_internal_has_timestamp() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool Envelope::has_timestamp() const { + return _internal_has_timestamp(); +} +inline void Envelope::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline uint64_t Envelope::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t Envelope::timestamp() const { + // @@protoc_insertion_point(field_get:SessionProtos.Envelope.timestamp) + return _internal_timestamp(); +} +inline void Envelope::_internal_set_timestamp(uint64_t value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.timestamp_ = value; +} +inline void Envelope::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:SessionProtos.Envelope.timestamp) +} + +// optional bytes content = 8; +inline bool Envelope::_internal_has_content() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool Envelope::has_content() const { + return _internal_has_content(); +} +inline void Envelope::clear_content() { + _impl_.content_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& Envelope::content() const { + // @@protoc_insertion_point(field_get:SessionProtos.Envelope.content) + return _internal_content(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void Envelope::set_content(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.content_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.Envelope.content) +} +inline std::string* Envelope::mutable_content() { + std::string* _s = _internal_mutable_content(); + // @@protoc_insertion_point(field_mutable:SessionProtos.Envelope.content) + return _s; +} +inline const std::string& Envelope::_internal_content() const { + return _impl_.content_.Get(); +} +inline void Envelope::_internal_set_content(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.content_.Set(value, GetArenaForAllocation()); +} +inline std::string* Envelope::_internal_mutable_content() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.content_.Mutable(GetArenaForAllocation()); +} +inline std::string* Envelope::release_content() { + // @@protoc_insertion_point(field_release:SessionProtos.Envelope.content) + if (!_internal_has_content()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.content_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.content_.IsDefault()) { + _impl_.content_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void Envelope::set_allocated_content(std::string* content) { + if (content != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.content_.SetAllocated(content, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.content_.IsDefault()) { + _impl_.content_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.Envelope.content) +} + +// optional uint64 serverTimestamp = 10; +inline bool Envelope::_internal_has_servertimestamp() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool Envelope::has_servertimestamp() const { + return _internal_has_servertimestamp(); +} +inline void Envelope::clear_servertimestamp() { + _impl_.servertimestamp_ = uint64_t{0u}; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline uint64_t Envelope::_internal_servertimestamp() const { + return _impl_.servertimestamp_; +} +inline uint64_t Envelope::servertimestamp() const { + // @@protoc_insertion_point(field_get:SessionProtos.Envelope.serverTimestamp) + return _internal_servertimestamp(); +} +inline void Envelope::_internal_set_servertimestamp(uint64_t value) { + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.servertimestamp_ = value; +} +inline void Envelope::set_servertimestamp(uint64_t value) { + _internal_set_servertimestamp(value); + // @@protoc_insertion_point(field_set:SessionProtos.Envelope.serverTimestamp) +} + +// ------------------------------------------------------------------- + +// TypingMessage + +// required uint64 timestamp = 1; +inline bool TypingMessage::_internal_has_timestamp() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool TypingMessage::has_timestamp() const { + return _internal_has_timestamp(); +} +inline void TypingMessage::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline uint64_t TypingMessage::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t TypingMessage::timestamp() const { + // @@protoc_insertion_point(field_get:SessionProtos.TypingMessage.timestamp) + return _internal_timestamp(); +} +inline void TypingMessage::_internal_set_timestamp(uint64_t value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.timestamp_ = value; +} +inline void TypingMessage::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:SessionProtos.TypingMessage.timestamp) +} + +// required .SessionProtos.TypingMessage.Action action = 2; +inline bool TypingMessage::_internal_has_action() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool TypingMessage::has_action() const { + return _internal_has_action(); +} +inline void TypingMessage::clear_action() { + _impl_.action_ = 0; + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline ::SessionProtos::TypingMessage_Action TypingMessage::_internal_action() const { + return static_cast< ::SessionProtos::TypingMessage_Action >(_impl_.action_); +} +inline ::SessionProtos::TypingMessage_Action TypingMessage::action() const { + // @@protoc_insertion_point(field_get:SessionProtos.TypingMessage.action) + return _internal_action(); +} +inline void TypingMessage::_internal_set_action(::SessionProtos::TypingMessage_Action value) { + assert(::SessionProtos::TypingMessage_Action_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.action_ = value; +} +inline void TypingMessage::set_action(::SessionProtos::TypingMessage_Action value) { + _internal_set_action(value); + // @@protoc_insertion_point(field_set:SessionProtos.TypingMessage.action) +} + +// ------------------------------------------------------------------- + +// UnsendRequest + +// required uint64 timestamp = 1; +inline bool UnsendRequest::_internal_has_timestamp() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool UnsendRequest::has_timestamp() const { + return _internal_has_timestamp(); +} +inline void UnsendRequest::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline uint64_t UnsendRequest::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t UnsendRequest::timestamp() const { + // @@protoc_insertion_point(field_get:SessionProtos.UnsendRequest.timestamp) + return _internal_timestamp(); +} +inline void UnsendRequest::_internal_set_timestamp(uint64_t value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.timestamp_ = value; +} +inline void UnsendRequest::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:SessionProtos.UnsendRequest.timestamp) +} + +// required string author = 2; +inline bool UnsendRequest::_internal_has_author() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool UnsendRequest::has_author() const { + return _internal_has_author(); +} +inline void UnsendRequest::clear_author() { + _impl_.author_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& UnsendRequest::author() const { + // @@protoc_insertion_point(field_get:SessionProtos.UnsendRequest.author) + return _internal_author(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void UnsendRequest::set_author(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.author_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.UnsendRequest.author) +} +inline std::string* UnsendRequest::mutable_author() { + std::string* _s = _internal_mutable_author(); + // @@protoc_insertion_point(field_mutable:SessionProtos.UnsendRequest.author) + return _s; +} +inline const std::string& UnsendRequest::_internal_author() const { + return _impl_.author_.Get(); +} +inline void UnsendRequest::_internal_set_author(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.author_.Set(value, GetArenaForAllocation()); +} +inline std::string* UnsendRequest::_internal_mutable_author() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.author_.Mutable(GetArenaForAllocation()); +} +inline std::string* UnsendRequest::release_author() { + // @@protoc_insertion_point(field_release:SessionProtos.UnsendRequest.author) + if (!_internal_has_author()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.author_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.author_.IsDefault()) { + _impl_.author_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void UnsendRequest::set_allocated_author(std::string* author) { + if (author != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.author_.SetAllocated(author, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.author_.IsDefault()) { + _impl_.author_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.UnsendRequest.author) +} + +// ------------------------------------------------------------------- + +// MessageRequestResponse + +// required bool isApproved = 1; +inline bool MessageRequestResponse::_internal_has_isapproved() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool MessageRequestResponse::has_isapproved() const { + return _internal_has_isapproved(); +} +inline void MessageRequestResponse::clear_isapproved() { + _impl_.isapproved_ = false; + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline bool MessageRequestResponse::_internal_isapproved() const { + return _impl_.isapproved_; +} +inline bool MessageRequestResponse::isapproved() const { + // @@protoc_insertion_point(field_get:SessionProtos.MessageRequestResponse.isApproved) + return _internal_isapproved(); +} +inline void MessageRequestResponse::_internal_set_isapproved(bool value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.isapproved_ = value; +} +inline void MessageRequestResponse::set_isapproved(bool value) { + _internal_set_isapproved(value); + // @@protoc_insertion_point(field_set:SessionProtos.MessageRequestResponse.isApproved) +} + +// optional bytes profileKey = 2; +inline bool MessageRequestResponse::_internal_has_profilekey() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool MessageRequestResponse::has_profilekey() const { + return _internal_has_profilekey(); +} +inline void MessageRequestResponse::clear_profilekey() { + _impl_.profilekey_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& MessageRequestResponse::profilekey() const { + // @@protoc_insertion_point(field_get:SessionProtos.MessageRequestResponse.profileKey) + return _internal_profilekey(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void MessageRequestResponse::set_profilekey(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.profilekey_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.MessageRequestResponse.profileKey) +} +inline std::string* MessageRequestResponse::mutable_profilekey() { + std::string* _s = _internal_mutable_profilekey(); + // @@protoc_insertion_point(field_mutable:SessionProtos.MessageRequestResponse.profileKey) + return _s; +} +inline const std::string& MessageRequestResponse::_internal_profilekey() const { + return _impl_.profilekey_.Get(); +} +inline void MessageRequestResponse::_internal_set_profilekey(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.profilekey_.Set(value, GetArenaForAllocation()); +} +inline std::string* MessageRequestResponse::_internal_mutable_profilekey() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.profilekey_.Mutable(GetArenaForAllocation()); +} +inline std::string* MessageRequestResponse::release_profilekey() { + // @@protoc_insertion_point(field_release:SessionProtos.MessageRequestResponse.profileKey) + if (!_internal_has_profilekey()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.profilekey_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilekey_.IsDefault()) { + _impl_.profilekey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void MessageRequestResponse::set_allocated_profilekey(std::string* profilekey) { + if (profilekey != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.profilekey_.SetAllocated(profilekey, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilekey_.IsDefault()) { + _impl_.profilekey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.MessageRequestResponse.profileKey) +} + +// optional .SessionProtos.LokiProfile profile = 3; +inline bool MessageRequestResponse::_internal_has_profile() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || _impl_.profile_ != nullptr); + return value; +} +inline bool MessageRequestResponse::has_profile() const { + return _internal_has_profile(); +} +inline void MessageRequestResponse::clear_profile() { + if (_impl_.profile_ != nullptr) _impl_.profile_->Clear(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const ::SessionProtos::LokiProfile& MessageRequestResponse::_internal_profile() const { + const ::SessionProtos::LokiProfile* p = _impl_.profile_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_LokiProfile_default_instance_); +} +inline const ::SessionProtos::LokiProfile& MessageRequestResponse::profile() const { + // @@protoc_insertion_point(field_get:SessionProtos.MessageRequestResponse.profile) + return _internal_profile(); +} +inline void MessageRequestResponse::unsafe_arena_set_allocated_profile( + ::SessionProtos::LokiProfile* profile) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.profile_); + } + _impl_.profile_ = profile; + if (profile) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.MessageRequestResponse.profile) +} +inline ::SessionProtos::LokiProfile* MessageRequestResponse::release_profile() { + _impl_._has_bits_[0] &= ~0x00000002u; + ::SessionProtos::LokiProfile* temp = _impl_.profile_; + _impl_.profile_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::LokiProfile* MessageRequestResponse::unsafe_arena_release_profile() { + // @@protoc_insertion_point(field_release:SessionProtos.MessageRequestResponse.profile) + _impl_._has_bits_[0] &= ~0x00000002u; + ::SessionProtos::LokiProfile* temp = _impl_.profile_; + _impl_.profile_ = nullptr; + return temp; +} +inline ::SessionProtos::LokiProfile* MessageRequestResponse::_internal_mutable_profile() { + _impl_._has_bits_[0] |= 0x00000002u; + if (_impl_.profile_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::LokiProfile>(GetArenaForAllocation()); + _impl_.profile_ = p; + } + return _impl_.profile_; +} +inline ::SessionProtos::LokiProfile* MessageRequestResponse::mutable_profile() { + ::SessionProtos::LokiProfile* _msg = _internal_mutable_profile(); + // @@protoc_insertion_point(field_mutable:SessionProtos.MessageRequestResponse.profile) + return _msg; +} +inline void MessageRequestResponse::set_allocated_profile(::SessionProtos::LokiProfile* profile) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.profile_; + } + if (profile) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(profile); + if (message_arena != submessage_arena) { + profile = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, profile, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.profile_ = profile; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.MessageRequestResponse.profile) +} + +// ------------------------------------------------------------------- + +// Content + +// optional .SessionProtos.DataMessage dataMessage = 1; +inline bool Content::_internal_has_datamessage() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || _impl_.datamessage_ != nullptr); + return value; +} +inline bool Content::has_datamessage() const { + return _internal_has_datamessage(); +} +inline void Content::clear_datamessage() { + if (_impl_.datamessage_ != nullptr) _impl_.datamessage_->Clear(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const ::SessionProtos::DataMessage& Content::_internal_datamessage() const { + const ::SessionProtos::DataMessage* p = _impl_.datamessage_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_DataMessage_default_instance_); +} +inline const ::SessionProtos::DataMessage& Content::datamessage() const { + // @@protoc_insertion_point(field_get:SessionProtos.Content.dataMessage) + return _internal_datamessage(); +} +inline void Content::unsafe_arena_set_allocated_datamessage( + ::SessionProtos::DataMessage* datamessage) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.datamessage_); + } + _impl_.datamessage_ = datamessage; + if (datamessage) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.Content.dataMessage) +} +inline ::SessionProtos::DataMessage* Content::release_datamessage() { + _impl_._has_bits_[0] &= ~0x00000001u; + ::SessionProtos::DataMessage* temp = _impl_.datamessage_; + _impl_.datamessage_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::DataMessage* Content::unsafe_arena_release_datamessage() { + // @@protoc_insertion_point(field_release:SessionProtos.Content.dataMessage) + _impl_._has_bits_[0] &= ~0x00000001u; + ::SessionProtos::DataMessage* temp = _impl_.datamessage_; + _impl_.datamessage_ = nullptr; + return temp; +} +inline ::SessionProtos::DataMessage* Content::_internal_mutable_datamessage() { + _impl_._has_bits_[0] |= 0x00000001u; + if (_impl_.datamessage_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::DataMessage>(GetArenaForAllocation()); + _impl_.datamessage_ = p; + } + return _impl_.datamessage_; +} +inline ::SessionProtos::DataMessage* Content::mutable_datamessage() { + ::SessionProtos::DataMessage* _msg = _internal_mutable_datamessage(); + // @@protoc_insertion_point(field_mutable:SessionProtos.Content.dataMessage) + return _msg; +} +inline void Content::set_allocated_datamessage(::SessionProtos::DataMessage* datamessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.datamessage_; + } + if (datamessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(datamessage); + if (message_arena != submessage_arena) { + datamessage = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, datamessage, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.datamessage_ = datamessage; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.Content.dataMessage) +} + +// optional .SessionProtos.CallMessage callMessage = 3; +inline bool Content::_internal_has_callmessage() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || _impl_.callmessage_ != nullptr); + return value; +} +inline bool Content::has_callmessage() const { + return _internal_has_callmessage(); +} +inline void Content::clear_callmessage() { + if (_impl_.callmessage_ != nullptr) _impl_.callmessage_->Clear(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const ::SessionProtos::CallMessage& Content::_internal_callmessage() const { + const ::SessionProtos::CallMessage* p = _impl_.callmessage_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_CallMessage_default_instance_); +} +inline const ::SessionProtos::CallMessage& Content::callmessage() const { + // @@protoc_insertion_point(field_get:SessionProtos.Content.callMessage) + return _internal_callmessage(); +} +inline void Content::unsafe_arena_set_allocated_callmessage( + ::SessionProtos::CallMessage* callmessage) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.callmessage_); + } + _impl_.callmessage_ = callmessage; + if (callmessage) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.Content.callMessage) +} +inline ::SessionProtos::CallMessage* Content::release_callmessage() { + _impl_._has_bits_[0] &= ~0x00000002u; + ::SessionProtos::CallMessage* temp = _impl_.callmessage_; + _impl_.callmessage_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::CallMessage* Content::unsafe_arena_release_callmessage() { + // @@protoc_insertion_point(field_release:SessionProtos.Content.callMessage) + _impl_._has_bits_[0] &= ~0x00000002u; + ::SessionProtos::CallMessage* temp = _impl_.callmessage_; + _impl_.callmessage_ = nullptr; + return temp; +} +inline ::SessionProtos::CallMessage* Content::_internal_mutable_callmessage() { + _impl_._has_bits_[0] |= 0x00000002u; + if (_impl_.callmessage_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::CallMessage>(GetArenaForAllocation()); + _impl_.callmessage_ = p; + } + return _impl_.callmessage_; +} +inline ::SessionProtos::CallMessage* Content::mutable_callmessage() { + ::SessionProtos::CallMessage* _msg = _internal_mutable_callmessage(); + // @@protoc_insertion_point(field_mutable:SessionProtos.Content.callMessage) + return _msg; +} +inline void Content::set_allocated_callmessage(::SessionProtos::CallMessage* callmessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.callmessage_; + } + if (callmessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(callmessage); + if (message_arena != submessage_arena) { + callmessage = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, callmessage, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.callmessage_ = callmessage; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.Content.callMessage) +} + +// optional .SessionProtos.ReceiptMessage receiptMessage = 5; +inline bool Content::_internal_has_receiptmessage() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + PROTOBUF_ASSUME(!value || _impl_.receiptmessage_ != nullptr); + return value; +} +inline bool Content::has_receiptmessage() const { + return _internal_has_receiptmessage(); +} +inline void Content::clear_receiptmessage() { + if (_impl_.receiptmessage_ != nullptr) _impl_.receiptmessage_->Clear(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const ::SessionProtos::ReceiptMessage& Content::_internal_receiptmessage() const { + const ::SessionProtos::ReceiptMessage* p = _impl_.receiptmessage_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_ReceiptMessage_default_instance_); +} +inline const ::SessionProtos::ReceiptMessage& Content::receiptmessage() const { + // @@protoc_insertion_point(field_get:SessionProtos.Content.receiptMessage) + return _internal_receiptmessage(); +} +inline void Content::unsafe_arena_set_allocated_receiptmessage( + ::SessionProtos::ReceiptMessage* receiptmessage) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.receiptmessage_); + } + _impl_.receiptmessage_ = receiptmessage; + if (receiptmessage) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.Content.receiptMessage) +} +inline ::SessionProtos::ReceiptMessage* Content::release_receiptmessage() { + _impl_._has_bits_[0] &= ~0x00000004u; + ::SessionProtos::ReceiptMessage* temp = _impl_.receiptmessage_; + _impl_.receiptmessage_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::ReceiptMessage* Content::unsafe_arena_release_receiptmessage() { + // @@protoc_insertion_point(field_release:SessionProtos.Content.receiptMessage) + _impl_._has_bits_[0] &= ~0x00000004u; + ::SessionProtos::ReceiptMessage* temp = _impl_.receiptmessage_; + _impl_.receiptmessage_ = nullptr; + return temp; +} +inline ::SessionProtos::ReceiptMessage* Content::_internal_mutable_receiptmessage() { + _impl_._has_bits_[0] |= 0x00000004u; + if (_impl_.receiptmessage_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::ReceiptMessage>(GetArenaForAllocation()); + _impl_.receiptmessage_ = p; + } + return _impl_.receiptmessage_; +} +inline ::SessionProtos::ReceiptMessage* Content::mutable_receiptmessage() { + ::SessionProtos::ReceiptMessage* _msg = _internal_mutable_receiptmessage(); + // @@protoc_insertion_point(field_mutable:SessionProtos.Content.receiptMessage) + return _msg; +} +inline void Content::set_allocated_receiptmessage(::SessionProtos::ReceiptMessage* receiptmessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.receiptmessage_; + } + if (receiptmessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(receiptmessage); + if (message_arena != submessage_arena) { + receiptmessage = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, receiptmessage, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.receiptmessage_ = receiptmessage; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.Content.receiptMessage) +} + +// optional .SessionProtos.TypingMessage typingMessage = 6; +inline bool Content::_internal_has_typingmessage() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + PROTOBUF_ASSUME(!value || _impl_.typingmessage_ != nullptr); + return value; +} +inline bool Content::has_typingmessage() const { + return _internal_has_typingmessage(); +} +inline void Content::clear_typingmessage() { + if (_impl_.typingmessage_ != nullptr) _impl_.typingmessage_->Clear(); + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline const ::SessionProtos::TypingMessage& Content::_internal_typingmessage() const { + const ::SessionProtos::TypingMessage* p = _impl_.typingmessage_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_TypingMessage_default_instance_); +} +inline const ::SessionProtos::TypingMessage& Content::typingmessage() const { + // @@protoc_insertion_point(field_get:SessionProtos.Content.typingMessage) + return _internal_typingmessage(); +} +inline void Content::unsafe_arena_set_allocated_typingmessage( + ::SessionProtos::TypingMessage* typingmessage) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.typingmessage_); + } + _impl_.typingmessage_ = typingmessage; + if (typingmessage) { + _impl_._has_bits_[0] |= 0x00000008u; + } else { + _impl_._has_bits_[0] &= ~0x00000008u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.Content.typingMessage) +} +inline ::SessionProtos::TypingMessage* Content::release_typingmessage() { + _impl_._has_bits_[0] &= ~0x00000008u; + ::SessionProtos::TypingMessage* temp = _impl_.typingmessage_; + _impl_.typingmessage_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::TypingMessage* Content::unsafe_arena_release_typingmessage() { + // @@protoc_insertion_point(field_release:SessionProtos.Content.typingMessage) + _impl_._has_bits_[0] &= ~0x00000008u; + ::SessionProtos::TypingMessage* temp = _impl_.typingmessage_; + _impl_.typingmessage_ = nullptr; + return temp; +} +inline ::SessionProtos::TypingMessage* Content::_internal_mutable_typingmessage() { + _impl_._has_bits_[0] |= 0x00000008u; + if (_impl_.typingmessage_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::TypingMessage>(GetArenaForAllocation()); + _impl_.typingmessage_ = p; + } + return _impl_.typingmessage_; +} +inline ::SessionProtos::TypingMessage* Content::mutable_typingmessage() { + ::SessionProtos::TypingMessage* _msg = _internal_mutable_typingmessage(); + // @@protoc_insertion_point(field_mutable:SessionProtos.Content.typingMessage) + return _msg; +} +inline void Content::set_allocated_typingmessage(::SessionProtos::TypingMessage* typingmessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.typingmessage_; + } + if (typingmessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(typingmessage); + if (message_arena != submessage_arena) { + typingmessage = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, typingmessage, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000008u; + } else { + _impl_._has_bits_[0] &= ~0x00000008u; + } + _impl_.typingmessage_ = typingmessage; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.Content.typingMessage) +} + +// optional .SessionProtos.ConfigurationMessage configurationMessage = 7; +inline bool Content::_internal_has_configurationmessage() const { + bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; + PROTOBUF_ASSUME(!value || _impl_.configurationmessage_ != nullptr); + return value; +} +inline bool Content::has_configurationmessage() const { + return _internal_has_configurationmessage(); +} +inline void Content::clear_configurationmessage() { + if (_impl_.configurationmessage_ != nullptr) _impl_.configurationmessage_->Clear(); + _impl_._has_bits_[0] &= ~0x00000010u; +} +inline const ::SessionProtos::ConfigurationMessage& Content::_internal_configurationmessage() const { + const ::SessionProtos::ConfigurationMessage* p = _impl_.configurationmessage_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_ConfigurationMessage_default_instance_); +} +inline const ::SessionProtos::ConfigurationMessage& Content::configurationmessage() const { + // @@protoc_insertion_point(field_get:SessionProtos.Content.configurationMessage) + return _internal_configurationmessage(); +} +inline void Content::unsafe_arena_set_allocated_configurationmessage( + ::SessionProtos::ConfigurationMessage* configurationmessage) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.configurationmessage_); + } + _impl_.configurationmessage_ = configurationmessage; + if (configurationmessage) { + _impl_._has_bits_[0] |= 0x00000010u; + } else { + _impl_._has_bits_[0] &= ~0x00000010u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.Content.configurationMessage) +} +inline ::SessionProtos::ConfigurationMessage* Content::release_configurationmessage() { + _impl_._has_bits_[0] &= ~0x00000010u; + ::SessionProtos::ConfigurationMessage* temp = _impl_.configurationmessage_; + _impl_.configurationmessage_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::ConfigurationMessage* Content::unsafe_arena_release_configurationmessage() { + // @@protoc_insertion_point(field_release:SessionProtos.Content.configurationMessage) + _impl_._has_bits_[0] &= ~0x00000010u; + ::SessionProtos::ConfigurationMessage* temp = _impl_.configurationmessage_; + _impl_.configurationmessage_ = nullptr; + return temp; +} +inline ::SessionProtos::ConfigurationMessage* Content::_internal_mutable_configurationmessage() { + _impl_._has_bits_[0] |= 0x00000010u; + if (_impl_.configurationmessage_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::ConfigurationMessage>(GetArenaForAllocation()); + _impl_.configurationmessage_ = p; + } + return _impl_.configurationmessage_; +} +inline ::SessionProtos::ConfigurationMessage* Content::mutable_configurationmessage() { + ::SessionProtos::ConfigurationMessage* _msg = _internal_mutable_configurationmessage(); + // @@protoc_insertion_point(field_mutable:SessionProtos.Content.configurationMessage) + return _msg; +} +inline void Content::set_allocated_configurationmessage(::SessionProtos::ConfigurationMessage* configurationmessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.configurationmessage_; + } + if (configurationmessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(configurationmessage); + if (message_arena != submessage_arena) { + configurationmessage = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, configurationmessage, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000010u; + } else { + _impl_._has_bits_[0] &= ~0x00000010u; + } + _impl_.configurationmessage_ = configurationmessage; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.Content.configurationMessage) +} + +// optional .SessionProtos.DataExtractionNotification dataExtractionNotification = 8; +inline bool Content::_internal_has_dataextractionnotification() const { + bool value = (_impl_._has_bits_[0] & 0x00000020u) != 0; + PROTOBUF_ASSUME(!value || _impl_.dataextractionnotification_ != nullptr); + return value; +} +inline bool Content::has_dataextractionnotification() const { + return _internal_has_dataextractionnotification(); +} +inline void Content::clear_dataextractionnotification() { + if (_impl_.dataextractionnotification_ != nullptr) _impl_.dataextractionnotification_->Clear(); + _impl_._has_bits_[0] &= ~0x00000020u; +} +inline const ::SessionProtos::DataExtractionNotification& Content::_internal_dataextractionnotification() const { + const ::SessionProtos::DataExtractionNotification* p = _impl_.dataextractionnotification_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_DataExtractionNotification_default_instance_); +} +inline const ::SessionProtos::DataExtractionNotification& Content::dataextractionnotification() const { + // @@protoc_insertion_point(field_get:SessionProtos.Content.dataExtractionNotification) + return _internal_dataextractionnotification(); +} +inline void Content::unsafe_arena_set_allocated_dataextractionnotification( + ::SessionProtos::DataExtractionNotification* dataextractionnotification) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.dataextractionnotification_); + } + _impl_.dataextractionnotification_ = dataextractionnotification; + if (dataextractionnotification) { + _impl_._has_bits_[0] |= 0x00000020u; + } else { + _impl_._has_bits_[0] &= ~0x00000020u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.Content.dataExtractionNotification) +} +inline ::SessionProtos::DataExtractionNotification* Content::release_dataextractionnotification() { + _impl_._has_bits_[0] &= ~0x00000020u; + ::SessionProtos::DataExtractionNotification* temp = _impl_.dataextractionnotification_; + _impl_.dataextractionnotification_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::DataExtractionNotification* Content::unsafe_arena_release_dataextractionnotification() { + // @@protoc_insertion_point(field_release:SessionProtos.Content.dataExtractionNotification) + _impl_._has_bits_[0] &= ~0x00000020u; + ::SessionProtos::DataExtractionNotification* temp = _impl_.dataextractionnotification_; + _impl_.dataextractionnotification_ = nullptr; + return temp; +} +inline ::SessionProtos::DataExtractionNotification* Content::_internal_mutable_dataextractionnotification() { + _impl_._has_bits_[0] |= 0x00000020u; + if (_impl_.dataextractionnotification_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::DataExtractionNotification>(GetArenaForAllocation()); + _impl_.dataextractionnotification_ = p; + } + return _impl_.dataextractionnotification_; +} +inline ::SessionProtos::DataExtractionNotification* Content::mutable_dataextractionnotification() { + ::SessionProtos::DataExtractionNotification* _msg = _internal_mutable_dataextractionnotification(); + // @@protoc_insertion_point(field_mutable:SessionProtos.Content.dataExtractionNotification) + return _msg; +} +inline void Content::set_allocated_dataextractionnotification(::SessionProtos::DataExtractionNotification* dataextractionnotification) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.dataextractionnotification_; + } + if (dataextractionnotification) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(dataextractionnotification); + if (message_arena != submessage_arena) { + dataextractionnotification = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, dataextractionnotification, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000020u; + } else { + _impl_._has_bits_[0] &= ~0x00000020u; + } + _impl_.dataextractionnotification_ = dataextractionnotification; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.Content.dataExtractionNotification) +} + +// optional .SessionProtos.UnsendRequest unsendRequest = 9; +inline bool Content::_internal_has_unsendrequest() const { + bool value = (_impl_._has_bits_[0] & 0x00000040u) != 0; + PROTOBUF_ASSUME(!value || _impl_.unsendrequest_ != nullptr); + return value; +} +inline bool Content::has_unsendrequest() const { + return _internal_has_unsendrequest(); +} +inline void Content::clear_unsendrequest() { + if (_impl_.unsendrequest_ != nullptr) _impl_.unsendrequest_->Clear(); + _impl_._has_bits_[0] &= ~0x00000040u; +} +inline const ::SessionProtos::UnsendRequest& Content::_internal_unsendrequest() const { + const ::SessionProtos::UnsendRequest* p = _impl_.unsendrequest_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_UnsendRequest_default_instance_); +} +inline const ::SessionProtos::UnsendRequest& Content::unsendrequest() const { + // @@protoc_insertion_point(field_get:SessionProtos.Content.unsendRequest) + return _internal_unsendrequest(); +} +inline void Content::unsafe_arena_set_allocated_unsendrequest( + ::SessionProtos::UnsendRequest* unsendrequest) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.unsendrequest_); + } + _impl_.unsendrequest_ = unsendrequest; + if (unsendrequest) { + _impl_._has_bits_[0] |= 0x00000040u; + } else { + _impl_._has_bits_[0] &= ~0x00000040u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.Content.unsendRequest) +} +inline ::SessionProtos::UnsendRequest* Content::release_unsendrequest() { + _impl_._has_bits_[0] &= ~0x00000040u; + ::SessionProtos::UnsendRequest* temp = _impl_.unsendrequest_; + _impl_.unsendrequest_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::UnsendRequest* Content::unsafe_arena_release_unsendrequest() { + // @@protoc_insertion_point(field_release:SessionProtos.Content.unsendRequest) + _impl_._has_bits_[0] &= ~0x00000040u; + ::SessionProtos::UnsendRequest* temp = _impl_.unsendrequest_; + _impl_.unsendrequest_ = nullptr; + return temp; +} +inline ::SessionProtos::UnsendRequest* Content::_internal_mutable_unsendrequest() { + _impl_._has_bits_[0] |= 0x00000040u; + if (_impl_.unsendrequest_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::UnsendRequest>(GetArenaForAllocation()); + _impl_.unsendrequest_ = p; + } + return _impl_.unsendrequest_; +} +inline ::SessionProtos::UnsendRequest* Content::mutable_unsendrequest() { + ::SessionProtos::UnsendRequest* _msg = _internal_mutable_unsendrequest(); + // @@protoc_insertion_point(field_mutable:SessionProtos.Content.unsendRequest) + return _msg; +} +inline void Content::set_allocated_unsendrequest(::SessionProtos::UnsendRequest* unsendrequest) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.unsendrequest_; + } + if (unsendrequest) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(unsendrequest); + if (message_arena != submessage_arena) { + unsendrequest = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, unsendrequest, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000040u; + } else { + _impl_._has_bits_[0] &= ~0x00000040u; + } + _impl_.unsendrequest_ = unsendrequest; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.Content.unsendRequest) +} + +// optional .SessionProtos.MessageRequestResponse messageRequestResponse = 10; +inline bool Content::_internal_has_messagerequestresponse() const { + bool value = (_impl_._has_bits_[0] & 0x00000080u) != 0; + PROTOBUF_ASSUME(!value || _impl_.messagerequestresponse_ != nullptr); + return value; +} +inline bool Content::has_messagerequestresponse() const { + return _internal_has_messagerequestresponse(); +} +inline void Content::clear_messagerequestresponse() { + if (_impl_.messagerequestresponse_ != nullptr) _impl_.messagerequestresponse_->Clear(); + _impl_._has_bits_[0] &= ~0x00000080u; +} +inline const ::SessionProtos::MessageRequestResponse& Content::_internal_messagerequestresponse() const { + const ::SessionProtos::MessageRequestResponse* p = _impl_.messagerequestresponse_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_MessageRequestResponse_default_instance_); +} +inline const ::SessionProtos::MessageRequestResponse& Content::messagerequestresponse() const { + // @@protoc_insertion_point(field_get:SessionProtos.Content.messageRequestResponse) + return _internal_messagerequestresponse(); +} +inline void Content::unsafe_arena_set_allocated_messagerequestresponse( + ::SessionProtos::MessageRequestResponse* messagerequestresponse) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.messagerequestresponse_); + } + _impl_.messagerequestresponse_ = messagerequestresponse; + if (messagerequestresponse) { + _impl_._has_bits_[0] |= 0x00000080u; + } else { + _impl_._has_bits_[0] &= ~0x00000080u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.Content.messageRequestResponse) +} +inline ::SessionProtos::MessageRequestResponse* Content::release_messagerequestresponse() { + _impl_._has_bits_[0] &= ~0x00000080u; + ::SessionProtos::MessageRequestResponse* temp = _impl_.messagerequestresponse_; + _impl_.messagerequestresponse_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::MessageRequestResponse* Content::unsafe_arena_release_messagerequestresponse() { + // @@protoc_insertion_point(field_release:SessionProtos.Content.messageRequestResponse) + _impl_._has_bits_[0] &= ~0x00000080u; + ::SessionProtos::MessageRequestResponse* temp = _impl_.messagerequestresponse_; + _impl_.messagerequestresponse_ = nullptr; + return temp; +} +inline ::SessionProtos::MessageRequestResponse* Content::_internal_mutable_messagerequestresponse() { + _impl_._has_bits_[0] |= 0x00000080u; + if (_impl_.messagerequestresponse_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::MessageRequestResponse>(GetArenaForAllocation()); + _impl_.messagerequestresponse_ = p; + } + return _impl_.messagerequestresponse_; +} +inline ::SessionProtos::MessageRequestResponse* Content::mutable_messagerequestresponse() { + ::SessionProtos::MessageRequestResponse* _msg = _internal_mutable_messagerequestresponse(); + // @@protoc_insertion_point(field_mutable:SessionProtos.Content.messageRequestResponse) + return _msg; +} +inline void Content::set_allocated_messagerequestresponse(::SessionProtos::MessageRequestResponse* messagerequestresponse) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.messagerequestresponse_; + } + if (messagerequestresponse) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(messagerequestresponse); + if (message_arena != submessage_arena) { + messagerequestresponse = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, messagerequestresponse, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000080u; + } else { + _impl_._has_bits_[0] &= ~0x00000080u; + } + _impl_.messagerequestresponse_ = messagerequestresponse; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.Content.messageRequestResponse) +} + +// optional .SessionProtos.SharedConfigMessage sharedConfigMessage = 11; +inline bool Content::_internal_has_sharedconfigmessage() const { + bool value = (_impl_._has_bits_[0] & 0x00000100u) != 0; + PROTOBUF_ASSUME(!value || _impl_.sharedconfigmessage_ != nullptr); + return value; +} +inline bool Content::has_sharedconfigmessage() const { + return _internal_has_sharedconfigmessage(); +} +inline void Content::clear_sharedconfigmessage() { + if (_impl_.sharedconfigmessage_ != nullptr) _impl_.sharedconfigmessage_->Clear(); + _impl_._has_bits_[0] &= ~0x00000100u; +} +inline const ::SessionProtos::SharedConfigMessage& Content::_internal_sharedconfigmessage() const { + const ::SessionProtos::SharedConfigMessage* p = _impl_.sharedconfigmessage_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_SharedConfigMessage_default_instance_); +} +inline const ::SessionProtos::SharedConfigMessage& Content::sharedconfigmessage() const { + // @@protoc_insertion_point(field_get:SessionProtos.Content.sharedConfigMessage) + return _internal_sharedconfigmessage(); +} +inline void Content::unsafe_arena_set_allocated_sharedconfigmessage( + ::SessionProtos::SharedConfigMessage* sharedconfigmessage) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.sharedconfigmessage_); + } + _impl_.sharedconfigmessage_ = sharedconfigmessage; + if (sharedconfigmessage) { + _impl_._has_bits_[0] |= 0x00000100u; + } else { + _impl_._has_bits_[0] &= ~0x00000100u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.Content.sharedConfigMessage) +} +inline ::SessionProtos::SharedConfigMessage* Content::release_sharedconfigmessage() { + _impl_._has_bits_[0] &= ~0x00000100u; + ::SessionProtos::SharedConfigMessage* temp = _impl_.sharedconfigmessage_; + _impl_.sharedconfigmessage_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::SharedConfigMessage* Content::unsafe_arena_release_sharedconfigmessage() { + // @@protoc_insertion_point(field_release:SessionProtos.Content.sharedConfigMessage) + _impl_._has_bits_[0] &= ~0x00000100u; + ::SessionProtos::SharedConfigMessage* temp = _impl_.sharedconfigmessage_; + _impl_.sharedconfigmessage_ = nullptr; + return temp; +} +inline ::SessionProtos::SharedConfigMessage* Content::_internal_mutable_sharedconfigmessage() { + _impl_._has_bits_[0] |= 0x00000100u; + if (_impl_.sharedconfigmessage_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::SharedConfigMessage>(GetArenaForAllocation()); + _impl_.sharedconfigmessage_ = p; + } + return _impl_.sharedconfigmessage_; +} +inline ::SessionProtos::SharedConfigMessage* Content::mutable_sharedconfigmessage() { + ::SessionProtos::SharedConfigMessage* _msg = _internal_mutable_sharedconfigmessage(); + // @@protoc_insertion_point(field_mutable:SessionProtos.Content.sharedConfigMessage) + return _msg; +} +inline void Content::set_allocated_sharedconfigmessage(::SessionProtos::SharedConfigMessage* sharedconfigmessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.sharedconfigmessage_; + } + if (sharedconfigmessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(sharedconfigmessage); + if (message_arena != submessage_arena) { + sharedconfigmessage = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, sharedconfigmessage, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000100u; + } else { + _impl_._has_bits_[0] &= ~0x00000100u; + } + _impl_.sharedconfigmessage_ = sharedconfigmessage; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.Content.sharedConfigMessage) +} + +// ------------------------------------------------------------------- + +// CallMessage + +// required .SessionProtos.CallMessage.Type type = 1; +inline bool CallMessage::_internal_has_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool CallMessage::has_type() const { + return _internal_has_type(); +} +inline void CallMessage::clear_type() { + _impl_.type_ = 6; + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline ::SessionProtos::CallMessage_Type CallMessage::_internal_type() const { + return static_cast< ::SessionProtos::CallMessage_Type >(_impl_.type_); +} +inline ::SessionProtos::CallMessage_Type CallMessage::type() const { + // @@protoc_insertion_point(field_get:SessionProtos.CallMessage.type) + return _internal_type(); +} +inline void CallMessage::_internal_set_type(::SessionProtos::CallMessage_Type value) { + assert(::SessionProtos::CallMessage_Type_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.type_ = value; +} +inline void CallMessage::set_type(::SessionProtos::CallMessage_Type value) { + _internal_set_type(value); + // @@protoc_insertion_point(field_set:SessionProtos.CallMessage.type) +} + +// repeated string sdps = 2; +inline int CallMessage::_internal_sdps_size() const { + return _impl_.sdps_.size(); +} +inline int CallMessage::sdps_size() const { + return _internal_sdps_size(); +} +inline void CallMessage::clear_sdps() { + _impl_.sdps_.Clear(); +} +inline std::string* CallMessage::add_sdps() { + std::string* _s = _internal_add_sdps(); + // @@protoc_insertion_point(field_add_mutable:SessionProtos.CallMessage.sdps) + return _s; +} +inline const std::string& CallMessage::_internal_sdps(int index) const { + return _impl_.sdps_.Get(index); +} +inline const std::string& CallMessage::sdps(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.CallMessage.sdps) + return _internal_sdps(index); +} +inline std::string* CallMessage::mutable_sdps(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.CallMessage.sdps) + return _impl_.sdps_.Mutable(index); +} +inline void CallMessage::set_sdps(int index, const std::string& value) { + _impl_.sdps_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set:SessionProtos.CallMessage.sdps) +} +inline void CallMessage::set_sdps(int index, std::string&& value) { + _impl_.sdps_.Mutable(index)->assign(std::move(value)); + // @@protoc_insertion_point(field_set:SessionProtos.CallMessage.sdps) +} +inline void CallMessage::set_sdps(int index, const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.sdps_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set_char:SessionProtos.CallMessage.sdps) +} +inline void CallMessage::set_sdps(int index, const char* value, size_t size) { + _impl_.sdps_.Mutable(index)->assign( + reinterpret_cast(value), size); + // @@protoc_insertion_point(field_set_pointer:SessionProtos.CallMessage.sdps) +} +inline std::string* CallMessage::_internal_add_sdps() { + return _impl_.sdps_.Add(); +} +inline void CallMessage::add_sdps(const std::string& value) { + _impl_.sdps_.Add()->assign(value); + // @@protoc_insertion_point(field_add:SessionProtos.CallMessage.sdps) +} +inline void CallMessage::add_sdps(std::string&& value) { + _impl_.sdps_.Add(std::move(value)); + // @@protoc_insertion_point(field_add:SessionProtos.CallMessage.sdps) +} +inline void CallMessage::add_sdps(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.sdps_.Add()->assign(value); + // @@protoc_insertion_point(field_add_char:SessionProtos.CallMessage.sdps) +} +inline void CallMessage::add_sdps(const char* value, size_t size) { + _impl_.sdps_.Add()->assign(reinterpret_cast(value), size); + // @@protoc_insertion_point(field_add_pointer:SessionProtos.CallMessage.sdps) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& +CallMessage::sdps() const { + // @@protoc_insertion_point(field_list:SessionProtos.CallMessage.sdps) + return _impl_.sdps_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* +CallMessage::mutable_sdps() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.CallMessage.sdps) + return &_impl_.sdps_; +} + +// repeated uint32 sdpMLineIndexes = 3; +inline int CallMessage::_internal_sdpmlineindexes_size() const { + return _impl_.sdpmlineindexes_.size(); +} +inline int CallMessage::sdpmlineindexes_size() const { + return _internal_sdpmlineindexes_size(); +} +inline void CallMessage::clear_sdpmlineindexes() { + _impl_.sdpmlineindexes_.Clear(); +} +inline uint32_t CallMessage::_internal_sdpmlineindexes(int index) const { + return _impl_.sdpmlineindexes_.Get(index); +} +inline uint32_t CallMessage::sdpmlineindexes(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.CallMessage.sdpMLineIndexes) + return _internal_sdpmlineindexes(index); +} +inline void CallMessage::set_sdpmlineindexes(int index, uint32_t value) { + _impl_.sdpmlineindexes_.Set(index, value); + // @@protoc_insertion_point(field_set:SessionProtos.CallMessage.sdpMLineIndexes) +} +inline void CallMessage::_internal_add_sdpmlineindexes(uint32_t value) { + _impl_.sdpmlineindexes_.Add(value); +} +inline void CallMessage::add_sdpmlineindexes(uint32_t value) { + _internal_add_sdpmlineindexes(value); + // @@protoc_insertion_point(field_add:SessionProtos.CallMessage.sdpMLineIndexes) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint32_t >& +CallMessage::_internal_sdpmlineindexes() const { + return _impl_.sdpmlineindexes_; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint32_t >& +CallMessage::sdpmlineindexes() const { + // @@protoc_insertion_point(field_list:SessionProtos.CallMessage.sdpMLineIndexes) + return _internal_sdpmlineindexes(); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint32_t >* +CallMessage::_internal_mutable_sdpmlineindexes() { + return &_impl_.sdpmlineindexes_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint32_t >* +CallMessage::mutable_sdpmlineindexes() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.CallMessage.sdpMLineIndexes) + return _internal_mutable_sdpmlineindexes(); +} + +// repeated string sdpMids = 4; +inline int CallMessage::_internal_sdpmids_size() const { + return _impl_.sdpmids_.size(); +} +inline int CallMessage::sdpmids_size() const { + return _internal_sdpmids_size(); +} +inline void CallMessage::clear_sdpmids() { + _impl_.sdpmids_.Clear(); +} +inline std::string* CallMessage::add_sdpmids() { + std::string* _s = _internal_add_sdpmids(); + // @@protoc_insertion_point(field_add_mutable:SessionProtos.CallMessage.sdpMids) + return _s; +} +inline const std::string& CallMessage::_internal_sdpmids(int index) const { + return _impl_.sdpmids_.Get(index); +} +inline const std::string& CallMessage::sdpmids(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.CallMessage.sdpMids) + return _internal_sdpmids(index); +} +inline std::string* CallMessage::mutable_sdpmids(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.CallMessage.sdpMids) + return _impl_.sdpmids_.Mutable(index); +} +inline void CallMessage::set_sdpmids(int index, const std::string& value) { + _impl_.sdpmids_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set:SessionProtos.CallMessage.sdpMids) +} +inline void CallMessage::set_sdpmids(int index, std::string&& value) { + _impl_.sdpmids_.Mutable(index)->assign(std::move(value)); + // @@protoc_insertion_point(field_set:SessionProtos.CallMessage.sdpMids) +} +inline void CallMessage::set_sdpmids(int index, const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.sdpmids_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set_char:SessionProtos.CallMessage.sdpMids) +} +inline void CallMessage::set_sdpmids(int index, const char* value, size_t size) { + _impl_.sdpmids_.Mutable(index)->assign( + reinterpret_cast(value), size); + // @@protoc_insertion_point(field_set_pointer:SessionProtos.CallMessage.sdpMids) +} +inline std::string* CallMessage::_internal_add_sdpmids() { + return _impl_.sdpmids_.Add(); +} +inline void CallMessage::add_sdpmids(const std::string& value) { + _impl_.sdpmids_.Add()->assign(value); + // @@protoc_insertion_point(field_add:SessionProtos.CallMessage.sdpMids) +} +inline void CallMessage::add_sdpmids(std::string&& value) { + _impl_.sdpmids_.Add(std::move(value)); + // @@protoc_insertion_point(field_add:SessionProtos.CallMessage.sdpMids) +} +inline void CallMessage::add_sdpmids(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.sdpmids_.Add()->assign(value); + // @@protoc_insertion_point(field_add_char:SessionProtos.CallMessage.sdpMids) +} +inline void CallMessage::add_sdpmids(const char* value, size_t size) { + _impl_.sdpmids_.Add()->assign(reinterpret_cast(value), size); + // @@protoc_insertion_point(field_add_pointer:SessionProtos.CallMessage.sdpMids) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& +CallMessage::sdpmids() const { + // @@protoc_insertion_point(field_list:SessionProtos.CallMessage.sdpMids) + return _impl_.sdpmids_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* +CallMessage::mutable_sdpmids() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.CallMessage.sdpMids) + return &_impl_.sdpmids_; +} + +// required string uuid = 5; +inline bool CallMessage::_internal_has_uuid() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool CallMessage::has_uuid() const { + return _internal_has_uuid(); +} +inline void CallMessage::clear_uuid() { + _impl_.uuid_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& CallMessage::uuid() const { + // @@protoc_insertion_point(field_get:SessionProtos.CallMessage.uuid) + return _internal_uuid(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void CallMessage::set_uuid(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.uuid_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.CallMessage.uuid) +} +inline std::string* CallMessage::mutable_uuid() { + std::string* _s = _internal_mutable_uuid(); + // @@protoc_insertion_point(field_mutable:SessionProtos.CallMessage.uuid) + return _s; +} +inline const std::string& CallMessage::_internal_uuid() const { + return _impl_.uuid_.Get(); +} +inline void CallMessage::_internal_set_uuid(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.uuid_.Set(value, GetArenaForAllocation()); +} +inline std::string* CallMessage::_internal_mutable_uuid() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.uuid_.Mutable(GetArenaForAllocation()); +} +inline std::string* CallMessage::release_uuid() { + // @@protoc_insertion_point(field_release:SessionProtos.CallMessage.uuid) + if (!_internal_has_uuid()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.uuid_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.uuid_.IsDefault()) { + _impl_.uuid_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void CallMessage::set_allocated_uuid(std::string* uuid) { + if (uuid != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.uuid_.SetAllocated(uuid, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.uuid_.IsDefault()) { + _impl_.uuid_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.CallMessage.uuid) +} + +// ------------------------------------------------------------------- + +// KeyPair + +// required bytes publicKey = 1; +inline bool KeyPair::_internal_has_publickey() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool KeyPair::has_publickey() const { + return _internal_has_publickey(); +} +inline void KeyPair::clear_publickey() { + _impl_.publickey_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& KeyPair::publickey() const { + // @@protoc_insertion_point(field_get:SessionProtos.KeyPair.publicKey) + return _internal_publickey(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void KeyPair::set_publickey(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.publickey_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.KeyPair.publicKey) +} +inline std::string* KeyPair::mutable_publickey() { + std::string* _s = _internal_mutable_publickey(); + // @@protoc_insertion_point(field_mutable:SessionProtos.KeyPair.publicKey) + return _s; +} +inline const std::string& KeyPair::_internal_publickey() const { + return _impl_.publickey_.Get(); +} +inline void KeyPair::_internal_set_publickey(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.publickey_.Set(value, GetArenaForAllocation()); +} +inline std::string* KeyPair::_internal_mutable_publickey() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.publickey_.Mutable(GetArenaForAllocation()); +} +inline std::string* KeyPair::release_publickey() { + // @@protoc_insertion_point(field_release:SessionProtos.KeyPair.publicKey) + if (!_internal_has_publickey()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.publickey_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.publickey_.IsDefault()) { + _impl_.publickey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void KeyPair::set_allocated_publickey(std::string* publickey) { + if (publickey != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.publickey_.SetAllocated(publickey, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.publickey_.IsDefault()) { + _impl_.publickey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.KeyPair.publicKey) +} + +// required bytes privateKey = 2; +inline bool KeyPair::_internal_has_privatekey() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool KeyPair::has_privatekey() const { + return _internal_has_privatekey(); +} +inline void KeyPair::clear_privatekey() { + _impl_.privatekey_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& KeyPair::privatekey() const { + // @@protoc_insertion_point(field_get:SessionProtos.KeyPair.privateKey) + return _internal_privatekey(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void KeyPair::set_privatekey(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.privatekey_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.KeyPair.privateKey) +} +inline std::string* KeyPair::mutable_privatekey() { + std::string* _s = _internal_mutable_privatekey(); + // @@protoc_insertion_point(field_mutable:SessionProtos.KeyPair.privateKey) + return _s; +} +inline const std::string& KeyPair::_internal_privatekey() const { + return _impl_.privatekey_.Get(); +} +inline void KeyPair::_internal_set_privatekey(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.privatekey_.Set(value, GetArenaForAllocation()); +} +inline std::string* KeyPair::_internal_mutable_privatekey() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.privatekey_.Mutable(GetArenaForAllocation()); +} +inline std::string* KeyPair::release_privatekey() { + // @@protoc_insertion_point(field_release:SessionProtos.KeyPair.privateKey) + if (!_internal_has_privatekey()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.privatekey_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.privatekey_.IsDefault()) { + _impl_.privatekey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void KeyPair::set_allocated_privatekey(std::string* privatekey) { + if (privatekey != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.privatekey_.SetAllocated(privatekey, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.privatekey_.IsDefault()) { + _impl_.privatekey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.KeyPair.privateKey) +} + +// ------------------------------------------------------------------- + +// DataExtractionNotification + +// required .SessionProtos.DataExtractionNotification.Type type = 1; +inline bool DataExtractionNotification::_internal_has_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool DataExtractionNotification::has_type() const { + return _internal_has_type(); +} +inline void DataExtractionNotification::clear_type() { + _impl_.type_ = 1; + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline ::SessionProtos::DataExtractionNotification_Type DataExtractionNotification::_internal_type() const { + return static_cast< ::SessionProtos::DataExtractionNotification_Type >(_impl_.type_); +} +inline ::SessionProtos::DataExtractionNotification_Type DataExtractionNotification::type() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataExtractionNotification.type) + return _internal_type(); +} +inline void DataExtractionNotification::_internal_set_type(::SessionProtos::DataExtractionNotification_Type value) { + assert(::SessionProtos::DataExtractionNotification_Type_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.type_ = value; +} +inline void DataExtractionNotification::set_type(::SessionProtos::DataExtractionNotification_Type value) { + _internal_set_type(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataExtractionNotification.type) +} + +// optional uint64 timestamp = 2; +inline bool DataExtractionNotification::_internal_has_timestamp() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool DataExtractionNotification::has_timestamp() const { + return _internal_has_timestamp(); +} +inline void DataExtractionNotification::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline uint64_t DataExtractionNotification::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t DataExtractionNotification::timestamp() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataExtractionNotification.timestamp) + return _internal_timestamp(); +} +inline void DataExtractionNotification::_internal_set_timestamp(uint64_t value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.timestamp_ = value; +} +inline void DataExtractionNotification::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataExtractionNotification.timestamp) +} + +// ------------------------------------------------------------------- + +// LokiProfile + +// optional string displayName = 1; +inline bool LokiProfile::_internal_has_displayname() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool LokiProfile::has_displayname() const { + return _internal_has_displayname(); +} +inline void LokiProfile::clear_displayname() { + _impl_.displayname_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& LokiProfile::displayname() const { + // @@protoc_insertion_point(field_get:SessionProtos.LokiProfile.displayName) + return _internal_displayname(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void LokiProfile::set_displayname(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.displayname_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.LokiProfile.displayName) +} +inline std::string* LokiProfile::mutable_displayname() { + std::string* _s = _internal_mutable_displayname(); + // @@protoc_insertion_point(field_mutable:SessionProtos.LokiProfile.displayName) + return _s; +} +inline const std::string& LokiProfile::_internal_displayname() const { + return _impl_.displayname_.Get(); +} +inline void LokiProfile::_internal_set_displayname(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.displayname_.Set(value, GetArenaForAllocation()); +} +inline std::string* LokiProfile::_internal_mutable_displayname() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.displayname_.Mutable(GetArenaForAllocation()); +} +inline std::string* LokiProfile::release_displayname() { + // @@protoc_insertion_point(field_release:SessionProtos.LokiProfile.displayName) + if (!_internal_has_displayname()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.displayname_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.displayname_.IsDefault()) { + _impl_.displayname_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void LokiProfile::set_allocated_displayname(std::string* displayname) { + if (displayname != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.displayname_.SetAllocated(displayname, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.displayname_.IsDefault()) { + _impl_.displayname_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.LokiProfile.displayName) +} + +// optional string profilePicture = 2; +inline bool LokiProfile::_internal_has_profilepicture() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool LokiProfile::has_profilepicture() const { + return _internal_has_profilepicture(); +} +inline void LokiProfile::clear_profilepicture() { + _impl_.profilepicture_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& LokiProfile::profilepicture() const { + // @@protoc_insertion_point(field_get:SessionProtos.LokiProfile.profilePicture) + return _internal_profilepicture(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void LokiProfile::set_profilepicture(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.profilepicture_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.LokiProfile.profilePicture) +} +inline std::string* LokiProfile::mutable_profilepicture() { + std::string* _s = _internal_mutable_profilepicture(); + // @@protoc_insertion_point(field_mutable:SessionProtos.LokiProfile.profilePicture) + return _s; +} +inline const std::string& LokiProfile::_internal_profilepicture() const { + return _impl_.profilepicture_.Get(); +} +inline void LokiProfile::_internal_set_profilepicture(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.profilepicture_.Set(value, GetArenaForAllocation()); +} +inline std::string* LokiProfile::_internal_mutable_profilepicture() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.profilepicture_.Mutable(GetArenaForAllocation()); +} +inline std::string* LokiProfile::release_profilepicture() { + // @@protoc_insertion_point(field_release:SessionProtos.LokiProfile.profilePicture) + if (!_internal_has_profilepicture()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.profilepicture_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilepicture_.IsDefault()) { + _impl_.profilepicture_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void LokiProfile::set_allocated_profilepicture(std::string* profilepicture) { + if (profilepicture != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.profilepicture_.SetAllocated(profilepicture, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilepicture_.IsDefault()) { + _impl_.profilepicture_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.LokiProfile.profilePicture) +} + +// ------------------------------------------------------------------- + +// DataMessage_Quote_QuotedAttachment + +// optional string contentType = 1; +inline bool DataMessage_Quote_QuotedAttachment::_internal_has_contenttype() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool DataMessage_Quote_QuotedAttachment::has_contenttype() const { + return _internal_has_contenttype(); +} +inline void DataMessage_Quote_QuotedAttachment::clear_contenttype() { + _impl_.contenttype_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& DataMessage_Quote_QuotedAttachment::contenttype() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Quote.QuotedAttachment.contentType) + return _internal_contenttype(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_Quote_QuotedAttachment::set_contenttype(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.contenttype_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.Quote.QuotedAttachment.contentType) +} +inline std::string* DataMessage_Quote_QuotedAttachment::mutable_contenttype() { + std::string* _s = _internal_mutable_contenttype(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.Quote.QuotedAttachment.contentType) + return _s; +} +inline const std::string& DataMessage_Quote_QuotedAttachment::_internal_contenttype() const { + return _impl_.contenttype_.Get(); +} +inline void DataMessage_Quote_QuotedAttachment::_internal_set_contenttype(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.contenttype_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_Quote_QuotedAttachment::_internal_mutable_contenttype() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.contenttype_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_Quote_QuotedAttachment::release_contenttype() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.Quote.QuotedAttachment.contentType) + if (!_internal_has_contenttype()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.contenttype_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.contenttype_.IsDefault()) { + _impl_.contenttype_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_Quote_QuotedAttachment::set_allocated_contenttype(std::string* contenttype) { + if (contenttype != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.contenttype_.SetAllocated(contenttype, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.contenttype_.IsDefault()) { + _impl_.contenttype_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.Quote.QuotedAttachment.contentType) +} + +// optional string fileName = 2; +inline bool DataMessage_Quote_QuotedAttachment::_internal_has_filename() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool DataMessage_Quote_QuotedAttachment::has_filename() const { + return _internal_has_filename(); +} +inline void DataMessage_Quote_QuotedAttachment::clear_filename() { + _impl_.filename_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& DataMessage_Quote_QuotedAttachment::filename() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Quote.QuotedAttachment.fileName) + return _internal_filename(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_Quote_QuotedAttachment::set_filename(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.filename_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.Quote.QuotedAttachment.fileName) +} +inline std::string* DataMessage_Quote_QuotedAttachment::mutable_filename() { + std::string* _s = _internal_mutable_filename(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.Quote.QuotedAttachment.fileName) + return _s; +} +inline const std::string& DataMessage_Quote_QuotedAttachment::_internal_filename() const { + return _impl_.filename_.Get(); +} +inline void DataMessage_Quote_QuotedAttachment::_internal_set_filename(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.filename_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_Quote_QuotedAttachment::_internal_mutable_filename() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.filename_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_Quote_QuotedAttachment::release_filename() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.Quote.QuotedAttachment.fileName) + if (!_internal_has_filename()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.filename_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.filename_.IsDefault()) { + _impl_.filename_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_Quote_QuotedAttachment::set_allocated_filename(std::string* filename) { + if (filename != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.filename_.SetAllocated(filename, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.filename_.IsDefault()) { + _impl_.filename_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.Quote.QuotedAttachment.fileName) +} + +// optional .SessionProtos.AttachmentPointer thumbnail = 3; +inline bool DataMessage_Quote_QuotedAttachment::_internal_has_thumbnail() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + PROTOBUF_ASSUME(!value || _impl_.thumbnail_ != nullptr); + return value; +} +inline bool DataMessage_Quote_QuotedAttachment::has_thumbnail() const { + return _internal_has_thumbnail(); +} +inline void DataMessage_Quote_QuotedAttachment::clear_thumbnail() { + if (_impl_.thumbnail_ != nullptr) _impl_.thumbnail_->Clear(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const ::SessionProtos::AttachmentPointer& DataMessage_Quote_QuotedAttachment::_internal_thumbnail() const { + const ::SessionProtos::AttachmentPointer* p = _impl_.thumbnail_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_AttachmentPointer_default_instance_); +} +inline const ::SessionProtos::AttachmentPointer& DataMessage_Quote_QuotedAttachment::thumbnail() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Quote.QuotedAttachment.thumbnail) + return _internal_thumbnail(); +} +inline void DataMessage_Quote_QuotedAttachment::unsafe_arena_set_allocated_thumbnail( + ::SessionProtos::AttachmentPointer* thumbnail) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.thumbnail_); + } + _impl_.thumbnail_ = thumbnail; + if (thumbnail) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.DataMessage.Quote.QuotedAttachment.thumbnail) +} +inline ::SessionProtos::AttachmentPointer* DataMessage_Quote_QuotedAttachment::release_thumbnail() { + _impl_._has_bits_[0] &= ~0x00000004u; + ::SessionProtos::AttachmentPointer* temp = _impl_.thumbnail_; + _impl_.thumbnail_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::AttachmentPointer* DataMessage_Quote_QuotedAttachment::unsafe_arena_release_thumbnail() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.Quote.QuotedAttachment.thumbnail) + _impl_._has_bits_[0] &= ~0x00000004u; + ::SessionProtos::AttachmentPointer* temp = _impl_.thumbnail_; + _impl_.thumbnail_ = nullptr; + return temp; +} +inline ::SessionProtos::AttachmentPointer* DataMessage_Quote_QuotedAttachment::_internal_mutable_thumbnail() { + _impl_._has_bits_[0] |= 0x00000004u; + if (_impl_.thumbnail_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::AttachmentPointer>(GetArenaForAllocation()); + _impl_.thumbnail_ = p; + } + return _impl_.thumbnail_; +} +inline ::SessionProtos::AttachmentPointer* DataMessage_Quote_QuotedAttachment::mutable_thumbnail() { + ::SessionProtos::AttachmentPointer* _msg = _internal_mutable_thumbnail(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.Quote.QuotedAttachment.thumbnail) + return _msg; +} +inline void DataMessage_Quote_QuotedAttachment::set_allocated_thumbnail(::SessionProtos::AttachmentPointer* thumbnail) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.thumbnail_; + } + if (thumbnail) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(thumbnail); + if (message_arena != submessage_arena) { + thumbnail = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, thumbnail, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.thumbnail_ = thumbnail; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.Quote.QuotedAttachment.thumbnail) +} + +// optional uint32 flags = 4; +inline bool DataMessage_Quote_QuotedAttachment::_internal_has_flags() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool DataMessage_Quote_QuotedAttachment::has_flags() const { + return _internal_has_flags(); +} +inline void DataMessage_Quote_QuotedAttachment::clear_flags() { + _impl_.flags_ = 0u; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline uint32_t DataMessage_Quote_QuotedAttachment::_internal_flags() const { + return _impl_.flags_; +} +inline uint32_t DataMessage_Quote_QuotedAttachment::flags() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Quote.QuotedAttachment.flags) + return _internal_flags(); +} +inline void DataMessage_Quote_QuotedAttachment::_internal_set_flags(uint32_t value) { + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.flags_ = value; +} +inline void DataMessage_Quote_QuotedAttachment::set_flags(uint32_t value) { + _internal_set_flags(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.Quote.QuotedAttachment.flags) +} + +// ------------------------------------------------------------------- + +// DataMessage_Quote + +// required uint64 id = 1; +inline bool DataMessage_Quote::_internal_has_id() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool DataMessage_Quote::has_id() const { + return _internal_has_id(); +} +inline void DataMessage_Quote::clear_id() { + _impl_.id_ = uint64_t{0u}; + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline uint64_t DataMessage_Quote::_internal_id() const { + return _impl_.id_; +} +inline uint64_t DataMessage_Quote::id() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Quote.id) + return _internal_id(); +} +inline void DataMessage_Quote::_internal_set_id(uint64_t value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.id_ = value; +} +inline void DataMessage_Quote::set_id(uint64_t value) { + _internal_set_id(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.Quote.id) +} + +// required string author = 2; +inline bool DataMessage_Quote::_internal_has_author() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool DataMessage_Quote::has_author() const { + return _internal_has_author(); +} +inline void DataMessage_Quote::clear_author() { + _impl_.author_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& DataMessage_Quote::author() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Quote.author) + return _internal_author(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_Quote::set_author(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.author_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.Quote.author) +} +inline std::string* DataMessage_Quote::mutable_author() { + std::string* _s = _internal_mutable_author(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.Quote.author) + return _s; +} +inline const std::string& DataMessage_Quote::_internal_author() const { + return _impl_.author_.Get(); +} +inline void DataMessage_Quote::_internal_set_author(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.author_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_Quote::_internal_mutable_author() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.author_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_Quote::release_author() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.Quote.author) + if (!_internal_has_author()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.author_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.author_.IsDefault()) { + _impl_.author_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_Quote::set_allocated_author(std::string* author) { + if (author != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.author_.SetAllocated(author, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.author_.IsDefault()) { + _impl_.author_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.Quote.author) +} + +// optional string text = 3; +inline bool DataMessage_Quote::_internal_has_text() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool DataMessage_Quote::has_text() const { + return _internal_has_text(); +} +inline void DataMessage_Quote::clear_text() { + _impl_.text_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& DataMessage_Quote::text() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Quote.text) + return _internal_text(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_Quote::set_text(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.text_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.Quote.text) +} +inline std::string* DataMessage_Quote::mutable_text() { + std::string* _s = _internal_mutable_text(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.Quote.text) + return _s; +} +inline const std::string& DataMessage_Quote::_internal_text() const { + return _impl_.text_.Get(); +} +inline void DataMessage_Quote::_internal_set_text(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.text_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_Quote::_internal_mutable_text() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.text_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_Quote::release_text() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.Quote.text) + if (!_internal_has_text()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.text_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.text_.IsDefault()) { + _impl_.text_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_Quote::set_allocated_text(std::string* text) { + if (text != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.text_.SetAllocated(text, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.text_.IsDefault()) { + _impl_.text_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.Quote.text) +} + +// repeated .SessionProtos.DataMessage.Quote.QuotedAttachment attachments = 4; +inline int DataMessage_Quote::_internal_attachments_size() const { + return _impl_.attachments_.size(); +} +inline int DataMessage_Quote::attachments_size() const { + return _internal_attachments_size(); +} +inline void DataMessage_Quote::clear_attachments() { + _impl_.attachments_.Clear(); +} +inline ::SessionProtos::DataMessage_Quote_QuotedAttachment* DataMessage_Quote::mutable_attachments(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.Quote.attachments) + return _impl_.attachments_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_Quote_QuotedAttachment >* +DataMessage_Quote::mutable_attachments() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.DataMessage.Quote.attachments) + return &_impl_.attachments_; +} +inline const ::SessionProtos::DataMessage_Quote_QuotedAttachment& DataMessage_Quote::_internal_attachments(int index) const { + return _impl_.attachments_.Get(index); +} +inline const ::SessionProtos::DataMessage_Quote_QuotedAttachment& DataMessage_Quote::attachments(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Quote.attachments) + return _internal_attachments(index); +} +inline ::SessionProtos::DataMessage_Quote_QuotedAttachment* DataMessage_Quote::_internal_add_attachments() { + return _impl_.attachments_.Add(); +} +inline ::SessionProtos::DataMessage_Quote_QuotedAttachment* DataMessage_Quote::add_attachments() { + ::SessionProtos::DataMessage_Quote_QuotedAttachment* _add = _internal_add_attachments(); + // @@protoc_insertion_point(field_add:SessionProtos.DataMessage.Quote.attachments) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_Quote_QuotedAttachment >& +DataMessage_Quote::attachments() const { + // @@protoc_insertion_point(field_list:SessionProtos.DataMessage.Quote.attachments) + return _impl_.attachments_; +} + +// ------------------------------------------------------------------- + +// DataMessage_Preview + +// required string url = 1; +inline bool DataMessage_Preview::_internal_has_url() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool DataMessage_Preview::has_url() const { + return _internal_has_url(); +} +inline void DataMessage_Preview::clear_url() { + _impl_.url_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& DataMessage_Preview::url() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Preview.url) + return _internal_url(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_Preview::set_url(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.url_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.Preview.url) +} +inline std::string* DataMessage_Preview::mutable_url() { + std::string* _s = _internal_mutable_url(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.Preview.url) + return _s; +} +inline const std::string& DataMessage_Preview::_internal_url() const { + return _impl_.url_.Get(); +} +inline void DataMessage_Preview::_internal_set_url(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.url_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_Preview::_internal_mutable_url() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.url_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_Preview::release_url() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.Preview.url) + if (!_internal_has_url()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.url_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.url_.IsDefault()) { + _impl_.url_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_Preview::set_allocated_url(std::string* url) { + if (url != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.url_.SetAllocated(url, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.url_.IsDefault()) { + _impl_.url_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.Preview.url) +} + +// optional string title = 2; +inline bool DataMessage_Preview::_internal_has_title() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool DataMessage_Preview::has_title() const { + return _internal_has_title(); +} +inline void DataMessage_Preview::clear_title() { + _impl_.title_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& DataMessage_Preview::title() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Preview.title) + return _internal_title(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_Preview::set_title(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.title_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.Preview.title) +} +inline std::string* DataMessage_Preview::mutable_title() { + std::string* _s = _internal_mutable_title(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.Preview.title) + return _s; +} +inline const std::string& DataMessage_Preview::_internal_title() const { + return _impl_.title_.Get(); +} +inline void DataMessage_Preview::_internal_set_title(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.title_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_Preview::_internal_mutable_title() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.title_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_Preview::release_title() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.Preview.title) + if (!_internal_has_title()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.title_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.title_.IsDefault()) { + _impl_.title_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_Preview::set_allocated_title(std::string* title) { + if (title != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.title_.SetAllocated(title, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.title_.IsDefault()) { + _impl_.title_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.Preview.title) +} + +// optional .SessionProtos.AttachmentPointer image = 3; +inline bool DataMessage_Preview::_internal_has_image() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + PROTOBUF_ASSUME(!value || _impl_.image_ != nullptr); + return value; +} +inline bool DataMessage_Preview::has_image() const { + return _internal_has_image(); +} +inline void DataMessage_Preview::clear_image() { + if (_impl_.image_ != nullptr) _impl_.image_->Clear(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const ::SessionProtos::AttachmentPointer& DataMessage_Preview::_internal_image() const { + const ::SessionProtos::AttachmentPointer* p = _impl_.image_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_AttachmentPointer_default_instance_); +} +inline const ::SessionProtos::AttachmentPointer& DataMessage_Preview::image() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Preview.image) + return _internal_image(); +} +inline void DataMessage_Preview::unsafe_arena_set_allocated_image( + ::SessionProtos::AttachmentPointer* image) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.image_); + } + _impl_.image_ = image; + if (image) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.DataMessage.Preview.image) +} +inline ::SessionProtos::AttachmentPointer* DataMessage_Preview::release_image() { + _impl_._has_bits_[0] &= ~0x00000004u; + ::SessionProtos::AttachmentPointer* temp = _impl_.image_; + _impl_.image_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::AttachmentPointer* DataMessage_Preview::unsafe_arena_release_image() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.Preview.image) + _impl_._has_bits_[0] &= ~0x00000004u; + ::SessionProtos::AttachmentPointer* temp = _impl_.image_; + _impl_.image_ = nullptr; + return temp; +} +inline ::SessionProtos::AttachmentPointer* DataMessage_Preview::_internal_mutable_image() { + _impl_._has_bits_[0] |= 0x00000004u; + if (_impl_.image_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::AttachmentPointer>(GetArenaForAllocation()); + _impl_.image_ = p; + } + return _impl_.image_; +} +inline ::SessionProtos::AttachmentPointer* DataMessage_Preview::mutable_image() { + ::SessionProtos::AttachmentPointer* _msg = _internal_mutable_image(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.Preview.image) + return _msg; +} +inline void DataMessage_Preview::set_allocated_image(::SessionProtos::AttachmentPointer* image) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.image_; + } + if (image) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(image); + if (message_arena != submessage_arena) { + image = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, image, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.image_ = image; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.Preview.image) +} + +// ------------------------------------------------------------------- + +// DataMessage_Reaction + +// required uint64 id = 1; +inline bool DataMessage_Reaction::_internal_has_id() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool DataMessage_Reaction::has_id() const { + return _internal_has_id(); +} +inline void DataMessage_Reaction::clear_id() { + _impl_.id_ = uint64_t{0u}; + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline uint64_t DataMessage_Reaction::_internal_id() const { + return _impl_.id_; +} +inline uint64_t DataMessage_Reaction::id() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Reaction.id) + return _internal_id(); +} +inline void DataMessage_Reaction::_internal_set_id(uint64_t value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.id_ = value; +} +inline void DataMessage_Reaction::set_id(uint64_t value) { + _internal_set_id(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.Reaction.id) +} + +// required string author = 2; +inline bool DataMessage_Reaction::_internal_has_author() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool DataMessage_Reaction::has_author() const { + return _internal_has_author(); +} +inline void DataMessage_Reaction::clear_author() { + _impl_.author_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& DataMessage_Reaction::author() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Reaction.author) + return _internal_author(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_Reaction::set_author(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.author_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.Reaction.author) +} +inline std::string* DataMessage_Reaction::mutable_author() { + std::string* _s = _internal_mutable_author(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.Reaction.author) + return _s; +} +inline const std::string& DataMessage_Reaction::_internal_author() const { + return _impl_.author_.Get(); +} +inline void DataMessage_Reaction::_internal_set_author(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.author_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_Reaction::_internal_mutable_author() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.author_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_Reaction::release_author() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.Reaction.author) + if (!_internal_has_author()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.author_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.author_.IsDefault()) { + _impl_.author_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_Reaction::set_allocated_author(std::string* author) { + if (author != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.author_.SetAllocated(author, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.author_.IsDefault()) { + _impl_.author_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.Reaction.author) +} + +// optional string emoji = 3; +inline bool DataMessage_Reaction::_internal_has_emoji() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool DataMessage_Reaction::has_emoji() const { + return _internal_has_emoji(); +} +inline void DataMessage_Reaction::clear_emoji() { + _impl_.emoji_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& DataMessage_Reaction::emoji() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Reaction.emoji) + return _internal_emoji(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_Reaction::set_emoji(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.emoji_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.Reaction.emoji) +} +inline std::string* DataMessage_Reaction::mutable_emoji() { + std::string* _s = _internal_mutable_emoji(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.Reaction.emoji) + return _s; +} +inline const std::string& DataMessage_Reaction::_internal_emoji() const { + return _impl_.emoji_.Get(); +} +inline void DataMessage_Reaction::_internal_set_emoji(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.emoji_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_Reaction::_internal_mutable_emoji() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.emoji_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_Reaction::release_emoji() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.Reaction.emoji) + if (!_internal_has_emoji()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.emoji_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.emoji_.IsDefault()) { + _impl_.emoji_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_Reaction::set_allocated_emoji(std::string* emoji) { + if (emoji != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.emoji_.SetAllocated(emoji, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.emoji_.IsDefault()) { + _impl_.emoji_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.Reaction.emoji) +} + +// required .SessionProtos.DataMessage.Reaction.Action action = 4; +inline bool DataMessage_Reaction::_internal_has_action() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool DataMessage_Reaction::has_action() const { + return _internal_has_action(); +} +inline void DataMessage_Reaction::clear_action() { + _impl_.action_ = 0; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline ::SessionProtos::DataMessage_Reaction_Action DataMessage_Reaction::_internal_action() const { + return static_cast< ::SessionProtos::DataMessage_Reaction_Action >(_impl_.action_); +} +inline ::SessionProtos::DataMessage_Reaction_Action DataMessage_Reaction::action() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.Reaction.action) + return _internal_action(); +} +inline void DataMessage_Reaction::_internal_set_action(::SessionProtos::DataMessage_Reaction_Action value) { + assert(::SessionProtos::DataMessage_Reaction_Action_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.action_ = value; +} +inline void DataMessage_Reaction::set_action(::SessionProtos::DataMessage_Reaction_Action value) { + _internal_set_action(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.Reaction.action) +} + +// ------------------------------------------------------------------- + +// DataMessage_OpenGroupInvitation + +// required string url = 1; +inline bool DataMessage_OpenGroupInvitation::_internal_has_url() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool DataMessage_OpenGroupInvitation::has_url() const { + return _internal_has_url(); +} +inline void DataMessage_OpenGroupInvitation::clear_url() { + _impl_.url_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& DataMessage_OpenGroupInvitation::url() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.OpenGroupInvitation.url) + return _internal_url(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_OpenGroupInvitation::set_url(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.url_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.OpenGroupInvitation.url) +} +inline std::string* DataMessage_OpenGroupInvitation::mutable_url() { + std::string* _s = _internal_mutable_url(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.OpenGroupInvitation.url) + return _s; +} +inline const std::string& DataMessage_OpenGroupInvitation::_internal_url() const { + return _impl_.url_.Get(); +} +inline void DataMessage_OpenGroupInvitation::_internal_set_url(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.url_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_OpenGroupInvitation::_internal_mutable_url() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.url_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_OpenGroupInvitation::release_url() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.OpenGroupInvitation.url) + if (!_internal_has_url()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.url_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.url_.IsDefault()) { + _impl_.url_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_OpenGroupInvitation::set_allocated_url(std::string* url) { + if (url != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.url_.SetAllocated(url, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.url_.IsDefault()) { + _impl_.url_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.OpenGroupInvitation.url) +} + +// required string name = 3; +inline bool DataMessage_OpenGroupInvitation::_internal_has_name() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool DataMessage_OpenGroupInvitation::has_name() const { + return _internal_has_name(); +} +inline void DataMessage_OpenGroupInvitation::clear_name() { + _impl_.name_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& DataMessage_OpenGroupInvitation::name() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.OpenGroupInvitation.name) + return _internal_name(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_OpenGroupInvitation::set_name(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.name_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.OpenGroupInvitation.name) +} +inline std::string* DataMessage_OpenGroupInvitation::mutable_name() { + std::string* _s = _internal_mutable_name(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.OpenGroupInvitation.name) + return _s; +} +inline const std::string& DataMessage_OpenGroupInvitation::_internal_name() const { + return _impl_.name_.Get(); +} +inline void DataMessage_OpenGroupInvitation::_internal_set_name(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.name_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_OpenGroupInvitation::_internal_mutable_name() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.name_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_OpenGroupInvitation::release_name() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.OpenGroupInvitation.name) + if (!_internal_has_name()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.name_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_OpenGroupInvitation::set_allocated_name(std::string* name) { + if (name != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.name_.SetAllocated(name, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.OpenGroupInvitation.name) +} + +// ------------------------------------------------------------------- + +// DataMessage_ClosedGroupControlMessage_KeyPairWrapper + +// required bytes publicKey = 1; +inline bool DataMessage_ClosedGroupControlMessage_KeyPairWrapper::_internal_has_publickey() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool DataMessage_ClosedGroupControlMessage_KeyPairWrapper::has_publickey() const { + return _internal_has_publickey(); +} +inline void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::clear_publickey() { + _impl_.publickey_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& DataMessage_ClosedGroupControlMessage_KeyPairWrapper::publickey() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.publicKey) + return _internal_publickey(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::set_publickey(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.publickey_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.publicKey) +} +inline std::string* DataMessage_ClosedGroupControlMessage_KeyPairWrapper::mutable_publickey() { + std::string* _s = _internal_mutable_publickey(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.publicKey) + return _s; +} +inline const std::string& DataMessage_ClosedGroupControlMessage_KeyPairWrapper::_internal_publickey() const { + return _impl_.publickey_.Get(); +} +inline void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::_internal_set_publickey(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.publickey_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_ClosedGroupControlMessage_KeyPairWrapper::_internal_mutable_publickey() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.publickey_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_ClosedGroupControlMessage_KeyPairWrapper::release_publickey() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.publicKey) + if (!_internal_has_publickey()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.publickey_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.publickey_.IsDefault()) { + _impl_.publickey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::set_allocated_publickey(std::string* publickey) { + if (publickey != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.publickey_.SetAllocated(publickey, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.publickey_.IsDefault()) { + _impl_.publickey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.publicKey) +} + +// required bytes encryptedKeyPair = 2; +inline bool DataMessage_ClosedGroupControlMessage_KeyPairWrapper::_internal_has_encryptedkeypair() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool DataMessage_ClosedGroupControlMessage_KeyPairWrapper::has_encryptedkeypair() const { + return _internal_has_encryptedkeypair(); +} +inline void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::clear_encryptedkeypair() { + _impl_.encryptedkeypair_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& DataMessage_ClosedGroupControlMessage_KeyPairWrapper::encryptedkeypair() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.encryptedKeyPair) + return _internal_encryptedkeypair(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::set_encryptedkeypair(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.encryptedkeypair_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.encryptedKeyPair) +} +inline std::string* DataMessage_ClosedGroupControlMessage_KeyPairWrapper::mutable_encryptedkeypair() { + std::string* _s = _internal_mutable_encryptedkeypair(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.encryptedKeyPair) + return _s; +} +inline const std::string& DataMessage_ClosedGroupControlMessage_KeyPairWrapper::_internal_encryptedkeypair() const { + return _impl_.encryptedkeypair_.Get(); +} +inline void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::_internal_set_encryptedkeypair(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.encryptedkeypair_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_ClosedGroupControlMessage_KeyPairWrapper::_internal_mutable_encryptedkeypair() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.encryptedkeypair_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_ClosedGroupControlMessage_KeyPairWrapper::release_encryptedkeypair() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.encryptedKeyPair) + if (!_internal_has_encryptedkeypair()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.encryptedkeypair_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.encryptedkeypair_.IsDefault()) { + _impl_.encryptedkeypair_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_ClosedGroupControlMessage_KeyPairWrapper::set_allocated_encryptedkeypair(std::string* encryptedkeypair) { + if (encryptedkeypair != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.encryptedkeypair_.SetAllocated(encryptedkeypair, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.encryptedkeypair_.IsDefault()) { + _impl_.encryptedkeypair_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper.encryptedKeyPair) +} + +// ------------------------------------------------------------------- + +// DataMessage_ClosedGroupControlMessage + +// required .SessionProtos.DataMessage.ClosedGroupControlMessage.Type type = 1; +inline bool DataMessage_ClosedGroupControlMessage::_internal_has_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; + return value; +} +inline bool DataMessage_ClosedGroupControlMessage::has_type() const { + return _internal_has_type(); +} +inline void DataMessage_ClosedGroupControlMessage::clear_type() { + _impl_.type_ = 1; + _impl_._has_bits_[0] &= ~0x00000010u; +} +inline ::SessionProtos::DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage::_internal_type() const { + return static_cast< ::SessionProtos::DataMessage_ClosedGroupControlMessage_Type >(_impl_.type_); +} +inline ::SessionProtos::DataMessage_ClosedGroupControlMessage_Type DataMessage_ClosedGroupControlMessage::type() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.ClosedGroupControlMessage.type) + return _internal_type(); +} +inline void DataMessage_ClosedGroupControlMessage::_internal_set_type(::SessionProtos::DataMessage_ClosedGroupControlMessage_Type value) { + assert(::SessionProtos::DataMessage_ClosedGroupControlMessage_Type_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000010u; + _impl_.type_ = value; +} +inline void DataMessage_ClosedGroupControlMessage::set_type(::SessionProtos::DataMessage_ClosedGroupControlMessage_Type value) { + _internal_set_type(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.ClosedGroupControlMessage.type) +} + +// optional bytes publicKey = 2; +inline bool DataMessage_ClosedGroupControlMessage::_internal_has_publickey() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool DataMessage_ClosedGroupControlMessage::has_publickey() const { + return _internal_has_publickey(); +} +inline void DataMessage_ClosedGroupControlMessage::clear_publickey() { + _impl_.publickey_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& DataMessage_ClosedGroupControlMessage::publickey() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.ClosedGroupControlMessage.publicKey) + return _internal_publickey(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_ClosedGroupControlMessage::set_publickey(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.publickey_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.ClosedGroupControlMessage.publicKey) +} +inline std::string* DataMessage_ClosedGroupControlMessage::mutable_publickey() { + std::string* _s = _internal_mutable_publickey(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.ClosedGroupControlMessage.publicKey) + return _s; +} +inline const std::string& DataMessage_ClosedGroupControlMessage::_internal_publickey() const { + return _impl_.publickey_.Get(); +} +inline void DataMessage_ClosedGroupControlMessage::_internal_set_publickey(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.publickey_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_ClosedGroupControlMessage::_internal_mutable_publickey() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.publickey_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_ClosedGroupControlMessage::release_publickey() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.ClosedGroupControlMessage.publicKey) + if (!_internal_has_publickey()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.publickey_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.publickey_.IsDefault()) { + _impl_.publickey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_ClosedGroupControlMessage::set_allocated_publickey(std::string* publickey) { + if (publickey != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.publickey_.SetAllocated(publickey, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.publickey_.IsDefault()) { + _impl_.publickey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.ClosedGroupControlMessage.publicKey) +} + +// optional string name = 3; +inline bool DataMessage_ClosedGroupControlMessage::_internal_has_name() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool DataMessage_ClosedGroupControlMessage::has_name() const { + return _internal_has_name(); +} +inline void DataMessage_ClosedGroupControlMessage::clear_name() { + _impl_.name_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& DataMessage_ClosedGroupControlMessage::name() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.ClosedGroupControlMessage.name) + return _internal_name(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage_ClosedGroupControlMessage::set_name(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.name_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.ClosedGroupControlMessage.name) +} +inline std::string* DataMessage_ClosedGroupControlMessage::mutable_name() { + std::string* _s = _internal_mutable_name(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.ClosedGroupControlMessage.name) + return _s; +} +inline const std::string& DataMessage_ClosedGroupControlMessage::_internal_name() const { + return _impl_.name_.Get(); +} +inline void DataMessage_ClosedGroupControlMessage::_internal_set_name(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.name_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage_ClosedGroupControlMessage::_internal_mutable_name() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.name_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage_ClosedGroupControlMessage::release_name() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.ClosedGroupControlMessage.name) + if (!_internal_has_name()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.name_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage_ClosedGroupControlMessage::set_allocated_name(std::string* name) { + if (name != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.name_.SetAllocated(name, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.ClosedGroupControlMessage.name) +} + +// optional .SessionProtos.KeyPair encryptionKeyPair = 4; +inline bool DataMessage_ClosedGroupControlMessage::_internal_has_encryptionkeypair() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + PROTOBUF_ASSUME(!value || _impl_.encryptionkeypair_ != nullptr); + return value; +} +inline bool DataMessage_ClosedGroupControlMessage::has_encryptionkeypair() const { + return _internal_has_encryptionkeypair(); +} +inline void DataMessage_ClosedGroupControlMessage::clear_encryptionkeypair() { + if (_impl_.encryptionkeypair_ != nullptr) _impl_.encryptionkeypair_->Clear(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const ::SessionProtos::KeyPair& DataMessage_ClosedGroupControlMessage::_internal_encryptionkeypair() const { + const ::SessionProtos::KeyPair* p = _impl_.encryptionkeypair_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_KeyPair_default_instance_); +} +inline const ::SessionProtos::KeyPair& DataMessage_ClosedGroupControlMessage::encryptionkeypair() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.ClosedGroupControlMessage.encryptionKeyPair) + return _internal_encryptionkeypair(); +} +inline void DataMessage_ClosedGroupControlMessage::unsafe_arena_set_allocated_encryptionkeypair( + ::SessionProtos::KeyPair* encryptionkeypair) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.encryptionkeypair_); + } + _impl_.encryptionkeypair_ = encryptionkeypair; + if (encryptionkeypair) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.DataMessage.ClosedGroupControlMessage.encryptionKeyPair) +} +inline ::SessionProtos::KeyPair* DataMessage_ClosedGroupControlMessage::release_encryptionkeypair() { + _impl_._has_bits_[0] &= ~0x00000004u; + ::SessionProtos::KeyPair* temp = _impl_.encryptionkeypair_; + _impl_.encryptionkeypair_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::KeyPair* DataMessage_ClosedGroupControlMessage::unsafe_arena_release_encryptionkeypair() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.ClosedGroupControlMessage.encryptionKeyPair) + _impl_._has_bits_[0] &= ~0x00000004u; + ::SessionProtos::KeyPair* temp = _impl_.encryptionkeypair_; + _impl_.encryptionkeypair_ = nullptr; + return temp; +} +inline ::SessionProtos::KeyPair* DataMessage_ClosedGroupControlMessage::_internal_mutable_encryptionkeypair() { + _impl_._has_bits_[0] |= 0x00000004u; + if (_impl_.encryptionkeypair_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::KeyPair>(GetArenaForAllocation()); + _impl_.encryptionkeypair_ = p; + } + return _impl_.encryptionkeypair_; +} +inline ::SessionProtos::KeyPair* DataMessage_ClosedGroupControlMessage::mutable_encryptionkeypair() { + ::SessionProtos::KeyPair* _msg = _internal_mutable_encryptionkeypair(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.ClosedGroupControlMessage.encryptionKeyPair) + return _msg; +} +inline void DataMessage_ClosedGroupControlMessage::set_allocated_encryptionkeypair(::SessionProtos::KeyPair* encryptionkeypair) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.encryptionkeypair_; + } + if (encryptionkeypair) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(encryptionkeypair); + if (message_arena != submessage_arena) { + encryptionkeypair = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, encryptionkeypair, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.encryptionkeypair_ = encryptionkeypair; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.ClosedGroupControlMessage.encryptionKeyPair) +} + +// repeated bytes members = 5; +inline int DataMessage_ClosedGroupControlMessage::_internal_members_size() const { + return _impl_.members_.size(); +} +inline int DataMessage_ClosedGroupControlMessage::members_size() const { + return _internal_members_size(); +} +inline void DataMessage_ClosedGroupControlMessage::clear_members() { + _impl_.members_.Clear(); +} +inline std::string* DataMessage_ClosedGroupControlMessage::add_members() { + std::string* _s = _internal_add_members(); + // @@protoc_insertion_point(field_add_mutable:SessionProtos.DataMessage.ClosedGroupControlMessage.members) + return _s; +} +inline const std::string& DataMessage_ClosedGroupControlMessage::_internal_members(int index) const { + return _impl_.members_.Get(index); +} +inline const std::string& DataMessage_ClosedGroupControlMessage::members(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.ClosedGroupControlMessage.members) + return _internal_members(index); +} +inline std::string* DataMessage_ClosedGroupControlMessage::mutable_members(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.ClosedGroupControlMessage.members) + return _impl_.members_.Mutable(index); +} +inline void DataMessage_ClosedGroupControlMessage::set_members(int index, const std::string& value) { + _impl_.members_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.ClosedGroupControlMessage.members) +} +inline void DataMessage_ClosedGroupControlMessage::set_members(int index, std::string&& value) { + _impl_.members_.Mutable(index)->assign(std::move(value)); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.ClosedGroupControlMessage.members) +} +inline void DataMessage_ClosedGroupControlMessage::set_members(int index, const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.members_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set_char:SessionProtos.DataMessage.ClosedGroupControlMessage.members) +} +inline void DataMessage_ClosedGroupControlMessage::set_members(int index, const void* value, size_t size) { + _impl_.members_.Mutable(index)->assign( + reinterpret_cast(value), size); + // @@protoc_insertion_point(field_set_pointer:SessionProtos.DataMessage.ClosedGroupControlMessage.members) +} +inline std::string* DataMessage_ClosedGroupControlMessage::_internal_add_members() { + return _impl_.members_.Add(); +} +inline void DataMessage_ClosedGroupControlMessage::add_members(const std::string& value) { + _impl_.members_.Add()->assign(value); + // @@protoc_insertion_point(field_add:SessionProtos.DataMessage.ClosedGroupControlMessage.members) +} +inline void DataMessage_ClosedGroupControlMessage::add_members(std::string&& value) { + _impl_.members_.Add(std::move(value)); + // @@protoc_insertion_point(field_add:SessionProtos.DataMessage.ClosedGroupControlMessage.members) +} +inline void DataMessage_ClosedGroupControlMessage::add_members(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.members_.Add()->assign(value); + // @@protoc_insertion_point(field_add_char:SessionProtos.DataMessage.ClosedGroupControlMessage.members) +} +inline void DataMessage_ClosedGroupControlMessage::add_members(const void* value, size_t size) { + _impl_.members_.Add()->assign(reinterpret_cast(value), size); + // @@protoc_insertion_point(field_add_pointer:SessionProtos.DataMessage.ClosedGroupControlMessage.members) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& +DataMessage_ClosedGroupControlMessage::members() const { + // @@protoc_insertion_point(field_list:SessionProtos.DataMessage.ClosedGroupControlMessage.members) + return _impl_.members_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* +DataMessage_ClosedGroupControlMessage::mutable_members() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.DataMessage.ClosedGroupControlMessage.members) + return &_impl_.members_; +} + +// repeated bytes admins = 6; +inline int DataMessage_ClosedGroupControlMessage::_internal_admins_size() const { + return _impl_.admins_.size(); +} +inline int DataMessage_ClosedGroupControlMessage::admins_size() const { + return _internal_admins_size(); +} +inline void DataMessage_ClosedGroupControlMessage::clear_admins() { + _impl_.admins_.Clear(); +} +inline std::string* DataMessage_ClosedGroupControlMessage::add_admins() { + std::string* _s = _internal_add_admins(); + // @@protoc_insertion_point(field_add_mutable:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) + return _s; +} +inline const std::string& DataMessage_ClosedGroupControlMessage::_internal_admins(int index) const { + return _impl_.admins_.Get(index); +} +inline const std::string& DataMessage_ClosedGroupControlMessage::admins(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) + return _internal_admins(index); +} +inline std::string* DataMessage_ClosedGroupControlMessage::mutable_admins(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) + return _impl_.admins_.Mutable(index); +} +inline void DataMessage_ClosedGroupControlMessage::set_admins(int index, const std::string& value) { + _impl_.admins_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) +} +inline void DataMessage_ClosedGroupControlMessage::set_admins(int index, std::string&& value) { + _impl_.admins_.Mutable(index)->assign(std::move(value)); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) +} +inline void DataMessage_ClosedGroupControlMessage::set_admins(int index, const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.admins_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set_char:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) +} +inline void DataMessage_ClosedGroupControlMessage::set_admins(int index, const void* value, size_t size) { + _impl_.admins_.Mutable(index)->assign( + reinterpret_cast(value), size); + // @@protoc_insertion_point(field_set_pointer:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) +} +inline std::string* DataMessage_ClosedGroupControlMessage::_internal_add_admins() { + return _impl_.admins_.Add(); +} +inline void DataMessage_ClosedGroupControlMessage::add_admins(const std::string& value) { + _impl_.admins_.Add()->assign(value); + // @@protoc_insertion_point(field_add:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) +} +inline void DataMessage_ClosedGroupControlMessage::add_admins(std::string&& value) { + _impl_.admins_.Add(std::move(value)); + // @@protoc_insertion_point(field_add:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) +} +inline void DataMessage_ClosedGroupControlMessage::add_admins(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.admins_.Add()->assign(value); + // @@protoc_insertion_point(field_add_char:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) +} +inline void DataMessage_ClosedGroupControlMessage::add_admins(const void* value, size_t size) { + _impl_.admins_.Add()->assign(reinterpret_cast(value), size); + // @@protoc_insertion_point(field_add_pointer:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& +DataMessage_ClosedGroupControlMessage::admins() const { + // @@protoc_insertion_point(field_list:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) + return _impl_.admins_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* +DataMessage_ClosedGroupControlMessage::mutable_admins() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.DataMessage.ClosedGroupControlMessage.admins) + return &_impl_.admins_; +} + +// repeated .SessionProtos.DataMessage.ClosedGroupControlMessage.KeyPairWrapper wrappers = 7; +inline int DataMessage_ClosedGroupControlMessage::_internal_wrappers_size() const { + return _impl_.wrappers_.size(); +} +inline int DataMessage_ClosedGroupControlMessage::wrappers_size() const { + return _internal_wrappers_size(); +} +inline void DataMessage_ClosedGroupControlMessage::clear_wrappers() { + _impl_.wrappers_.Clear(); +} +inline ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper* DataMessage_ClosedGroupControlMessage::mutable_wrappers(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.ClosedGroupControlMessage.wrappers) + return _impl_.wrappers_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper >* +DataMessage_ClosedGroupControlMessage::mutable_wrappers() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.DataMessage.ClosedGroupControlMessage.wrappers) + return &_impl_.wrappers_; +} +inline const ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper& DataMessage_ClosedGroupControlMessage::_internal_wrappers(int index) const { + return _impl_.wrappers_.Get(index); +} +inline const ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper& DataMessage_ClosedGroupControlMessage::wrappers(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.ClosedGroupControlMessage.wrappers) + return _internal_wrappers(index); +} +inline ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper* DataMessage_ClosedGroupControlMessage::_internal_add_wrappers() { + return _impl_.wrappers_.Add(); +} +inline ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper* DataMessage_ClosedGroupControlMessage::add_wrappers() { + ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper* _add = _internal_add_wrappers(); + // @@protoc_insertion_point(field_add:SessionProtos.DataMessage.ClosedGroupControlMessage.wrappers) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_ClosedGroupControlMessage_KeyPairWrapper >& +DataMessage_ClosedGroupControlMessage::wrappers() const { + // @@protoc_insertion_point(field_list:SessionProtos.DataMessage.ClosedGroupControlMessage.wrappers) + return _impl_.wrappers_; +} + +// optional uint32 expirationTimer = 8; +inline bool DataMessage_ClosedGroupControlMessage::_internal_has_expirationtimer() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool DataMessage_ClosedGroupControlMessage::has_expirationtimer() const { + return _internal_has_expirationtimer(); +} +inline void DataMessage_ClosedGroupControlMessage::clear_expirationtimer() { + _impl_.expirationtimer_ = 0u; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline uint32_t DataMessage_ClosedGroupControlMessage::_internal_expirationtimer() const { + return _impl_.expirationtimer_; +} +inline uint32_t DataMessage_ClosedGroupControlMessage::expirationtimer() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.ClosedGroupControlMessage.expirationTimer) + return _internal_expirationtimer(); +} +inline void DataMessage_ClosedGroupControlMessage::_internal_set_expirationtimer(uint32_t value) { + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.expirationtimer_ = value; +} +inline void DataMessage_ClosedGroupControlMessage::set_expirationtimer(uint32_t value) { + _internal_set_expirationtimer(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.ClosedGroupControlMessage.expirationTimer) +} + +// ------------------------------------------------------------------- + +// DataMessage + +// optional string body = 1; +inline bool DataMessage::_internal_has_body() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool DataMessage::has_body() const { + return _internal_has_body(); +} +inline void DataMessage::clear_body() { + _impl_.body_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& DataMessage::body() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.body) + return _internal_body(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage::set_body(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.body_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.body) +} +inline std::string* DataMessage::mutable_body() { + std::string* _s = _internal_mutable_body(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.body) + return _s; +} +inline const std::string& DataMessage::_internal_body() const { + return _impl_.body_.Get(); +} +inline void DataMessage::_internal_set_body(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.body_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage::_internal_mutable_body() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.body_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage::release_body() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.body) + if (!_internal_has_body()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.body_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.body_.IsDefault()) { + _impl_.body_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage::set_allocated_body(std::string* body) { + if (body != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.body_.SetAllocated(body, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.body_.IsDefault()) { + _impl_.body_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.body) +} + +// repeated .SessionProtos.AttachmentPointer attachments = 2; +inline int DataMessage::_internal_attachments_size() const { + return _impl_.attachments_.size(); +} +inline int DataMessage::attachments_size() const { + return _internal_attachments_size(); +} +inline void DataMessage::clear_attachments() { + _impl_.attachments_.Clear(); +} +inline ::SessionProtos::AttachmentPointer* DataMessage::mutable_attachments(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.attachments) + return _impl_.attachments_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::AttachmentPointer >* +DataMessage::mutable_attachments() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.DataMessage.attachments) + return &_impl_.attachments_; +} +inline const ::SessionProtos::AttachmentPointer& DataMessage::_internal_attachments(int index) const { + return _impl_.attachments_.Get(index); +} +inline const ::SessionProtos::AttachmentPointer& DataMessage::attachments(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.attachments) + return _internal_attachments(index); +} +inline ::SessionProtos::AttachmentPointer* DataMessage::_internal_add_attachments() { + return _impl_.attachments_.Add(); +} +inline ::SessionProtos::AttachmentPointer* DataMessage::add_attachments() { + ::SessionProtos::AttachmentPointer* _add = _internal_add_attachments(); + // @@protoc_insertion_point(field_add:SessionProtos.DataMessage.attachments) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::AttachmentPointer >& +DataMessage::attachments() const { + // @@protoc_insertion_point(field_list:SessionProtos.DataMessage.attachments) + return _impl_.attachments_; +} + +// optional uint32 flags = 4; +inline bool DataMessage::_internal_has_flags() const { + bool value = (_impl_._has_bits_[0] & 0x00000100u) != 0; + return value; +} +inline bool DataMessage::has_flags() const { + return _internal_has_flags(); +} +inline void DataMessage::clear_flags() { + _impl_.flags_ = 0u; + _impl_._has_bits_[0] &= ~0x00000100u; +} +inline uint32_t DataMessage::_internal_flags() const { + return _impl_.flags_; +} +inline uint32_t DataMessage::flags() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.flags) + return _internal_flags(); +} +inline void DataMessage::_internal_set_flags(uint32_t value) { + _impl_._has_bits_[0] |= 0x00000100u; + _impl_.flags_ = value; +} +inline void DataMessage::set_flags(uint32_t value) { + _internal_set_flags(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.flags) +} + +// optional uint32 expireTimer = 5; +inline bool DataMessage::_internal_has_expiretimer() const { + bool value = (_impl_._has_bits_[0] & 0x00000200u) != 0; + return value; +} +inline bool DataMessage::has_expiretimer() const { + return _internal_has_expiretimer(); +} +inline void DataMessage::clear_expiretimer() { + _impl_.expiretimer_ = 0u; + _impl_._has_bits_[0] &= ~0x00000200u; +} +inline uint32_t DataMessage::_internal_expiretimer() const { + return _impl_.expiretimer_; +} +inline uint32_t DataMessage::expiretimer() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.expireTimer) + return _internal_expiretimer(); +} +inline void DataMessage::_internal_set_expiretimer(uint32_t value) { + _impl_._has_bits_[0] |= 0x00000200u; + _impl_.expiretimer_ = value; +} +inline void DataMessage::set_expiretimer(uint32_t value) { + _internal_set_expiretimer(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.expireTimer) +} + +// optional bytes profileKey = 6; +inline bool DataMessage::_internal_has_profilekey() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool DataMessage::has_profilekey() const { + return _internal_has_profilekey(); +} +inline void DataMessage::clear_profilekey() { + _impl_.profilekey_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& DataMessage::profilekey() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.profileKey) + return _internal_profilekey(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage::set_profilekey(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.profilekey_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.profileKey) +} +inline std::string* DataMessage::mutable_profilekey() { + std::string* _s = _internal_mutable_profilekey(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.profileKey) + return _s; +} +inline const std::string& DataMessage::_internal_profilekey() const { + return _impl_.profilekey_.Get(); +} +inline void DataMessage::_internal_set_profilekey(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.profilekey_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage::_internal_mutable_profilekey() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.profilekey_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage::release_profilekey() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.profileKey) + if (!_internal_has_profilekey()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.profilekey_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilekey_.IsDefault()) { + _impl_.profilekey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage::set_allocated_profilekey(std::string* profilekey) { + if (profilekey != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.profilekey_.SetAllocated(profilekey, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilekey_.IsDefault()) { + _impl_.profilekey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.profileKey) +} + +// optional uint64 timestamp = 7; +inline bool DataMessage::_internal_has_timestamp() const { + bool value = (_impl_._has_bits_[0] & 0x00000400u) != 0; + return value; +} +inline bool DataMessage::has_timestamp() const { + return _internal_has_timestamp(); +} +inline void DataMessage::clear_timestamp() { + _impl_.timestamp_ = uint64_t{0u}; + _impl_._has_bits_[0] &= ~0x00000400u; +} +inline uint64_t DataMessage::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline uint64_t DataMessage::timestamp() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.timestamp) + return _internal_timestamp(); +} +inline void DataMessage::_internal_set_timestamp(uint64_t value) { + _impl_._has_bits_[0] |= 0x00000400u; + _impl_.timestamp_ = value; +} +inline void DataMessage::set_timestamp(uint64_t value) { + _internal_set_timestamp(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.timestamp) +} + +// optional .SessionProtos.DataMessage.Quote quote = 8; +inline bool DataMessage::_internal_has_quote() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + PROTOBUF_ASSUME(!value || _impl_.quote_ != nullptr); + return value; +} +inline bool DataMessage::has_quote() const { + return _internal_has_quote(); +} +inline void DataMessage::clear_quote() { + if (_impl_.quote_ != nullptr) _impl_.quote_->Clear(); + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline const ::SessionProtos::DataMessage_Quote& DataMessage::_internal_quote() const { + const ::SessionProtos::DataMessage_Quote* p = _impl_.quote_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_DataMessage_Quote_default_instance_); +} +inline const ::SessionProtos::DataMessage_Quote& DataMessage::quote() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.quote) + return _internal_quote(); +} +inline void DataMessage::unsafe_arena_set_allocated_quote( + ::SessionProtos::DataMessage_Quote* quote) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.quote_); + } + _impl_.quote_ = quote; + if (quote) { + _impl_._has_bits_[0] |= 0x00000008u; + } else { + _impl_._has_bits_[0] &= ~0x00000008u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.DataMessage.quote) +} +inline ::SessionProtos::DataMessage_Quote* DataMessage::release_quote() { + _impl_._has_bits_[0] &= ~0x00000008u; + ::SessionProtos::DataMessage_Quote* temp = _impl_.quote_; + _impl_.quote_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::DataMessage_Quote* DataMessage::unsafe_arena_release_quote() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.quote) + _impl_._has_bits_[0] &= ~0x00000008u; + ::SessionProtos::DataMessage_Quote* temp = _impl_.quote_; + _impl_.quote_ = nullptr; + return temp; +} +inline ::SessionProtos::DataMessage_Quote* DataMessage::_internal_mutable_quote() { + _impl_._has_bits_[0] |= 0x00000008u; + if (_impl_.quote_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::DataMessage_Quote>(GetArenaForAllocation()); + _impl_.quote_ = p; + } + return _impl_.quote_; +} +inline ::SessionProtos::DataMessage_Quote* DataMessage::mutable_quote() { + ::SessionProtos::DataMessage_Quote* _msg = _internal_mutable_quote(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.quote) + return _msg; +} +inline void DataMessage::set_allocated_quote(::SessionProtos::DataMessage_Quote* quote) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.quote_; + } + if (quote) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(quote); + if (message_arena != submessage_arena) { + quote = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, quote, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000008u; + } else { + _impl_._has_bits_[0] &= ~0x00000008u; + } + _impl_.quote_ = quote; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.quote) +} + +// repeated .SessionProtos.DataMessage.Preview preview = 10; +inline int DataMessage::_internal_preview_size() const { + return _impl_.preview_.size(); +} +inline int DataMessage::preview_size() const { + return _internal_preview_size(); +} +inline void DataMessage::clear_preview() { + _impl_.preview_.Clear(); +} +inline ::SessionProtos::DataMessage_Preview* DataMessage::mutable_preview(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.preview) + return _impl_.preview_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_Preview >* +DataMessage::mutable_preview() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.DataMessage.preview) + return &_impl_.preview_; +} +inline const ::SessionProtos::DataMessage_Preview& DataMessage::_internal_preview(int index) const { + return _impl_.preview_.Get(index); +} +inline const ::SessionProtos::DataMessage_Preview& DataMessage::preview(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.preview) + return _internal_preview(index); +} +inline ::SessionProtos::DataMessage_Preview* DataMessage::_internal_add_preview() { + return _impl_.preview_.Add(); +} +inline ::SessionProtos::DataMessage_Preview* DataMessage::add_preview() { + ::SessionProtos::DataMessage_Preview* _add = _internal_add_preview(); + // @@protoc_insertion_point(field_add:SessionProtos.DataMessage.preview) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::DataMessage_Preview >& +DataMessage::preview() const { + // @@protoc_insertion_point(field_list:SessionProtos.DataMessage.preview) + return _impl_.preview_; +} + +// optional .SessionProtos.DataMessage.Reaction reaction = 11; +inline bool DataMessage::_internal_has_reaction() const { + bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; + PROTOBUF_ASSUME(!value || _impl_.reaction_ != nullptr); + return value; +} +inline bool DataMessage::has_reaction() const { + return _internal_has_reaction(); +} +inline void DataMessage::clear_reaction() { + if (_impl_.reaction_ != nullptr) _impl_.reaction_->Clear(); + _impl_._has_bits_[0] &= ~0x00000010u; +} +inline const ::SessionProtos::DataMessage_Reaction& DataMessage::_internal_reaction() const { + const ::SessionProtos::DataMessage_Reaction* p = _impl_.reaction_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_DataMessage_Reaction_default_instance_); +} +inline const ::SessionProtos::DataMessage_Reaction& DataMessage::reaction() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.reaction) + return _internal_reaction(); +} +inline void DataMessage::unsafe_arena_set_allocated_reaction( + ::SessionProtos::DataMessage_Reaction* reaction) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.reaction_); + } + _impl_.reaction_ = reaction; + if (reaction) { + _impl_._has_bits_[0] |= 0x00000010u; + } else { + _impl_._has_bits_[0] &= ~0x00000010u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.DataMessage.reaction) +} +inline ::SessionProtos::DataMessage_Reaction* DataMessage::release_reaction() { + _impl_._has_bits_[0] &= ~0x00000010u; + ::SessionProtos::DataMessage_Reaction* temp = _impl_.reaction_; + _impl_.reaction_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::DataMessage_Reaction* DataMessage::unsafe_arena_release_reaction() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.reaction) + _impl_._has_bits_[0] &= ~0x00000010u; + ::SessionProtos::DataMessage_Reaction* temp = _impl_.reaction_; + _impl_.reaction_ = nullptr; + return temp; +} +inline ::SessionProtos::DataMessage_Reaction* DataMessage::_internal_mutable_reaction() { + _impl_._has_bits_[0] |= 0x00000010u; + if (_impl_.reaction_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::DataMessage_Reaction>(GetArenaForAllocation()); + _impl_.reaction_ = p; + } + return _impl_.reaction_; +} +inline ::SessionProtos::DataMessage_Reaction* DataMessage::mutable_reaction() { + ::SessionProtos::DataMessage_Reaction* _msg = _internal_mutable_reaction(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.reaction) + return _msg; +} +inline void DataMessage::set_allocated_reaction(::SessionProtos::DataMessage_Reaction* reaction) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.reaction_; + } + if (reaction) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(reaction); + if (message_arena != submessage_arena) { + reaction = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, reaction, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000010u; + } else { + _impl_._has_bits_[0] &= ~0x00000010u; + } + _impl_.reaction_ = reaction; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.reaction) +} + +// optional .SessionProtos.LokiProfile profile = 101; +inline bool DataMessage::_internal_has_profile() const { + bool value = (_impl_._has_bits_[0] & 0x00000020u) != 0; + PROTOBUF_ASSUME(!value || _impl_.profile_ != nullptr); + return value; +} +inline bool DataMessage::has_profile() const { + return _internal_has_profile(); +} +inline void DataMessage::clear_profile() { + if (_impl_.profile_ != nullptr) _impl_.profile_->Clear(); + _impl_._has_bits_[0] &= ~0x00000020u; +} +inline const ::SessionProtos::LokiProfile& DataMessage::_internal_profile() const { + const ::SessionProtos::LokiProfile* p = _impl_.profile_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_LokiProfile_default_instance_); +} +inline const ::SessionProtos::LokiProfile& DataMessage::profile() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.profile) + return _internal_profile(); +} +inline void DataMessage::unsafe_arena_set_allocated_profile( + ::SessionProtos::LokiProfile* profile) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.profile_); + } + _impl_.profile_ = profile; + if (profile) { + _impl_._has_bits_[0] |= 0x00000020u; + } else { + _impl_._has_bits_[0] &= ~0x00000020u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.DataMessage.profile) +} +inline ::SessionProtos::LokiProfile* DataMessage::release_profile() { + _impl_._has_bits_[0] &= ~0x00000020u; + ::SessionProtos::LokiProfile* temp = _impl_.profile_; + _impl_.profile_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::LokiProfile* DataMessage::unsafe_arena_release_profile() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.profile) + _impl_._has_bits_[0] &= ~0x00000020u; + ::SessionProtos::LokiProfile* temp = _impl_.profile_; + _impl_.profile_ = nullptr; + return temp; +} +inline ::SessionProtos::LokiProfile* DataMessage::_internal_mutable_profile() { + _impl_._has_bits_[0] |= 0x00000020u; + if (_impl_.profile_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::LokiProfile>(GetArenaForAllocation()); + _impl_.profile_ = p; + } + return _impl_.profile_; +} +inline ::SessionProtos::LokiProfile* DataMessage::mutable_profile() { + ::SessionProtos::LokiProfile* _msg = _internal_mutable_profile(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.profile) + return _msg; +} +inline void DataMessage::set_allocated_profile(::SessionProtos::LokiProfile* profile) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.profile_; + } + if (profile) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(profile); + if (message_arena != submessage_arena) { + profile = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, profile, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000020u; + } else { + _impl_._has_bits_[0] &= ~0x00000020u; + } + _impl_.profile_ = profile; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.profile) +} + +// optional .SessionProtos.DataMessage.OpenGroupInvitation openGroupInvitation = 102; +inline bool DataMessage::_internal_has_opengroupinvitation() const { + bool value = (_impl_._has_bits_[0] & 0x00000040u) != 0; + PROTOBUF_ASSUME(!value || _impl_.opengroupinvitation_ != nullptr); + return value; +} +inline bool DataMessage::has_opengroupinvitation() const { + return _internal_has_opengroupinvitation(); +} +inline void DataMessage::clear_opengroupinvitation() { + if (_impl_.opengroupinvitation_ != nullptr) _impl_.opengroupinvitation_->Clear(); + _impl_._has_bits_[0] &= ~0x00000040u; +} +inline const ::SessionProtos::DataMessage_OpenGroupInvitation& DataMessage::_internal_opengroupinvitation() const { + const ::SessionProtos::DataMessage_OpenGroupInvitation* p = _impl_.opengroupinvitation_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_DataMessage_OpenGroupInvitation_default_instance_); +} +inline const ::SessionProtos::DataMessage_OpenGroupInvitation& DataMessage::opengroupinvitation() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.openGroupInvitation) + return _internal_opengroupinvitation(); +} +inline void DataMessage::unsafe_arena_set_allocated_opengroupinvitation( + ::SessionProtos::DataMessage_OpenGroupInvitation* opengroupinvitation) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.opengroupinvitation_); + } + _impl_.opengroupinvitation_ = opengroupinvitation; + if (opengroupinvitation) { + _impl_._has_bits_[0] |= 0x00000040u; + } else { + _impl_._has_bits_[0] &= ~0x00000040u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.DataMessage.openGroupInvitation) +} +inline ::SessionProtos::DataMessage_OpenGroupInvitation* DataMessage::release_opengroupinvitation() { + _impl_._has_bits_[0] &= ~0x00000040u; + ::SessionProtos::DataMessage_OpenGroupInvitation* temp = _impl_.opengroupinvitation_; + _impl_.opengroupinvitation_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::DataMessage_OpenGroupInvitation* DataMessage::unsafe_arena_release_opengroupinvitation() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.openGroupInvitation) + _impl_._has_bits_[0] &= ~0x00000040u; + ::SessionProtos::DataMessage_OpenGroupInvitation* temp = _impl_.opengroupinvitation_; + _impl_.opengroupinvitation_ = nullptr; + return temp; +} +inline ::SessionProtos::DataMessage_OpenGroupInvitation* DataMessage::_internal_mutable_opengroupinvitation() { + _impl_._has_bits_[0] |= 0x00000040u; + if (_impl_.opengroupinvitation_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::DataMessage_OpenGroupInvitation>(GetArenaForAllocation()); + _impl_.opengroupinvitation_ = p; + } + return _impl_.opengroupinvitation_; +} +inline ::SessionProtos::DataMessage_OpenGroupInvitation* DataMessage::mutable_opengroupinvitation() { + ::SessionProtos::DataMessage_OpenGroupInvitation* _msg = _internal_mutable_opengroupinvitation(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.openGroupInvitation) + return _msg; +} +inline void DataMessage::set_allocated_opengroupinvitation(::SessionProtos::DataMessage_OpenGroupInvitation* opengroupinvitation) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.opengroupinvitation_; + } + if (opengroupinvitation) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(opengroupinvitation); + if (message_arena != submessage_arena) { + opengroupinvitation = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, opengroupinvitation, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000040u; + } else { + _impl_._has_bits_[0] &= ~0x00000040u; + } + _impl_.opengroupinvitation_ = opengroupinvitation; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.openGroupInvitation) +} + +// optional .SessionProtos.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; +inline bool DataMessage::_internal_has_closedgroupcontrolmessage() const { + bool value = (_impl_._has_bits_[0] & 0x00000080u) != 0; + PROTOBUF_ASSUME(!value || _impl_.closedgroupcontrolmessage_ != nullptr); + return value; +} +inline bool DataMessage::has_closedgroupcontrolmessage() const { + return _internal_has_closedgroupcontrolmessage(); +} +inline void DataMessage::clear_closedgroupcontrolmessage() { + if (_impl_.closedgroupcontrolmessage_ != nullptr) _impl_.closedgroupcontrolmessage_->Clear(); + _impl_._has_bits_[0] &= ~0x00000080u; +} +inline const ::SessionProtos::DataMessage_ClosedGroupControlMessage& DataMessage::_internal_closedgroupcontrolmessage() const { + const ::SessionProtos::DataMessage_ClosedGroupControlMessage* p = _impl_.closedgroupcontrolmessage_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_DataMessage_ClosedGroupControlMessage_default_instance_); +} +inline const ::SessionProtos::DataMessage_ClosedGroupControlMessage& DataMessage::closedgroupcontrolmessage() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.closedGroupControlMessage) + return _internal_closedgroupcontrolmessage(); +} +inline void DataMessage::unsafe_arena_set_allocated_closedgroupcontrolmessage( + ::SessionProtos::DataMessage_ClosedGroupControlMessage* closedgroupcontrolmessage) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.closedgroupcontrolmessage_); + } + _impl_.closedgroupcontrolmessage_ = closedgroupcontrolmessage; + if (closedgroupcontrolmessage) { + _impl_._has_bits_[0] |= 0x00000080u; + } else { + _impl_._has_bits_[0] &= ~0x00000080u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.DataMessage.closedGroupControlMessage) +} +inline ::SessionProtos::DataMessage_ClosedGroupControlMessage* DataMessage::release_closedgroupcontrolmessage() { + _impl_._has_bits_[0] &= ~0x00000080u; + ::SessionProtos::DataMessage_ClosedGroupControlMessage* temp = _impl_.closedgroupcontrolmessage_; + _impl_.closedgroupcontrolmessage_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::DataMessage_ClosedGroupControlMessage* DataMessage::unsafe_arena_release_closedgroupcontrolmessage() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.closedGroupControlMessage) + _impl_._has_bits_[0] &= ~0x00000080u; + ::SessionProtos::DataMessage_ClosedGroupControlMessage* temp = _impl_.closedgroupcontrolmessage_; + _impl_.closedgroupcontrolmessage_ = nullptr; + return temp; +} +inline ::SessionProtos::DataMessage_ClosedGroupControlMessage* DataMessage::_internal_mutable_closedgroupcontrolmessage() { + _impl_._has_bits_[0] |= 0x00000080u; + if (_impl_.closedgroupcontrolmessage_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::DataMessage_ClosedGroupControlMessage>(GetArenaForAllocation()); + _impl_.closedgroupcontrolmessage_ = p; + } + return _impl_.closedgroupcontrolmessage_; +} +inline ::SessionProtos::DataMessage_ClosedGroupControlMessage* DataMessage::mutable_closedgroupcontrolmessage() { + ::SessionProtos::DataMessage_ClosedGroupControlMessage* _msg = _internal_mutable_closedgroupcontrolmessage(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.closedGroupControlMessage) + return _msg; +} +inline void DataMessage::set_allocated_closedgroupcontrolmessage(::SessionProtos::DataMessage_ClosedGroupControlMessage* closedgroupcontrolmessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.closedgroupcontrolmessage_; + } + if (closedgroupcontrolmessage) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(closedgroupcontrolmessage); + if (message_arena != submessage_arena) { + closedgroupcontrolmessage = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, closedgroupcontrolmessage, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000080u; + } else { + _impl_._has_bits_[0] &= ~0x00000080u; + } + _impl_.closedgroupcontrolmessage_ = closedgroupcontrolmessage; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.closedGroupControlMessage) +} + +// optional string syncTarget = 105; +inline bool DataMessage::_internal_has_synctarget() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool DataMessage::has_synctarget() const { + return _internal_has_synctarget(); +} +inline void DataMessage::clear_synctarget() { + _impl_.synctarget_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const std::string& DataMessage::synctarget() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.syncTarget) + return _internal_synctarget(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void DataMessage::set_synctarget(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.synctarget_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.syncTarget) +} +inline std::string* DataMessage::mutable_synctarget() { + std::string* _s = _internal_mutable_synctarget(); + // @@protoc_insertion_point(field_mutable:SessionProtos.DataMessage.syncTarget) + return _s; +} +inline const std::string& DataMessage::_internal_synctarget() const { + return _impl_.synctarget_.Get(); +} +inline void DataMessage::_internal_set_synctarget(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.synctarget_.Set(value, GetArenaForAllocation()); +} +inline std::string* DataMessage::_internal_mutable_synctarget() { + _impl_._has_bits_[0] |= 0x00000004u; + return _impl_.synctarget_.Mutable(GetArenaForAllocation()); +} +inline std::string* DataMessage::release_synctarget() { + // @@protoc_insertion_point(field_release:SessionProtos.DataMessage.syncTarget) + if (!_internal_has_synctarget()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000004u; + auto* p = _impl_.synctarget_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.synctarget_.IsDefault()) { + _impl_.synctarget_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void DataMessage::set_allocated_synctarget(std::string* synctarget) { + if (synctarget != nullptr) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.synctarget_.SetAllocated(synctarget, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.synctarget_.IsDefault()) { + _impl_.synctarget_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.DataMessage.syncTarget) +} + +// optional bool blocksCommunityMessageRequests = 106; +inline bool DataMessage::_internal_has_blockscommunitymessagerequests() const { + bool value = (_impl_._has_bits_[0] & 0x00000800u) != 0; + return value; +} +inline bool DataMessage::has_blockscommunitymessagerequests() const { + return _internal_has_blockscommunitymessagerequests(); +} +inline void DataMessage::clear_blockscommunitymessagerequests() { + _impl_.blockscommunitymessagerequests_ = false; + _impl_._has_bits_[0] &= ~0x00000800u; +} +inline bool DataMessage::_internal_blockscommunitymessagerequests() const { + return _impl_.blockscommunitymessagerequests_; +} +inline bool DataMessage::blockscommunitymessagerequests() const { + // @@protoc_insertion_point(field_get:SessionProtos.DataMessage.blocksCommunityMessageRequests) + return _internal_blockscommunitymessagerequests(); +} +inline void DataMessage::_internal_set_blockscommunitymessagerequests(bool value) { + _impl_._has_bits_[0] |= 0x00000800u; + _impl_.blockscommunitymessagerequests_ = value; +} +inline void DataMessage::set_blockscommunitymessagerequests(bool value) { + _internal_set_blockscommunitymessagerequests(value); + // @@protoc_insertion_point(field_set:SessionProtos.DataMessage.blocksCommunityMessageRequests) +} + +// ------------------------------------------------------------------- + +// ConfigurationMessage_ClosedGroup + +// optional bytes publicKey = 1; +inline bool ConfigurationMessage_ClosedGroup::_internal_has_publickey() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool ConfigurationMessage_ClosedGroup::has_publickey() const { + return _internal_has_publickey(); +} +inline void ConfigurationMessage_ClosedGroup::clear_publickey() { + _impl_.publickey_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& ConfigurationMessage_ClosedGroup::publickey() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.ClosedGroup.publicKey) + return _internal_publickey(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ConfigurationMessage_ClosedGroup::set_publickey(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.publickey_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.ClosedGroup.publicKey) +} +inline std::string* ConfigurationMessage_ClosedGroup::mutable_publickey() { + std::string* _s = _internal_mutable_publickey(); + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.ClosedGroup.publicKey) + return _s; +} +inline const std::string& ConfigurationMessage_ClosedGroup::_internal_publickey() const { + return _impl_.publickey_.Get(); +} +inline void ConfigurationMessage_ClosedGroup::_internal_set_publickey(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.publickey_.Set(value, GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage_ClosedGroup::_internal_mutable_publickey() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.publickey_.Mutable(GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage_ClosedGroup::release_publickey() { + // @@protoc_insertion_point(field_release:SessionProtos.ConfigurationMessage.ClosedGroup.publicKey) + if (!_internal_has_publickey()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.publickey_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.publickey_.IsDefault()) { + _impl_.publickey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ConfigurationMessage_ClosedGroup::set_allocated_publickey(std::string* publickey) { + if (publickey != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.publickey_.SetAllocated(publickey, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.publickey_.IsDefault()) { + _impl_.publickey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.ConfigurationMessage.ClosedGroup.publicKey) +} + +// optional string name = 2; +inline bool ConfigurationMessage_ClosedGroup::_internal_has_name() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool ConfigurationMessage_ClosedGroup::has_name() const { + return _internal_has_name(); +} +inline void ConfigurationMessage_ClosedGroup::clear_name() { + _impl_.name_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& ConfigurationMessage_ClosedGroup::name() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.ClosedGroup.name) + return _internal_name(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ConfigurationMessage_ClosedGroup::set_name(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.name_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.ClosedGroup.name) +} +inline std::string* ConfigurationMessage_ClosedGroup::mutable_name() { + std::string* _s = _internal_mutable_name(); + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.ClosedGroup.name) + return _s; +} +inline const std::string& ConfigurationMessage_ClosedGroup::_internal_name() const { + return _impl_.name_.Get(); +} +inline void ConfigurationMessage_ClosedGroup::_internal_set_name(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.name_.Set(value, GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage_ClosedGroup::_internal_mutable_name() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.name_.Mutable(GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage_ClosedGroup::release_name() { + // @@protoc_insertion_point(field_release:SessionProtos.ConfigurationMessage.ClosedGroup.name) + if (!_internal_has_name()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.name_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ConfigurationMessage_ClosedGroup::set_allocated_name(std::string* name) { + if (name != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.name_.SetAllocated(name, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.ConfigurationMessage.ClosedGroup.name) +} + +// optional .SessionProtos.KeyPair encryptionKeyPair = 3; +inline bool ConfigurationMessage_ClosedGroup::_internal_has_encryptionkeypair() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + PROTOBUF_ASSUME(!value || _impl_.encryptionkeypair_ != nullptr); + return value; +} +inline bool ConfigurationMessage_ClosedGroup::has_encryptionkeypair() const { + return _internal_has_encryptionkeypair(); +} +inline void ConfigurationMessage_ClosedGroup::clear_encryptionkeypair() { + if (_impl_.encryptionkeypair_ != nullptr) _impl_.encryptionkeypair_->Clear(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const ::SessionProtos::KeyPair& ConfigurationMessage_ClosedGroup::_internal_encryptionkeypair() const { + const ::SessionProtos::KeyPair* p = _impl_.encryptionkeypair_; + return p != nullptr ? *p : reinterpret_cast( + ::SessionProtos::_KeyPair_default_instance_); +} +inline const ::SessionProtos::KeyPair& ConfigurationMessage_ClosedGroup::encryptionkeypair() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.ClosedGroup.encryptionKeyPair) + return _internal_encryptionkeypair(); +} +inline void ConfigurationMessage_ClosedGroup::unsafe_arena_set_allocated_encryptionkeypair( + ::SessionProtos::KeyPair* encryptionkeypair) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.encryptionkeypair_); + } + _impl_.encryptionkeypair_ = encryptionkeypair; + if (encryptionkeypair) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:SessionProtos.ConfigurationMessage.ClosedGroup.encryptionKeyPair) +} +inline ::SessionProtos::KeyPair* ConfigurationMessage_ClosedGroup::release_encryptionkeypair() { + _impl_._has_bits_[0] &= ~0x00000004u; + ::SessionProtos::KeyPair* temp = _impl_.encryptionkeypair_; + _impl_.encryptionkeypair_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::SessionProtos::KeyPair* ConfigurationMessage_ClosedGroup::unsafe_arena_release_encryptionkeypair() { + // @@protoc_insertion_point(field_release:SessionProtos.ConfigurationMessage.ClosedGroup.encryptionKeyPair) + _impl_._has_bits_[0] &= ~0x00000004u; + ::SessionProtos::KeyPair* temp = _impl_.encryptionkeypair_; + _impl_.encryptionkeypair_ = nullptr; + return temp; +} +inline ::SessionProtos::KeyPair* ConfigurationMessage_ClosedGroup::_internal_mutable_encryptionkeypair() { + _impl_._has_bits_[0] |= 0x00000004u; + if (_impl_.encryptionkeypair_ == nullptr) { + auto* p = CreateMaybeMessage<::SessionProtos::KeyPair>(GetArenaForAllocation()); + _impl_.encryptionkeypair_ = p; + } + return _impl_.encryptionkeypair_; +} +inline ::SessionProtos::KeyPair* ConfigurationMessage_ClosedGroup::mutable_encryptionkeypair() { + ::SessionProtos::KeyPair* _msg = _internal_mutable_encryptionkeypair(); + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.ClosedGroup.encryptionKeyPair) + return _msg; +} +inline void ConfigurationMessage_ClosedGroup::set_allocated_encryptionkeypair(::SessionProtos::KeyPair* encryptionkeypair) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.encryptionkeypair_; + } + if (encryptionkeypair) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(encryptionkeypair); + if (message_arena != submessage_arena) { + encryptionkeypair = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, encryptionkeypair, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.encryptionkeypair_ = encryptionkeypair; + // @@protoc_insertion_point(field_set_allocated:SessionProtos.ConfigurationMessage.ClosedGroup.encryptionKeyPair) +} + +// repeated bytes members = 4; +inline int ConfigurationMessage_ClosedGroup::_internal_members_size() const { + return _impl_.members_.size(); +} +inline int ConfigurationMessage_ClosedGroup::members_size() const { + return _internal_members_size(); +} +inline void ConfigurationMessage_ClosedGroup::clear_members() { + _impl_.members_.Clear(); +} +inline std::string* ConfigurationMessage_ClosedGroup::add_members() { + std::string* _s = _internal_add_members(); + // @@protoc_insertion_point(field_add_mutable:SessionProtos.ConfigurationMessage.ClosedGroup.members) + return _s; +} +inline const std::string& ConfigurationMessage_ClosedGroup::_internal_members(int index) const { + return _impl_.members_.Get(index); +} +inline const std::string& ConfigurationMessage_ClosedGroup::members(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.ClosedGroup.members) + return _internal_members(index); +} +inline std::string* ConfigurationMessage_ClosedGroup::mutable_members(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.ClosedGroup.members) + return _impl_.members_.Mutable(index); +} +inline void ConfigurationMessage_ClosedGroup::set_members(int index, const std::string& value) { + _impl_.members_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.ClosedGroup.members) +} +inline void ConfigurationMessage_ClosedGroup::set_members(int index, std::string&& value) { + _impl_.members_.Mutable(index)->assign(std::move(value)); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.ClosedGroup.members) +} +inline void ConfigurationMessage_ClosedGroup::set_members(int index, const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.members_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set_char:SessionProtos.ConfigurationMessage.ClosedGroup.members) +} +inline void ConfigurationMessage_ClosedGroup::set_members(int index, const void* value, size_t size) { + _impl_.members_.Mutable(index)->assign( + reinterpret_cast(value), size); + // @@protoc_insertion_point(field_set_pointer:SessionProtos.ConfigurationMessage.ClosedGroup.members) +} +inline std::string* ConfigurationMessage_ClosedGroup::_internal_add_members() { + return _impl_.members_.Add(); +} +inline void ConfigurationMessage_ClosedGroup::add_members(const std::string& value) { + _impl_.members_.Add()->assign(value); + // @@protoc_insertion_point(field_add:SessionProtos.ConfigurationMessage.ClosedGroup.members) +} +inline void ConfigurationMessage_ClosedGroup::add_members(std::string&& value) { + _impl_.members_.Add(std::move(value)); + // @@protoc_insertion_point(field_add:SessionProtos.ConfigurationMessage.ClosedGroup.members) +} +inline void ConfigurationMessage_ClosedGroup::add_members(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.members_.Add()->assign(value); + // @@protoc_insertion_point(field_add_char:SessionProtos.ConfigurationMessage.ClosedGroup.members) +} +inline void ConfigurationMessage_ClosedGroup::add_members(const void* value, size_t size) { + _impl_.members_.Add()->assign(reinterpret_cast(value), size); + // @@protoc_insertion_point(field_add_pointer:SessionProtos.ConfigurationMessage.ClosedGroup.members) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& +ConfigurationMessage_ClosedGroup::members() const { + // @@protoc_insertion_point(field_list:SessionProtos.ConfigurationMessage.ClosedGroup.members) + return _impl_.members_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* +ConfigurationMessage_ClosedGroup::mutable_members() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.ConfigurationMessage.ClosedGroup.members) + return &_impl_.members_; +} + +// repeated bytes admins = 5; +inline int ConfigurationMessage_ClosedGroup::_internal_admins_size() const { + return _impl_.admins_.size(); +} +inline int ConfigurationMessage_ClosedGroup::admins_size() const { + return _internal_admins_size(); +} +inline void ConfigurationMessage_ClosedGroup::clear_admins() { + _impl_.admins_.Clear(); +} +inline std::string* ConfigurationMessage_ClosedGroup::add_admins() { + std::string* _s = _internal_add_admins(); + // @@protoc_insertion_point(field_add_mutable:SessionProtos.ConfigurationMessage.ClosedGroup.admins) + return _s; +} +inline const std::string& ConfigurationMessage_ClosedGroup::_internal_admins(int index) const { + return _impl_.admins_.Get(index); +} +inline const std::string& ConfigurationMessage_ClosedGroup::admins(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.ClosedGroup.admins) + return _internal_admins(index); +} +inline std::string* ConfigurationMessage_ClosedGroup::mutable_admins(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.ClosedGroup.admins) + return _impl_.admins_.Mutable(index); +} +inline void ConfigurationMessage_ClosedGroup::set_admins(int index, const std::string& value) { + _impl_.admins_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.ClosedGroup.admins) +} +inline void ConfigurationMessage_ClosedGroup::set_admins(int index, std::string&& value) { + _impl_.admins_.Mutable(index)->assign(std::move(value)); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.ClosedGroup.admins) +} +inline void ConfigurationMessage_ClosedGroup::set_admins(int index, const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.admins_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set_char:SessionProtos.ConfigurationMessage.ClosedGroup.admins) +} +inline void ConfigurationMessage_ClosedGroup::set_admins(int index, const void* value, size_t size) { + _impl_.admins_.Mutable(index)->assign( + reinterpret_cast(value), size); + // @@protoc_insertion_point(field_set_pointer:SessionProtos.ConfigurationMessage.ClosedGroup.admins) +} +inline std::string* ConfigurationMessage_ClosedGroup::_internal_add_admins() { + return _impl_.admins_.Add(); +} +inline void ConfigurationMessage_ClosedGroup::add_admins(const std::string& value) { + _impl_.admins_.Add()->assign(value); + // @@protoc_insertion_point(field_add:SessionProtos.ConfigurationMessage.ClosedGroup.admins) +} +inline void ConfigurationMessage_ClosedGroup::add_admins(std::string&& value) { + _impl_.admins_.Add(std::move(value)); + // @@protoc_insertion_point(field_add:SessionProtos.ConfigurationMessage.ClosedGroup.admins) +} +inline void ConfigurationMessage_ClosedGroup::add_admins(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.admins_.Add()->assign(value); + // @@protoc_insertion_point(field_add_char:SessionProtos.ConfigurationMessage.ClosedGroup.admins) +} +inline void ConfigurationMessage_ClosedGroup::add_admins(const void* value, size_t size) { + _impl_.admins_.Add()->assign(reinterpret_cast(value), size); + // @@protoc_insertion_point(field_add_pointer:SessionProtos.ConfigurationMessage.ClosedGroup.admins) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& +ConfigurationMessage_ClosedGroup::admins() const { + // @@protoc_insertion_point(field_list:SessionProtos.ConfigurationMessage.ClosedGroup.admins) + return _impl_.admins_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* +ConfigurationMessage_ClosedGroup::mutable_admins() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.ConfigurationMessage.ClosedGroup.admins) + return &_impl_.admins_; +} + +// optional uint32 expirationTimer = 6; +inline bool ConfigurationMessage_ClosedGroup::_internal_has_expirationtimer() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool ConfigurationMessage_ClosedGroup::has_expirationtimer() const { + return _internal_has_expirationtimer(); +} +inline void ConfigurationMessage_ClosedGroup::clear_expirationtimer() { + _impl_.expirationtimer_ = 0u; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline uint32_t ConfigurationMessage_ClosedGroup::_internal_expirationtimer() const { + return _impl_.expirationtimer_; +} +inline uint32_t ConfigurationMessage_ClosedGroup::expirationtimer() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.ClosedGroup.expirationTimer) + return _internal_expirationtimer(); +} +inline void ConfigurationMessage_ClosedGroup::_internal_set_expirationtimer(uint32_t value) { + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.expirationtimer_ = value; +} +inline void ConfigurationMessage_ClosedGroup::set_expirationtimer(uint32_t value) { + _internal_set_expirationtimer(value); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.ClosedGroup.expirationTimer) +} + +// ------------------------------------------------------------------- + +// ConfigurationMessage_Contact + +// required bytes publicKey = 1; +inline bool ConfigurationMessage_Contact::_internal_has_publickey() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool ConfigurationMessage_Contact::has_publickey() const { + return _internal_has_publickey(); +} +inline void ConfigurationMessage_Contact::clear_publickey() { + _impl_.publickey_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& ConfigurationMessage_Contact::publickey() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.Contact.publicKey) + return _internal_publickey(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ConfigurationMessage_Contact::set_publickey(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.publickey_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.Contact.publicKey) +} +inline std::string* ConfigurationMessage_Contact::mutable_publickey() { + std::string* _s = _internal_mutable_publickey(); + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.Contact.publicKey) + return _s; +} +inline const std::string& ConfigurationMessage_Contact::_internal_publickey() const { + return _impl_.publickey_.Get(); +} +inline void ConfigurationMessage_Contact::_internal_set_publickey(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.publickey_.Set(value, GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage_Contact::_internal_mutable_publickey() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.publickey_.Mutable(GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage_Contact::release_publickey() { + // @@protoc_insertion_point(field_release:SessionProtos.ConfigurationMessage.Contact.publicKey) + if (!_internal_has_publickey()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.publickey_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.publickey_.IsDefault()) { + _impl_.publickey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ConfigurationMessage_Contact::set_allocated_publickey(std::string* publickey) { + if (publickey != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.publickey_.SetAllocated(publickey, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.publickey_.IsDefault()) { + _impl_.publickey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.ConfigurationMessage.Contact.publicKey) +} + +// required string name = 2; +inline bool ConfigurationMessage_Contact::_internal_has_name() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool ConfigurationMessage_Contact::has_name() const { + return _internal_has_name(); +} +inline void ConfigurationMessage_Contact::clear_name() { + _impl_.name_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& ConfigurationMessage_Contact::name() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.Contact.name) + return _internal_name(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ConfigurationMessage_Contact::set_name(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.name_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.Contact.name) +} +inline std::string* ConfigurationMessage_Contact::mutable_name() { + std::string* _s = _internal_mutable_name(); + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.Contact.name) + return _s; +} +inline const std::string& ConfigurationMessage_Contact::_internal_name() const { + return _impl_.name_.Get(); +} +inline void ConfigurationMessage_Contact::_internal_set_name(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.name_.Set(value, GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage_Contact::_internal_mutable_name() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.name_.Mutable(GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage_Contact::release_name() { + // @@protoc_insertion_point(field_release:SessionProtos.ConfigurationMessage.Contact.name) + if (!_internal_has_name()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.name_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ConfigurationMessage_Contact::set_allocated_name(std::string* name) { + if (name != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.name_.SetAllocated(name, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.name_.IsDefault()) { + _impl_.name_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.ConfigurationMessage.Contact.name) +} + +// optional string profilePicture = 3; +inline bool ConfigurationMessage_Contact::_internal_has_profilepicture() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool ConfigurationMessage_Contact::has_profilepicture() const { + return _internal_has_profilepicture(); +} +inline void ConfigurationMessage_Contact::clear_profilepicture() { + _impl_.profilepicture_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const std::string& ConfigurationMessage_Contact::profilepicture() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.Contact.profilePicture) + return _internal_profilepicture(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ConfigurationMessage_Contact::set_profilepicture(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.profilepicture_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.Contact.profilePicture) +} +inline std::string* ConfigurationMessage_Contact::mutable_profilepicture() { + std::string* _s = _internal_mutable_profilepicture(); + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.Contact.profilePicture) + return _s; +} +inline const std::string& ConfigurationMessage_Contact::_internal_profilepicture() const { + return _impl_.profilepicture_.Get(); +} +inline void ConfigurationMessage_Contact::_internal_set_profilepicture(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.profilepicture_.Set(value, GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage_Contact::_internal_mutable_profilepicture() { + _impl_._has_bits_[0] |= 0x00000004u; + return _impl_.profilepicture_.Mutable(GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage_Contact::release_profilepicture() { + // @@protoc_insertion_point(field_release:SessionProtos.ConfigurationMessage.Contact.profilePicture) + if (!_internal_has_profilepicture()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000004u; + auto* p = _impl_.profilepicture_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilepicture_.IsDefault()) { + _impl_.profilepicture_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ConfigurationMessage_Contact::set_allocated_profilepicture(std::string* profilepicture) { + if (profilepicture != nullptr) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.profilepicture_.SetAllocated(profilepicture, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilepicture_.IsDefault()) { + _impl_.profilepicture_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.ConfigurationMessage.Contact.profilePicture) +} + +// optional bytes profileKey = 4; +inline bool ConfigurationMessage_Contact::_internal_has_profilekey() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool ConfigurationMessage_Contact::has_profilekey() const { + return _internal_has_profilekey(); +} +inline void ConfigurationMessage_Contact::clear_profilekey() { + _impl_.profilekey_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline const std::string& ConfigurationMessage_Contact::profilekey() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.Contact.profileKey) + return _internal_profilekey(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ConfigurationMessage_Contact::set_profilekey(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.profilekey_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.Contact.profileKey) +} +inline std::string* ConfigurationMessage_Contact::mutable_profilekey() { + std::string* _s = _internal_mutable_profilekey(); + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.Contact.profileKey) + return _s; +} +inline const std::string& ConfigurationMessage_Contact::_internal_profilekey() const { + return _impl_.profilekey_.Get(); +} +inline void ConfigurationMessage_Contact::_internal_set_profilekey(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.profilekey_.Set(value, GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage_Contact::_internal_mutable_profilekey() { + _impl_._has_bits_[0] |= 0x00000008u; + return _impl_.profilekey_.Mutable(GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage_Contact::release_profilekey() { + // @@protoc_insertion_point(field_release:SessionProtos.ConfigurationMessage.Contact.profileKey) + if (!_internal_has_profilekey()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000008u; + auto* p = _impl_.profilekey_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilekey_.IsDefault()) { + _impl_.profilekey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ConfigurationMessage_Contact::set_allocated_profilekey(std::string* profilekey) { + if (profilekey != nullptr) { + _impl_._has_bits_[0] |= 0x00000008u; + } else { + _impl_._has_bits_[0] &= ~0x00000008u; + } + _impl_.profilekey_.SetAllocated(profilekey, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilekey_.IsDefault()) { + _impl_.profilekey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.ConfigurationMessage.Contact.profileKey) +} + +// optional bool isApproved = 5; +inline bool ConfigurationMessage_Contact::_internal_has_isapproved() const { + bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; + return value; +} +inline bool ConfigurationMessage_Contact::has_isapproved() const { + return _internal_has_isapproved(); +} +inline void ConfigurationMessage_Contact::clear_isapproved() { + _impl_.isapproved_ = false; + _impl_._has_bits_[0] &= ~0x00000010u; +} +inline bool ConfigurationMessage_Contact::_internal_isapproved() const { + return _impl_.isapproved_; +} +inline bool ConfigurationMessage_Contact::isapproved() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.Contact.isApproved) + return _internal_isapproved(); +} +inline void ConfigurationMessage_Contact::_internal_set_isapproved(bool value) { + _impl_._has_bits_[0] |= 0x00000010u; + _impl_.isapproved_ = value; +} +inline void ConfigurationMessage_Contact::set_isapproved(bool value) { + _internal_set_isapproved(value); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.Contact.isApproved) +} + +// optional bool isBlocked = 6; +inline bool ConfigurationMessage_Contact::_internal_has_isblocked() const { + bool value = (_impl_._has_bits_[0] & 0x00000020u) != 0; + return value; +} +inline bool ConfigurationMessage_Contact::has_isblocked() const { + return _internal_has_isblocked(); +} +inline void ConfigurationMessage_Contact::clear_isblocked() { + _impl_.isblocked_ = false; + _impl_._has_bits_[0] &= ~0x00000020u; +} +inline bool ConfigurationMessage_Contact::_internal_isblocked() const { + return _impl_.isblocked_; +} +inline bool ConfigurationMessage_Contact::isblocked() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.Contact.isBlocked) + return _internal_isblocked(); +} +inline void ConfigurationMessage_Contact::_internal_set_isblocked(bool value) { + _impl_._has_bits_[0] |= 0x00000020u; + _impl_.isblocked_ = value; +} +inline void ConfigurationMessage_Contact::set_isblocked(bool value) { + _internal_set_isblocked(value); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.Contact.isBlocked) +} + +// optional bool didApproveMe = 7; +inline bool ConfigurationMessage_Contact::_internal_has_didapproveme() const { + bool value = (_impl_._has_bits_[0] & 0x00000040u) != 0; + return value; +} +inline bool ConfigurationMessage_Contact::has_didapproveme() const { + return _internal_has_didapproveme(); +} +inline void ConfigurationMessage_Contact::clear_didapproveme() { + _impl_.didapproveme_ = false; + _impl_._has_bits_[0] &= ~0x00000040u; +} +inline bool ConfigurationMessage_Contact::_internal_didapproveme() const { + return _impl_.didapproveme_; +} +inline bool ConfigurationMessage_Contact::didapproveme() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.Contact.didApproveMe) + return _internal_didapproveme(); +} +inline void ConfigurationMessage_Contact::_internal_set_didapproveme(bool value) { + _impl_._has_bits_[0] |= 0x00000040u; + _impl_.didapproveme_ = value; +} +inline void ConfigurationMessage_Contact::set_didapproveme(bool value) { + _internal_set_didapproveme(value); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.Contact.didApproveMe) +} + +// ------------------------------------------------------------------- + +// ConfigurationMessage + +// repeated .SessionProtos.ConfigurationMessage.ClosedGroup closedGroups = 1; +inline int ConfigurationMessage::_internal_closedgroups_size() const { + return _impl_.closedgroups_.size(); +} +inline int ConfigurationMessage::closedgroups_size() const { + return _internal_closedgroups_size(); +} +inline void ConfigurationMessage::clear_closedgroups() { + _impl_.closedgroups_.Clear(); +} +inline ::SessionProtos::ConfigurationMessage_ClosedGroup* ConfigurationMessage::mutable_closedgroups(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.closedGroups) + return _impl_.closedgroups_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::ConfigurationMessage_ClosedGroup >* +ConfigurationMessage::mutable_closedgroups() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.ConfigurationMessage.closedGroups) + return &_impl_.closedgroups_; +} +inline const ::SessionProtos::ConfigurationMessage_ClosedGroup& ConfigurationMessage::_internal_closedgroups(int index) const { + return _impl_.closedgroups_.Get(index); +} +inline const ::SessionProtos::ConfigurationMessage_ClosedGroup& ConfigurationMessage::closedgroups(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.closedGroups) + return _internal_closedgroups(index); +} +inline ::SessionProtos::ConfigurationMessage_ClosedGroup* ConfigurationMessage::_internal_add_closedgroups() { + return _impl_.closedgroups_.Add(); +} +inline ::SessionProtos::ConfigurationMessage_ClosedGroup* ConfigurationMessage::add_closedgroups() { + ::SessionProtos::ConfigurationMessage_ClosedGroup* _add = _internal_add_closedgroups(); + // @@protoc_insertion_point(field_add:SessionProtos.ConfigurationMessage.closedGroups) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::ConfigurationMessage_ClosedGroup >& +ConfigurationMessage::closedgroups() const { + // @@protoc_insertion_point(field_list:SessionProtos.ConfigurationMessage.closedGroups) + return _impl_.closedgroups_; +} + +// repeated string openGroups = 2; +inline int ConfigurationMessage::_internal_opengroups_size() const { + return _impl_.opengroups_.size(); +} +inline int ConfigurationMessage::opengroups_size() const { + return _internal_opengroups_size(); +} +inline void ConfigurationMessage::clear_opengroups() { + _impl_.opengroups_.Clear(); +} +inline std::string* ConfigurationMessage::add_opengroups() { + std::string* _s = _internal_add_opengroups(); + // @@protoc_insertion_point(field_add_mutable:SessionProtos.ConfigurationMessage.openGroups) + return _s; +} +inline const std::string& ConfigurationMessage::_internal_opengroups(int index) const { + return _impl_.opengroups_.Get(index); +} +inline const std::string& ConfigurationMessage::opengroups(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.openGroups) + return _internal_opengroups(index); +} +inline std::string* ConfigurationMessage::mutable_opengroups(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.openGroups) + return _impl_.opengroups_.Mutable(index); +} +inline void ConfigurationMessage::set_opengroups(int index, const std::string& value) { + _impl_.opengroups_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.openGroups) +} +inline void ConfigurationMessage::set_opengroups(int index, std::string&& value) { + _impl_.opengroups_.Mutable(index)->assign(std::move(value)); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.openGroups) +} +inline void ConfigurationMessage::set_opengroups(int index, const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.opengroups_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set_char:SessionProtos.ConfigurationMessage.openGroups) +} +inline void ConfigurationMessage::set_opengroups(int index, const char* value, size_t size) { + _impl_.opengroups_.Mutable(index)->assign( + reinterpret_cast(value), size); + // @@protoc_insertion_point(field_set_pointer:SessionProtos.ConfigurationMessage.openGroups) +} +inline std::string* ConfigurationMessage::_internal_add_opengroups() { + return _impl_.opengroups_.Add(); +} +inline void ConfigurationMessage::add_opengroups(const std::string& value) { + _impl_.opengroups_.Add()->assign(value); + // @@protoc_insertion_point(field_add:SessionProtos.ConfigurationMessage.openGroups) +} +inline void ConfigurationMessage::add_opengroups(std::string&& value) { + _impl_.opengroups_.Add(std::move(value)); + // @@protoc_insertion_point(field_add:SessionProtos.ConfigurationMessage.openGroups) +} +inline void ConfigurationMessage::add_opengroups(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.opengroups_.Add()->assign(value); + // @@protoc_insertion_point(field_add_char:SessionProtos.ConfigurationMessage.openGroups) +} +inline void ConfigurationMessage::add_opengroups(const char* value, size_t size) { + _impl_.opengroups_.Add()->assign(reinterpret_cast(value), size); + // @@protoc_insertion_point(field_add_pointer:SessionProtos.ConfigurationMessage.openGroups) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& +ConfigurationMessage::opengroups() const { + // @@protoc_insertion_point(field_list:SessionProtos.ConfigurationMessage.openGroups) + return _impl_.opengroups_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* +ConfigurationMessage::mutable_opengroups() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.ConfigurationMessage.openGroups) + return &_impl_.opengroups_; +} + +// optional string displayName = 3; +inline bool ConfigurationMessage::_internal_has_displayname() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool ConfigurationMessage::has_displayname() const { + return _internal_has_displayname(); +} +inline void ConfigurationMessage::clear_displayname() { + _impl_.displayname_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& ConfigurationMessage::displayname() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.displayName) + return _internal_displayname(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ConfigurationMessage::set_displayname(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.displayname_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.displayName) +} +inline std::string* ConfigurationMessage::mutable_displayname() { + std::string* _s = _internal_mutable_displayname(); + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.displayName) + return _s; +} +inline const std::string& ConfigurationMessage::_internal_displayname() const { + return _impl_.displayname_.Get(); +} +inline void ConfigurationMessage::_internal_set_displayname(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.displayname_.Set(value, GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage::_internal_mutable_displayname() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.displayname_.Mutable(GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage::release_displayname() { + // @@protoc_insertion_point(field_release:SessionProtos.ConfigurationMessage.displayName) + if (!_internal_has_displayname()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.displayname_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.displayname_.IsDefault()) { + _impl_.displayname_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ConfigurationMessage::set_allocated_displayname(std::string* displayname) { + if (displayname != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.displayname_.SetAllocated(displayname, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.displayname_.IsDefault()) { + _impl_.displayname_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.ConfigurationMessage.displayName) +} + +// optional string profilePicture = 4; +inline bool ConfigurationMessage::_internal_has_profilepicture() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool ConfigurationMessage::has_profilepicture() const { + return _internal_has_profilepicture(); +} +inline void ConfigurationMessage::clear_profilepicture() { + _impl_.profilepicture_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& ConfigurationMessage::profilepicture() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.profilePicture) + return _internal_profilepicture(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ConfigurationMessage::set_profilepicture(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.profilepicture_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.profilePicture) +} +inline std::string* ConfigurationMessage::mutable_profilepicture() { + std::string* _s = _internal_mutable_profilepicture(); + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.profilePicture) + return _s; +} +inline const std::string& ConfigurationMessage::_internal_profilepicture() const { + return _impl_.profilepicture_.Get(); +} +inline void ConfigurationMessage::_internal_set_profilepicture(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.profilepicture_.Set(value, GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage::_internal_mutable_profilepicture() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.profilepicture_.Mutable(GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage::release_profilepicture() { + // @@protoc_insertion_point(field_release:SessionProtos.ConfigurationMessage.profilePicture) + if (!_internal_has_profilepicture()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.profilepicture_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilepicture_.IsDefault()) { + _impl_.profilepicture_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ConfigurationMessage::set_allocated_profilepicture(std::string* profilepicture) { + if (profilepicture != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.profilepicture_.SetAllocated(profilepicture, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilepicture_.IsDefault()) { + _impl_.profilepicture_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.ConfigurationMessage.profilePicture) +} + +// optional bytes profileKey = 5; +inline bool ConfigurationMessage::_internal_has_profilekey() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool ConfigurationMessage::has_profilekey() const { + return _internal_has_profilekey(); +} +inline void ConfigurationMessage::clear_profilekey() { + _impl_.profilekey_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const std::string& ConfigurationMessage::profilekey() const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.profileKey) + return _internal_profilekey(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ConfigurationMessage::set_profilekey(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.profilekey_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.ConfigurationMessage.profileKey) +} +inline std::string* ConfigurationMessage::mutable_profilekey() { + std::string* _s = _internal_mutable_profilekey(); + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.profileKey) + return _s; +} +inline const std::string& ConfigurationMessage::_internal_profilekey() const { + return _impl_.profilekey_.Get(); +} +inline void ConfigurationMessage::_internal_set_profilekey(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.profilekey_.Set(value, GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage::_internal_mutable_profilekey() { + _impl_._has_bits_[0] |= 0x00000004u; + return _impl_.profilekey_.Mutable(GetArenaForAllocation()); +} +inline std::string* ConfigurationMessage::release_profilekey() { + // @@protoc_insertion_point(field_release:SessionProtos.ConfigurationMessage.profileKey) + if (!_internal_has_profilekey()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000004u; + auto* p = _impl_.profilekey_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilekey_.IsDefault()) { + _impl_.profilekey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void ConfigurationMessage::set_allocated_profilekey(std::string* profilekey) { + if (profilekey != nullptr) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.profilekey_.SetAllocated(profilekey, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.profilekey_.IsDefault()) { + _impl_.profilekey_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.ConfigurationMessage.profileKey) +} + +// repeated .SessionProtos.ConfigurationMessage.Contact contacts = 6; +inline int ConfigurationMessage::_internal_contacts_size() const { + return _impl_.contacts_.size(); +} +inline int ConfigurationMessage::contacts_size() const { + return _internal_contacts_size(); +} +inline void ConfigurationMessage::clear_contacts() { + _impl_.contacts_.Clear(); +} +inline ::SessionProtos::ConfigurationMessage_Contact* ConfigurationMessage::mutable_contacts(int index) { + // @@protoc_insertion_point(field_mutable:SessionProtos.ConfigurationMessage.contacts) + return _impl_.contacts_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::ConfigurationMessage_Contact >* +ConfigurationMessage::mutable_contacts() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.ConfigurationMessage.contacts) + return &_impl_.contacts_; +} +inline const ::SessionProtos::ConfigurationMessage_Contact& ConfigurationMessage::_internal_contacts(int index) const { + return _impl_.contacts_.Get(index); +} +inline const ::SessionProtos::ConfigurationMessage_Contact& ConfigurationMessage::contacts(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.ConfigurationMessage.contacts) + return _internal_contacts(index); +} +inline ::SessionProtos::ConfigurationMessage_Contact* ConfigurationMessage::_internal_add_contacts() { + return _impl_.contacts_.Add(); +} +inline ::SessionProtos::ConfigurationMessage_Contact* ConfigurationMessage::add_contacts() { + ::SessionProtos::ConfigurationMessage_Contact* _add = _internal_add_contacts(); + // @@protoc_insertion_point(field_add:SessionProtos.ConfigurationMessage.contacts) + return _add; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SessionProtos::ConfigurationMessage_Contact >& +ConfigurationMessage::contacts() const { + // @@protoc_insertion_point(field_list:SessionProtos.ConfigurationMessage.contacts) + return _impl_.contacts_; +} + +// ------------------------------------------------------------------- + +// ReceiptMessage + +// required .SessionProtos.ReceiptMessage.Type type = 1; +inline bool ReceiptMessage::_internal_has_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool ReceiptMessage::has_type() const { + return _internal_has_type(); +} +inline void ReceiptMessage::clear_type() { + _impl_.type_ = 0; + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline ::SessionProtos::ReceiptMessage_Type ReceiptMessage::_internal_type() const { + return static_cast< ::SessionProtos::ReceiptMessage_Type >(_impl_.type_); +} +inline ::SessionProtos::ReceiptMessage_Type ReceiptMessage::type() const { + // @@protoc_insertion_point(field_get:SessionProtos.ReceiptMessage.type) + return _internal_type(); +} +inline void ReceiptMessage::_internal_set_type(::SessionProtos::ReceiptMessage_Type value) { + assert(::SessionProtos::ReceiptMessage_Type_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.type_ = value; +} +inline void ReceiptMessage::set_type(::SessionProtos::ReceiptMessage_Type value) { + _internal_set_type(value); + // @@protoc_insertion_point(field_set:SessionProtos.ReceiptMessage.type) +} + +// repeated uint64 timestamp = 2; +inline int ReceiptMessage::_internal_timestamp_size() const { + return _impl_.timestamp_.size(); +} +inline int ReceiptMessage::timestamp_size() const { + return _internal_timestamp_size(); +} +inline void ReceiptMessage::clear_timestamp() { + _impl_.timestamp_.Clear(); +} +inline uint64_t ReceiptMessage::_internal_timestamp(int index) const { + return _impl_.timestamp_.Get(index); +} +inline uint64_t ReceiptMessage::timestamp(int index) const { + // @@protoc_insertion_point(field_get:SessionProtos.ReceiptMessage.timestamp) + return _internal_timestamp(index); +} +inline void ReceiptMessage::set_timestamp(int index, uint64_t value) { + _impl_.timestamp_.Set(index, value); + // @@protoc_insertion_point(field_set:SessionProtos.ReceiptMessage.timestamp) +} +inline void ReceiptMessage::_internal_add_timestamp(uint64_t value) { + _impl_.timestamp_.Add(value); +} +inline void ReceiptMessage::add_timestamp(uint64_t value) { + _internal_add_timestamp(value); + // @@protoc_insertion_point(field_add:SessionProtos.ReceiptMessage.timestamp) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint64_t >& +ReceiptMessage::_internal_timestamp() const { + return _impl_.timestamp_; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint64_t >& +ReceiptMessage::timestamp() const { + // @@protoc_insertion_point(field_list:SessionProtos.ReceiptMessage.timestamp) + return _internal_timestamp(); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint64_t >* +ReceiptMessage::_internal_mutable_timestamp() { + return &_impl_.timestamp_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< uint64_t >* +ReceiptMessage::mutable_timestamp() { + // @@protoc_insertion_point(field_mutable_list:SessionProtos.ReceiptMessage.timestamp) + return _internal_mutable_timestamp(); +} + +// ------------------------------------------------------------------- + +// AttachmentPointer + +// required fixed64 id = 1; +inline bool AttachmentPointer::_internal_has_id() const { + bool value = (_impl_._has_bits_[0] & 0x00000080u) != 0; + return value; +} +inline bool AttachmentPointer::has_id() const { + return _internal_has_id(); +} +inline void AttachmentPointer::clear_id() { + _impl_.id_ = uint64_t{0u}; + _impl_._has_bits_[0] &= ~0x00000080u; +} +inline uint64_t AttachmentPointer::_internal_id() const { + return _impl_.id_; +} +inline uint64_t AttachmentPointer::id() const { + // @@protoc_insertion_point(field_get:SessionProtos.AttachmentPointer.id) + return _internal_id(); +} +inline void AttachmentPointer::_internal_set_id(uint64_t value) { + _impl_._has_bits_[0] |= 0x00000080u; + _impl_.id_ = value; +} +inline void AttachmentPointer::set_id(uint64_t value) { + _internal_set_id(value); + // @@protoc_insertion_point(field_set:SessionProtos.AttachmentPointer.id) +} + +// optional string contentType = 2; +inline bool AttachmentPointer::_internal_has_contenttype() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool AttachmentPointer::has_contenttype() const { + return _internal_has_contenttype(); +} +inline void AttachmentPointer::clear_contenttype() { + _impl_.contenttype_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& AttachmentPointer::contenttype() const { + // @@protoc_insertion_point(field_get:SessionProtos.AttachmentPointer.contentType) + return _internal_contenttype(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void AttachmentPointer::set_contenttype(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.contenttype_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.AttachmentPointer.contentType) +} +inline std::string* AttachmentPointer::mutable_contenttype() { + std::string* _s = _internal_mutable_contenttype(); + // @@protoc_insertion_point(field_mutable:SessionProtos.AttachmentPointer.contentType) + return _s; +} +inline const std::string& AttachmentPointer::_internal_contenttype() const { + return _impl_.contenttype_.Get(); +} +inline void AttachmentPointer::_internal_set_contenttype(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.contenttype_.Set(value, GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::_internal_mutable_contenttype() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.contenttype_.Mutable(GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::release_contenttype() { + // @@protoc_insertion_point(field_release:SessionProtos.AttachmentPointer.contentType) + if (!_internal_has_contenttype()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.contenttype_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.contenttype_.IsDefault()) { + _impl_.contenttype_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void AttachmentPointer::set_allocated_contenttype(std::string* contenttype) { + if (contenttype != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.contenttype_.SetAllocated(contenttype, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.contenttype_.IsDefault()) { + _impl_.contenttype_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.AttachmentPointer.contentType) +} + +// optional bytes key = 3; +inline bool AttachmentPointer::_internal_has_key() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool AttachmentPointer::has_key() const { + return _internal_has_key(); +} +inline void AttachmentPointer::clear_key() { + _impl_.key_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& AttachmentPointer::key() const { + // @@protoc_insertion_point(field_get:SessionProtos.AttachmentPointer.key) + return _internal_key(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void AttachmentPointer::set_key(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.key_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.AttachmentPointer.key) +} +inline std::string* AttachmentPointer::mutable_key() { + std::string* _s = _internal_mutable_key(); + // @@protoc_insertion_point(field_mutable:SessionProtos.AttachmentPointer.key) + return _s; +} +inline const std::string& AttachmentPointer::_internal_key() const { + return _impl_.key_.Get(); +} +inline void AttachmentPointer::_internal_set_key(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.key_.Set(value, GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::_internal_mutable_key() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.key_.Mutable(GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::release_key() { + // @@protoc_insertion_point(field_release:SessionProtos.AttachmentPointer.key) + if (!_internal_has_key()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.key_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.key_.IsDefault()) { + _impl_.key_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void AttachmentPointer::set_allocated_key(std::string* key) { + if (key != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.key_.SetAllocated(key, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.key_.IsDefault()) { + _impl_.key_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.AttachmentPointer.key) +} + +// optional uint32 size = 4; +inline bool AttachmentPointer::_internal_has_size() const { + bool value = (_impl_._has_bits_[0] & 0x00000100u) != 0; + return value; +} +inline bool AttachmentPointer::has_size() const { + return _internal_has_size(); +} +inline void AttachmentPointer::clear_size() { + _impl_.size_ = 0u; + _impl_._has_bits_[0] &= ~0x00000100u; +} +inline uint32_t AttachmentPointer::_internal_size() const { + return _impl_.size_; +} +inline uint32_t AttachmentPointer::size() const { + // @@protoc_insertion_point(field_get:SessionProtos.AttachmentPointer.size) + return _internal_size(); +} +inline void AttachmentPointer::_internal_set_size(uint32_t value) { + _impl_._has_bits_[0] |= 0x00000100u; + _impl_.size_ = value; +} +inline void AttachmentPointer::set_size(uint32_t value) { + _internal_set_size(value); + // @@protoc_insertion_point(field_set:SessionProtos.AttachmentPointer.size) +} + +// optional bytes thumbnail = 5; +inline bool AttachmentPointer::_internal_has_thumbnail() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool AttachmentPointer::has_thumbnail() const { + return _internal_has_thumbnail(); +} +inline void AttachmentPointer::clear_thumbnail() { + _impl_.thumbnail_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const std::string& AttachmentPointer::thumbnail() const { + // @@protoc_insertion_point(field_get:SessionProtos.AttachmentPointer.thumbnail) + return _internal_thumbnail(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void AttachmentPointer::set_thumbnail(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.thumbnail_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.AttachmentPointer.thumbnail) +} +inline std::string* AttachmentPointer::mutable_thumbnail() { + std::string* _s = _internal_mutable_thumbnail(); + // @@protoc_insertion_point(field_mutable:SessionProtos.AttachmentPointer.thumbnail) + return _s; +} +inline const std::string& AttachmentPointer::_internal_thumbnail() const { + return _impl_.thumbnail_.Get(); +} +inline void AttachmentPointer::_internal_set_thumbnail(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.thumbnail_.Set(value, GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::_internal_mutable_thumbnail() { + _impl_._has_bits_[0] |= 0x00000004u; + return _impl_.thumbnail_.Mutable(GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::release_thumbnail() { + // @@protoc_insertion_point(field_release:SessionProtos.AttachmentPointer.thumbnail) + if (!_internal_has_thumbnail()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000004u; + auto* p = _impl_.thumbnail_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.thumbnail_.IsDefault()) { + _impl_.thumbnail_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void AttachmentPointer::set_allocated_thumbnail(std::string* thumbnail) { + if (thumbnail != nullptr) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.thumbnail_.SetAllocated(thumbnail, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.thumbnail_.IsDefault()) { + _impl_.thumbnail_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.AttachmentPointer.thumbnail) +} + +// optional bytes digest = 6; +inline bool AttachmentPointer::_internal_has_digest() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool AttachmentPointer::has_digest() const { + return _internal_has_digest(); +} +inline void AttachmentPointer::clear_digest() { + _impl_.digest_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline const std::string& AttachmentPointer::digest() const { + // @@protoc_insertion_point(field_get:SessionProtos.AttachmentPointer.digest) + return _internal_digest(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void AttachmentPointer::set_digest(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.digest_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.AttachmentPointer.digest) +} +inline std::string* AttachmentPointer::mutable_digest() { + std::string* _s = _internal_mutable_digest(); + // @@protoc_insertion_point(field_mutable:SessionProtos.AttachmentPointer.digest) + return _s; +} +inline const std::string& AttachmentPointer::_internal_digest() const { + return _impl_.digest_.Get(); +} +inline void AttachmentPointer::_internal_set_digest(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.digest_.Set(value, GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::_internal_mutable_digest() { + _impl_._has_bits_[0] |= 0x00000008u; + return _impl_.digest_.Mutable(GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::release_digest() { + // @@protoc_insertion_point(field_release:SessionProtos.AttachmentPointer.digest) + if (!_internal_has_digest()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000008u; + auto* p = _impl_.digest_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.digest_.IsDefault()) { + _impl_.digest_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void AttachmentPointer::set_allocated_digest(std::string* digest) { + if (digest != nullptr) { + _impl_._has_bits_[0] |= 0x00000008u; + } else { + _impl_._has_bits_[0] &= ~0x00000008u; + } + _impl_.digest_.SetAllocated(digest, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.digest_.IsDefault()) { + _impl_.digest_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.AttachmentPointer.digest) +} + +// optional string fileName = 7; +inline bool AttachmentPointer::_internal_has_filename() const { + bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0; + return value; +} +inline bool AttachmentPointer::has_filename() const { + return _internal_has_filename(); +} +inline void AttachmentPointer::clear_filename() { + _impl_.filename_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000010u; +} +inline const std::string& AttachmentPointer::filename() const { + // @@protoc_insertion_point(field_get:SessionProtos.AttachmentPointer.fileName) + return _internal_filename(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void AttachmentPointer::set_filename(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000010u; + _impl_.filename_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.AttachmentPointer.fileName) +} +inline std::string* AttachmentPointer::mutable_filename() { + std::string* _s = _internal_mutable_filename(); + // @@protoc_insertion_point(field_mutable:SessionProtos.AttachmentPointer.fileName) + return _s; +} +inline const std::string& AttachmentPointer::_internal_filename() const { + return _impl_.filename_.Get(); +} +inline void AttachmentPointer::_internal_set_filename(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000010u; + _impl_.filename_.Set(value, GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::_internal_mutable_filename() { + _impl_._has_bits_[0] |= 0x00000010u; + return _impl_.filename_.Mutable(GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::release_filename() { + // @@protoc_insertion_point(field_release:SessionProtos.AttachmentPointer.fileName) + if (!_internal_has_filename()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000010u; + auto* p = _impl_.filename_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.filename_.IsDefault()) { + _impl_.filename_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void AttachmentPointer::set_allocated_filename(std::string* filename) { + if (filename != nullptr) { + _impl_._has_bits_[0] |= 0x00000010u; + } else { + _impl_._has_bits_[0] &= ~0x00000010u; + } + _impl_.filename_.SetAllocated(filename, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.filename_.IsDefault()) { + _impl_.filename_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.AttachmentPointer.fileName) +} + +// optional uint32 flags = 8; +inline bool AttachmentPointer::_internal_has_flags() const { + bool value = (_impl_._has_bits_[0] & 0x00000200u) != 0; + return value; +} +inline bool AttachmentPointer::has_flags() const { + return _internal_has_flags(); +} +inline void AttachmentPointer::clear_flags() { + _impl_.flags_ = 0u; + _impl_._has_bits_[0] &= ~0x00000200u; +} +inline uint32_t AttachmentPointer::_internal_flags() const { + return _impl_.flags_; +} +inline uint32_t AttachmentPointer::flags() const { + // @@protoc_insertion_point(field_get:SessionProtos.AttachmentPointer.flags) + return _internal_flags(); +} +inline void AttachmentPointer::_internal_set_flags(uint32_t value) { + _impl_._has_bits_[0] |= 0x00000200u; + _impl_.flags_ = value; +} +inline void AttachmentPointer::set_flags(uint32_t value) { + _internal_set_flags(value); + // @@protoc_insertion_point(field_set:SessionProtos.AttachmentPointer.flags) +} + +// optional uint32 width = 9; +inline bool AttachmentPointer::_internal_has_width() const { + bool value = (_impl_._has_bits_[0] & 0x00000400u) != 0; + return value; +} +inline bool AttachmentPointer::has_width() const { + return _internal_has_width(); +} +inline void AttachmentPointer::clear_width() { + _impl_.width_ = 0u; + _impl_._has_bits_[0] &= ~0x00000400u; +} +inline uint32_t AttachmentPointer::_internal_width() const { + return _impl_.width_; +} +inline uint32_t AttachmentPointer::width() const { + // @@protoc_insertion_point(field_get:SessionProtos.AttachmentPointer.width) + return _internal_width(); +} +inline void AttachmentPointer::_internal_set_width(uint32_t value) { + _impl_._has_bits_[0] |= 0x00000400u; + _impl_.width_ = value; +} +inline void AttachmentPointer::set_width(uint32_t value) { + _internal_set_width(value); + // @@protoc_insertion_point(field_set:SessionProtos.AttachmentPointer.width) +} + +// optional uint32 height = 10; +inline bool AttachmentPointer::_internal_has_height() const { + bool value = (_impl_._has_bits_[0] & 0x00000800u) != 0; + return value; +} +inline bool AttachmentPointer::has_height() const { + return _internal_has_height(); +} +inline void AttachmentPointer::clear_height() { + _impl_.height_ = 0u; + _impl_._has_bits_[0] &= ~0x00000800u; +} +inline uint32_t AttachmentPointer::_internal_height() const { + return _impl_.height_; +} +inline uint32_t AttachmentPointer::height() const { + // @@protoc_insertion_point(field_get:SessionProtos.AttachmentPointer.height) + return _internal_height(); +} +inline void AttachmentPointer::_internal_set_height(uint32_t value) { + _impl_._has_bits_[0] |= 0x00000800u; + _impl_.height_ = value; +} +inline void AttachmentPointer::set_height(uint32_t value) { + _internal_set_height(value); + // @@protoc_insertion_point(field_set:SessionProtos.AttachmentPointer.height) +} + +// optional string caption = 11; +inline bool AttachmentPointer::_internal_has_caption() const { + bool value = (_impl_._has_bits_[0] & 0x00000020u) != 0; + return value; +} +inline bool AttachmentPointer::has_caption() const { + return _internal_has_caption(); +} +inline void AttachmentPointer::clear_caption() { + _impl_.caption_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000020u; +} +inline const std::string& AttachmentPointer::caption() const { + // @@protoc_insertion_point(field_get:SessionProtos.AttachmentPointer.caption) + return _internal_caption(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void AttachmentPointer::set_caption(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000020u; + _impl_.caption_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.AttachmentPointer.caption) +} +inline std::string* AttachmentPointer::mutable_caption() { + std::string* _s = _internal_mutable_caption(); + // @@protoc_insertion_point(field_mutable:SessionProtos.AttachmentPointer.caption) + return _s; +} +inline const std::string& AttachmentPointer::_internal_caption() const { + return _impl_.caption_.Get(); +} +inline void AttachmentPointer::_internal_set_caption(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000020u; + _impl_.caption_.Set(value, GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::_internal_mutable_caption() { + _impl_._has_bits_[0] |= 0x00000020u; + return _impl_.caption_.Mutable(GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::release_caption() { + // @@protoc_insertion_point(field_release:SessionProtos.AttachmentPointer.caption) + if (!_internal_has_caption()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000020u; + auto* p = _impl_.caption_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.caption_.IsDefault()) { + _impl_.caption_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void AttachmentPointer::set_allocated_caption(std::string* caption) { + if (caption != nullptr) { + _impl_._has_bits_[0] |= 0x00000020u; + } else { + _impl_._has_bits_[0] &= ~0x00000020u; + } + _impl_.caption_.SetAllocated(caption, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.caption_.IsDefault()) { + _impl_.caption_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.AttachmentPointer.caption) +} + +// optional string url = 101; +inline bool AttachmentPointer::_internal_has_url() const { + bool value = (_impl_._has_bits_[0] & 0x00000040u) != 0; + return value; +} +inline bool AttachmentPointer::has_url() const { + return _internal_has_url(); +} +inline void AttachmentPointer::clear_url() { + _impl_.url_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000040u; +} +inline const std::string& AttachmentPointer::url() const { + // @@protoc_insertion_point(field_get:SessionProtos.AttachmentPointer.url) + return _internal_url(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void AttachmentPointer::set_url(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000040u; + _impl_.url_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.AttachmentPointer.url) +} +inline std::string* AttachmentPointer::mutable_url() { + std::string* _s = _internal_mutable_url(); + // @@protoc_insertion_point(field_mutable:SessionProtos.AttachmentPointer.url) + return _s; +} +inline const std::string& AttachmentPointer::_internal_url() const { + return _impl_.url_.Get(); +} +inline void AttachmentPointer::_internal_set_url(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000040u; + _impl_.url_.Set(value, GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::_internal_mutable_url() { + _impl_._has_bits_[0] |= 0x00000040u; + return _impl_.url_.Mutable(GetArenaForAllocation()); +} +inline std::string* AttachmentPointer::release_url() { + // @@protoc_insertion_point(field_release:SessionProtos.AttachmentPointer.url) + if (!_internal_has_url()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000040u; + auto* p = _impl_.url_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.url_.IsDefault()) { + _impl_.url_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void AttachmentPointer::set_allocated_url(std::string* url) { + if (url != nullptr) { + _impl_._has_bits_[0] |= 0x00000040u; + } else { + _impl_._has_bits_[0] &= ~0x00000040u; + } + _impl_.url_.SetAllocated(url, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.url_.IsDefault()) { + _impl_.url_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.AttachmentPointer.url) +} + +// ------------------------------------------------------------------- + +// SharedConfigMessage + +// required .SessionProtos.SharedConfigMessage.Kind kind = 1; +inline bool SharedConfigMessage::_internal_has_kind() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool SharedConfigMessage::has_kind() const { + return _internal_has_kind(); +} +inline void SharedConfigMessage::clear_kind() { + _impl_.kind_ = 1; + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline ::SessionProtos::SharedConfigMessage_Kind SharedConfigMessage::_internal_kind() const { + return static_cast< ::SessionProtos::SharedConfigMessage_Kind >(_impl_.kind_); +} +inline ::SessionProtos::SharedConfigMessage_Kind SharedConfigMessage::kind() const { + // @@protoc_insertion_point(field_get:SessionProtos.SharedConfigMessage.kind) + return _internal_kind(); +} +inline void SharedConfigMessage::_internal_set_kind(::SessionProtos::SharedConfigMessage_Kind value) { + assert(::SessionProtos::SharedConfigMessage_Kind_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.kind_ = value; +} +inline void SharedConfigMessage::set_kind(::SessionProtos::SharedConfigMessage_Kind value) { + _internal_set_kind(value); + // @@protoc_insertion_point(field_set:SessionProtos.SharedConfigMessage.kind) +} + +// required int64 seqno = 2; +inline bool SharedConfigMessage::_internal_has_seqno() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool SharedConfigMessage::has_seqno() const { + return _internal_has_seqno(); +} +inline void SharedConfigMessage::clear_seqno() { + _impl_.seqno_ = int64_t{0}; + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline int64_t SharedConfigMessage::_internal_seqno() const { + return _impl_.seqno_; +} +inline int64_t SharedConfigMessage::seqno() const { + // @@protoc_insertion_point(field_get:SessionProtos.SharedConfigMessage.seqno) + return _internal_seqno(); +} +inline void SharedConfigMessage::_internal_set_seqno(int64_t value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.seqno_ = value; +} +inline void SharedConfigMessage::set_seqno(int64_t value) { + _internal_set_seqno(value); + // @@protoc_insertion_point(field_set:SessionProtos.SharedConfigMessage.seqno) +} + +// required bytes data = 3; +inline bool SharedConfigMessage::_internal_has_data() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool SharedConfigMessage::has_data() const { + return _internal_has_data(); +} +inline void SharedConfigMessage::clear_data() { + _impl_.data_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& SharedConfigMessage::data() const { + // @@protoc_insertion_point(field_get:SessionProtos.SharedConfigMessage.data) + return _internal_data(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void SharedConfigMessage::set_data(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.data_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:SessionProtos.SharedConfigMessage.data) +} +inline std::string* SharedConfigMessage::mutable_data() { + std::string* _s = _internal_mutable_data(); + // @@protoc_insertion_point(field_mutable:SessionProtos.SharedConfigMessage.data) + return _s; +} +inline const std::string& SharedConfigMessage::_internal_data() const { + return _impl_.data_.Get(); +} +inline void SharedConfigMessage::_internal_set_data(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.data_.Set(value, GetArenaForAllocation()); +} +inline std::string* SharedConfigMessage::_internal_mutable_data() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.data_.Mutable(GetArenaForAllocation()); +} +inline std::string* SharedConfigMessage::release_data() { + // @@protoc_insertion_point(field_release:SessionProtos.SharedConfigMessage.data) + if (!_internal_has_data()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.data_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.data_.IsDefault()) { + _impl_.data_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void SharedConfigMessage::set_allocated_data(std::string* data) { + if (data != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.data_.SetAllocated(data, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.data_.IsDefault()) { + _impl_.data_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:SessionProtos.SharedConfigMessage.data) +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif // __GNUC__ +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + + +// @@protoc_insertion_point(namespace_scope) + +} // namespace SessionProtos + +PROTOBUF_NAMESPACE_OPEN + +template <> struct is_proto_enum< ::SessionProtos::Envelope_Type> : ::std::true_type {}; +template <> struct is_proto_enum< ::SessionProtos::TypingMessage_Action> : ::std::true_type {}; +template <> struct is_proto_enum< ::SessionProtos::CallMessage_Type> : ::std::true_type {}; +template <> struct is_proto_enum< ::SessionProtos::DataExtractionNotification_Type> : ::std::true_type {}; +template <> struct is_proto_enum< ::SessionProtos::DataMessage_Quote_QuotedAttachment_Flags> : ::std::true_type {}; +template <> struct is_proto_enum< ::SessionProtos::DataMessage_Reaction_Action> : ::std::true_type {}; +template <> struct is_proto_enum< ::SessionProtos::DataMessage_ClosedGroupControlMessage_Type> : ::std::true_type {}; +template <> struct is_proto_enum< ::SessionProtos::DataMessage_Flags> : ::std::true_type {}; +template <> struct is_proto_enum< ::SessionProtos::ReceiptMessage_Type> : ::std::true_type {}; +template <> struct is_proto_enum< ::SessionProtos::AttachmentPointer_Flags> : ::std::true_type {}; +template <> struct is_proto_enum< ::SessionProtos::SharedConfigMessage_Kind> : ::std::true_type {}; + +PROTOBUF_NAMESPACE_CLOSE + +// @@protoc_insertion_point(global_scope) + +#include +#endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_SessionProtos_2eproto diff --git a/proto/SessionProtos.proto b/proto/SessionProtos.proto new file mode 100644 index 00000000..d112322b --- /dev/null +++ b/proto/SessionProtos.proto @@ -0,0 +1,292 @@ +// iOS - since we use a modern proto-compiler, we must specify the legacy proto format. +syntax = "proto2"; + +// iOS - package name determines class prefix +package SessionProtos; + +option optimize_for = LITE_RUNTIME; + +message Envelope { + + enum Type { + SESSION_MESSAGE = 6; + CLOSED_GROUP_MESSAGE = 7; + } + + // @required + required Type type = 1; + optional string source = 2; + optional uint32 sourceDevice = 7; + // @required + required uint64 timestamp = 5; + optional bytes content = 8; + optional uint64 serverTimestamp = 10; +} + +message TypingMessage { + + enum Action { + STARTED = 0; + STOPPED = 1; + } + + // @required + required uint64 timestamp = 1; + // @required + required Action action = 2; +} + +message UnsendRequest { + // @required + required uint64 timestamp = 1; + // @required + required string author = 2; +} + +message MessageRequestResponse { + // @required + required bool isApproved = 1; // Whether the request was approved + optional bytes profileKey = 2; + optional LokiProfile profile = 3; +} + +message Content { + optional DataMessage dataMessage = 1; + optional CallMessage callMessage = 3; + optional ReceiptMessage receiptMessage = 5; + optional TypingMessage typingMessage = 6; + optional ConfigurationMessage configurationMessage = 7; + optional DataExtractionNotification dataExtractionNotification = 8; + optional UnsendRequest unsendRequest = 9; + optional MessageRequestResponse messageRequestResponse = 10; + optional SharedConfigMessage sharedConfigMessage = 11; +} + +message CallMessage { + + enum Type { + PRE_OFFER = 6; + OFFER = 1; + ANSWER = 2; + PROVISIONAL_ANSWER = 3; + ICE_CANDIDATES = 4; + END_CALL = 5; + } + + // Multiple ICE candidates may be batched together for performance + + // @required + required Type type = 1; + repeated string sdps = 2; + repeated uint32 sdpMLineIndexes = 3; + repeated string sdpMids = 4; + // @required + required string uuid = 5; +} + +message KeyPair { + // @required + required bytes publicKey = 1; + // @required + required bytes privateKey = 2; +} + +message DataExtractionNotification { + + enum Type { + SCREENSHOT = 1; + MEDIA_SAVED = 2; // timestamp + } + + // @required + required Type type = 1; + optional uint64 timestamp = 2; +} + +message LokiProfile { + optional string displayName = 1; + optional string profilePicture = 2; +} + +message DataMessage { + + enum Flags { + EXPIRATION_TIMER_UPDATE = 2; + } + + message Quote { + + message QuotedAttachment { + + enum Flags { + VOICE_MESSAGE = 1; + } + + optional string contentType = 1; + optional string fileName = 2; + optional AttachmentPointer thumbnail = 3; + optional uint32 flags = 4; + } + + // @required + required uint64 id = 1; + // @required + required string author = 2; + optional string text = 3; + repeated QuotedAttachment attachments = 4; + } + + message Preview { + // @required + required string url = 1; + optional string title = 2; + optional AttachmentPointer image = 3; + } + + message Reaction { + enum Action { + REACT = 0; + REMOVE = 1; + } + // @required + required uint64 id = 1; // Message timestamp + // @required + required string author = 2; + optional string emoji = 3; + // @required + required Action action = 4; + } + + message OpenGroupInvitation { + // @required + required string url = 1; + // @required + required string name = 3; + } + + message ClosedGroupControlMessage { + + enum Type { + NEW = 1; // publicKey, name, encryptionKeyPair, members, admins, expirationTimer + ENCRYPTION_KEY_PAIR = 3; // publicKey, wrappers + NAME_CHANGE = 4; // name + MEMBERS_ADDED = 5; // members + MEMBERS_REMOVED = 6; // members + MEMBER_LEFT = 7; + ENCRYPTION_KEY_PAIR_REQUEST = 8; + } + + message KeyPairWrapper { + // @required + required bytes publicKey = 1; // The public key of the user the key pair is meant for + // @required + required bytes encryptedKeyPair = 2; // The encrypted key pair + } + + // @required + required Type type = 1; + optional bytes publicKey = 2; + optional string name = 3; + optional KeyPair encryptionKeyPair = 4; + repeated bytes members = 5; + repeated bytes admins = 6; + repeated KeyPairWrapper wrappers = 7; + optional uint32 expirationTimer = 8; + } + + optional string body = 1; + repeated AttachmentPointer attachments = 2; + // optional GroupContext group = 3; // No longer used + optional uint32 flags = 4; + optional uint32 expireTimer = 5; + optional bytes profileKey = 6; + optional uint64 timestamp = 7; + optional Quote quote = 8; + repeated Preview preview = 10; + optional Reaction reaction = 11; + optional LokiProfile profile = 101; + optional OpenGroupInvitation openGroupInvitation = 102; + optional ClosedGroupControlMessage closedGroupControlMessage = 104; + optional string syncTarget = 105; + optional bool blocksCommunityMessageRequests = 106; +} + +message ConfigurationMessage { + + message ClosedGroup { + optional bytes publicKey = 1; + optional string name = 2; + optional KeyPair encryptionKeyPair = 3; + repeated bytes members = 4; + repeated bytes admins = 5; + optional uint32 expirationTimer = 6; + } + + message Contact { + // @required + required bytes publicKey = 1; + // @required + required string name = 2; + optional string profilePicture = 3; + optional bytes profileKey = 4; + optional bool isApproved = 5; // added for msg requests + optional bool isBlocked = 6; // added for msg requests + optional bool didApproveMe = 7; // added for msg requests + } + + repeated ClosedGroup closedGroups = 1; + repeated string openGroups = 2; + optional string displayName = 3; + optional string profilePicture = 4; + optional bytes profileKey = 5; + repeated Contact contacts = 6; +} + +message ReceiptMessage { + + enum Type { + DELIVERY = 0; + READ = 1; + } + + // @required + required Type type = 1; + repeated uint64 timestamp = 2; +} + +message AttachmentPointer { + + enum Flags { + VOICE_MESSAGE = 1; + } + + // @required + required fixed64 id = 1; + optional string contentType = 2; + optional bytes key = 3; + optional uint32 size = 4; + optional bytes thumbnail = 5; + optional bytes digest = 6; + optional string fileName = 7; + optional uint32 flags = 8; + optional uint32 width = 9; + optional uint32 height = 10; + optional string caption = 11; + optional string url = 101; +} + +message SharedConfigMessage { + enum Kind { + USER_PROFILE = 1; + CONTACTS = 2; + CONVO_INFO_VOLATILE = 3; + USER_GROUPS = 4; + } + + // @required + required Kind kind = 1; + // @required + required int64 seqno = 2; + // @required + required bytes data = 3; +} diff --git a/proto/WebSocketResources.pb.cc b/proto/WebSocketResources.pb.cc new file mode 100644 index 00000000..9f9fdb77 --- /dev/null +++ b/proto/WebSocketResources.pb.cc @@ -0,0 +1,1223 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: WebSocketResources.proto + +#include "WebSocketResources.pb.h" + +#include + +#include +#include +#include +#include +// @@protoc_insertion_point(includes) +#include + +PROTOBUF_PRAGMA_INIT_SEG + +namespace _pb = ::PROTOBUF_NAMESPACE_ID; +namespace _pbi = _pb::internal; + +namespace WebSocketProtos { +PROTOBUF_CONSTEXPR WebSocketRequestMessage::WebSocketRequestMessage( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.headers_)*/{} + , /*decltype(_impl_.verb_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.path_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.body_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.requestid_)*/uint64_t{0u}} {} +struct WebSocketRequestMessageDefaultTypeInternal { + PROTOBUF_CONSTEXPR WebSocketRequestMessageDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~WebSocketRequestMessageDefaultTypeInternal() {} + union { + WebSocketRequestMessage _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 WebSocketRequestMessageDefaultTypeInternal _WebSocketRequestMessage_default_instance_; +PROTOBUF_CONSTEXPR WebSocketResponseMessage::WebSocketResponseMessage( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.headers_)*/{} + , /*decltype(_impl_.message_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.body_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.requestid_)*/uint64_t{0u} + , /*decltype(_impl_.status_)*/0u} {} +struct WebSocketResponseMessageDefaultTypeInternal { + PROTOBUF_CONSTEXPR WebSocketResponseMessageDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~WebSocketResponseMessageDefaultTypeInternal() {} + union { + WebSocketResponseMessage _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 WebSocketResponseMessageDefaultTypeInternal _WebSocketResponseMessage_default_instance_; +PROTOBUF_CONSTEXPR WebSocketMessage::WebSocketMessage( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_._has_bits_)*/{} + , /*decltype(_impl_._cached_size_)*/{} + , /*decltype(_impl_.request_)*/nullptr + , /*decltype(_impl_.response_)*/nullptr + , /*decltype(_impl_.type_)*/0} {} +struct WebSocketMessageDefaultTypeInternal { + PROTOBUF_CONSTEXPR WebSocketMessageDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~WebSocketMessageDefaultTypeInternal() {} + union { + WebSocketMessage _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 WebSocketMessageDefaultTypeInternal _WebSocketMessage_default_instance_; +} // namespace WebSocketProtos +namespace WebSocketProtos { +bool WebSocketMessage_Type_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed WebSocketMessage_Type_strings[3] = {}; + +static const char WebSocketMessage_Type_names[] = + "REQUEST" + "RESPONSE" + "UNKNOWN"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry WebSocketMessage_Type_entries[] = { + { {WebSocketMessage_Type_names + 0, 7}, 1 }, + { {WebSocketMessage_Type_names + 7, 8}, 2 }, + { {WebSocketMessage_Type_names + 15, 7}, 0 }, +}; + +static const int WebSocketMessage_Type_entries_by_number[] = { + 2, // 0 -> UNKNOWN + 0, // 1 -> REQUEST + 1, // 2 -> RESPONSE +}; + +const std::string& WebSocketMessage_Type_Name( + WebSocketMessage_Type value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + WebSocketMessage_Type_entries, + WebSocketMessage_Type_entries_by_number, + 3, WebSocketMessage_Type_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + WebSocketMessage_Type_entries, + WebSocketMessage_Type_entries_by_number, + 3, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + WebSocketMessage_Type_strings[idx].get(); +} +bool WebSocketMessage_Type_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, WebSocketMessage_Type* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + WebSocketMessage_Type_entries, 3, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) +constexpr WebSocketMessage_Type WebSocketMessage::UNKNOWN; +constexpr WebSocketMessage_Type WebSocketMessage::REQUEST; +constexpr WebSocketMessage_Type WebSocketMessage::RESPONSE; +constexpr WebSocketMessage_Type WebSocketMessage::Type_MIN; +constexpr WebSocketMessage_Type WebSocketMessage::Type_MAX; +constexpr int WebSocketMessage::Type_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)) + +// =================================================================== + +class WebSocketRequestMessage::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_verb(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_path(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_body(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_requestid(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } +}; + +WebSocketRequestMessage::WebSocketRequestMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:WebSocketProtos.WebSocketRequestMessage) +} +WebSocketRequestMessage::WebSocketRequestMessage(const WebSocketRequestMessage& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + WebSocketRequestMessage* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.headers_){from._impl_.headers_} + , decltype(_impl_.verb_){} + , decltype(_impl_.path_){} + , decltype(_impl_.body_){} + , decltype(_impl_.requestid_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.verb_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.verb_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_verb()) { + _this->_impl_.verb_.Set(from._internal_verb(), + _this->GetArenaForAllocation()); + } + _impl_.path_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.path_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_path()) { + _this->_impl_.path_.Set(from._internal_path(), + _this->GetArenaForAllocation()); + } + _impl_.body_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.body_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_body()) { + _this->_impl_.body_.Set(from._internal_body(), + _this->GetArenaForAllocation()); + } + _this->_impl_.requestid_ = from._impl_.requestid_; + // @@protoc_insertion_point(copy_constructor:WebSocketProtos.WebSocketRequestMessage) +} + +inline void WebSocketRequestMessage::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.headers_){arena} + , decltype(_impl_.verb_){} + , decltype(_impl_.path_){} + , decltype(_impl_.body_){} + , decltype(_impl_.requestid_){uint64_t{0u}} + }; + _impl_.verb_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.verb_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.path_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.path_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.body_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.body_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +WebSocketRequestMessage::~WebSocketRequestMessage() { + // @@protoc_insertion_point(destructor:WebSocketProtos.WebSocketRequestMessage) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void WebSocketRequestMessage::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.headers_.~RepeatedPtrField(); + _impl_.verb_.Destroy(); + _impl_.path_.Destroy(); + _impl_.body_.Destroy(); +} + +void WebSocketRequestMessage::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void WebSocketRequestMessage::Clear() { +// @@protoc_insertion_point(message_clear_start:WebSocketProtos.WebSocketRequestMessage) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.headers_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _impl_.verb_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.path_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000004u) { + _impl_.body_.ClearNonDefaultToEmpty(); + } + } + _impl_.requestid_ = uint64_t{0u}; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* WebSocketRequestMessage::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional string verb = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_verb(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string path = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + auto str = _internal_mutable_path(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes body = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_body(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional uint64 requestId = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 32)) { + _Internal::set_has_requestid(&has_bits); + _impl_.requestid_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated string headers = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + ptr -= 1; + do { + ptr += 1; + auto str = _internal_add_headers(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<42>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* WebSocketRequestMessage::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:WebSocketProtos.WebSocketRequestMessage) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional string verb = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 1, this->_internal_verb(), target); + } + + // optional string path = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteStringMaybeAliased( + 2, this->_internal_path(), target); + } + + // optional bytes body = 3; + if (cached_has_bits & 0x00000004u) { + target = stream->WriteBytesMaybeAliased( + 3, this->_internal_body(), target); + } + + // optional uint64 requestId = 4; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(4, this->_internal_requestid(), target); + } + + // repeated string headers = 5; + for (int i = 0, n = this->_internal_headers_size(); i < n; i++) { + const auto& s = this->_internal_headers(i); + target = stream->WriteString(5, s, target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:WebSocketProtos.WebSocketRequestMessage) + return target; +} + +size_t WebSocketRequestMessage::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:WebSocketProtos.WebSocketRequestMessage) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated string headers = 5; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(_impl_.headers_.size()); + for (int i = 0, n = _impl_.headers_.size(); i < n; i++) { + total_size += ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + _impl_.headers_.Get(i)); + } + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + // optional string verb = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_verb()); + } + + // optional string path = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_path()); + } + + // optional bytes body = 3; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_body()); + } + + // optional uint64 requestId = 4; + if (cached_has_bits & 0x00000008u) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_requestid()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void WebSocketRequestMessage::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void WebSocketRequestMessage::MergeFrom(const WebSocketRequestMessage& from) { + WebSocketRequestMessage* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:WebSocketProtos.WebSocketRequestMessage) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.headers_.MergeFrom(from._impl_.headers_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_verb(from._internal_verb()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_path(from._internal_path()); + } + if (cached_has_bits & 0x00000004u) { + _this->_internal_set_body(from._internal_body()); + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.requestid_ = from._impl_.requestid_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void WebSocketRequestMessage::CopyFrom(const WebSocketRequestMessage& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:WebSocketProtos.WebSocketRequestMessage) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool WebSocketRequestMessage::IsInitialized() const { + return true; +} + +void WebSocketRequestMessage::InternalSwap(WebSocketRequestMessage* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.headers_.InternalSwap(&other->_impl_.headers_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.verb_, lhs_arena, + &other->_impl_.verb_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.path_, lhs_arena, + &other->_impl_.path_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.body_, lhs_arena, + &other->_impl_.body_, rhs_arena + ); + swap(_impl_.requestid_, other->_impl_.requestid_); +} + +std::string WebSocketRequestMessage::GetTypeName() const { + return "WebSocketProtos.WebSocketRequestMessage"; +} + + +// =================================================================== + +class WebSocketResponseMessage::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_requestid(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_status(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static void set_has_message(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_body(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +WebSocketResponseMessage::WebSocketResponseMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:WebSocketProtos.WebSocketResponseMessage) +} +WebSocketResponseMessage::WebSocketResponseMessage(const WebSocketResponseMessage& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + WebSocketResponseMessage* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.headers_){from._impl_.headers_} + , decltype(_impl_.message_){} + , decltype(_impl_.body_){} + , decltype(_impl_.requestid_){} + , decltype(_impl_.status_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + _impl_.message_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.message_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_message()) { + _this->_impl_.message_.Set(from._internal_message(), + _this->GetArenaForAllocation()); + } + _impl_.body_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.body_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (from._internal_has_body()) { + _this->_impl_.body_.Set(from._internal_body(), + _this->GetArenaForAllocation()); + } + ::memcpy(&_impl_.requestid_, &from._impl_.requestid_, + static_cast(reinterpret_cast(&_impl_.status_) - + reinterpret_cast(&_impl_.requestid_)) + sizeof(_impl_.status_)); + // @@protoc_insertion_point(copy_constructor:WebSocketProtos.WebSocketResponseMessage) +} + +inline void WebSocketResponseMessage::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.headers_){arena} + , decltype(_impl_.message_){} + , decltype(_impl_.body_){} + , decltype(_impl_.requestid_){uint64_t{0u}} + , decltype(_impl_.status_){0u} + }; + _impl_.message_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.message_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.body_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.body_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +WebSocketResponseMessage::~WebSocketResponseMessage() { + // @@protoc_insertion_point(destructor:WebSocketProtos.WebSocketResponseMessage) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void WebSocketResponseMessage::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.headers_.~RepeatedPtrField(); + _impl_.message_.Destroy(); + _impl_.body_.Destroy(); +} + +void WebSocketResponseMessage::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void WebSocketResponseMessage::Clear() { +// @@protoc_insertion_point(message_clear_start:WebSocketProtos.WebSocketResponseMessage) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.headers_.Clear(); + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + _impl_.message_.ClearNonDefaultToEmpty(); + } + if (cached_has_bits & 0x00000002u) { + _impl_.body_.ClearNonDefaultToEmpty(); + } + } + if (cached_has_bits & 0x0000000cu) { + ::memset(&_impl_.requestid_, 0, static_cast( + reinterpret_cast(&_impl_.status_) - + reinterpret_cast(&_impl_.requestid_)) + sizeof(_impl_.status_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* WebSocketResponseMessage::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional uint64 requestId = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + _Internal::set_has_requestid(&has_bits); + _impl_.requestid_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional uint32 status = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 16)) { + _Internal::set_has_status(&has_bits); + _impl_.status_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional string message = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + auto str = _internal_mutable_message(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional bytes body = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 34)) { + auto str = _internal_mutable_body(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // repeated string headers = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + ptr -= 1; + do { + ptr += 1; + auto str = _internal_add_headers(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<42>(ptr)); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* WebSocketResponseMessage::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:WebSocketProtos.WebSocketResponseMessage) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional uint64 requestId = 1; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_requestid(), target); + } + + // optional uint32 status = 2; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray(2, this->_internal_status(), target); + } + + // optional string message = 3; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteStringMaybeAliased( + 3, this->_internal_message(), target); + } + + // optional bytes body = 4; + if (cached_has_bits & 0x00000002u) { + target = stream->WriteBytesMaybeAliased( + 4, this->_internal_body(), target); + } + + // repeated string headers = 5; + for (int i = 0, n = this->_internal_headers_size(); i < n; i++) { + const auto& s = this->_internal_headers(i); + target = stream->WriteString(5, s, target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:WebSocketProtos.WebSocketResponseMessage) + return target; +} + +size_t WebSocketResponseMessage::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:WebSocketProtos.WebSocketResponseMessage) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated string headers = 5; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(_impl_.headers_.size()); + for (int i = 0, n = _impl_.headers_.size(); i < n; i++) { + total_size += ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + _impl_.headers_.Get(i)); + } + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + // optional string message = 3; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_message()); + } + + // optional bytes body = 4; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_body()); + } + + // optional uint64 requestId = 1; + if (cached_has_bits & 0x00000004u) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_requestid()); + } + + // optional uint32 status = 2; + if (cached_has_bits & 0x00000008u) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_status()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void WebSocketResponseMessage::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void WebSocketResponseMessage::MergeFrom(const WebSocketResponseMessage& from) { + WebSocketResponseMessage* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:WebSocketProtos.WebSocketResponseMessage) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + _this->_impl_.headers_.MergeFrom(from._impl_.headers_); + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_set_message(from._internal_message()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_set_body(from._internal_body()); + } + if (cached_has_bits & 0x00000004u) { + _this->_impl_.requestid_ = from._impl_.requestid_; + } + if (cached_has_bits & 0x00000008u) { + _this->_impl_.status_ = from._impl_.status_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void WebSocketResponseMessage::CopyFrom(const WebSocketResponseMessage& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:WebSocketProtos.WebSocketResponseMessage) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool WebSocketResponseMessage::IsInitialized() const { + return true; +} + +void WebSocketResponseMessage::InternalSwap(WebSocketResponseMessage* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.headers_.InternalSwap(&other->_impl_.headers_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.message_, lhs_arena, + &other->_impl_.message_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.body_, lhs_arena, + &other->_impl_.body_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(WebSocketResponseMessage, _impl_.status_) + + sizeof(WebSocketResponseMessage::_impl_.status_) + - PROTOBUF_FIELD_OFFSET(WebSocketResponseMessage, _impl_.requestid_)>( + reinterpret_cast(&_impl_.requestid_), + reinterpret_cast(&other->_impl_.requestid_)); +} + +std::string WebSocketResponseMessage::GetTypeName() const { + return "WebSocketProtos.WebSocketResponseMessage"; +} + + +// =================================================================== + +class WebSocketMessage::_Internal { + public: + using HasBits = decltype(std::declval()._impl_._has_bits_); + static void set_has_type(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static const ::WebSocketProtos::WebSocketRequestMessage& request(const WebSocketMessage* msg); + static void set_has_request(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::WebSocketProtos::WebSocketResponseMessage& response(const WebSocketMessage* msg); + static void set_has_response(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +const ::WebSocketProtos::WebSocketRequestMessage& +WebSocketMessage::_Internal::request(const WebSocketMessage* msg) { + return *msg->_impl_.request_; +} +const ::WebSocketProtos::WebSocketResponseMessage& +WebSocketMessage::_Internal::response(const WebSocketMessage* msg) { + return *msg->_impl_.response_; +} +WebSocketMessage::WebSocketMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:WebSocketProtos.WebSocketMessage) +} +WebSocketMessage::WebSocketMessage(const WebSocketMessage& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite() { + WebSocketMessage* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){from._impl_._has_bits_} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.request_){nullptr} + , decltype(_impl_.response_){nullptr} + , decltype(_impl_.type_){}}; + + _internal_metadata_.MergeFrom(from._internal_metadata_); + if (from._internal_has_request()) { + _this->_impl_.request_ = new ::WebSocketProtos::WebSocketRequestMessage(*from._impl_.request_); + } + if (from._internal_has_response()) { + _this->_impl_.response_ = new ::WebSocketProtos::WebSocketResponseMessage(*from._impl_.response_); + } + _this->_impl_.type_ = from._impl_.type_; + // @@protoc_insertion_point(copy_constructor:WebSocketProtos.WebSocketMessage) +} + +inline void WebSocketMessage::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_._has_bits_){} + , /*decltype(_impl_._cached_size_)*/{} + , decltype(_impl_.request_){nullptr} + , decltype(_impl_.response_){nullptr} + , decltype(_impl_.type_){0} + }; +} + +WebSocketMessage::~WebSocketMessage() { + // @@protoc_insertion_point(destructor:WebSocketProtos.WebSocketMessage) + if (auto *arena = _internal_metadata_.DeleteReturnArena()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void WebSocketMessage::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + if (this != internal_default_instance()) delete _impl_.request_; + if (this != internal_default_instance()) delete _impl_.response_; +} + +void WebSocketMessage::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void WebSocketMessage::Clear() { +// @@protoc_insertion_point(message_clear_start:WebSocketProtos.WebSocketMessage) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(_impl_.request_ != nullptr); + _impl_.request_->Clear(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(_impl_.response_ != nullptr); + _impl_.response_->Clear(); + } + } + _impl_.type_ = 0; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* WebSocketMessage::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // optional .WebSocketProtos.WebSocketMessage.Type type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 8)) { + uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::WebSocketProtos::WebSocketMessage_Type_IsValid(val))) { + _internal_set_type(static_cast<::WebSocketProtos::WebSocketMessage_Type>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else + goto handle_unusual; + continue; + // optional .WebSocketProtos.WebSocketRequestMessage request = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 18)) { + ptr = ctx->ParseMessage(_internal_mutable_request(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // optional .WebSocketProtos.WebSocketResponseMessage response = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_response(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + _impl_._has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* WebSocketMessage::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:WebSocketProtos.WebSocketMessage) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + // optional .WebSocketProtos.WebSocketMessage.Type type = 1; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 1, this->_internal_type(), target); + } + + // optional .WebSocketProtos.WebSocketRequestMessage request = 2; + if (cached_has_bits & 0x00000001u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(2, _Internal::request(this), + _Internal::request(this).GetCachedSize(), target, stream); + } + + // optional .WebSocketProtos.WebSocketResponseMessage response = 3; + if (cached_has_bits & 0x00000002u) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(3, _Internal::response(this), + _Internal::response(this).GetCachedSize(), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(), + static_cast(_internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:WebSocketProtos.WebSocketMessage) + return target; +} + +size_t WebSocketMessage::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:WebSocketProtos.WebSocketMessage) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + // optional .WebSocketProtos.WebSocketRequestMessage request = 2; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.request_); + } + + // optional .WebSocketProtos.WebSocketResponseMessage response = 3; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.response_); + } + + // optional .WebSocketProtos.WebSocketMessage.Type type = 1; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this->_internal_type()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size(); + } + int cached_size = ::_pbi::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void WebSocketMessage::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::_pbi::DownCast( + &from)); +} + +void WebSocketMessage::MergeFrom(const WebSocketMessage& from) { + WebSocketMessage* const _this = this; + // @@protoc_insertion_point(class_specific_merge_from_start:WebSocketProtos.WebSocketMessage) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _this->_internal_mutable_request()->::WebSocketProtos::WebSocketRequestMessage::MergeFrom( + from._internal_request()); + } + if (cached_has_bits & 0x00000002u) { + _this->_internal_mutable_response()->::WebSocketProtos::WebSocketResponseMessage::MergeFrom( + from._internal_response()); + } + if (cached_has_bits & 0x00000004u) { + _this->_impl_.type_ = from._impl_.type_; + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + } + _this->_internal_metadata_.MergeFrom(from._internal_metadata_); +} + +void WebSocketMessage::CopyFrom(const WebSocketMessage& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:WebSocketProtos.WebSocketMessage) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool WebSocketMessage::IsInitialized() const { + return true; +} + +void WebSocketMessage::InternalSwap(WebSocketMessage* other) { + using std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(WebSocketMessage, _impl_.type_) + + sizeof(WebSocketMessage::_impl_.type_) + - PROTOBUF_FIELD_OFFSET(WebSocketMessage, _impl_.request_)>( + reinterpret_cast(&_impl_.request_), + reinterpret_cast(&other->_impl_.request_)); +} + +std::string WebSocketMessage::GetTypeName() const { + return "WebSocketProtos.WebSocketMessage"; +} + + +// @@protoc_insertion_point(namespace_scope) +} // namespace WebSocketProtos +PROTOBUF_NAMESPACE_OPEN +template<> PROTOBUF_NOINLINE ::WebSocketProtos::WebSocketRequestMessage* +Arena::CreateMaybeMessage< ::WebSocketProtos::WebSocketRequestMessage >(Arena* arena) { + return Arena::CreateMessageInternal< ::WebSocketProtos::WebSocketRequestMessage >(arena); +} +template<> PROTOBUF_NOINLINE ::WebSocketProtos::WebSocketResponseMessage* +Arena::CreateMaybeMessage< ::WebSocketProtos::WebSocketResponseMessage >(Arena* arena) { + return Arena::CreateMessageInternal< ::WebSocketProtos::WebSocketResponseMessage >(arena); +} +template<> PROTOBUF_NOINLINE ::WebSocketProtos::WebSocketMessage* +Arena::CreateMaybeMessage< ::WebSocketProtos::WebSocketMessage >(Arena* arena) { + return Arena::CreateMessageInternal< ::WebSocketProtos::WebSocketMessage >(arena); +} +PROTOBUF_NAMESPACE_CLOSE + +// @@protoc_insertion_point(global_scope) +#include diff --git a/proto/WebSocketResources.pb.h b/proto/WebSocketResources.pb.h new file mode 100644 index 00000000..b4f41d86 --- /dev/null +++ b/proto/WebSocketResources.pb.h @@ -0,0 +1,1567 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: WebSocketResources.proto + +#ifndef GOOGLE_PROTOBUF_INCLUDED_WebSocketResources_2eproto +#define GOOGLE_PROTOBUF_INCLUDED_WebSocketResources_2eproto + +#include +#include + +#include +#if PROTOBUF_VERSION < 3021000 +#error This file was generated by a newer version of protoc which is +#error incompatible with your Protocol Buffer headers. Please update +#error your headers. +#endif +#if 3021012 < PROTOBUF_MIN_PROTOC_VERSION +#error This file was generated by an older version of protoc which is +#error incompatible with your Protocol Buffer headers. Please +#error regenerate this file with a newer version of protoc. +#endif + +#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: export +#include // IWYU pragma: export +#include +// @@protoc_insertion_point(includes) +#include +#define PROTOBUF_INTERNAL_EXPORT_WebSocketResources_2eproto +PROTOBUF_NAMESPACE_OPEN +namespace internal { +class AnyMetadata; +} // namespace internal +PROTOBUF_NAMESPACE_CLOSE + +// Internal implementation detail -- do not use these members. +struct TableStruct_WebSocketResources_2eproto { + static const uint32_t offsets[]; +}; +namespace WebSocketProtos { +class WebSocketMessage; +struct WebSocketMessageDefaultTypeInternal; +extern WebSocketMessageDefaultTypeInternal _WebSocketMessage_default_instance_; +class WebSocketRequestMessage; +struct WebSocketRequestMessageDefaultTypeInternal; +extern WebSocketRequestMessageDefaultTypeInternal _WebSocketRequestMessage_default_instance_; +class WebSocketResponseMessage; +struct WebSocketResponseMessageDefaultTypeInternal; +extern WebSocketResponseMessageDefaultTypeInternal _WebSocketResponseMessage_default_instance_; +} // namespace WebSocketProtos +PROTOBUF_NAMESPACE_OPEN +template<> ::WebSocketProtos::WebSocketMessage* Arena::CreateMaybeMessage<::WebSocketProtos::WebSocketMessage>(Arena*); +template<> ::WebSocketProtos::WebSocketRequestMessage* Arena::CreateMaybeMessage<::WebSocketProtos::WebSocketRequestMessage>(Arena*); +template<> ::WebSocketProtos::WebSocketResponseMessage* Arena::CreateMaybeMessage<::WebSocketProtos::WebSocketResponseMessage>(Arena*); +PROTOBUF_NAMESPACE_CLOSE +namespace WebSocketProtos { + +enum WebSocketMessage_Type : int { + WebSocketMessage_Type_UNKNOWN = 0, + WebSocketMessage_Type_REQUEST = 1, + WebSocketMessage_Type_RESPONSE = 2 +}; +bool WebSocketMessage_Type_IsValid(int value); +constexpr WebSocketMessage_Type WebSocketMessage_Type_Type_MIN = WebSocketMessage_Type_UNKNOWN; +constexpr WebSocketMessage_Type WebSocketMessage_Type_Type_MAX = WebSocketMessage_Type_RESPONSE; +constexpr int WebSocketMessage_Type_Type_ARRAYSIZE = WebSocketMessage_Type_Type_MAX + 1; + +const std::string& WebSocketMessage_Type_Name(WebSocketMessage_Type value); +template +inline const std::string& WebSocketMessage_Type_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function WebSocketMessage_Type_Name."); + return WebSocketMessage_Type_Name(static_cast(enum_t_value)); +} +bool WebSocketMessage_Type_Parse( + ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, WebSocketMessage_Type* value); +// =================================================================== + +class WebSocketRequestMessage final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:WebSocketProtos.WebSocketRequestMessage) */ { + public: + inline WebSocketRequestMessage() : WebSocketRequestMessage(nullptr) {} + ~WebSocketRequestMessage() override; + explicit PROTOBUF_CONSTEXPR WebSocketRequestMessage(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + WebSocketRequestMessage(const WebSocketRequestMessage& from); + WebSocketRequestMessage(WebSocketRequestMessage&& from) noexcept + : WebSocketRequestMessage() { + *this = ::std::move(from); + } + + inline WebSocketRequestMessage& operator=(const WebSocketRequestMessage& from) { + CopyFrom(from); + return *this; + } + inline WebSocketRequestMessage& operator=(WebSocketRequestMessage&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const WebSocketRequestMessage& default_instance() { + return *internal_default_instance(); + } + static inline const WebSocketRequestMessage* internal_default_instance() { + return reinterpret_cast( + &_WebSocketRequestMessage_default_instance_); + } + static constexpr int kIndexInFileMessages = + 0; + + friend void swap(WebSocketRequestMessage& a, WebSocketRequestMessage& b) { + a.Swap(&b); + } + inline void Swap(WebSocketRequestMessage* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(WebSocketRequestMessage* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + WebSocketRequestMessage* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const WebSocketRequestMessage& from); + void MergeFrom(const WebSocketRequestMessage& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(WebSocketRequestMessage* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "WebSocketProtos.WebSocketRequestMessage"; + } + protected: + explicit WebSocketRequestMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kHeadersFieldNumber = 5, + kVerbFieldNumber = 1, + kPathFieldNumber = 2, + kBodyFieldNumber = 3, + kRequestIdFieldNumber = 4, + }; + // repeated string headers = 5; + int headers_size() const; + private: + int _internal_headers_size() const; + public: + void clear_headers(); + const std::string& headers(int index) const; + std::string* mutable_headers(int index); + void set_headers(int index, const std::string& value); + void set_headers(int index, std::string&& value); + void set_headers(int index, const char* value); + void set_headers(int index, const char* value, size_t size); + std::string* add_headers(); + void add_headers(const std::string& value); + void add_headers(std::string&& value); + void add_headers(const char* value); + void add_headers(const char* value, size_t size); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& headers() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* mutable_headers(); + private: + const std::string& _internal_headers(int index) const; + std::string* _internal_add_headers(); + public: + + // optional string verb = 1; + bool has_verb() const; + private: + bool _internal_has_verb() const; + public: + void clear_verb(); + const std::string& verb() const; + template + void set_verb(ArgT0&& arg0, ArgT... args); + std::string* mutable_verb(); + PROTOBUF_NODISCARD std::string* release_verb(); + void set_allocated_verb(std::string* verb); + private: + const std::string& _internal_verb() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_verb(const std::string& value); + std::string* _internal_mutable_verb(); + public: + + // optional string path = 2; + bool has_path() const; + private: + bool _internal_has_path() const; + public: + void clear_path(); + const std::string& path() const; + template + void set_path(ArgT0&& arg0, ArgT... args); + std::string* mutable_path(); + PROTOBUF_NODISCARD std::string* release_path(); + void set_allocated_path(std::string* path); + private: + const std::string& _internal_path() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_path(const std::string& value); + std::string* _internal_mutable_path(); + public: + + // optional bytes body = 3; + bool has_body() const; + private: + bool _internal_has_body() const; + public: + void clear_body(); + const std::string& body() const; + template + void set_body(ArgT0&& arg0, ArgT... args); + std::string* mutable_body(); + PROTOBUF_NODISCARD std::string* release_body(); + void set_allocated_body(std::string* body); + private: + const std::string& _internal_body() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_body(const std::string& value); + std::string* _internal_mutable_body(); + public: + + // optional uint64 requestId = 4; + bool has_requestid() const; + private: + bool _internal_has_requestid() const; + public: + void clear_requestid(); + uint64_t requestid() const; + void set_requestid(uint64_t value); + private: + uint64_t _internal_requestid() const; + void _internal_set_requestid(uint64_t value); + public: + + // @@protoc_insertion_point(class_scope:WebSocketProtos.WebSocketRequestMessage) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField headers_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr verb_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr path_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr body_; + uint64_t requestid_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_WebSocketResources_2eproto; +}; +// ------------------------------------------------------------------- + +class WebSocketResponseMessage final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:WebSocketProtos.WebSocketResponseMessage) */ { + public: + inline WebSocketResponseMessage() : WebSocketResponseMessage(nullptr) {} + ~WebSocketResponseMessage() override; + explicit PROTOBUF_CONSTEXPR WebSocketResponseMessage(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + WebSocketResponseMessage(const WebSocketResponseMessage& from); + WebSocketResponseMessage(WebSocketResponseMessage&& from) noexcept + : WebSocketResponseMessage() { + *this = ::std::move(from); + } + + inline WebSocketResponseMessage& operator=(const WebSocketResponseMessage& from) { + CopyFrom(from); + return *this; + } + inline WebSocketResponseMessage& operator=(WebSocketResponseMessage&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const WebSocketResponseMessage& default_instance() { + return *internal_default_instance(); + } + static inline const WebSocketResponseMessage* internal_default_instance() { + return reinterpret_cast( + &_WebSocketResponseMessage_default_instance_); + } + static constexpr int kIndexInFileMessages = + 1; + + friend void swap(WebSocketResponseMessage& a, WebSocketResponseMessage& b) { + a.Swap(&b); + } + inline void Swap(WebSocketResponseMessage* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(WebSocketResponseMessage* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + WebSocketResponseMessage* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const WebSocketResponseMessage& from); + void MergeFrom(const WebSocketResponseMessage& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(WebSocketResponseMessage* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "WebSocketProtos.WebSocketResponseMessage"; + } + protected: + explicit WebSocketResponseMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kHeadersFieldNumber = 5, + kMessageFieldNumber = 3, + kBodyFieldNumber = 4, + kRequestIdFieldNumber = 1, + kStatusFieldNumber = 2, + }; + // repeated string headers = 5; + int headers_size() const; + private: + int _internal_headers_size() const; + public: + void clear_headers(); + const std::string& headers(int index) const; + std::string* mutable_headers(int index); + void set_headers(int index, const std::string& value); + void set_headers(int index, std::string&& value); + void set_headers(int index, const char* value); + void set_headers(int index, const char* value, size_t size); + std::string* add_headers(); + void add_headers(const std::string& value); + void add_headers(std::string&& value); + void add_headers(const char* value); + void add_headers(const char* value, size_t size); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& headers() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* mutable_headers(); + private: + const std::string& _internal_headers(int index) const; + std::string* _internal_add_headers(); + public: + + // optional string message = 3; + bool has_message() const; + private: + bool _internal_has_message() const; + public: + void clear_message(); + const std::string& message() const; + template + void set_message(ArgT0&& arg0, ArgT... args); + std::string* mutable_message(); + PROTOBUF_NODISCARD std::string* release_message(); + void set_allocated_message(std::string* message); + private: + const std::string& _internal_message() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_message(const std::string& value); + std::string* _internal_mutable_message(); + public: + + // optional bytes body = 4; + bool has_body() const; + private: + bool _internal_has_body() const; + public: + void clear_body(); + const std::string& body() const; + template + void set_body(ArgT0&& arg0, ArgT... args); + std::string* mutable_body(); + PROTOBUF_NODISCARD std::string* release_body(); + void set_allocated_body(std::string* body); + private: + const std::string& _internal_body() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_body(const std::string& value); + std::string* _internal_mutable_body(); + public: + + // optional uint64 requestId = 1; + bool has_requestid() const; + private: + bool _internal_has_requestid() const; + public: + void clear_requestid(); + uint64_t requestid() const; + void set_requestid(uint64_t value); + private: + uint64_t _internal_requestid() const; + void _internal_set_requestid(uint64_t value); + public: + + // optional uint32 status = 2; + bool has_status() const; + private: + bool _internal_has_status() const; + public: + void clear_status(); + uint32_t status() const; + void set_status(uint32_t value); + private: + uint32_t _internal_status() const; + void _internal_set_status(uint32_t value); + public: + + // @@protoc_insertion_point(class_scope:WebSocketProtos.WebSocketResponseMessage) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField headers_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr message_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr body_; + uint64_t requestid_; + uint32_t status_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_WebSocketResources_2eproto; +}; +// ------------------------------------------------------------------- + +class WebSocketMessage final : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:WebSocketProtos.WebSocketMessage) */ { + public: + inline WebSocketMessage() : WebSocketMessage(nullptr) {} + ~WebSocketMessage() override; + explicit PROTOBUF_CONSTEXPR WebSocketMessage(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + WebSocketMessage(const WebSocketMessage& from); + WebSocketMessage(WebSocketMessage&& from) noexcept + : WebSocketMessage() { + *this = ::std::move(from); + } + + inline WebSocketMessage& operator=(const WebSocketMessage& from) { + CopyFrom(from); + return *this; + } + inline WebSocketMessage& operator=(WebSocketMessage&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const WebSocketMessage& default_instance() { + return *internal_default_instance(); + } + static inline const WebSocketMessage* internal_default_instance() { + return reinterpret_cast( + &_WebSocketMessage_default_instance_); + } + static constexpr int kIndexInFileMessages = + 2; + + friend void swap(WebSocketMessage& a, WebSocketMessage& b) { + a.Swap(&b); + } + inline void Swap(WebSocketMessage* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(WebSocketMessage* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + WebSocketMessage* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final; + void CopyFrom(const WebSocketMessage& from); + void MergeFrom(const WebSocketMessage& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(WebSocketMessage* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "WebSocketProtos.WebSocketMessage"; + } + protected: + explicit WebSocketMessage(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef WebSocketMessage_Type Type; + static constexpr Type UNKNOWN = + WebSocketMessage_Type_UNKNOWN; + static constexpr Type REQUEST = + WebSocketMessage_Type_REQUEST; + static constexpr Type RESPONSE = + WebSocketMessage_Type_RESPONSE; + static inline bool Type_IsValid(int value) { + return WebSocketMessage_Type_IsValid(value); + } + static constexpr Type Type_MIN = + WebSocketMessage_Type_Type_MIN; + static constexpr Type Type_MAX = + WebSocketMessage_Type_Type_MAX; + static constexpr int Type_ARRAYSIZE = + WebSocketMessage_Type_Type_ARRAYSIZE; + template + static inline const std::string& Type_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Type_Name."); + return WebSocketMessage_Type_Name(enum_t_value); + } + static inline bool Type_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, + Type* value) { + return WebSocketMessage_Type_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kRequestFieldNumber = 2, + kResponseFieldNumber = 3, + kTypeFieldNumber = 1, + }; + // optional .WebSocketProtos.WebSocketRequestMessage request = 2; + bool has_request() const; + private: + bool _internal_has_request() const; + public: + void clear_request(); + const ::WebSocketProtos::WebSocketRequestMessage& request() const; + PROTOBUF_NODISCARD ::WebSocketProtos::WebSocketRequestMessage* release_request(); + ::WebSocketProtos::WebSocketRequestMessage* mutable_request(); + void set_allocated_request(::WebSocketProtos::WebSocketRequestMessage* request); + private: + const ::WebSocketProtos::WebSocketRequestMessage& _internal_request() const; + ::WebSocketProtos::WebSocketRequestMessage* _internal_mutable_request(); + public: + void unsafe_arena_set_allocated_request( + ::WebSocketProtos::WebSocketRequestMessage* request); + ::WebSocketProtos::WebSocketRequestMessage* unsafe_arena_release_request(); + + // optional .WebSocketProtos.WebSocketResponseMessage response = 3; + bool has_response() const; + private: + bool _internal_has_response() const; + public: + void clear_response(); + const ::WebSocketProtos::WebSocketResponseMessage& response() const; + PROTOBUF_NODISCARD ::WebSocketProtos::WebSocketResponseMessage* release_response(); + ::WebSocketProtos::WebSocketResponseMessage* mutable_response(); + void set_allocated_response(::WebSocketProtos::WebSocketResponseMessage* response); + private: + const ::WebSocketProtos::WebSocketResponseMessage& _internal_response() const; + ::WebSocketProtos::WebSocketResponseMessage* _internal_mutable_response(); + public: + void unsafe_arena_set_allocated_response( + ::WebSocketProtos::WebSocketResponseMessage* response); + ::WebSocketProtos::WebSocketResponseMessage* unsafe_arena_release_response(); + + // optional .WebSocketProtos.WebSocketMessage.Type type = 1; + bool has_type() const; + private: + bool _internal_has_type() const; + public: + void clear_type(); + ::WebSocketProtos::WebSocketMessage_Type type() const; + void set_type(::WebSocketProtos::WebSocketMessage_Type value); + private: + ::WebSocketProtos::WebSocketMessage_Type _internal_type() const; + void _internal_set_type(::WebSocketProtos::WebSocketMessage_Type value); + public: + + // @@protoc_insertion_point(class_scope:WebSocketProtos.WebSocketMessage) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::WebSocketProtos::WebSocketRequestMessage* request_; + ::WebSocketProtos::WebSocketResponseMessage* response_; + int type_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_WebSocketResources_2eproto; +}; +// =================================================================== + + +// =================================================================== + +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif // __GNUC__ +// WebSocketRequestMessage + +// optional string verb = 1; +inline bool WebSocketRequestMessage::_internal_has_verb() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool WebSocketRequestMessage::has_verb() const { + return _internal_has_verb(); +} +inline void WebSocketRequestMessage::clear_verb() { + _impl_.verb_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& WebSocketRequestMessage::verb() const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketRequestMessage.verb) + return _internal_verb(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void WebSocketRequestMessage::set_verb(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.verb_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketRequestMessage.verb) +} +inline std::string* WebSocketRequestMessage::mutable_verb() { + std::string* _s = _internal_mutable_verb(); + // @@protoc_insertion_point(field_mutable:WebSocketProtos.WebSocketRequestMessage.verb) + return _s; +} +inline const std::string& WebSocketRequestMessage::_internal_verb() const { + return _impl_.verb_.Get(); +} +inline void WebSocketRequestMessage::_internal_set_verb(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.verb_.Set(value, GetArenaForAllocation()); +} +inline std::string* WebSocketRequestMessage::_internal_mutable_verb() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.verb_.Mutable(GetArenaForAllocation()); +} +inline std::string* WebSocketRequestMessage::release_verb() { + // @@protoc_insertion_point(field_release:WebSocketProtos.WebSocketRequestMessage.verb) + if (!_internal_has_verb()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.verb_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.verb_.IsDefault()) { + _impl_.verb_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void WebSocketRequestMessage::set_allocated_verb(std::string* verb) { + if (verb != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.verb_.SetAllocated(verb, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.verb_.IsDefault()) { + _impl_.verb_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:WebSocketProtos.WebSocketRequestMessage.verb) +} + +// optional string path = 2; +inline bool WebSocketRequestMessage::_internal_has_path() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool WebSocketRequestMessage::has_path() const { + return _internal_has_path(); +} +inline void WebSocketRequestMessage::clear_path() { + _impl_.path_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& WebSocketRequestMessage::path() const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketRequestMessage.path) + return _internal_path(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void WebSocketRequestMessage::set_path(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.path_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketRequestMessage.path) +} +inline std::string* WebSocketRequestMessage::mutable_path() { + std::string* _s = _internal_mutable_path(); + // @@protoc_insertion_point(field_mutable:WebSocketProtos.WebSocketRequestMessage.path) + return _s; +} +inline const std::string& WebSocketRequestMessage::_internal_path() const { + return _impl_.path_.Get(); +} +inline void WebSocketRequestMessage::_internal_set_path(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.path_.Set(value, GetArenaForAllocation()); +} +inline std::string* WebSocketRequestMessage::_internal_mutable_path() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.path_.Mutable(GetArenaForAllocation()); +} +inline std::string* WebSocketRequestMessage::release_path() { + // @@protoc_insertion_point(field_release:WebSocketProtos.WebSocketRequestMessage.path) + if (!_internal_has_path()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.path_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.path_.IsDefault()) { + _impl_.path_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void WebSocketRequestMessage::set_allocated_path(std::string* path) { + if (path != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.path_.SetAllocated(path, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.path_.IsDefault()) { + _impl_.path_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:WebSocketProtos.WebSocketRequestMessage.path) +} + +// optional bytes body = 3; +inline bool WebSocketRequestMessage::_internal_has_body() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool WebSocketRequestMessage::has_body() const { + return _internal_has_body(); +} +inline void WebSocketRequestMessage::clear_body() { + _impl_.body_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline const std::string& WebSocketRequestMessage::body() const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketRequestMessage.body) + return _internal_body(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void WebSocketRequestMessage::set_body(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.body_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketRequestMessage.body) +} +inline std::string* WebSocketRequestMessage::mutable_body() { + std::string* _s = _internal_mutable_body(); + // @@protoc_insertion_point(field_mutable:WebSocketProtos.WebSocketRequestMessage.body) + return _s; +} +inline const std::string& WebSocketRequestMessage::_internal_body() const { + return _impl_.body_.Get(); +} +inline void WebSocketRequestMessage::_internal_set_body(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.body_.Set(value, GetArenaForAllocation()); +} +inline std::string* WebSocketRequestMessage::_internal_mutable_body() { + _impl_._has_bits_[0] |= 0x00000004u; + return _impl_.body_.Mutable(GetArenaForAllocation()); +} +inline std::string* WebSocketRequestMessage::release_body() { + // @@protoc_insertion_point(field_release:WebSocketProtos.WebSocketRequestMessage.body) + if (!_internal_has_body()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000004u; + auto* p = _impl_.body_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.body_.IsDefault()) { + _impl_.body_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void WebSocketRequestMessage::set_allocated_body(std::string* body) { + if (body != nullptr) { + _impl_._has_bits_[0] |= 0x00000004u; + } else { + _impl_._has_bits_[0] &= ~0x00000004u; + } + _impl_.body_.SetAllocated(body, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.body_.IsDefault()) { + _impl_.body_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:WebSocketProtos.WebSocketRequestMessage.body) +} + +// repeated string headers = 5; +inline int WebSocketRequestMessage::_internal_headers_size() const { + return _impl_.headers_.size(); +} +inline int WebSocketRequestMessage::headers_size() const { + return _internal_headers_size(); +} +inline void WebSocketRequestMessage::clear_headers() { + _impl_.headers_.Clear(); +} +inline std::string* WebSocketRequestMessage::add_headers() { + std::string* _s = _internal_add_headers(); + // @@protoc_insertion_point(field_add_mutable:WebSocketProtos.WebSocketRequestMessage.headers) + return _s; +} +inline const std::string& WebSocketRequestMessage::_internal_headers(int index) const { + return _impl_.headers_.Get(index); +} +inline const std::string& WebSocketRequestMessage::headers(int index) const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketRequestMessage.headers) + return _internal_headers(index); +} +inline std::string* WebSocketRequestMessage::mutable_headers(int index) { + // @@protoc_insertion_point(field_mutable:WebSocketProtos.WebSocketRequestMessage.headers) + return _impl_.headers_.Mutable(index); +} +inline void WebSocketRequestMessage::set_headers(int index, const std::string& value) { + _impl_.headers_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketRequestMessage.headers) +} +inline void WebSocketRequestMessage::set_headers(int index, std::string&& value) { + _impl_.headers_.Mutable(index)->assign(std::move(value)); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketRequestMessage.headers) +} +inline void WebSocketRequestMessage::set_headers(int index, const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.headers_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set_char:WebSocketProtos.WebSocketRequestMessage.headers) +} +inline void WebSocketRequestMessage::set_headers(int index, const char* value, size_t size) { + _impl_.headers_.Mutable(index)->assign( + reinterpret_cast(value), size); + // @@protoc_insertion_point(field_set_pointer:WebSocketProtos.WebSocketRequestMessage.headers) +} +inline std::string* WebSocketRequestMessage::_internal_add_headers() { + return _impl_.headers_.Add(); +} +inline void WebSocketRequestMessage::add_headers(const std::string& value) { + _impl_.headers_.Add()->assign(value); + // @@protoc_insertion_point(field_add:WebSocketProtos.WebSocketRequestMessage.headers) +} +inline void WebSocketRequestMessage::add_headers(std::string&& value) { + _impl_.headers_.Add(std::move(value)); + // @@protoc_insertion_point(field_add:WebSocketProtos.WebSocketRequestMessage.headers) +} +inline void WebSocketRequestMessage::add_headers(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.headers_.Add()->assign(value); + // @@protoc_insertion_point(field_add_char:WebSocketProtos.WebSocketRequestMessage.headers) +} +inline void WebSocketRequestMessage::add_headers(const char* value, size_t size) { + _impl_.headers_.Add()->assign(reinterpret_cast(value), size); + // @@protoc_insertion_point(field_add_pointer:WebSocketProtos.WebSocketRequestMessage.headers) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& +WebSocketRequestMessage::headers() const { + // @@protoc_insertion_point(field_list:WebSocketProtos.WebSocketRequestMessage.headers) + return _impl_.headers_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* +WebSocketRequestMessage::mutable_headers() { + // @@protoc_insertion_point(field_mutable_list:WebSocketProtos.WebSocketRequestMessage.headers) + return &_impl_.headers_; +} + +// optional uint64 requestId = 4; +inline bool WebSocketRequestMessage::_internal_has_requestid() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool WebSocketRequestMessage::has_requestid() const { + return _internal_has_requestid(); +} +inline void WebSocketRequestMessage::clear_requestid() { + _impl_.requestid_ = uint64_t{0u}; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline uint64_t WebSocketRequestMessage::_internal_requestid() const { + return _impl_.requestid_; +} +inline uint64_t WebSocketRequestMessage::requestid() const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketRequestMessage.requestId) + return _internal_requestid(); +} +inline void WebSocketRequestMessage::_internal_set_requestid(uint64_t value) { + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.requestid_ = value; +} +inline void WebSocketRequestMessage::set_requestid(uint64_t value) { + _internal_set_requestid(value); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketRequestMessage.requestId) +} + +// ------------------------------------------------------------------- + +// WebSocketResponseMessage + +// optional uint64 requestId = 1; +inline bool WebSocketResponseMessage::_internal_has_requestid() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool WebSocketResponseMessage::has_requestid() const { + return _internal_has_requestid(); +} +inline void WebSocketResponseMessage::clear_requestid() { + _impl_.requestid_ = uint64_t{0u}; + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline uint64_t WebSocketResponseMessage::_internal_requestid() const { + return _impl_.requestid_; +} +inline uint64_t WebSocketResponseMessage::requestid() const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketResponseMessage.requestId) + return _internal_requestid(); +} +inline void WebSocketResponseMessage::_internal_set_requestid(uint64_t value) { + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.requestid_ = value; +} +inline void WebSocketResponseMessage::set_requestid(uint64_t value) { + _internal_set_requestid(value); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketResponseMessage.requestId) +} + +// optional uint32 status = 2; +inline bool WebSocketResponseMessage::_internal_has_status() const { + bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool WebSocketResponseMessage::has_status() const { + return _internal_has_status(); +} +inline void WebSocketResponseMessage::clear_status() { + _impl_.status_ = 0u; + _impl_._has_bits_[0] &= ~0x00000008u; +} +inline uint32_t WebSocketResponseMessage::_internal_status() const { + return _impl_.status_; +} +inline uint32_t WebSocketResponseMessage::status() const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketResponseMessage.status) + return _internal_status(); +} +inline void WebSocketResponseMessage::_internal_set_status(uint32_t value) { + _impl_._has_bits_[0] |= 0x00000008u; + _impl_.status_ = value; +} +inline void WebSocketResponseMessage::set_status(uint32_t value) { + _internal_set_status(value); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketResponseMessage.status) +} + +// optional string message = 3; +inline bool WebSocketResponseMessage::_internal_has_message() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool WebSocketResponseMessage::has_message() const { + return _internal_has_message(); +} +inline void WebSocketResponseMessage::clear_message() { + _impl_.message_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const std::string& WebSocketResponseMessage::message() const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketResponseMessage.message) + return _internal_message(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void WebSocketResponseMessage::set_message(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.message_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketResponseMessage.message) +} +inline std::string* WebSocketResponseMessage::mutable_message() { + std::string* _s = _internal_mutable_message(); + // @@protoc_insertion_point(field_mutable:WebSocketProtos.WebSocketResponseMessage.message) + return _s; +} +inline const std::string& WebSocketResponseMessage::_internal_message() const { + return _impl_.message_.Get(); +} +inline void WebSocketResponseMessage::_internal_set_message(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000001u; + _impl_.message_.Set(value, GetArenaForAllocation()); +} +inline std::string* WebSocketResponseMessage::_internal_mutable_message() { + _impl_._has_bits_[0] |= 0x00000001u; + return _impl_.message_.Mutable(GetArenaForAllocation()); +} +inline std::string* WebSocketResponseMessage::release_message() { + // @@protoc_insertion_point(field_release:WebSocketProtos.WebSocketResponseMessage.message) + if (!_internal_has_message()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000001u; + auto* p = _impl_.message_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.message_.IsDefault()) { + _impl_.message_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void WebSocketResponseMessage::set_allocated_message(std::string* message) { + if (message != nullptr) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.message_.SetAllocated(message, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.message_.IsDefault()) { + _impl_.message_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:WebSocketProtos.WebSocketResponseMessage.message) +} + +// repeated string headers = 5; +inline int WebSocketResponseMessage::_internal_headers_size() const { + return _impl_.headers_.size(); +} +inline int WebSocketResponseMessage::headers_size() const { + return _internal_headers_size(); +} +inline void WebSocketResponseMessage::clear_headers() { + _impl_.headers_.Clear(); +} +inline std::string* WebSocketResponseMessage::add_headers() { + std::string* _s = _internal_add_headers(); + // @@protoc_insertion_point(field_add_mutable:WebSocketProtos.WebSocketResponseMessage.headers) + return _s; +} +inline const std::string& WebSocketResponseMessage::_internal_headers(int index) const { + return _impl_.headers_.Get(index); +} +inline const std::string& WebSocketResponseMessage::headers(int index) const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketResponseMessage.headers) + return _internal_headers(index); +} +inline std::string* WebSocketResponseMessage::mutable_headers(int index) { + // @@protoc_insertion_point(field_mutable:WebSocketProtos.WebSocketResponseMessage.headers) + return _impl_.headers_.Mutable(index); +} +inline void WebSocketResponseMessage::set_headers(int index, const std::string& value) { + _impl_.headers_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketResponseMessage.headers) +} +inline void WebSocketResponseMessage::set_headers(int index, std::string&& value) { + _impl_.headers_.Mutable(index)->assign(std::move(value)); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketResponseMessage.headers) +} +inline void WebSocketResponseMessage::set_headers(int index, const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.headers_.Mutable(index)->assign(value); + // @@protoc_insertion_point(field_set_char:WebSocketProtos.WebSocketResponseMessage.headers) +} +inline void WebSocketResponseMessage::set_headers(int index, const char* value, size_t size) { + _impl_.headers_.Mutable(index)->assign( + reinterpret_cast(value), size); + // @@protoc_insertion_point(field_set_pointer:WebSocketProtos.WebSocketResponseMessage.headers) +} +inline std::string* WebSocketResponseMessage::_internal_add_headers() { + return _impl_.headers_.Add(); +} +inline void WebSocketResponseMessage::add_headers(const std::string& value) { + _impl_.headers_.Add()->assign(value); + // @@protoc_insertion_point(field_add:WebSocketProtos.WebSocketResponseMessage.headers) +} +inline void WebSocketResponseMessage::add_headers(std::string&& value) { + _impl_.headers_.Add(std::move(value)); + // @@protoc_insertion_point(field_add:WebSocketProtos.WebSocketResponseMessage.headers) +} +inline void WebSocketResponseMessage::add_headers(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _impl_.headers_.Add()->assign(value); + // @@protoc_insertion_point(field_add_char:WebSocketProtos.WebSocketResponseMessage.headers) +} +inline void WebSocketResponseMessage::add_headers(const char* value, size_t size) { + _impl_.headers_.Add()->assign(reinterpret_cast(value), size); + // @@protoc_insertion_point(field_add_pointer:WebSocketProtos.WebSocketResponseMessage.headers) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField& +WebSocketResponseMessage::headers() const { + // @@protoc_insertion_point(field_list:WebSocketProtos.WebSocketResponseMessage.headers) + return _impl_.headers_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField* +WebSocketResponseMessage::mutable_headers() { + // @@protoc_insertion_point(field_mutable_list:WebSocketProtos.WebSocketResponseMessage.headers) + return &_impl_.headers_; +} + +// optional bytes body = 4; +inline bool WebSocketResponseMessage::_internal_has_body() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool WebSocketResponseMessage::has_body() const { + return _internal_has_body(); +} +inline void WebSocketResponseMessage::clear_body() { + _impl_.body_.ClearToEmpty(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const std::string& WebSocketResponseMessage::body() const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketResponseMessage.body) + return _internal_body(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void WebSocketResponseMessage::set_body(ArgT0&& arg0, ArgT... args) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.body_.SetBytes(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketResponseMessage.body) +} +inline std::string* WebSocketResponseMessage::mutable_body() { + std::string* _s = _internal_mutable_body(); + // @@protoc_insertion_point(field_mutable:WebSocketProtos.WebSocketResponseMessage.body) + return _s; +} +inline const std::string& WebSocketResponseMessage::_internal_body() const { + return _impl_.body_.Get(); +} +inline void WebSocketResponseMessage::_internal_set_body(const std::string& value) { + _impl_._has_bits_[0] |= 0x00000002u; + _impl_.body_.Set(value, GetArenaForAllocation()); +} +inline std::string* WebSocketResponseMessage::_internal_mutable_body() { + _impl_._has_bits_[0] |= 0x00000002u; + return _impl_.body_.Mutable(GetArenaForAllocation()); +} +inline std::string* WebSocketResponseMessage::release_body() { + // @@protoc_insertion_point(field_release:WebSocketProtos.WebSocketResponseMessage.body) + if (!_internal_has_body()) { + return nullptr; + } + _impl_._has_bits_[0] &= ~0x00000002u; + auto* p = _impl_.body_.Release(); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.body_.IsDefault()) { + _impl_.body_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + return p; +} +inline void WebSocketResponseMessage::set_allocated_body(std::string* body) { + if (body != nullptr) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.body_.SetAllocated(body, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.body_.IsDefault()) { + _impl_.body_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:WebSocketProtos.WebSocketResponseMessage.body) +} + +// ------------------------------------------------------------------- + +// WebSocketMessage + +// optional .WebSocketProtos.WebSocketMessage.Type type = 1; +inline bool WebSocketMessage::_internal_has_type() const { + bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool WebSocketMessage::has_type() const { + return _internal_has_type(); +} +inline void WebSocketMessage::clear_type() { + _impl_.type_ = 0; + _impl_._has_bits_[0] &= ~0x00000004u; +} +inline ::WebSocketProtos::WebSocketMessage_Type WebSocketMessage::_internal_type() const { + return static_cast< ::WebSocketProtos::WebSocketMessage_Type >(_impl_.type_); +} +inline ::WebSocketProtos::WebSocketMessage_Type WebSocketMessage::type() const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketMessage.type) + return _internal_type(); +} +inline void WebSocketMessage::_internal_set_type(::WebSocketProtos::WebSocketMessage_Type value) { + assert(::WebSocketProtos::WebSocketMessage_Type_IsValid(value)); + _impl_._has_bits_[0] |= 0x00000004u; + _impl_.type_ = value; +} +inline void WebSocketMessage::set_type(::WebSocketProtos::WebSocketMessage_Type value) { + _internal_set_type(value); + // @@protoc_insertion_point(field_set:WebSocketProtos.WebSocketMessage.type) +} + +// optional .WebSocketProtos.WebSocketRequestMessage request = 2; +inline bool WebSocketMessage::_internal_has_request() const { + bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || _impl_.request_ != nullptr); + return value; +} +inline bool WebSocketMessage::has_request() const { + return _internal_has_request(); +} +inline void WebSocketMessage::clear_request() { + if (_impl_.request_ != nullptr) _impl_.request_->Clear(); + _impl_._has_bits_[0] &= ~0x00000001u; +} +inline const ::WebSocketProtos::WebSocketRequestMessage& WebSocketMessage::_internal_request() const { + const ::WebSocketProtos::WebSocketRequestMessage* p = _impl_.request_; + return p != nullptr ? *p : reinterpret_cast( + ::WebSocketProtos::_WebSocketRequestMessage_default_instance_); +} +inline const ::WebSocketProtos::WebSocketRequestMessage& WebSocketMessage::request() const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketMessage.request) + return _internal_request(); +} +inline void WebSocketMessage::unsafe_arena_set_allocated_request( + ::WebSocketProtos::WebSocketRequestMessage* request) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.request_); + } + _impl_.request_ = request; + if (request) { + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:WebSocketProtos.WebSocketMessage.request) +} +inline ::WebSocketProtos::WebSocketRequestMessage* WebSocketMessage::release_request() { + _impl_._has_bits_[0] &= ~0x00000001u; + ::WebSocketProtos::WebSocketRequestMessage* temp = _impl_.request_; + _impl_.request_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::WebSocketProtos::WebSocketRequestMessage* WebSocketMessage::unsafe_arena_release_request() { + // @@protoc_insertion_point(field_release:WebSocketProtos.WebSocketMessage.request) + _impl_._has_bits_[0] &= ~0x00000001u; + ::WebSocketProtos::WebSocketRequestMessage* temp = _impl_.request_; + _impl_.request_ = nullptr; + return temp; +} +inline ::WebSocketProtos::WebSocketRequestMessage* WebSocketMessage::_internal_mutable_request() { + _impl_._has_bits_[0] |= 0x00000001u; + if (_impl_.request_ == nullptr) { + auto* p = CreateMaybeMessage<::WebSocketProtos::WebSocketRequestMessage>(GetArenaForAllocation()); + _impl_.request_ = p; + } + return _impl_.request_; +} +inline ::WebSocketProtos::WebSocketRequestMessage* WebSocketMessage::mutable_request() { + ::WebSocketProtos::WebSocketRequestMessage* _msg = _internal_mutable_request(); + // @@protoc_insertion_point(field_mutable:WebSocketProtos.WebSocketMessage.request) + return _msg; +} +inline void WebSocketMessage::set_allocated_request(::WebSocketProtos::WebSocketRequestMessage* request) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.request_; + } + if (request) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(request); + if (message_arena != submessage_arena) { + request = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, request, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000001u; + } else { + _impl_._has_bits_[0] &= ~0x00000001u; + } + _impl_.request_ = request; + // @@protoc_insertion_point(field_set_allocated:WebSocketProtos.WebSocketMessage.request) +} + +// optional .WebSocketProtos.WebSocketResponseMessage response = 3; +inline bool WebSocketMessage::_internal_has_response() const { + bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || _impl_.response_ != nullptr); + return value; +} +inline bool WebSocketMessage::has_response() const { + return _internal_has_response(); +} +inline void WebSocketMessage::clear_response() { + if (_impl_.response_ != nullptr) _impl_.response_->Clear(); + _impl_._has_bits_[0] &= ~0x00000002u; +} +inline const ::WebSocketProtos::WebSocketResponseMessage& WebSocketMessage::_internal_response() const { + const ::WebSocketProtos::WebSocketResponseMessage* p = _impl_.response_; + return p != nullptr ? *p : reinterpret_cast( + ::WebSocketProtos::_WebSocketResponseMessage_default_instance_); +} +inline const ::WebSocketProtos::WebSocketResponseMessage& WebSocketMessage::response() const { + // @@protoc_insertion_point(field_get:WebSocketProtos.WebSocketMessage.response) + return _internal_response(); +} +inline void WebSocketMessage::unsafe_arena_set_allocated_response( + ::WebSocketProtos::WebSocketResponseMessage* response) { + if (GetArenaForAllocation() == nullptr) { + delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.response_); + } + _impl_.response_ = response; + if (response) { + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:WebSocketProtos.WebSocketMessage.response) +} +inline ::WebSocketProtos::WebSocketResponseMessage* WebSocketMessage::release_response() { + _impl_._has_bits_[0] &= ~0x00000002u; + ::WebSocketProtos::WebSocketResponseMessage* temp = _impl_.response_; + _impl_.response_ = nullptr; +#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE + auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp); + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + if (GetArenaForAllocation() == nullptr) { delete old; } +#else // PROTOBUF_FORCE_COPY_IN_RELEASE + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } +#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE + return temp; +} +inline ::WebSocketProtos::WebSocketResponseMessage* WebSocketMessage::unsafe_arena_release_response() { + // @@protoc_insertion_point(field_release:WebSocketProtos.WebSocketMessage.response) + _impl_._has_bits_[0] &= ~0x00000002u; + ::WebSocketProtos::WebSocketResponseMessage* temp = _impl_.response_; + _impl_.response_ = nullptr; + return temp; +} +inline ::WebSocketProtos::WebSocketResponseMessage* WebSocketMessage::_internal_mutable_response() { + _impl_._has_bits_[0] |= 0x00000002u; + if (_impl_.response_ == nullptr) { + auto* p = CreateMaybeMessage<::WebSocketProtos::WebSocketResponseMessage>(GetArenaForAllocation()); + _impl_.response_ = p; + } + return _impl_.response_; +} +inline ::WebSocketProtos::WebSocketResponseMessage* WebSocketMessage::mutable_response() { + ::WebSocketProtos::WebSocketResponseMessage* _msg = _internal_mutable_response(); + // @@protoc_insertion_point(field_mutable:WebSocketProtos.WebSocketMessage.response) + return _msg; +} +inline void WebSocketMessage::set_allocated_response(::WebSocketProtos::WebSocketResponseMessage* response) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + if (message_arena == nullptr) { + delete _impl_.response_; + } + if (response) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(response); + if (message_arena != submessage_arena) { + response = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, response, submessage_arena); + } + _impl_._has_bits_[0] |= 0x00000002u; + } else { + _impl_._has_bits_[0] &= ~0x00000002u; + } + _impl_.response_ = response; + // @@protoc_insertion_point(field_set_allocated:WebSocketProtos.WebSocketMessage.response) +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif // __GNUC__ +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + + +// @@protoc_insertion_point(namespace_scope) + +} // namespace WebSocketProtos + +PROTOBUF_NAMESPACE_OPEN + +template <> struct is_proto_enum< ::WebSocketProtos::WebSocketMessage_Type> : ::std::true_type {}; + +PROTOBUF_NAMESPACE_CLOSE + +// @@protoc_insertion_point(global_scope) + +#include +#endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_WebSocketResources_2eproto diff --git a/proto/WebSocketResources.proto b/proto/WebSocketResources.proto new file mode 100644 index 00000000..9107fc9a --- /dev/null +++ b/proto/WebSocketResources.proto @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2014-2016 Open Whisper Systems + * + * Licensed according to the LICENSE file in this repository. + */ + +// iOS - since we use a modern proto-compiler, we must specify +// the legacy proto format. +syntax = "proto2"; + +// iOS - package name determines class prefix +package WebSocketProtos; + +option optimize_for = LITE_RUNTIME; + +option java_package = "org.whispersystems.signalservice.internal.websocket"; +option java_outer_classname = "WebSocketProtos"; + +message WebSocketRequestMessage { + // @required + optional string verb = 1; + // @required + optional string path = 2; + optional bytes body = 3; + repeated string headers = 5; + // @required + optional uint64 requestId = 4; +} + +message WebSocketResponseMessage { + // @required + optional uint64 requestId = 1; + // @required + optional uint32 status = 2; + optional string message = 3; + repeated string headers = 5; + optional bytes body = 4; +} + +message WebSocketMessage { + enum Type { + UNKNOWN = 0; + REQUEST = 1; + RESPONSE = 2; + } + + // @required + optional Type type = 1; + optional WebSocketRequestMessage request = 2; + optional WebSocketResponseMessage response = 3; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f4f836b5..e637f7de 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,19 +1,60 @@ add_library(common INTERFACE) +target_link_libraries(common INTERFACE + oxenc::oxenc + oxen::logging) target_include_directories(common INTERFACE ../include) if(WARNINGS_AS_ERRORS) target_compile_options(common INTERFACE -Werror) + message(STATUS "Compiling with fatal warnings (-Werror)") + if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND + CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 11 AND + CMAKE_CXX_COMPILER_VERSION VERSION_LESS 14) + # -Wstringop-overflow triggers (falsely) in protobuf in GCC 11/12/13 so disable it there + # (fingers crossed for 14). + message(STATUS "Disabling -Werror for buggy GCC stringop-overflow") + target_compile_options(common INTERFACE -Wno-error=stringop-overflow) + endif() endif() -add_library(crypto + +set(export_targets) +macro(add_libsession_util_library name) + add_library(${name} ${ARGN}) + + set_target_properties( + ${name} + PROPERTIES + OUTPUT_NAME session-${name} + SOVERSION ${LIBSESSION_LIBVERSION}) + + libsession_static_bundle(${name}) + + list(APPEND export_targets ${name}) +endmacro() + + + +add_libsession_util_library(util + file.cpp + logging.cpp + util.cpp +) + +add_libsession_util_library(crypto + blinding.cpp + curve25519.cpp + ed25519.cpp + hash.cpp + multi_encrypt.cpp + random.cpp + session_encrypt.cpp + sodium_array.cpp xed25519.cpp ) -set_target_properties( - crypto - PROPERTIES OUTPUT_NAME session-util-crypto) -add_library(config +add_libsession_util_library(config bt_merge.cpp config.cpp config/base.cpp @@ -22,26 +63,64 @@ add_library(config config/convo_info_volatile.cpp config/encrypt.cpp config/error.c + config/groups/info.cpp + config/groups/keys.cpp + config/groups/members.cpp config/internal.cpp + config/protos.cpp config/user_groups.cpp config/user_profile.cpp fields.cpp ) -set_target_properties( - config - PROPERTIES OUTPUT_NAME session-util-config) + + + +target_link_libraries(util + PUBLIC + common + oxen::logging +) target_link_libraries(crypto PUBLIC + util + PRIVATE libsodium::sodium-internal - common) +) + target_link_libraries(config PUBLIC crypto - oxenc::oxenc + libsession::protos + PRIVATE + libsodium::sodium-internal libzstd::static - common) +) +if(ENABLE_ONIONREQ) + add_libsession_util_library(onionreq + onionreq/builder.cpp + onionreq/hop_encryption.cpp + onionreq/key_types.cpp + onionreq/parser.cpp + onionreq/response_parser.cpp + session_network.cpp + ) + + target_link_libraries(onionreq + PUBLIC + crypto + quic + PRIVATE + nlohmann_json::nlohmann_json + libsodium::sodium-internal + nettle::nettle + ) + + if (BUILD_STATIC_DEPS) + target_include_directories(onionreq PUBLIC ${CMAKE_BINARY_DIR}/static-deps/include) + endif() +endif() if(WARNINGS_AS_ERRORS AND NOT USE_LTO AND CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION MATCHES "^11\\.") # GCC 11 has an overzealous (and false) stringop-overread warning, but only when LTO is off. @@ -49,6 +128,14 @@ if(WARNINGS_AS_ERRORS AND NOT USE_LTO AND CMAKE_C_COMPILER_ID STREQUAL "GNU" AND target_compile_options(config PUBLIC -Wno-error=stringop-overread) endif() +# GCC's stringop overflow and overread warnings are very buggy, producing false positives for all +# sorts of valid things, so make sure we don't have -Werror for them. We cap this check for now at +# 15 (where these still occur in the latest gcc snapshot) with the vain hope that maybe they'll get +# fixed for gcc 16. +if(WARNINGS_AS_ERRORS AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND + CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 12.0.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16.0.0) + target_compile_options(crypto PUBLIC -Wno-error=stringop-overflow -Wno-error=stringop-overread) +endif() if(LIBSESSION_UTIL_VERSIONTAG) set(PROJECT_VERSION_TAG "${LIBSESSION_UTIL_VERSIONTAG}") @@ -76,17 +163,20 @@ else() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.c.in" "${CMAKE_CURRENT_BINARY_DIR}/version.c") endif() endif() -add_library(version version.c) -set_target_properties(version PROPERTIES OUTPUT_NAME session-util-version) +add_library(version STATIC version.c) +libsession_static_bundle(version) target_include_directories(version PRIVATE ../include) target_link_libraries(common INTERFACE version) - -add_library(libsession::config ALIAS config) -add_library(libsession::crypto ALIAS crypto) +foreach(tgt ${export_targets}) + add_library("libsession::${tgt}" ALIAS "${tgt}") +endforeach() export( - TARGETS config crypto common version + TARGETS ${export_targets} common version NAMESPACE libsession:: - FILE libsessionTargets.cmake + APPEND FILE libsessionTargets.cmake ) + +list(APPEND libsession_export_targets ${export_targets}) +set(libsession_export_targets "${libsession_export_targets}" PARENT_SCOPE) diff --git a/src/blinding.cpp b/src/blinding.cpp new file mode 100644 index 00000000..927aa5ea --- /dev/null +++ b/src/blinding.cpp @@ -0,0 +1,691 @@ +#include "session/blinding.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include "session/ed25519.hpp" +#include "session/export.h" +#include "session/platform.h" +#include "session/platform.hpp" +#include "session/xed25519.hpp" + +namespace session { + +using namespace std::literals; + +using uc32 = std::array; +using uc33 = std::array; +using uc64 = std::array; + +std::array blind15_factor(std::span server_pk) { + assert(server_pk.size() == 32); + + crypto_generichash_blake2b_state st; + crypto_generichash_blake2b_init(&st, nullptr, 0, 64); + crypto_generichash_blake2b_update(&st, server_pk.data(), server_pk.size()); + uc64 blind_hash; + crypto_generichash_blake2b_final(&st, blind_hash.data(), blind_hash.size()); + + uc32 k; + crypto_core_ed25519_scalar_reduce(k.data(), blind_hash.data()); + return k; +} + +std::array blind25_factor( + std::span session_id, std::span server_pk) { + assert(session_id.size() == 32 || session_id.size() == 33); + assert(server_pk.size() == 32); + + crypto_generichash_blake2b_state st; + crypto_generichash_blake2b_init(&st, nullptr, 0, 64); + if (session_id.size() == 32) { + constexpr unsigned char prefix = 0x05; + crypto_generichash_blake2b_update(&st, &prefix, 1); + } + crypto_generichash_blake2b_update(&st, session_id.data(), session_id.size()); + crypto_generichash_blake2b_update(&st, server_pk.data(), server_pk.size()); + uc64 blind_hash; + crypto_generichash_blake2b_final(&st, blind_hash.data(), blind_hash.size()); + + uc32 k; + crypto_core_ed25519_scalar_reduce(k.data(), blind_hash.data()); + return k; +} + +namespace { + + void blind15_id_impl( + std::span session_id, + std::span server_pk, + unsigned char* out) { + auto k = blind15_factor(server_pk); + if (session_id.size() == 33) + session_id = session_id.subspan(1); + auto ed_pk = xed25519::pubkey(session_id); + if (0 != crypto_scalarmult_ed25519_noclamp(out + 1, k.data(), ed_pk.data())) + throw std::runtime_error{"Cannot blind: invalid session_id (not on main subgroup)"}; + out[0] = 0x15; + } + + void blind25_id_impl( + std::span session_id, + std::span server_pk, + unsigned char* out) { + auto k = blind25_factor(session_id, server_pk); + if (session_id.size() == 33) + session_id = session_id.subspan(1); + auto ed_pk = xed25519::pubkey(session_id); + if (0 != crypto_scalarmult_ed25519_noclamp(out + 1, k.data(), ed_pk.data())) + throw std::runtime_error{"Cannot blind: invalid session_id (not on main subgroup)"}; + out[0] = 0x25; + } + +} // namespace + +std::vector blind15_id( + std::span session_id, std::span server_pk) { + if (session_id.size() == 33) { + if (session_id[0] != 0x05) + throw std::invalid_argument{"blind15_id: session_id must start with 0x05"}; + session_id = session_id.subspan(1); + } else if (session_id.size() != 32) { + throw std::invalid_argument{"blind15_id: session_id must be 32 or 33 bytes"}; + } + if (server_pk.size() != 32) + throw std::invalid_argument{"blind15_id: server_pk must be 32 bytes"}; + + std::vector result; + result.resize(33); + blind15_id_impl(session_id, server_pk, result.data()); + return result; +} + +std::array blind15_id(std::string_view session_id, std::string_view server_pk) { + if (session_id.size() != 66 || !oxenc::is_hex(session_id)) + throw std::invalid_argument{"blind15_id: session_id must be hex (66 digits)"}; + if (session_id[0] != '0' || session_id[1] != '5') + throw std::invalid_argument{"blind15_id: session_id must start with 05"}; + if (server_pk.size() != 64 || !oxenc::is_hex(server_pk)) + throw std::invalid_argument{"blind15_id: server_pk must be hex (64 digits)"}; + + uc33 raw_sid; + oxenc::from_hex(session_id.begin(), session_id.end(), raw_sid.begin()); + uc32 raw_server_pk; + oxenc::from_hex(server_pk.begin(), server_pk.end(), raw_server_pk.begin()); + + uc33 blinded; + blind15_id_impl(to_span(raw_sid), to_span(raw_server_pk), blinded.data()); + std::array result; + result[0] = oxenc::to_hex(blinded.begin(), blinded.end()); + blinded.back() ^= 0x80; + result[1] = oxenc::to_hex(blinded.begin(), blinded.end()); + return result; +} + +std::vector blind25_id( + std::span session_id, std::span server_pk) { + if (session_id.size() == 33) { + if (session_id[0] != 0x05) + throw std::invalid_argument{"blind25_id: session_id must start with 0x05"}; + } else if (session_id.size() != 32) { + throw std::invalid_argument{"blind25_id: session_id must be 32 or 33 bytes"}; + } + if (server_pk.size() != 32) + throw std::invalid_argument{"blind25_id: server_pk must be 32 bytes"}; + + std::vector result; + result.resize(33); + blind25_id_impl(session_id, server_pk, result.data()); + return result; +} + +std::string blind25_id(std::string_view session_id, std::string_view server_pk) { + if (session_id.size() != 66 || !oxenc::is_hex(session_id)) + throw std::invalid_argument{"blind25_id: session_id must be hex (66 digits)"}; + if (session_id[0] != '0' || session_id[1] != '5') + throw std::invalid_argument{"blind25_id: session_id must start with 05"}; + if (server_pk.size() != 64 || !oxenc::is_hex(server_pk)) + throw std::invalid_argument{"blind25_id: server_pk must be hex (64 digits)"}; + + uc33 raw_sid; + oxenc::from_hex(session_id.begin(), session_id.end(), raw_sid.begin()); + uc32 raw_server_pk; + oxenc::from_hex(server_pk.begin(), server_pk.end(), raw_server_pk.begin()); + + uc33 blinded; + blind25_id_impl(to_span(raw_sid), to_span(raw_server_pk), blinded.data()); + return oxenc::to_hex(blinded.begin(), blinded.end()); +} + +std::vector blinded15_id_from_ed( + std::span ed_pubkey, + std::span server_pk, + std::vector* session_id) { + if (ed_pubkey.size() != 32) + throw std::invalid_argument{"blind15_id_from_ed: ed_pubkey must be 32 bytes"}; + if (server_pk.size() != 32) + throw std::invalid_argument{"blind15_id_from_ed: server_pk must be 32 bytes"}; + if (session_id && !session_id->empty()) + throw std::invalid_argument{ + "blind15_id_from_ed: session_id pointer must be an empty string"}; + + if (session_id) { + session_id->resize(33); + session_id->front() = 0x05; + if (0 != crypto_sign_ed25519_pk_to_curve25519(session_id->data() + 1, ed_pubkey.data())) + throw std::runtime_error{"ed25519 pubkey to x25519 pubkey conversion failed"}; + } + + std::vector result; + result.resize(33); + auto k = blind15_factor(server_pk); + if (0 != crypto_scalarmult_ed25519_noclamp(result.data() + 1, k.data(), ed_pubkey.data())) + throw std::runtime_error{"Cannot blind: invalid session_id (not on main subgroup)"}; + result[0] = 0x15; + return result; +} + +std::vector blinded25_id_from_ed( + std::span ed_pubkey, + std::span server_pk, + std::vector* session_id) { + if (ed_pubkey.size() != 32) + throw std::invalid_argument{"blind25_id_from_ed: ed_pubkey must be 32 bytes"}; + if (server_pk.size() != 32) + throw std::invalid_argument{"blind25_id_from_ed: server_pk must be 32 bytes"}; + if (session_id && session_id->size() != 0 && session_id->size() != 33) + throw std::invalid_argument{"blind25_id_from_ed: session_id pointer must be 0 or 33 bytes"}; + + std::vector tmp_session_id; + if (!session_id) + session_id = &tmp_session_id; + if (session_id->size() == 0) { + session_id->resize(33); + session_id->front() = 0x05; + if (0 != crypto_sign_ed25519_pk_to_curve25519(session_id->data() + 1, ed_pubkey.data())) + throw std::runtime_error{"ed25519 pubkey to x25519 pubkey conversion failed"}; + } + + auto k = blind25_factor(*session_id, server_pk); + + std::vector result; + result.resize(33); + // Blinded25 ids are always constructed using the absolute value of the ed pubkey, so if + // negative we need to clear the sign bit to make it positive before computing the blinded + // pubkey. + uc32 pos_ed_pubkey; + std::memcpy(pos_ed_pubkey.data(), ed_pubkey.data(), 32); + pos_ed_pubkey[31] &= 0x7f; + + if (0 != crypto_scalarmult_ed25519_noclamp(result.data() + 1, k.data(), pos_ed_pubkey.data())) + throw std::runtime_error{"Cannot blind: invalid session_id (not on main subgroup)"}; + result[0] = 0x25; + return result; +} + +std::pair blind15_key_pair( + std::span ed25519_sk, + std::span server_pk, + uc32* k) { + std::array ed_sk_tmp; + if (ed25519_sk.size() == 32) { + std::array pk_ignore; + crypto_sign_ed25519_seed_keypair(pk_ignore.data(), ed_sk_tmp.data(), ed25519_sk.data()); + ed25519_sk = {ed_sk_tmp.data(), 64}; + } + if (ed25519_sk.size() != 64) + throw std::invalid_argument{ + "blind15_key_pair: Invalid ed25519_sk is not the expected 32- or 64-byte value"}; + + if (server_pk.size() != 32) + throw std::invalid_argument{"blind15_key_pair: server_pk must be 32 bytes"}; + + std::pair result; + auto& [A, a] = result; + + /// Generate the blinding factor (storing into `*k`, if a pointer was provided) + uc32 k_tmp; + if (!k) + k = &k_tmp; + *k = blind15_factor(server_pk); + + /// Generate a scalar for the private key + if (0 != crypto_sign_ed25519_sk_to_curve25519(a.data(), ed25519_sk.data())) + throw std::runtime_error{ + "blind15_key_pair: Invalid ed25519_sk; conversion to curve25519 seckey failed"}; + + // Turn a, A into their blinded versions + crypto_core_ed25519_scalar_mul(a.data(), k->data(), a.data()); + crypto_scalarmult_ed25519_base_noclamp(A.data(), a.data()); + + return result; +} + +std::pair blind25_key_pair( + std::span ed25519_sk, + std::span server_pk, + uc32* k_prime) { + std::array ed_sk_tmp; + if (ed25519_sk.size() == 32) { + std::array pk_ignore; + crypto_sign_ed25519_seed_keypair(pk_ignore.data(), ed_sk_tmp.data(), ed25519_sk.data()); + ed25519_sk = {ed_sk_tmp.data(), 64}; + } + if (ed25519_sk.size() != 64) + throw std::invalid_argument{ + "blind15_key_pair: Invalid ed25519_sk is not the expected 32- or 64-byte value"}; + + if (server_pk.size() != 32) + throw std::invalid_argument{"blind15_key_pair: server_pk must be 32 bytes"}; + + uc33 session_id; + session_id[0] = 0x05; + if (0 != crypto_sign_ed25519_pk_to_curve25519(session_id.data() + 1, ed25519_sk.data() + 32)) + throw std::runtime_error{ + "blind25_key_pair: Invalid ed25519_sk; conversion to curve25519 pubkey failed"}; + + std::span X{session_id.data() + 1, 32}; + + /// Generate the blinding factor (storing into `*k`, if a pointer was provided) + uc32 k_tmp; + if (!k_prime) + k_prime = &k_tmp; + *k_prime = blind25_factor(X, {server_pk.data(), server_pk.size()}); + + // For a negative pubkey we use k' = -k so that k'A == kA when A is positive, and k'A = -kA = + // k|A| when A is negative. + if (*(ed25519_sk.data() + 63) & 0x80) + crypto_core_ed25519_scalar_negate(k_prime->data(), k_prime->data()); + + std::pair result; + auto& [A, a] = result; + + // Generate the private key (scalar), a; (the sodium function naming here is misleading; this + // call actually has nothing to do with conversion to X25519, it just so happens that the + // conversion method is the easiest way to get `a` out of libsodium). + if (0 != crypto_sign_ed25519_sk_to_curve25519(a.data(), ed25519_sk.data())) + throw std::runtime_error{ + "blind25_key_pair: Invalid ed25519_sk; conversion to curve25519 seckey failed"}; + + // Turn a, A into their blinded versions + crypto_core_ed25519_scalar_mul(a.data(), k_prime->data(), a.data()); + crypto_scalarmult_ed25519_base_noclamp(A.data(), a.data()); + + return result; +} + +static const auto version_blinding_hash_key_sig = to_span("VersionCheckKey_sig"); + +std::pair blind_version_key_pair(std::span ed25519_sk) { + if (ed25519_sk.size() != 32 && ed25519_sk.size() != 64) + throw std::invalid_argument{ + "blind_version_key_pair: Invalid ed25519_sk is not the expected 32- or 64-byte " + "value"}; + + std::pair result; + cleared_uc32 blind_seed; + auto& [pk, sk] = result; + crypto_generichash_blake2b( + blind_seed.data(), + 32, + ed25519_sk.data(), + 32, + version_blinding_hash_key_sig.data(), + version_blinding_hash_key_sig.size()); + + // Reuse `sk` to avoid needing extra secure erasing: + if (0 != crypto_sign_ed25519_seed_keypair(pk.data(), sk.data(), blind_seed.data())) + throw std::runtime_error{"blind_version_key_pair: ed25519 generation from seed failed"}; + + return result; +} + +static const auto hash_key_seed = to_span("SessCommBlind25_seed"); +static const auto hash_key_sig = to_span("SessCommBlind25_sig"); + +std::vector blind25_sign( + std::span ed25519_sk, + std::string_view server_pk_in, + std::span message) { + std::array ed_sk_tmp; + if (ed25519_sk.size() == 32) { + std::array pk_ignore; + crypto_sign_ed25519_seed_keypair(pk_ignore.data(), ed_sk_tmp.data(), ed25519_sk.data()); + ed25519_sk = {ed_sk_tmp.data(), 64}; + } + if (ed25519_sk.size() != 64) + throw std::invalid_argument{ + "blind25_sign: Invalid ed25519_sk is not the expected 32- or 64-byte value"}; + uc32 server_pk; + if (server_pk_in.size() == 32) + std::memcpy(server_pk.data(), server_pk_in.data(), 32); + else if (server_pk_in.size() == 64 && oxenc::is_hex(server_pk_in)) + oxenc::from_hex(server_pk_in.begin(), server_pk_in.end(), server_pk.begin()); + else + throw std::invalid_argument{"blind25_sign: Invalid server_pk: expected 32 bytes or 64 hex"}; + + auto [A, a] = blind25_key_pair(ed25519_sk, to_span(server_pk)); + + uc32 seedhash; + crypto_generichash_blake2b( + seedhash.data(), + seedhash.size(), + ed25519_sk.data(), + 32, + hash_key_seed.data(), + hash_key_seed.size()); + + uc64 r_hash; + crypto_generichash_blake2b_state st; + crypto_generichash_blake2b_init(&st, hash_key_sig.data(), hash_key_sig.size(), r_hash.size()); + crypto_generichash_blake2b_update(&st, seedhash.data(), seedhash.size()); + crypto_generichash_blake2b_update(&st, A.data(), A.size()); + crypto_generichash_blake2b_update(&st, message.data(), message.size()); + crypto_generichash_blake2b_final(&st, r_hash.data(), r_hash.size()); + + uc32 r; + crypto_core_ed25519_scalar_reduce(r.data(), r_hash.data()); + + std::vector result; + result.resize(64); + auto* sig_R = result.data(); + auto* sig_S = result.data() + 32; + crypto_scalarmult_ed25519_base_noclamp(sig_R, r.data()); + + crypto_hash_sha512_state st2; + crypto_hash_sha512_init(&st2); + crypto_hash_sha512_update(&st2, sig_R, 32); + crypto_hash_sha512_update(&st2, A.data(), A.size()); + crypto_hash_sha512_update(&st2, message.data(), message.size()); + uc64 hram; + crypto_hash_sha512_final(&st2, hram.data()); + + crypto_core_ed25519_scalar_reduce(sig_S, hram.data()); // S = H(R||A||M) + + crypto_core_ed25519_scalar_mul(sig_S, sig_S, a.data()); // S = H(R||A||M) a + crypto_core_ed25519_scalar_add(sig_S, sig_S, r.data()); // S = r + H(R||A||M) a + + return result; +} + +std::vector blind15_sign( + std::span ed25519_sk, + std::string_view server_pk_in, + std::span message) { + std::array ed_sk_tmp; + if (ed25519_sk.size() == 32) { + std::array pk_ignore; + crypto_sign_ed25519_seed_keypair(pk_ignore.data(), ed_sk_tmp.data(), ed25519_sk.data()); + ed25519_sk = {ed_sk_tmp.data(), 64}; + } + if (ed25519_sk.size() != 64) + throw std::invalid_argument{ + "blind15_sign: Invalid ed25519_sk is not the expected 32- or 64-byte value"}; + + uc32 server_pk; + if (server_pk_in.size() == 32) + std::memcpy(server_pk.data(), server_pk_in.data(), 32); + else if (server_pk_in.size() == 64 && oxenc::is_hex(server_pk_in)) + oxenc::from_hex(server_pk_in.begin(), server_pk_in.end(), server_pk.begin()); + else + throw std::invalid_argument{"blind15_sign: Invalid server_pk: expected 32 bytes or 64 hex"}; + + auto [blind_15_pk, blind_15_sk] = blind15_key_pair(ed25519_sk, {server_pk.data(), 32}); + + // H_rh = sha512(s.encode()).digest()[32:] + uc64 hrh; + crypto_hash_sha512_state st1; + crypto_hash_sha512_init(&st1); + crypto_hash_sha512_update(&st1, ed25519_sk.data(), 64); + crypto_hash_sha512_final(&st1, hrh.data()); + + // r = salt.crypto_core_ed25519_scalar_reduce(sha512_multipart(H_rh, kA, message_parts)) + auto hrh_suffix = hrh.data() + 32; + uc32 r; + uc64 r_hash; + crypto_hash_sha512_state st2; + crypto_hash_sha512_init(&st2); + crypto_hash_sha512_update(&st2, hrh_suffix, 32); + crypto_hash_sha512_update(&st2, blind_15_pk.data(), blind_15_pk.size()); + crypto_hash_sha512_update(&st2, message.data(), message.size()); + crypto_hash_sha512_final(&st2, r_hash.data()); + crypto_core_ed25519_scalar_reduce(r.data(), r_hash.data()); + + // sig_R = salt.crypto_scalarmult_ed25519_base_noclamp(r) + std::vector result; + result.resize(64); + auto* sig_R = result.data(); + auto* sig_S = result.data() + 32; + crypto_scalarmult_ed25519_base_noclamp(sig_R, r.data()); + + // HRAM = salt.crypto_core_ed25519_scalar_reduce(sha512_multipart(sig_R, kA, message_parts)) + uc64 hram; + crypto_hash_sha512_state st3; + crypto_hash_sha512_init(&st3); + crypto_hash_sha512_update(&st3, sig_R, 32); + crypto_hash_sha512_update(&st3, blind_15_pk.data(), blind_15_pk.size()); + crypto_hash_sha512_update(&st3, message.data(), message.size()); + crypto_hash_sha512_final(&st3, hram.data()); + + // sig_s = salt.crypto_core_ed25519_scalar_add(r, salt.crypto_core_ed25519_scalar_mul(HRAM, ka)) + crypto_core_ed25519_scalar_reduce(sig_S, hram.data()); // S = H(R||A||M) + crypto_core_ed25519_scalar_mul(sig_S, sig_S, blind_15_sk.data()); // S = H(R||A||M) a + crypto_core_ed25519_scalar_add(sig_S, sig_S, r.data()); // S = r + H(R||A||M) a + + return result; +} + +std::vector blind_version_sign_request( + std::span ed25519_sk, + uint64_t timestamp, + std::string_view method, + std::string_view path, + std::optional> body) { + auto [pk, sk] = blind_version_key_pair(ed25519_sk); + + // Signature should be on `TIMESTAMP || METHOD || PATH || BODY` + std::vector ts = to_vector(std::to_string(timestamp)); + std::vector buf; + buf.reserve(10 /* timestamp */ + method.size() + path.size() + (body ? body->size() : 0)); + buf.insert(buf.end(), ts.begin(), ts.end()); + buf.insert(buf.end(), method.begin(), method.end()); + buf.insert(buf.end(), path.begin(), path.end()); + + if (body) + buf.insert(buf.end(), body->begin(), body->end()); + + return ed25519::sign({sk.data(), sk.size()}, buf); +} + +std::vector blind_version_sign( + std::span ed25519_sk, Platform platform, uint64_t timestamp) { + auto [pk, sk] = blind_version_key_pair(ed25519_sk); + + // Signature should be on `TIMESTAMP || METHOD || PATH` + std::vector ts = to_vector(std::to_string(timestamp)); + std::vector method = to_vector("GET"); + std::vector buf; + buf.reserve(10 + 6 + 33); + buf.insert(buf.end(), ts.begin(), ts.end()); + buf.insert(buf.end(), method.begin(), method.end()); + + std::vector url; + switch (platform) { + case Platform::android: url = to_vector("/session_version?platform=android"); break; + case Platform::desktop: url = to_vector("/session_version?platform=desktop"); break; + case Platform::ios: url = to_vector("/session_version?platform=ios"); break; + default: url = to_vector("/session_version?platform=desktop"); break; + } + buf.insert(buf.end(), url.begin(), url.end()); + + return ed25519::sign({sk.data(), sk.size()}, buf); +} + +bool session_id_matches_blinded_id( + std::string_view session_id, std::string_view blinded_id, std::string_view server_pk) { + if (session_id.size() != 66 || !oxenc::is_hex(session_id)) + throw std::invalid_argument{ + "session_id_matches_blinded_id: session_id must be hex (66 digits)"}; + if (session_id[0] != '0' || session_id[1] != '5') + throw std::invalid_argument{"session_id_matches_blinded_id: session_id must start with 05"}; + if (blinded_id[1] != '5' && (blinded_id[0] != '1' || blinded_id[0] != '2')) + throw std::invalid_argument{ + "session_id_matches_blinded_id: blinded_id must start with 15 or 25"}; + if (server_pk.size() != 64 || !oxenc::is_hex(server_pk)) + throw std::invalid_argument{ + "session_id_matches_blinded_id: server_pk must be hex (64 digits)"}; + + std::string converted_blind_id1, converted_blind_id2; + std::vector converted_blind_id1_raw; + + switch (blinded_id[0]) { + case '1': { + auto [converted_blind_id1, converted_blind_id2] = blind15_id(session_id, server_pk); + return (blinded_id == converted_blind_id1 || blinded_id == converted_blind_id2); + } + + // blind25 doesn't run into the negative issue that blind15 did + case '2': return blinded_id == blind25_id(session_id, server_pk); + default: throw std::invalid_argument{"Invalid blinded_id: must start with 15 or 25"}; + } +} + +} // namespace session + +using namespace session; + +LIBSESSION_C_API bool session_blind15_key_pair( + const unsigned char* ed25519_seckey, + const unsigned char* server_pk, + unsigned char* blinded_pk_out, + unsigned char* blinded_sk_out) { + try { + auto [b_pk, b_sk] = session::blind15_key_pair({ed25519_seckey, 64}, {server_pk, 32}); + std::memcpy(blinded_pk_out, b_pk.data(), b_pk.size()); + std::memcpy(blinded_sk_out, b_sk.data(), b_sk.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_blind25_key_pair( + const unsigned char* ed25519_seckey, + const unsigned char* server_pk, + unsigned char* blinded_pk_out, + unsigned char* blinded_sk_out) { + try { + auto [b_pk, b_sk] = session::blind25_key_pair({ed25519_seckey, 64}, {server_pk, 32}); + std::memcpy(blinded_pk_out, b_pk.data(), b_pk.size()); + std::memcpy(blinded_sk_out, b_sk.data(), b_sk.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_blind_version_key_pair( + const unsigned char* ed25519_seckey, + unsigned char* blinded_pk_out, + unsigned char* blinded_sk_out) { + try { + auto [b_pk, b_sk] = session::blind_version_key_pair({ed25519_seckey, 64}); + std::memcpy(blinded_pk_out, b_pk.data(), b_pk.size()); + std::memcpy(blinded_sk_out, b_sk.data(), b_sk.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_blind15_sign( + const unsigned char* ed25519_seckey, + const unsigned char* server_pk, + const unsigned char* msg, + size_t msg_len, + unsigned char* blinded_sig_out) { + try { + auto sig = session::blind15_sign( + {ed25519_seckey, 64}, + {reinterpret_cast(server_pk), 32}, + {msg, msg_len}); + std::memcpy(blinded_sig_out, sig.data(), sig.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_blind25_sign( + const unsigned char* ed25519_seckey, + const unsigned char* server_pk, + const unsigned char* msg, + size_t msg_len, + unsigned char* blinded_sig_out) { + try { + auto sig = session::blind25_sign( + {ed25519_seckey, 64}, + {reinterpret_cast(server_pk), 32}, + {msg, msg_len}); + std::memcpy(blinded_sig_out, sig.data(), sig.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_blind_version_sign_request( + const unsigned char* ed25519_seckey, + size_t timestamp, + const char* method, + const char* path, + const unsigned char* body, + size_t body_len, + unsigned char* blinded_sig_out) { + std::string_view method_sv{method}; + std::string_view path_sv{path}; + + std::optional> body_sv{std::nullopt}; + if (body) + body_sv = std::span{body, body_len}; + + try { + auto sig = session::blind_version_sign_request( + {ed25519_seckey, 64}, timestamp, method_sv, path_sv, body_sv); + std::memcpy(blinded_sig_out, sig.data(), sig.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_blind_version_sign( + const unsigned char* ed25519_seckey, + CLIENT_PLATFORM platform, + size_t timestamp, + unsigned char* blinded_sig_out) { + try { + auto sig = session::blind_version_sign( + {ed25519_seckey, 64}, static_cast(platform), timestamp); + std::memcpy(blinded_sig_out, sig.data(), sig.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_id_matches_blinded_id( + const char* session_id, const char* blinded_id, const char* server_pk) { + try { + return session::session_id_matches_blinded_id( + {session_id, 66}, {blinded_id, 66}, {server_pk, 64}); + } catch (...) { + return false; + } +} diff --git a/src/config.cpp b/src/config.cpp index 2410908c..ee232910 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -15,6 +14,7 @@ #include #include +#include "config/internal.hpp" #include "session/bt_merge.hpp" #include "session/util.hpp" @@ -50,7 +50,7 @@ namespace { return {s.empty(), false}; } std::pair prune_(dict_value& v) { - return var::visit([](auto& x) { return prune_(x); }, unwrap(v)); + return std::visit([](auto& x) { return prune_(x); }, unwrap(v)); } // diff helper functions @@ -78,9 +78,9 @@ namespace { oxenc::bt_list additions, removals; for (auto& a : added) - var::visit([&additions](auto& x) { additions.emplace_back(std::move(x)); }, a); + std::visit([&additions](auto& x) { additions.emplace_back(std::move(x)); }, a); for (auto& r : removed) - var::visit([&removals](auto& x) { removals.emplace_back(std::move(x)); }, r); + std::visit([&removals](auto& x) { removals.emplace_back(std::move(x)); }, r); return oxenc::bt_list{{std::move(additions)}, {std::move(removals)}}; } @@ -91,7 +91,6 @@ namespace { auto oldit = old.begin(), newit = new_.begin(); while (oldit != old.end() || newit != new_.end()) { - bool is_new = false; if (oldit == old.end() || (newit != new_.end() && newit->first < oldit->first)) { // newit is a new item; fall through to handle below @@ -116,19 +115,18 @@ namespace { if (o.index() != n.index()) { // The fundamental type (scalar, dict, set) changed, so we'll treat this as a // new value (which implicitly deletes a value of a wrong type when merging). - is_new = true; ++oldit; // fall through to handler below } else { const auto& key = newit->first; if (auto* ov = std::get_if(&o)) { - if (*ov != var::get(n)) + if (*ov != std::get(n)) df[key] = ""sv; } else if (auto* dv = std::get_if(&o)) { - if (auto subdiff = diff_impl(*dv, var::get(n))) + if (auto subdiff = diff_impl(*dv, std::get(n))) df[key] = std::move(*subdiff); - } else if (auto subdiff = diff_impl(var::get(o), var::get(n))) { + } else if (auto subdiff = diff_impl(std::get(o), std::get(n))) { df[key] = std::move(*subdiff); } ++oldit; @@ -164,9 +162,9 @@ namespace { // Gets a string_view if the type is a string or string_view, nullopt otherwise. constexpr std::optional get_bt_str(const oxenc::bt_value& v) { if (std::holds_alternative(v)) - return var::get(v); + return std::get(v); if (std::holds_alternative(v)) - return var::get(v); + return std::get(v); return std::nullopt; } @@ -247,7 +245,7 @@ namespace { void serialize_data(oxenc::bt_list_producer&& out, const set& s); void serialize_data(oxenc::bt_dict_producer&& out, const dict& d) { for (const auto& pair : d) { - var::visit( + std::visit( [&](const auto& v) { auto& k = pair.first; using T = std::remove_cv_t>; @@ -256,7 +254,7 @@ namespace { else if constexpr (std::is_same_v) serialize_data(out.append_list(k), v); else - var::visit( + std::visit( [&](const auto& scalar) { out.append(pair.first, scalar); }, v); }, unwrap(pair.second)); @@ -264,7 +262,7 @@ namespace { } void serialize_data(oxenc::bt_list_producer&& out, const set& s) { for (auto& val : s) - var::visit([&](const auto& scalar) { out.append(scalar); }, val); + std::visit([&](const auto& scalar) { out.append(scalar); }, val); } void parse_data(set& s, oxenc::bt_list_consumer in); @@ -281,10 +279,10 @@ namespace { d.emplace_hint(d.end(), std::move(key), in.consume_integer()); else if (in.is_dict()) { auto it = d.emplace_hint(d.end(), std::move(key), dict{}); - parse_data(var::get(it->second), in.consume_dict_consumer()); + parse_data(std::get(it->second), in.consume_dict_consumer()); } else if (in.is_list()) { auto it = d.emplace_hint(d.end(), std::move(key), set{}); - parse_data(var::get(it->second), in.consume_list_consumer()); + parse_data(std::get(it->second), in.consume_list_consumer()); } else { throw oxenc::bt_deserialize_invalid{"Data contains invalid bencoded value type"}; } @@ -347,45 +345,7 @@ namespace { return std::string_view{reinterpret_cast(hash.data()), hash.size()}; } - oxenc::bt_dict::iterator append_unknown( - oxenc::bt_dict_producer& out, - oxenc::bt_dict::iterator it, - oxenc::bt_dict::iterator end, - std::string_view until) { - for (; it != end && it->first < until; ++it) - out.append_bt(it->first, it->second); - - assert(!(it != end && it->first == until)); - return it; - } - - /// Extracts and unknown keys in the top-level dict into `unknown` that have keys (strictly) - /// between previous and until. - void load_unknowns( - oxenc::bt_dict& unknown, - oxenc::bt_dict_consumer& in, - std::string_view previous, - std::string_view until) { - while (!in.is_finished() && in.key() < until) { - std::string key{in.key()}; - if (key <= previous || (!unknown.empty() && key <= unknown.rbegin()->first)) - throw oxenc::bt_deserialize_invalid{"top-level keys are out of order"}; - if (in.is_string()) - unknown.emplace_hint(unknown.end(), std::move(key), in.consume_string()); - else if (in.is_negative_integer()) - unknown.emplace_hint(unknown.end(), std::move(key), in.consume_integer()); - else if (in.is_integer()) - unknown.emplace_hint(unknown.end(), std::move(key), in.consume_integer()); - else if (in.is_list()) - unknown.emplace_hint(unknown.end(), std::move(key), in.consume_list()); - else if (in.is_dict()) - unknown.emplace_hint(unknown.end(), std::move(key), in.consume_dict()); - else - throw oxenc::bt_deserialize_invalid{"invalid bencoded value type"}; - } - } - - hash_t& hash_msg(hash_t& into, ustring_view serialized) { + hash_t& hash_msg(hash_t& into, std::span serialized) { crypto_generichash_blake2b( into.data(), into.size(), serialized.data(), serialized.size(), nullptr, 0); return into; @@ -425,7 +385,7 @@ namespace { if (*scalar_diff == "-") data.erase(k); else if (*scalar_diff == "") - data[k] = var::get(source_val); + data[k] = std::get(source_val); else throw config_error{ "Invalid diff value to apply at key " + k + ": expected '' or '-'"}; @@ -436,23 +396,23 @@ namespace { // with an empty dict. subdict = dict{}; - apply_diff(var::get(subdict), *dict_diff, var::get(source_val)); + apply_diff(std::get(subdict), *dict_diff, std::get(source_val)); } else if (set_diff) { auto& elem = data[k]; if (!std::holds_alternative(elem)) // If not a list (or new) replace with a new empty list elem = set{}; - auto& subset = var::get(elem); + auto& subset = std::get(elem); - for (const auto& added : var::get(*set_diff->begin())) { + for (const auto& added : std::get(*set_diff->begin())) { if (auto s = get_bt_scalar(added)) subset.insert(std::move(*s)); else throw config_error{"Invalid set diff added value: expected int or scalar"}; } for (const auto& removed : - var::get(*std::next(set_diff->begin()))) { + std::get(*std::next(set_diff->begin()))) { if (auto s = get_bt_scalar(removed)) subset.erase(*s); else @@ -466,6 +426,32 @@ namespace { } } // namespace +void verify_config_sig( + oxenc::bt_dict_consumer dict, + const ConfigMessage::verify_callable& verifier, + std::optional>* verified_signature, + bool trust_signature) { + if (dict.skip_until("~")) { + dict.consume_signature( + [&](std::span to_verify, std::span sig) { + if (sig.size() != 64) + throw signature_error{"Config signature is invalid (not 64B)"}; + if (verifier && !verifier(to_verify, sig)) + throw signature_error{"Config signature failed verification"}; + if (verified_signature && (verifier || trust_signature)) { + if (!*verified_signature) + verified_signature->emplace(); + std::memcpy((*verified_signature)->data(), sig.data(), 64); + } + }); + } else if (verifier) { + throw missing_signature{"Config signature is missing"}; + } + + if (!dict.is_finished()) + throw config_parse_error{"Invalid config: dict has invalid key(s) after \"~\""}; +} + bool MutableConfigMessage::prune() { return prune_(data_).second; } @@ -537,14 +523,14 @@ ConfigMessage::ConfigMessage() { } ConfigMessage::ConfigMessage( - ustring_view serialized, + std::span serialized, verify_callable verifier_, sign_callable signer_, int lag, - bool signature_optional) : + bool trust_signature) : verifier{std::move(verifier_)}, signer{std::move(signer_)}, lag{lag} { - oxenc::bt_dict_consumer dict{from_unsigned_sv(serialized)}; + oxenc::bt_dict_consumer dict{serialized}; try { hash_msg(seqno_hash_.second, serialized); @@ -568,46 +554,17 @@ ConfigMessage::ConfigMessage( load_unknowns(unknown_, dict, "=", "~"); - ustring_view to_verify, sig; - if (!dict.is_finished() && dict.key() == "~") { - // We get the key string_view here because it points into the buffer that we need. - // Currently it will be pointing at the "~", i.e.: - // - // [...previousdata...]1:~64:[sigdata] - // ^-- here - // - // but what we need is the data up to the end of `]`, so we subtract 2 off that to - // figure out the range of the full serialized data that should have been signed: - - auto key = dict.key(); - assert(to_unsigned(key.data()) > serialized.data() && - to_unsigned(key.data()) < serialized.data() + serialized.size()); - to_verify = serialized.substr(0, to_unsigned(key.data()) - serialized.data() - 2); - sig = to_unsigned_sv(dict.consume_string_view()); - } - - if (!dict.is_finished()) - throw config_parse_error{"Invalid config: dict has invalid key(s) after \"~\""}; - - if (verifier) { - if (sig.empty()) { - if (!signature_optional) - throw missing_signature{"Config signature is missing"}; - } else if (verified_signature_ = verifier(to_verify, sig); !verified_signature_) { - throw signature_error{"Config signature failed verification"}; - } - } + verify_config_sig(dict, verifier, &verified_signature_, trust_signature); } catch (const oxenc::bt_deserialize_invalid& err) { throw config_parse_error{"Failed to parse config file: "s + err.what()}; } } ConfigMessage::ConfigMessage( - const std::vector& serialized_confs, + const std::vector>& serialized_confs, verify_callable verifier_, sign_callable signer_, int lag, - bool signature_optional, std::function error_handler) : verifier{std::move(verifier_)}, signer{std::move(signer_)}, lag{lag} { @@ -615,7 +572,7 @@ ConfigMessage::ConfigMessage( for (size_t i = 0; i < serialized_confs.size(); i++) { const auto& data = serialized_confs[i]; try { - ConfigMessage m{data, verifier, signer, lag, signature_optional}; + ConfigMessage m{data, verifier, signer, lag}; configs.emplace_back(std::move(m), false); } catch (const config_error& e) { if (error_handler) @@ -631,12 +588,12 @@ ConfigMessage::ConfigMessage( // prune out redundant messages (i.e. messages already included in another message's diff, and // duplicates) - for (int i = 0; i < configs.size(); i++) { + for (size_t i = 0; i < configs.size(); i++) { auto& [conf, redundant] = configs[i]; if (conf.seqno() > max_seqno) max_seqno = conf.seqno(); - for (int j = 0; !redundant && j < configs.size(); j++) { + for (size_t j = 0; !redundant && j < configs.size(); j++) { if (i == j) continue; const auto& conf2 = configs[j].first; @@ -657,20 +614,39 @@ ConfigMessage::ConfigMessage( assert(curr_confs >= 1); if (curr_confs == 1) { - // We have just one config left after all that, so we become it directly as-is - for (int i = 0; i < configs.size(); i++) { + // We have just one non-redundant config left after all that, so we become it directly as-is + for (size_t i = 0; i < configs.size(); i++) { if (!configs[i].second) { *this = std::move(configs[i].first); unmerged_ = i; return; } } - assert(false); + assert(!"we counted one good config but couldn't find it?!"); } - unmerged_ = -1; + // Otherwise we have more than one valid config, so have to merge them. - // Clear any redundant messages + // ... Unless we require signature verification but can't sign, in which case we can't actually + // produce a proper merge, so we will just keep the highest (highest seqno, hash) config and use + // that, dropping the rest. Someone else (with signing power) will have to merge and push the + // merge out to us. + if (verifier && !signer) { + auto best_it = + std::max_element(configs.begin(), configs.end(), [](const auto& a, const auto& b) { + if (a.second != b.second) // Exactly one of the two is redundant + return a.second; // a < b iff a is redundant + return a.first.seqno_hash_ < b.first.seqno_hash_; + }); + *this = std::move(best_it->first); + unmerged_ = std::distance(configs.begin(), best_it); + return; + } + + unmerged_ = std::nullopt; + + // Clear any redundant messages. (we do it *here* rather than above because, in the + // single-good-config case, above, we need the index of the good config for `unmerged_`). configs.erase( std::remove_if(configs.begin(), configs.end(), [](const auto& c) { return c.second; }), configs.end()); @@ -714,35 +690,31 @@ ConfigMessage::ConfigMessage( } MutableConfigMessage::MutableConfigMessage( - const std::vector& serialized_confs, + const std::vector>& serialized_confs, verify_callable verifier, sign_callable signer, int lag, - bool signature_optional, std::function error_handler) : ConfigMessage{ serialized_confs, std::move(verifier), std::move(signer), lag, - signature_optional, std::move(error_handler)} { if (!merged()) increment_impl(); } MutableConfigMessage::MutableConfigMessage( - ustring_view config, + std::span config, verify_callable verifier, sign_callable signer, - int lag, - bool signature_optional) : + int lag) : MutableConfigMessage{ std::vector{{config}}, std::move(verifier), std::move(signer), lag, - signature_optional, [](size_t, const config_error& e) { throw e; }} {} const oxenc::bt_dict& ConfigMessage::diff() { @@ -750,18 +722,20 @@ const oxenc::bt_dict& ConfigMessage::diff() { } const oxenc::bt_dict& MutableConfigMessage::diff() { + verified_signature_.reset(); prune(); diff_ = diff_impl(orig_data_, data_).value_or(oxenc::bt_dict{}); return diff_; } -ustring ConfigMessage::serialize(bool enable_signing) { +std::vector ConfigMessage::serialize(bool enable_signing) { return serialize_impl( diff(), // implicitly prunes (if actually a mutable instance) enable_signing); } -ustring ConfigMessage::serialize_impl(const oxenc::bt_dict& curr_diff, bool enable_signing) { +std::vector ConfigMessage::serialize_impl( + const oxenc::bt_dict& curr_diff, bool enable_signing) { oxenc::bt_dict_producer outer{}; outer.append("#", seqno()); @@ -792,24 +766,30 @@ ustring ConfigMessage::serialize_impl(const oxenc::bt_dict& curr_diff, bool enab unknown_it = append_unknown(outer, unknown_it, unknown_.end(), "~"); assert(unknown_it == unknown_.end()); - if (signer && enable_signing) { - auto to_sign = to_unsigned_sv(outer.view()); - // The view contains the trailing "e", but we don't sign it (we are going to append the - // signature there instead): - to_sign.remove_suffix(1); - auto sig = signer(to_sign); - if (sig.size() != 64) - throw std::logic_error{"Invalid signature: signing function did not return 64 bytes"}; - - outer.append("~", from_unsigned_sv(sig)); - } - return ustring{to_unsigned_sv(outer.view())}; + if (verified_signature_) { + // We have the signature attached to the current message, so use it. (This will get cleared + // if we do anything that changes the config). + outer.append( + "~", + std::string_view{ + reinterpret_cast(verified_signature_->data()), + verified_signature_->size()}); + } else if (signer && enable_signing) { + outer.append_signature("~", [this](std::span to_sign) { + auto sig = signer(to_sign); + if (sig.size() != 64) + throw std::logic_error{ + "Invalid signature: signing function did not return 64 bytes"}; + return sig; + }); + } + return to_vector(outer.view()); } const hash_t& MutableConfigMessage::hash() { return hash(serialize()); } -const hash_t& MutableConfigMessage::hash(ustring_view serialized) { +const hash_t& MutableConfigMessage::hash(std::span serialized) { return hash_msg(seqno_hash_.second, serialized); } diff --git a/src/config/base.cpp b/src/config/base.cpp index 3f80d5e2..a8851934 100644 --- a/src/config/base.cpp +++ b/src/config/base.cpp @@ -1,27 +1,49 @@ #include "session/config/base.hpp" +#include +#include +#include +#include #include #include +#include +#include #include -#include +#include +#include +#include +#include #include #include #include +#include "internal.hpp" +#include "oxenc/bt_serialize.h" #include "session/config/base.h" #include "session/config/encrypt.hpp" +#include "session/config/protos.hpp" #include "session/export.h" #include "session/util.hpp" using namespace std::literals; +using namespace oxen::log::literals; namespace session::config { +namespace log = oxen::log; + +auto cat = log::Cat("config"); + void ConfigBase::set_state(ConfigState s) { - if (_state == ConfigState::Clean && !_curr_hash.empty()) { - _old_hashes.insert(std::move(_curr_hash)); - _curr_hash.clear(); + if (s == ConfigState::Dirty && is_readonly()) + throw std::runtime_error{"Unable to make changes to a read-only config object"}; + + if (_state == ConfigState::Clean && !_curr_hashes.empty()) { + _old_hashes.insert( + std::make_move_iterator(_curr_hashes.begin()), + std::make_move_iterator(_curr_hashes.end())); + _curr_hashes.clear(); } _state = s; _needs_dump = true; @@ -31,6 +53,8 @@ MutableConfigMessage& ConfigBase::dirty() { if (_state != ConfigState::Dirty) { set_state(ConfigState::Dirty); _config = std::make_unique(*_config, increment_seqno); + } else { + _needs_dump = true; } if (auto* mut = dynamic_cast(_config.get())) @@ -38,139 +62,477 @@ MutableConfigMessage& ConfigBase::dirty() { throw std::runtime_error{"Internal error: unexpected dirty but non-mutable ConfigMessage"}; } -int ConfigBase::merge(const std::vector>& configs) { - std::vector> config_views; +template +std::unique_ptr make_config_message(bool from_dirty, Args&&... args) { + if (from_dirty) + return std::make_unique(std::forward(args)...); + return std::make_unique(std::forward(args)...); +} + +std::unordered_set ConfigBase::merge( + const std::vector>>& configs) { + std::vector>> config_views; config_views.reserve(configs.size()); for (auto& [hash, data] : configs) config_views.emplace_back(hash, data); return merge(config_views); } -template -std::unique_ptr make_config_message(bool from_dirty, Args&&... args) { - if (from_dirty) - return std::make_unique(std::forward(args)...); - return std::make_unique(std::forward(args)...); +std::unordered_set ConfigBase::merge( + const std::vector>>& configs) { + if (accepts_protobuf() && !_keys.empty()) { + std::list> keep_alive; + std::vector>> parsed; + parsed.reserve(configs.size()); + + for (auto& [h, c] : configs) { + try { + auto unwrapped = protos::unwrap_config( + std::span{_keys.front().data(), _keys.front().size()}, + c, + storage_namespace()); + + // There was a release of one of the clients which resulted in double-wrapped + // config messages so we now need to try to double-unwrap in order to better + // support multi-device for users running those old versions + try { + auto unwrapped2 = protos::unwrap_config( + std::span{ + _keys.front().data(), _keys.front().size()}, + unwrapped, + storage_namespace()); + log::warning( + cat, + "Found double wraped message in namespace {}", + static_cast(storage_namespace())); + parsed.emplace_back(h, keep_alive.emplace_back(std::move(unwrapped2))); + } catch (...) { + parsed.emplace_back(h, keep_alive.emplace_back(std::move(unwrapped))); + } + } catch (...) { + parsed.emplace_back(h, c); + } + } + + return _merge(parsed); + } + + return _merge(configs); +} + +std::pair, std::vector>>> +ConfigBase::_handle_multipart(std::string_view msg_id, std::span message) { + assert(!message.empty() && message[0] == 'm'); + + // Handle multipart messages. Each part of a multipart message starts with `m` and then is + // immediately followed by a bt_list where: + // - element 0 is the hash of the final, uncompressed, re-assembled message. + // - element 1 is the numeric sequence number of the message, starting from 0. + // - element 2 is the total number of messages in the sequence. + // - element 3 is a chunk of the data. + // (and no trailing bt_list elements are allowed). + + try { + if (message.size() > MAX_MESSAGE_SIZE) + throw std::runtime_error{ + "Invalid multi-part message: message part exceeds max message size"}; + + oxenc::bt_list_consumer c{message.subspan<1>()}; + + auto h = c.consume>(); + hash_t final_hash; + if (h.size() != final_hash.size()) + throw std::runtime_error{"Invalid multi-part final message hash"}; + std::copy(h.begin(), h.end(), final_hash.begin()); + + auto index_bytes = c.consume_span(); + auto size_bytes = c.consume_span(); + if (index_bytes.size() != 1 || size_bytes.size() != 1) + throw std::runtime_error{"Invalid multi-part message part number encoding"}; + auto index = index_bytes[0]; + auto num_parts = size_bytes[0]; + if (num_parts <= 1 || index >= num_parts) + throw std::runtime_error{"Invalid multi-part message part numbering ({} of {})"_format( + index, num_parts)}; + + auto data = c.consume_span(); + if (data.empty()) + throw std::runtime_error{"Invalid multi-part message with empty data"}; + + if (!c.is_finished()) + throw std::runtime_error{"Invalid multi-part message with post-data elements"}; + + auto& parts = _multiparts[final_hash]; + if (parts.done) { + log::debug( + cat, + "message {} is a duplicate part {} of {} of an already-processed multipart " + "message; ignoring", + msg_id, + index, + num_parts); + return {true, std::nullopt}; + } + if (parts.parts.empty()) { + parts.size = num_parts; + } else { + if (num_parts != parts.size) + throw std::runtime_error{ + "message size ({}) does not match previous parts ({})"_format( + num_parts, parts.size)}; + } + + auto it = parts.parts.begin(); + while (it != parts.parts.end() && it->index < index) + ++it; + if (it != parts.parts.end() && it->index == index) { + log::debug( + cat, + "message {} is an already-seen multipart message ({} of {}); ignoring", + msg_id, + index, + num_parts); + return {true, std::nullopt}; + } + parts.parts.emplace(it, index, msg_id, data); + _needs_dump = true; + + if (parts.parts.size() == parts.size) { + // We've completed a set of multiparts! + + std::pair, std::vector> result{}; + auto& [msgids, recombined] = result; + + size_t final_size = 0; + for (const auto& p : parts.parts) + final_size += p.data.size(); + recombined.reserve(final_size); + for (const auto& p : parts.parts) { + msgids.emplace_back(std::move(p.message_id)); + recombined.insert(recombined.end(), p.data.begin(), p.data.end()); + } + + { + hash_t actual_hash; + hash::hash(actual_hash, recombined); + if (actual_hash != final_hash) + throw std::runtime_error{ + "recombined message hash ({}) does not match part hash ({})"_format( + oxenc::to_hex(actual_hash.begin(), actual_hash.end()), + oxenc::to_hex(final_hash.begin(), final_hash.end()))}; + } + + log::debug( + cat, + "message {} (part {} of {}) completed a multipart set (hash {}), {}B data", + msg_id, + index, + parts.size, + oxenc::to_hex(final_hash.begin(), final_hash.end()), + final_size); + + parts.finish(MULTIPART_MAX_REMEMBER); + + // Remove prefix padding of the recombined message: + if (auto it = std::find_if( + recombined.begin(), + recombined.end(), + [](unsigned char c) { return c != 0; }); + it != recombined.begin() && it != recombined.end()) { + auto p = std::distance(recombined.begin(), it); + std::memmove(recombined.data(), recombined.data() + p, recombined.size() - p); + recombined.resize(recombined.size() - p); + } + + if (recombined[0] == 'z') { + if (auto decompressed = zstd_decompress(std::span{ + recombined.data() + 1, recombined.size() - 1}); + decompressed && !decompressed->empty()) { + log::debug( + cat, + "multipart message {} inflated to {}B plaintext from {}B compressed", + oxenc::to_hex(final_hash.begin(), final_hash.end()), + decompressed->size(), + recombined.size()); + recombined = std::move(*decompressed); + } else + throw std::runtime_error{ + "Invalid recombined data (hash {}): decompression failed"_format( + oxenc::to_hex(final_hash.begin(), final_hash.end()), msg_id)}; + } + + if (recombined.empty()) + throw std::runtime_error{"recombined data is empty"}; + + if (recombined[0] != 'd') + throw std::runtime_error{"Recombined data has invalid/unsupported type {:?}"_format( + static_cast(recombined[0]))}; + + return {true, std::move(result)}; + } else { + parts.expiry = std::chrono::system_clock::now() + MULTIPART_MAX_WAIT; + log::debug( + cat, + "message {} (part {} of {}) stored without completing a multipart set for {}", + msg_id, + index, + parts.size, + oxenc::to_hex(final_hash.begin(), final_hash.end())); + return {true, std::nullopt}; + } + + } catch (const std::exception& e) { + log::error(cat, "invalid multi-part config message {}: {}", msg_id, e.what()); + return {false, std::nullopt}; + } } -int ConfigBase::merge(const std::vector>& configs) { +void ConfigBase::_expire_multiparts() { + auto now = std::chrono::system_clock::now(); + for (auto it = _multiparts.begin(); it != _multiparts.end();) { + auto& [hash, parts] = *it; + if (parts.expiry < now) + it = _multiparts.erase(it); + else + ++it; + } +} - if (_keys_size == 0) +void ConfigBase::_dump_multiparts(oxenc::bt_dict_producer&& multi) const { + auto now = std::chrono::system_clock::now(); + for (const auto& [fhash, parts] : _multiparts) { + if (parts.expiry < now) + continue; + auto pdata = multi.append_dict(to_string(fhash)); + pdata.append("#", parts.done ? 0 : parts.size); + pdata.append( + "T", + std::chrono::duration_cast( + parts.expiry.time_since_epoch()) + .count()); + if (!parts.done) { + auto parts_list = pdata.append_list("p"); + for (const auto& part : parts.parts) { + auto pd = parts_list.append_dict(); + pd.append("#", part.index); + pd.append("M", part.message_id); + pd.append("d", std::span{part.data}); + } + } + } +} + +void ConfigBase::_load_multiparts(oxenc::bt_dict_consumer&& multi) { + auto now = std::chrono::system_clock::now(); + while (!multi.is_finished()) { + auto [k, pdata] = multi.next_dict_consumer(); + if (k.size() != sizeof(hash_t)) { + log::warning( + cat, + "Invalid multipart key in config: expected {} bytes, but key is {} bytes", + sizeof(hash_t), + k.size()); + continue; + } + int size = pdata.require("#"); + auto exp = std::chrono::system_clock::time_point{ + std::chrono::milliseconds{pdata.require("T")}}; + if (exp < now) { + log::debug(cat, "Not loading expired multipart data"); + // We *could* set _needs_dump to true here to instruct a client to store it again, but + // there's no real need to force a re-dump as what we have is perfectly usable, and if + // this is the *only* thing that needs it then we just force rewriting the entire dump + // just to do a little cleanup which seems unnecessary. + // + // _needs_dump = true; + continue; + } + + hash_t key; + std::memcpy(key.data(), k.data(), k.size()); + PartialMessages pm; + pm.size = size; + pm.expiry = exp; + if (pm.size > 0) { + auto parts_list = pdata.require("p"); + while (!parts_list.is_finished()) { + auto pd = parts_list.consume_dict_consumer(); + auto index = pd.consume_integer(); + auto msgid = pd.consume_string_view(); + auto chunk = pd.consume_span(); + pm.parts.emplace_back(index, msgid, chunk); + } + } + _multiparts[key] = std::move(pm); + } +} + +std::unordered_set ConfigBase::_merge( + std::span>> configs) { + + if (_keys.empty()) throw std::logic_error{"Cannot merge configs without any decryption keys"}; const auto old_seqno = _config->seqno(); - std::vector all_hashes; - std::vector all_confs; + std::vector> all_hashes; // >1 hashes for multipart configs + std::vector> all_confs; all_hashes.reserve(configs.size() + 1); all_confs.reserve(configs.size() + 1); + + log::debug(cat, "Beginning merge of {} incoming configs", configs.size()); + log::trace( + cat, + "Current config is {} with seqno={}, storage hash(es) {}", + current_state_string(), + old_seqno, + _curr_hashes.empty() ? "" : fmt::format("{}", fmt::join(_curr_hashes, ", "))); + log::trace(cat, "Current old_hashes: {}", fmt::join(_old_hashes, ", ")); + // We serialize our current config and include it in the list of configs to be merged, as if it // had already been pushed to the server (so that this code will be identical whether or not the // value was pushed). - auto mine = _config->serialize(); - all_hashes.emplace_back(_curr_hash); - all_confs.emplace_back(mine); - - std::vector> plaintexts; - - // TODO: - // - handle multipart messages. Each part of a multipart message starts with `m` and then is - // immediately followed by a bt_list where: - // - element 0 is 'z' for a zstd-compressed message, 'p' for an uncompressed message. - // - element 1 is the hash of the final, uncompressed, re-assembled message. - // - element 2 is the numeric sequence number of the message, starting from 0. - // - element 3 is the total number of messages in the sequence. - // - element 4 is a chunk of the data. + // + // (We skip this for seqno=0, but that's just a default-constructed, nothing-in-the-config case + // for which we also can't have or produce a signature, so there's no point in even trying to + // merge it). + // + // Where we put it matters, however: if we don't have a _curr_hash for it then we want to put it + // at the end (rather than the beginning) so that it is identical to one of the incoming + // messages, *that* one becomes the config superset rather than our current, hash-unknown value. + + std::vector mine; + bool mine_last = false; + if (old_seqno != 0 || is_dirty()) { + mine = _config->serialize(); + + if (_curr_hashes.empty()) + mine_last = true; + else { + all_hashes.emplace_back(_curr_hashes.begin(), _curr_hashes.end()); + all_confs.emplace_back(mine); + } + } + + std::vector>> plaintexts; + + std::unordered_set good_hashes; + for (size_t ci = 0; ci < configs.size(); ci++) { auto& [hash, conf] = configs[ci]; - std::optional plaintext; bool decrypted = false; - for (size_t i = 0; !decrypted && i < _keys_size; i++) { + for (size_t i = 0; !decrypted && i < _keys.size(); i++) { try { plaintexts.emplace_back(hash, decrypt(conf, key(i), encryption_domain())); decrypted = true; } catch (const decrypt_error&) { - log(LogLevel::debug, - "Failed to decrypt message " + std::to_string(ci) + " using key " + - std::to_string(i)); + log::debug(cat, "Failed to decrypt message {} using key {}", ci, i); } } if (!decrypted) - log(LogLevel::warning, "Failed to decrypt message " + std::to_string(ci)); + log::warning( + cat, + "Failed to decrypt message {} for namespace {}", + ci, + static_cast(storage_namespace())); } - log(LogLevel::debug, - "successfully decrypted " + std::to_string(plaintexts.size()) + " of " + - std::to_string(configs.size()) + " incoming messages"); + log::debug( + cat, + "successfully decrypted {} of {} incoming messages", + plaintexts.size(), + configs.size()); for (auto& [hash, plain] : plaintexts) { // Remove prefix padding: - if (auto p = plain.find_first_not_of((unsigned char)0); p > 0 && p != std::string::npos) { + if (auto it = std::find_if( + plain.begin(), plain.end(), [](unsigned char c) { return c != 0; }); + it != plain.begin() && it != plain.end()) { + auto p = std::distance(plain.begin(), it); std::memmove(plain.data(), plain.data() + p, plain.size() - p); plain.resize(plain.size() - p); } if (plain.empty()) { - log(LogLevel::error, "Invalid config message: contains no data"); + log::error(cat, "Invalid config message: contains no data"); continue; } - // TODO FIXME (see above) - if (plain[0] == 'm') { - log(LogLevel::warning, "multi-part messages not yet supported!"); + bool was_multipart = plain[0] == 'm'; + + if (was_multipart) { + // Multipart message + + auto [accepted, completed] = _handle_multipart(hash, plain); + if (accepted) + good_hashes.emplace(hash); + + if (completed) { + all_hashes.push_back(std::move(completed->first)); + plain = std::move(completed->second); + all_confs.emplace_back(plain); + } + // else we didn't complete a set so nothing to do yet + continue; } - // 'z' prefix indicates zstd-compressed data: - if (plain[0] == 'z') { - struct zstd_decomp_freer { - void operator()(ZSTD_DStream* z) const { ZSTD_freeDStream(z); } - }; - std::unique_ptr z_decompressor{ZSTD_createDStream()}; - auto* zds = z_decompressor.get(); - - ZSTD_initDStream(zds); - ZSTD_inBuffer input{/*.src=*/plain.data() + 1, /*.size=*/plain.size() - 1, /*.pos=*/0}; - unsigned char out_buf[4096]; - ZSTD_outBuffer output{/*.dst=*/out_buf, /*.size=*/sizeof(out_buf)}; - bool failed = false; - size_t ret; - ustring decompressed; - do { - output.pos = 0; - ret = ZSTD_decompressStream(zds, &output, &input); - if (ZSTD_isError(ret)) { - failed = true; - break; - } - decompressed += ustring_view{out_buf, output.pos}; - } while (ret > 0 || input.pos < input.size); - if (failed || decompressed.empty()) { - log(LogLevel::warning, "Invalid config message: decompression failed"); + // Single-part message + bool was_compressed = plain[0] == 'z'; + + if (was_compressed) { // zstd-compressed data + if (auto decompressed = zstd_decompress( + std::span{plain.data() + 1, plain.size() - 1}); + decompressed && !decompressed->empty()) + plain = std::move(*decompressed); + else { + log::warning(cat, "Invalid config message: decompression failed"); continue; } - plain = std::move(decompressed); } - if (plain[0] != 'd') - log(LogLevel::error, - "invalid/unsupported config message with type " + - (plain[0] >= 0x20 && plain[0] <= 0x7e - ? "'" + std::string{from_unsigned_sv(plain.substr(0, 1))} + "'" - : "0x" + oxenc::to_hex(plain.begin(), plain.begin() + 1))); + if (plain[0] != 'd') { + log::error( + cat, + "invalid/unsupported config message with type {:?}", + static_cast(plain[0])); + continue; + } - all_hashes.emplace_back(hash); + good_hashes.emplace(hash); + all_hashes.emplace_back().emplace_back(hash); all_confs.emplace_back(plain); + + log::trace( + cat, + "Successfully parsed {}B {} config message {}", + plain.size(), + was_compressed && was_multipart ? "multi-part compressed" + : was_compressed ? "compressed" + : was_multipart ? "multi-part" + : "plaintext", + all_hashes.back()); + } + + if (mine_last) { + all_hashes.emplace_back(_curr_hashes.begin(), _curr_hashes.end()); + all_confs.emplace_back(mine); } + _expire_multiparts(); + + // This is only really possible when merging to a brand-new config object, but it *can* happen + // for instance if we only have some incomplete set of multiparts to load at the moment. + if (all_hashes.empty()) + return good_hashes; + std::set bad_confs; auto new_conf = make_config_message( _state == ConfigState::Dirty, all_confs, - nullptr, /* FIXME for signed messages: verifier */ - nullptr, /* FIXME for signed messages: signer */ + _config->verifier, + _config->signer, config_lags(), - false, /* signature not optional (if we have a verifier) */ [&](size_t i, const config_error& e) { - log(LogLevel::warning, e.what()); + log::warning(cat, "{}", e.what()); assert(i > 0); // i == 0 means we can't deserialize our own serialization bad_confs.insert(i); }); @@ -180,14 +542,55 @@ int ConfigBase::merge(const std::vector>& c // might be our current config, or might be one single one of the new incoming messages). // - confs that failed to parse (we can't understand them, so leave them behind as they may be // some future message). - int superconf = new_conf->unmerged_index(); // -1 if we had to merge - for (int i = 0; i < all_hashes.size(); i++) { - if (i != superconf && !bad_confs.count(i) && !all_hashes[i].empty()) - _old_hashes.emplace(all_hashes[i]); + std::optional superconf = new_conf->unmerged_index(); // nullopt if we had to merge + std::unordered_set superconf_hashes = + superconf && *superconf < all_hashes.size() + ? std::unordered_set< + std::string>{all_hashes[*superconf].begin(), all_hashes[*superconf].end()} + : std::unordered_set{}; + + const bool superconf_is_mine = + superconf && *superconf == (mine_last ? all_hashes.size() - 1 : 0); + + log::debug( + cat, + "Processed configs {}", + superconf && *superconf < all_hashes.size() + ? "with config superset [{}] (storage hash(es): {}; {} config)"_format( + *superconf, + all_hashes[*superconf].empty() + ? "" + : fmt::format("{}", fmt::join(all_hashes[*superconf], ", ")), + superconf_is_mine ? "current" : "incoming") + : "with merge required"); + + for (size_t i = 0; i < all_hashes.size(); i++) { + if (i != superconf && !bad_confs.count(i) && !all_hashes[i].empty() && + superconf_hashes != + std::unordered_set{all_hashes[i].begin(), all_hashes[i].end()}) { + bool all_already_existed = true; + + for (const auto& hash : all_hashes[i]) { + auto [it, ins] = _old_hashes.emplace(hash); + + if (ins) + all_already_existed = false; + } + + log::trace( + cat, + "Conf message [{}] {} obsolete", + fmt::join(all_hashes[i], ", "), + all_already_existed ? "was already" : "is now"); + } } if (new_conf->seqno() != old_seqno) { if (new_conf->merged()) { + log::debug( + cat, + "New configs required merging: current state {}, new state DIRTY", + current_state_string()); if (_state != ConfigState::Dirty) { // Merging resulted in a merge conflict resolution message, but won't currently be // mutable (because we weren't dirty to start with). Convert into a Mutable message @@ -199,8 +602,11 @@ int ConfigBase::merge(const std::vector>& c } set_state(ConfigState::Dirty); } else if ( - _state == ConfigState::Dirty && new_conf->unmerged_index() == 0 && + _state == ConfigState::Dirty && superconf_is_mine && new_conf->seqno() == old_seqno + 1) { + log::debug( + cat, + "Current DIRTY config already contains all incoming configs, nothing to do"); // Constructing a new MutableConfigMessage always increments the seqno (by design) but // in this case nothing changed: every other config got ignored and we didn't change // anything, so we can ignore the new config and just keep our current one, despite the @@ -208,24 +614,70 @@ int ConfigBase::merge(const std::vector>& c /* do nothing */ } else { _config = std::move(new_conf); - assert(_config->unmerged_index() >= 1 && _config->unmerged_index() < all_hashes.size()); + assert(((old_seqno == 0 && mine.empty()) || !superconf_is_mine) && + *superconf < all_hashes.size()); set_state(ConfigState::Clean); - _curr_hash = all_hashes[_config->unmerged_index()]; + _curr_hashes.clear(); + auto& hashes = all_hashes[*superconf]; + _curr_hashes.insert(hashes.begin(), hashes.end()); + + log::debug( + cat, + "Incoming config [{}] {} is super-set of current and incoming configs; " + "adopting it as clean current config", + *superconf, + fmt::join(_curr_hashes, ", ")); } } else { - // the merging affect nothing (if it had seqno would have been incremented), so don't - // pointlessly replace the inner config object. - assert(new_conf->unmerged_index() == 0); + log::debug(cat, "All incoming configs rejected or already included, nothing to do"); } - return all_confs.size() - bad_confs.size() - - 1; // -1 because we don't count the first one (reparsing ourself). + for (size_t i = 0; i < all_hashes.size(); i++) { + if (!mine.empty() && i == (mine_last ? all_hashes.size() - 1 : 0)) + continue; + if (bad_confs.count(i)) + for (const auto& h : all_hashes[i]) + good_hashes.erase(h); + } + + log::info( + cat, + "New configs merged ({} good, {} bad); config state is now {} with seqno {} (was {})", + good_hashes.size(), + bad_confs.size(), + current_state_string(), + _config->seqno(), + old_seqno); + + return good_hashes; +} + +const std::unordered_set& ConfigBase::curr_hashes() const { + return _curr_hashes; } -std::vector ConfigBase::current_hashes() const { +std::unordered_set ConfigBase::active_hashes() const { + // First copy any hashes that make up the currently active config: + std::unordered_set hashes{_curr_hashes}; + + auto now = std::chrono::system_clock::now(); + // Add include any pending partial configs that *might* be newer: + for (const auto& [_, part] : _multiparts) + if (!part.done && part.expiry > now) + for (const auto& p : part.parts) + hashes.insert(p.message_id); + + return hashes; +} + +std::vector ConfigBase::old_hashes() { std::vector hashes; - if (!_curr_hash.empty()) - hashes.push_back(_curr_hash); + if (!is_dirty()) { + for (auto& old : _old_hashes) + hashes.push_back(std::move(old)); + _old_hashes.clear(); + } + return hashes; } @@ -235,224 +687,452 @@ bool ConfigBase::needs_push() const { // Tries to compresses the message; if the compressed version (including the 'z' prefix tag) is // smaller than the source message then we modify `msg` to contain the 'z'-prefixed compressed -// message, otherwise we leave it as-is. -void compress_message(ustring& msg, int level) { +// message, otherwise we leave it as-is. Returns true if compression was beneficial and `msg` has +// been compressed; false if compression did not reduce the size and msg was left as-is. +void compress_message(std::vector& msg, int level) { if (!level) return; - ustring compressed; - compressed.resize(1 + ZSTD_compressBound(msg.size())); - compressed[0] = 'z'; // our zstd compression marker prefix byte - auto size = ZSTD_compress( - compressed.data() + 1, compressed.size() - 1, msg.data(), msg.size(), level); - if (ZSTD_isError(size)) - throw std::runtime_error{ - "Unable to compress message: " + std::string{ZSTD_getErrorName(size)}}; - compressed.resize(size + 1); + // "z" is our zstd compression marker prefix byte + std::vector compressed = zstd_compress(msg, level, to_span("z")); if (compressed.size() < msg.size()) msg = std::move(compressed); } -std::tuple> ConfigBase::push() { - if (_keys_size == 0) +std::tuple>, std::vector> +ConfigBase::push() { + if (_keys.empty()) throw std::logic_error{"Cannot push data without an encryption key!"}; - std::tuple> ret{ - _config->seqno(), _config->serialize(), {}}; + auto s = _config->seqno(); + + std::tuple>, std::vector> ret{ + s, {}, {}}; + auto& [seqno, msgs, obs] = ret; + + auto msg = _config->serialize(); - auto& [seqno, msg, obs] = ret; if (auto lvl = compression_level()) compress_message(msg, *lvl); + pad_message(msg); // Prefix pad with nulls - encrypt_inplace(msg, key(), encryption_domain()); - if (msg.size() > MAX_MESSAGE_SIZE) - throw std::length_error{"Config data is too large"}; + if (msg.size() > MAX_MULTIPART_SIZE) + throw std::length_error{ + "Config data is insanely large ({}B), even for multipart"_format(msg.size())}; + + if (msg.size() + ENCRYPT_DATA_OVERHEAD > MAX_MESSAGE_SIZE) { + // Multipart handling: if the above gives us a msg that exceeds the storage server limit + // then we need to split it up into multipart config messages, and then encrypt each piece. + // Each one (before encryption) starts with `m` and consists of a 4-element bt list: + // - element 0 is the hash of the recombined message (i.e. what we have right now in + // `msg`) + // - element 1 is the index of the message within the set, starting from 0, encoded as a + // fixed length 1-byte string (0-254). + // - element 2 is the size of the message parts, and must be at least 2 (2-255). + // - element 3 is the chunk of data (and so, when ordered by sequence number, each data + // chunk + // concatenated together gives us the `msg` value we have right now in this function). + hash_t final_hash; + hash::hash(final_hash, msg); + + constexpr size_t ENCODE_OVERHEAD = + 1 // The `m` prefix indicating a multipart message part + + 2 // the `l` and `e` encoding around the list + + 3 + 32 // '32:' followed by final_hash 32 bytes + + 2 + 1 // '1:x' part index (x is the uint8_t part index encoded as a byte) + + 2 + 1 // '1:y' num parts (y is the uint8_t parts count encoded as a byte) + + 6; // '76543:' data length prefix; just under 76800 for all but the last part + + constexpr size_t MAX_CHUNK_SIZE = + MAX_MESSAGE_SIZE - ENCODE_OVERHEAD - ENCRYPT_DATA_OVERHEAD; + + static_assert(MAX_CHUNK_SIZE < MAX_MESSAGE_SIZE); + static_assert( + (MAX_MULTIPART_SIZE + MAX_CHUNK_SIZE - 1) / MAX_CHUNK_SIZE <= 255, + "MAX_MULTIPART_SIZE is too large: more than 255 parts could result"); + + const uint8_t num_parts = (msg.size() + MAX_CHUNK_SIZE - 1) / MAX_CHUNK_SIZE; + msgs.reserve(num_parts); + + log::debug( + cat, + "splitting large config message ({}B, hash {}) into {} parts", + msg.size(), + oxenc::to_hex(final_hash.begin(), final_hash.end()), + num_parts); + + std::span remaining{msg}; + for (uint8_t index = 0; !remaining.empty(); ++index) { + auto& out = msgs.emplace_back(); + auto chunk = remaining.subspan(0, std::min(MAX_CHUNK_SIZE, remaining.size())); + remaining = remaining.subspan(chunk.size()); + out.reserve(chunk.size() + ENCODE_OVERHEAD + ENCRYPT_DATA_OVERHEAD); + out.resize(chunk.size() + ENCODE_OVERHEAD); + out[0] = 'm'; + { + oxenc::bt_list_producer lp{reinterpret_cast(out.data() + 1), out.size() - 1}; + lp.append(std::span{final_hash}); + lp.append(std::span{&index, 1}); + lp.append(std::span{&num_parts, 1}); + lp.append(chunk); + + // We should have filled the buffer exactly, except for the last part which, due to + // the variable length data prefix ("76543:" in the ENCODE_OVERHEAD comment above), + // could be up to 4 chars shorter (for example: "9:abcdefghi"). + assert(static_cast( + reinterpret_cast(out.data() + out.size()) - lp.end()) <= + (remaining.empty() ? 4 : 0)); + } + + encrypt_inplace(out, key(), encryption_domain()); + + _multiparts[final_hash].finish(MULTIPART_MAX_REMEMBER); + } + assert(msgs.size() > 1 && msgs.size() <= 255); + + } else { + encrypt_inplace(msg, key(), encryption_domain()); + + if (accepts_protobuf() && !_keys.empty()) { + auto pbwrapped = protos::wrap_config( + {_keys.front().data(), _keys.front().size()}, msg, s, storage_namespace()); + // If protobuf wrapping would push us *over* the max message size then we just skip the + // protobuf wrapping because older clients (that need protobuf) also don't support + // multipart anyway, so we can't produce a message they will accept no matter what. + if (pbwrapped.size() <= MAX_MESSAGE_SIZE) + msg = std::move(pbwrapped); + } + + assert(msg.size() <= MAX_MESSAGE_SIZE); + + msgs.push_back(std::move(msg)); + } if (is_dirty()) set_state(ConfigState::Waiting); - for (auto& old : _old_hashes) - obs.push_back(std::move(old)); + if (!is_readonly()) + for (auto& old : _old_hashes) + obs.push_back(std::move(old)); _old_hashes.clear(); return ret; } -void ConfigBase::confirm_pushed(seqno_t seqno, std::string msg_hash) { +void ConfigBase::confirm_pushed(seqno_t seqno, std::unordered_set msg_hashes) { // Make sure seqno hasn't changed; if it has then that means we set some other data *after* the // caller got the last data to push, and so we don't care about this confirmation. if (_state == ConfigState::Waiting && seqno == _config->seqno()) { set_state(ConfigState::Clean); - _curr_hash = std::move(msg_hash); + _curr_hashes = std::move(msg_hashes); + _needs_dump = true; } } -ustring ConfigBase::dump() { +std::vector ConfigBase::dump() { + if (is_readonly()) + _old_hashes.clear(); + + _expire_multiparts(); + + auto d = make_dump(); + _needs_dump = false; + return d; +} + +std::vector ConfigBase::make_dump() const { auto data = _config->serialize(false /* disable signing for local storage */); - auto data_sv = from_unsigned_sv(data); + auto data_sv = to_string_view(data); oxenc::bt_list old_hashes; - for (auto& old : _old_hashes) - old_hashes.emplace_back(old); - oxenc::bt_dict d{ - {"!", static_cast(_state)}, - {"$", data_sv}, - {"(", _curr_hash}, - {")", std::move(old_hashes)}, - }; - if (auto extra = extra_data(); !extra.empty()) - d.emplace("+", std::move(extra)); - _needs_dump = false; - auto dumped = oxenc::bt_serialize(d); - return ustring{to_unsigned_sv(dumped)}; + oxenc::bt_dict_producer d; + d.append("!", static_cast(_state)); + d.append("$", data_sv); + d.append_list("(", _curr_hashes); + + d.append_list(")").extend(_old_hashes.begin(), _old_hashes.end()); + + _dump_multiparts(d.append_dict("*")); + + extra_data(d.append_dict("+")); + + return to_vector(d.view()); } -ConfigBase::ConfigBase(std::optional dump) { +ConfigBase::ConfigBase( + std::optional> dump, + std::optional> ed25519_pubkey, + std::optional> ed25519_secretkey) { + if (sodium_init() == -1) throw std::runtime_error{"libsodium initialization failed!"}; + + init(dump, ed25519_pubkey, ed25519_secretkey); +} + +void ConfigSig::init_sig_keys( + std::optional> ed25519_pubkey, + std::optional> ed25519_secretkey) { + if (ed25519_secretkey) { + if (ed25519_pubkey && + to_string_view(*ed25519_pubkey) != to_string_view(ed25519_secretkey->subspan(32))) + throw std::invalid_argument{"Invalid signing keys: secret key and pubkey do not match"}; + set_sig_keys(*ed25519_secretkey); + } else if (ed25519_pubkey) { + set_sig_pubkey(*ed25519_pubkey); + } else { + clear_sig_keys(); + } +} + +void ConfigBase::init( + std::optional> dump, + std::optional> ed25519_pubkey, + std::optional> ed25519_secretkey) { if (!dump) { + _state = ConfigState::Clean; _config = std::make_unique(); - return; - } + } else { + oxenc::bt_dict_consumer d{*dump}; + if (!d.skip_until("!")) + throw std::runtime_error{ + "Unable to parse dumped config data: did not find '!' state key"}; + _state = static_cast(d.consume_integer()); + + if (!d.skip_until("$")) + throw std::runtime_error{ + "Unable to parse dumped config data: did not find '$' data key"}; + auto data = to_span(d.consume_string_view()); + if (_state == ConfigState::Dirty) + // If we dumped dirty data then we need to reload it as a mutable config message so that + // the seqno gets incremented. This "wastes" one seqno value (since we didn't send the + // old one), but that's minor and easier than extracting and restoring all the fields we + // set and is a little more robust against failure if we actually sent it but got killed + // before we could store a dump. + _config = std::make_unique( + data, + nullptr, // We omit verifier and signer for now because we don't want this dump + // to + nullptr, // be signed (since it's just a dump). + config_lags()); + else + _config = std::make_unique( + data, + nullptr, + nullptr, + config_lags(), + /*trust_signature=*/true); + + _curr_hashes.clear(); + if (d.skip_until("(")) { + if (d.is_list()) + _curr_hashes = d.consume>(); + else if (d.is_string()) { + // Backwards compatibility with a dump created before multipart configs: + if (auto hash = d.consume_string_view(); !hash.empty()) + _curr_hashes.emplace(hash); + } else { + throw std::runtime_error{ + "Invalid dumped config data: expected '(' containing list or string"}; + } + if (!d.skip_until(")")) + throw std::runtime_error{ + "Unable to parse dumped config data: found '(' without ')'"}; + for (auto old = d.consume_list_consumer(); !old.is_finished();) + _old_hashes.insert(old.consume_string()); + } - oxenc::bt_dict_consumer d{from_unsigned_sv(*dump)}; - if (!d.skip_until("!")) - throw std::runtime_error{"Unable to parse dumped config data: did not find '!' state key"}; - _state = static_cast(d.consume_integer()); - - if (!d.skip_until("$")) - throw std::runtime_error{"Unable to parse dumped config data: did not find '$' data key"}; - if (_state == ConfigState::Dirty) - // If we dumped dirty data then we need to reload it as a mutable config message so that the - // seqno gets incremented. This "wastes" one seqno value (since we didn't send the old - // one), but that's minor and easier than extracting and restoring all the fields we set and - // is a little more robust against failure if we actually sent it but got killed before we - // could store a dump. - _config = std::make_unique( - to_unsigned_sv(d.consume_string_view()), - nullptr, // FIXME: verifier; but maybe want to delay setting this since it - // shouldn't be signed? - nullptr, // FIXME: signer - config_lags(), - true /* signature optional because we don't sign the dump */); - else - _config = std::make_unique( - to_unsigned_sv(d.consume_string_view()), nullptr, nullptr, config_lags(), true); - - if (d.skip_until("(")) { - _curr_hash = d.consume_string(); - if (!d.skip_until(")")) - throw std::runtime_error{"Unable to parse dumped config data: found '(' without ')'"}; - for (auto old = d.consume_list_consumer(); !old.is_finished();) - _old_hashes.insert(old.consume_string()); - } + if (d.skip_until("*")) + _load_multiparts(d.consume_dict_consumer()); - if (d.skip_until("+")) - if (auto extra = d.consume_dict(); !extra.empty()) - load_extra_data(std::move(extra)); -} + if (d.skip_until("+")) + load_extra_data(d.consume_dict_consumer()); + } -ConfigBase::~ConfigBase() { - sodium_free(_keys); + init_sig_keys(ed25519_pubkey, ed25519_secretkey); } int ConfigBase::key_count() const { - return _keys_size; + return _keys.size(); } -bool ConfigBase::has_key(ustring_view key) const { +bool ConfigBase::has_key(std::span key) const { if (key.size() != 32) throw std::invalid_argument{"invalid key given to has_key(): not 32-bytes"}; auto* keyptr = key.data(); - for (size_t i = 0; i < _keys_size; i++) - if (sodium_memcmp(keyptr, _keys[i].data(), KEY_SIZE) == 0) + for (const auto& key : _keys) + if (sodium_memcmp(keyptr, key.data(), KEY_SIZE) == 0) return true; return false; } -std::vector ConfigBase::get_keys() const { - std::vector ret; - ret.reserve(_keys_size); - for (size_t i = 0; i < _keys_size; i++) - ret.emplace_back(_keys[i].data(), _keys[i].size()); +std::vector> ConfigBase::get_keys() const { + std::vector> ret; + ret.reserve(_keys.size()); + for (const auto& key : _keys) + ret.emplace_back(key.data(), key.size()); return ret; } -void ConfigBase::add_key(ustring_view key, bool high_priority) { +void ConfigBase::add_key( + std::span key, bool high_priority, bool dirty_config) { static_assert( sizeof(Key) == KEY_SIZE, "std::array appears to have some overhead which seems bad"); if (key.size() != KEY_SIZE) throw std::invalid_argument{"add_key failed: key size must be 32 bytes"}; - if (_keys_size > 0 && sodium_memcmp(_keys[0].data(), key.data(), KEY_SIZE) == 0) + if (!_keys.empty() && sodium_memcmp(_keys.front().data(), key.data(), KEY_SIZE) == 0) return; else if (!high_priority && has_key(key)) return; - if (_keys_capacity == 0) { + if (_keys.capacity() == 0) // There's not a lot of point in starting this off really small: sodium is likely going to // use at least a page size anyway. - _keys_capacity = 16; - _keys = static_cast(sodium_allocarray(_keys_capacity, KEY_SIZE)); - } - - if (_keys_size >= _keys_capacity) { - _keys_capacity *= 2; - auto new_keys = static_cast(sodium_allocarray(_keys_capacity, 32)); - if (high_priority) { - std::memcpy(new_keys[0].data(), key.data(), KEY_SIZE); - std::memcpy(&new_keys[1], _keys, _keys_size * KEY_SIZE); - } else { - std::memcpy(&new_keys[0], _keys, _keys_size * KEY_SIZE); - std::memcpy(new_keys[_keys_size].data(), key.data(), KEY_SIZE); - } - sodium_free(_keys); - _keys = new_keys; - } else if (high_priority) { - // shift everything up so we can insert at beginning - std::memmove(&_keys[1], &_keys[0], _keys_size * KEY_SIZE); - std::memcpy(_keys[0].data(), key.data(), KEY_SIZE); - } else { - // add at the end - std::memcpy(_keys[_keys_size].data(), key.data(), KEY_SIZE); - } - _keys_size++; + _keys.reserve(64); - // *Slightly* suboptimal in that we might change buffers above even when we didn't need to, but - // not worth worrying about optimizing. if (high_priority) remove_key(key, 1); + + auto& newkey = *_keys.emplace(high_priority ? _keys.begin() : _keys.end()); + std::memcpy(newkey.data(), key.data(), KEY_SIZE); + + if (dirty_config && !is_readonly() && (_keys.size() == 1 || high_priority)) + dirty(); } -int ConfigBase::clear_keys() { - int ret = _keys_size; - _keys_size = 0; +int ConfigBase::clear_keys(bool dirty_config) { + int ret = _keys.size(); + _keys.clear(); + _keys.shrink_to_fit(); + + if (dirty_config && !is_readonly() && ret > 0) + dirty(); + return ret; } -bool ConfigBase::remove_key(ustring_view key, size_t from) { - bool removed = false; - - for (size_t i = from; i < _keys_size; i++) { - if (sodium_memcmp(key.data(), _keys[i].data(), KEY_SIZE) == 0) { - if (i + 1 < _keys_size) - std::memmove(&_keys[i], &_keys[i + 1], (_keys_size - i - 1) * KEY_SIZE); - _keys_size--; - removed = true; - // Don't break, in case there are somehow duplicates in here - } +void ConfigBase::replace_keys( + const std::vector>& new_keys, bool dirty_config) { + if (new_keys.empty()) { + if (_keys.empty()) + return; + clear_keys(dirty_config); + return; } - return removed; + + for (auto& k : new_keys) + if (k.size() != KEY_SIZE) + throw std::invalid_argument{"replace_keys failed: keys must be 32 bytes"}; + + dirty_config = dirty_config && !is_readonly() && + (_keys.empty() || + sodium_memcmp(_keys.front().data(), new_keys.front().data(), KEY_SIZE) != 0); + + _keys.clear(); + for (auto& k : new_keys) + add_key(k, /*high_priority=*/false); // The first key gets the high priority spot even + // with `false` since we just emptied the list + + if (dirty_config) + dirty(); } -void ConfigBase::load_key(ustring_view ed25519_secretkey) { +bool ConfigBase::remove_key(std::span key, size_t from, bool dirty_config) { + auto starting_size = _keys.size(); + if (from >= starting_size) + return false; + + dirty_config = dirty_config && !is_readonly() && + sodium_memcmp(key.data(), _keys.front().data(), KEY_SIZE) == 0; + + _keys.erase( + std::remove_if( + _keys.begin() + from, + _keys.end(), + [&key](const auto& k) { + return sodium_memcmp(key.data(), k.data(), KEY_SIZE) == 0; + }), + _keys.end()); + + if (dirty_config) + dirty(); + + return _keys.size() < starting_size; +} + +void ConfigBase::load_key(std::span ed25519_secretkey) { if (!(ed25519_secretkey.size() == 64 || ed25519_secretkey.size() == 32)) throw std::invalid_argument{ encryption_domain() + " requires an Ed25519 64-byte secret key or 32-byte seed"s}; - add_key(ed25519_secretkey.substr(0, 32)); + add_key(ed25519_secretkey.subspan(0, 32)); +} + +void ConfigSig::set_sig_keys(std::span secret) { + if (secret.size() != 64) + throw std::invalid_argument{"Invalid sodium secret: expected 64 bytes"}; + clear_sig_keys(); + _sign_sk.reset(64); + std::memcpy(_sign_sk.data(), secret.data(), secret.size()); + _sign_pk.emplace(); + crypto_sign_ed25519_sk_to_pk(_sign_pk->data(), _sign_sk.data()); + + set_verifier([this](std::span data, std::span sig) { + return 0 == crypto_sign_ed25519_verify_detached( + sig.data(), data.data(), data.size(), _sign_pk->data()); + }); + set_signer([this](std::span data) { + std::vector sig; + sig.resize(64); + if (0 != crypto_sign_ed25519_detached( + sig.data(), nullptr, data.data(), data.size(), _sign_sk.data())) + throw std::runtime_error{"Internal error: config signing failed!"}; + return sig; + }); +} + +void ConfigSig::set_sig_pubkey(std::span pubkey) { + if (pubkey.size() != 32) + throw std::invalid_argument{"Invalid pubkey: expected 32 bytes"}; + _sign_pk.emplace(); + std::memcpy(_sign_pk->data(), pubkey.data(), 32); + + set_verifier([this](std::span data, std::span sig) { + return 0 == crypto_sign_ed25519_verify_detached( + sig.data(), data.data(), data.size(), _sign_pk->data()); + }); +} + +void ConfigSig::clear_sig_keys() { + _sign_pk.reset(); + _sign_sk.reset(); + set_signer(nullptr); + set_verifier(nullptr); +} + +void ConfigBase::set_verifier(ConfigMessage::verify_callable v) { + _config->verifier = std::move(v); +} + +void ConfigBase::set_signer(ConfigMessage::sign_callable s) { + _config->signer = std::move(s); +} + +std::array ConfigSig::seed_hash(std::string_view key) const { + if (!_sign_sk) + throw std::runtime_error{"Cannot make a seed hash without a signing secret key"}; + std::array out; + crypto_generichash_blake2b( + out.data(), + out.size(), + _sign_sk.data(), + 32, // Just the seed part of the value, not the last half (which is just the pubkey) + reinterpret_cast(key.data()), + std::min(key.size(), 64)); + return out; } void set_error(config_object* conf, std::string e) { @@ -469,6 +1149,7 @@ using namespace session; using namespace session::config; LIBSESSION_EXPORT void config_free(config_object* conf) { + delete static_cast*>(conf->internals); delete conf; } @@ -476,18 +1157,22 @@ LIBSESSION_EXPORT int16_t config_storage_namespace(const config_object* conf) { return static_cast(unbox(conf)->storage_namespace()); } -LIBSESSION_EXPORT int config_merge( +LIBSESSION_EXPORT config_string_list* config_merge( config_object* conf, - const char** msg_hashes, - const unsigned char** configs, + const char* const* msg_hashes, + const unsigned char* const* configs, const size_t* lengths, size_t count) { - auto& config = *unbox(conf); - std::vector> confs; - confs.reserve(count); - for (size_t i = 0; i < count; i++) - confs.emplace_back(msg_hashes[i], ustring_view{configs[i], lengths[i]}); - return config.merge(confs); + return wrap_exceptions(conf, [&] { + auto& config = *unbox(conf); + std::vector>> confs; + confs.reserve(count); + for (size_t i = 0; i < count; i++) + confs.emplace_back( + msg_hashes[i], std::span{configs[i], lengths[i]}); + + return make_string_list(config.merge(confs)); + }); } LIBSESSION_EXPORT bool config_needs_push(const config_object* conf) { @@ -495,86 +1180,157 @@ LIBSESSION_EXPORT bool config_needs_push(const config_object* conf) { } LIBSESSION_EXPORT config_push_data* config_push(config_object* conf) { - auto& config = *unbox(conf); - auto [seqno, data, obs] = config.push(); - - // We need to do one alloc here that holds everything: - // - the returned struct - // - pointers to the obsolete message hash strings - // - the data - // - the message hash strings - size_t buffer_size = sizeof(config_push_data) + obs.size() * sizeof(char*) + data.size(); - for (auto& o : obs) - buffer_size += o.size(); - buffer_size += obs.size(); // obs msg hash string NULL terminators - - auto* ret = static_cast(std::malloc(buffer_size)); - - ret->seqno = seqno; - - static_assert(alignof(config_push_data) >= alignof(char*)); - ret->obsolete = reinterpret_cast(ret + 1); - ret->obsolete_len = obs.size(); - - ret->config = reinterpret_cast(ret->obsolete + ret->obsolete_len); - ret->config_len = data.size(); - - std::memcpy(ret->config, data.data(), data.size()); - char* obsptr = reinterpret_cast(ret->config + ret->config_len); - for (size_t i = 0; i < obs.size(); i++) { - std::memcpy(obsptr, obs[i].c_str(), obs[i].size() + 1); - ret->obsolete[i] = obsptr; - obsptr += obs[i].size() + 1; - } + return wrap_exceptions(conf, [&] { + auto& config = *unbox(conf); + auto [seqno, data, obs] = config.push(); + + // We need to do one alloc here that holds everything. We overallocate the struct, using + // the extra allocated space beyond the end of the struct to store all the values the struct + // points at. + // + // In particular, in the beyond-the-end space, for N configdata and M obsolete hashes, we + // lay it out as follows: + // + // - N data pointers; `ret->config` points to the beginning of this: + // [*configdata1][*configdata2]...[*configdataN] + // - N size_t values; `ret->config_lens` points to the beginning of this: + // [size1][size2]...[sizeN] + // - M obsolete hash c string pointers; `ret->obsolete` points to the beginning of this: + // [*obs1][*obs2]...[*obsM] + // - packed data containing all the N config data and M obsolete hash null-terminated c + // strings pointed at in the above layout: + // [configdata1][configdata2]...[configdataN][obs1\0][obs2\0]...[obsM\0] + // + // For example: + // - `ret->config[1]` is the pointer `*configdata2`, which points at the beginning of + // [configdata2] in the packed data. `ret->config_lens[1]` is the length of that + // [configdata2]. + // - `ret->obs[0]` is the c string pointer containing the first obsolete hash, `obs1`; it + // points at the actual `[obs1\0]` value in the final packed data. + // + static_assert(alignof(config_push_data) >= alignof(char*)); + static_assert(sizeof(config_push_data) % alignof(char*) == 0); + static_assert(alignof(char*) == alignof(size_t*)); + static_assert(alignof(size_t) == alignof(char*)); + size_t buffer_size = sizeof(config_push_data) // struct data + + data.size() * sizeof(unsigned char*) // data pointer array + + data.size() * sizeof(size_t) // data sizes + + obs.size() * sizeof(char*); // obsolete pointer array + + // + configdata array data off the end: + for (auto& d : data) + buffer_size += d.size(); + // + obsolete hash data (including null terminator for each) off the end: + for (auto& o : obs) + buffer_size += o.size() + 1; + + auto* ret = static_cast(std::malloc(buffer_size)); + if (!ret) { + log::critical(cat, "Memory allocation failed in config_push!"); + return static_cast(nullptr); + } - return ret; + ret->seqno = seqno; + ret->config = reinterpret_cast(ret + 1); + ret->config_lens = reinterpret_cast(ret->config + data.size()); + ret->n_configs = data.size(); + ret->obsolete = reinterpret_cast(ret->config_lens + data.size()); + ret->obsolete_len = obs.size(); + + unsigned char* pos = reinterpret_cast(ret->obsolete + ret->obsolete_len); + for (size_t i = 0; i < data.size(); i++) { + std::memcpy(pos, data[i].data(), data[i].size()); + ret->config[i] = pos; + pos += (ret->config_lens[i] = data[i].size()); + } + for (size_t i = 0; i < obs.size(); i++) { + auto cstr_len = obs[i].size() + 1 /*NUL terminator*/; + std::memcpy(pos, obs[i].c_str(), cstr_len); + ret->obsolete[i] = reinterpret_cast(pos); + pos += cstr_len; + } + assert(pos - reinterpret_cast(ret) == buffer_size); + + return ret; + }); } LIBSESSION_EXPORT void config_confirm_pushed( - config_object* conf, seqno_t seqno, const char* msg_hash) { - unbox(conf)->confirm_pushed(seqno, msg_hash); + config_object* conf, seqno_t seqno, const char* const* msg_hashes, size_t hashes_len) { + std::unordered_set hashes; + for (size_t i = 0; i < hashes_len; i++) + hashes.emplace(msg_hashes[i]); + + unbox(conf)->confirm_pushed(seqno, std::move(hashes)); } -LIBSESSION_EXPORT void config_dump(config_object* conf, unsigned char** out, size_t* outlen) { - assert(out && outlen); - auto data = unbox(conf)->dump(); - *outlen = data.size(); - *out = static_cast(std::malloc(data.size())); - std::memcpy(*out, data.data(), data.size()); +LIBSESSION_EXPORT bool config_dump(config_object* conf, unsigned char** out, size_t* outlen) { + return wrap_exceptions( + conf, + [&] { + assert(out && outlen); + auto data = unbox(conf)->dump(); + *outlen = data.size(); + *out = static_cast(std::malloc(data.size())); + std::memcpy(*out, data.data(), data.size()); + return true; + }, + false); } LIBSESSION_EXPORT bool config_needs_dump(const config_object* conf) { return unbox(conf)->needs_dump(); } -LIBSESSION_EXPORT config_string_list* config_current_hashes(const config_object* conf) { - auto hashes = unbox(conf)->current_hashes(); - size_t sz = sizeof(config_string_list) + hashes.size() * sizeof(char*); - for (auto& h : hashes) - sz += h.size() + 1; - void* buf = std::malloc(sz); - auto* ret = static_cast(buf); - ret->len = hashes.size(); - - static_assert(alignof(config_string_list) >= alignof(char*)); - ret->value = reinterpret_cast(ret + 1); - char** next_ptr = ret->value; - char* next_str = reinterpret_cast(next_ptr + ret->len); - - for (size_t i = 0; i < ret->len; i++) { - *(next_ptr++) = next_str; - std::memcpy(next_str, hashes[i].c_str(), hashes[i].size() + 1); - next_str += hashes[i].size() + 1; +LIBSESSION_EXPORT config_string_list* config_curr_hashes(const config_object* conf) { + return make_string_list(unbox(conf)->curr_hashes()); +} + +LIBSESSION_EXPORT config_string_list* config_active_hashes(const config_object* conf) { + return make_string_list(unbox(conf)->active_hashes()); +} + +LIBSESSION_EXPORT config_string_list* config_old_hashes(config_object* conf) { + return make_string_list(unbox(conf)->old_hashes()); +} + +LIBSESSION_EXPORT unsigned char* config_get_keys(const config_object* conf, size_t* len) { + const auto keys = unbox(conf)->get_keys(); + assert(static_cast(std::count_if(keys.begin(), keys.end(), [](const auto& k) { + return k.size() == 32; + })) == keys.size()); + assert(len); + *len = keys.size(); + if (keys.empty()) + return nullptr; + auto* buf = static_cast(std::malloc(32 * keys.size())); + auto* cur = buf; + for (const auto& k : keys) { + std::memcpy(cur, k.data(), 32); + cur += 32; } - return ret; + return buf; } -LIBSESSION_EXPORT void config_add_key(config_object* conf, const unsigned char* key) { - unbox(conf)->add_key({key, 32}); +LIBSESSION_EXPORT bool config_add_key(config_object* conf, const unsigned char* key) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->add_key({key, 32}); + return true; + }, + false); } -LIBSESSION_EXPORT void config_add_key_low_prio(config_object* conf, const unsigned char* key) { - unbox(conf)->add_key({key, 32}, /*high_priority=*/false); + +LIBSESSION_EXPORT bool config_add_key_low_prio(config_object* conf, const unsigned char* key) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->add_key({key, 32}, /*high_priority=*/false); + return true; + }, + false); } LIBSESSION_EXPORT int config_clear_keys(config_object* conf) { return unbox(conf)->clear_keys(); @@ -586,7 +1342,11 @@ LIBSESSION_EXPORT int config_key_count(const config_object* conf) { return unbox(conf)->key_count(); } LIBSESSION_EXPORT bool config_has_key(const config_object* conf, const unsigned char* key) { - return unbox(conf)->has_key({key, 32}); + try { + return unbox(conf)->has_key({key, 32}); + } catch (...) { + return false; + } } LIBSESSION_EXPORT const unsigned char* config_key(const config_object* conf, size_t i) { return unbox(conf)->key(i).data(); @@ -596,14 +1356,35 @@ LIBSESSION_EXPORT const char* config_encryption_domain(const config_object* conf return unbox(conf)->encryption_domain(); } -LIBSESSION_EXPORT void config_set_logger( - config_object* conf, void (*callback)(config_log_level, const char*, void*), void* ctx) { - if (!callback) - unbox(conf)->logger = nullptr; - else - unbox(conf)->logger = [callback, ctx](LogLevel lvl, std::string msg) { - callback(static_cast(static_cast(lvl)), msg.c_str(), ctx); - }; +LIBSESSION_EXPORT bool config_set_sig_keys(config_object* conf, const unsigned char* secret) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set_sig_keys({secret, 64}); + return true; + }, + false); +} + +LIBSESSION_EXPORT bool config_set_sig_pubkey(config_object* conf, const unsigned char* pubkey) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set_sig_pubkey({pubkey, 32}); + return true; + }, + false); +} + +LIBSESSION_EXPORT const unsigned char* config_get_sig_pubkey(const config_object* conf) { + const auto& pk = unbox(conf)->get_sig_pubkey(); + if (pk) + return pk->data(); + return nullptr; +} + +LIBSESSION_EXPORT void config_clear_sig_keys(config_object* conf) { + unbox(conf)->clear_sig_keys(); } } // extern "C" diff --git a/src/config/community.cpp b/src/config/community.cpp index d6c2ed85..1832f6f6 100644 --- a/src/config/community.cpp +++ b/src/config/community.cpp @@ -23,7 +23,8 @@ community::community(std::string_view base_url_, std::string_view room_) { set_room(std::move(room_)); } -community::community(std::string_view base_url, std::string_view room, ustring_view pubkey_) : +community::community( + std::string_view base_url, std::string_view room, std::span pubkey_) : community{base_url, room} { set_pubkey(pubkey_); } @@ -45,10 +46,10 @@ void community::set_base_url(std::string_view new_url) { base_url_ = canonical_url(new_url); } -void community::set_pubkey(ustring_view pubkey) { +void community::set_pubkey(std::span pubkey) { if (pubkey.size() != 32) throw std::invalid_argument{"Invalid pubkey: expected a 32-byte pubkey"}; - pubkey_ = pubkey; + pubkey_.assign(pubkey.begin(), pubkey.end()); } void community::set_pubkey(std::string_view pubkey) { pubkey_ = decode_pubkey(pubkey); @@ -74,14 +75,12 @@ void community::set_room(std::string_view room) { localized_room_ = room; } -static constexpr std::string_view qs_pubkey{"?public_key="}; - std::string community::full_url() const { return full_url(base_url(), room(), pubkey()); } std::string community::full_url( - std::string_view base_url, std::string_view room, ustring_view pubkey) { + std::string_view base_url, std::string_view room, std::span pubkey) { std::string url{base_url}; url += '/'; url += room; @@ -90,67 +89,6 @@ std::string community::full_url( return url; } -// returns protocol, host, port. Port can be empty; throws on unparseable values. protocol and -// host get normalized to lower-case. Port will be 0 if not present in the URL, or if set to -// the default for the protocol. The URL must not include a path (though a single optional `/` -// after the domain is accepted and ignored). -std::tuple parse_url(std::string_view url) { - std::tuple result{}; - auto& [proto, host, port] = result; - if (auto pos = url.find("://"); pos != std::string::npos) { - auto proto_name = url.substr(0, pos); - url.remove_prefix(proto_name.size() + 3); - if (string_iequal(proto_name, "http")) - proto = "http://"; - else if (string_iequal(proto_name, "https")) - proto = "https://"; - } - if (proto.empty()) - throw std::invalid_argument{"Invalid community URL: invalid/missing protocol://"}; - - bool next_allow_dot = false; - bool has_dot = false; - while (!url.empty()) { - auto c = url.front(); - if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || c == '-') { - host += c; - next_allow_dot = true; - } else if (c >= 'A' && c <= 'Z') { - host += c + ('a' - 'A'); - next_allow_dot = true; - } else if (next_allow_dot && c == '.') { - host += '.'; - has_dot = true; - next_allow_dot = false; - } else { - break; - } - url.remove_prefix(1); - } - if (host.size() < 4 || !has_dot || host.back() == '.') - throw std::invalid_argument{"Invalid community URL: invalid hostname"}; - - if (!url.empty() && url.front() == ':') { - url.remove_prefix(1); - if (auto [p, ec] = std::from_chars(url.data(), url.data() + url.size(), port); - ec == std::errc{}) - url.remove_prefix(p - url.data()); - else - throw std::invalid_argument{"Invalid community URL: invalid port"}; - if ((port == 80 && proto == "http://") || (port == 443 && proto == "https://")) - port = 0; - } - - if (!url.empty() && url.front() == '/') - url.remove_prefix(1); - - // We don't (currently) allow a /path in a community URL - if (!url.empty()) - throw std::invalid_argument{"Invalid community URL: found unexpected trailing value"}; - - return result; -} - void community::canonicalize_url(std::string& url) { if (auto new_url = canonical_url(url); new_url != url) url = std::move(new_url); @@ -170,14 +108,17 @@ void community::canonicalize_room(std::string& room) { } std::string community::canonical_url(std::string_view url) { - const auto& [proto, host, port] = parse_url(url); + const auto& [proto, host, port, path] = parse_url(url); std::string result; result += proto; result += host; - if (port != 0) { + if (port) { result += ':'; - result += std::to_string(port); + result += std::to_string(*port); } + // We don't (currently) allow a /path in a community URL + if (path) + throw std::invalid_argument{"Invalid community URL: found unexpected trailing value"}; if (result.size() > BASE_URL_MAX_LENGTH) throw std::invalid_argument{"Invalid community URL: base URL is too long"}; return result; @@ -189,9 +130,9 @@ std::string community::canonical_room(std::string_view room) { return r; } -std::tuple> community::parse_partial_url( - std::string_view url) { - std::tuple> result; +std::tuple>> +community::parse_partial_url(std::string_view url) { + std::tuple>> result; auto& [base_url, room_token, maybe_pubkey] = result; // Consume the URL from back to front; first the public key: @@ -215,7 +156,8 @@ std::tuple> community::parse_pa return result; } -std::tuple community::parse_full_url(std::string_view full_url) { +std::tuple> community::parse_full_url( + std::string_view full_url) { auto [base, rm, maybe_pk] = parse_partial_url(full_url); if (!maybe_pk) throw std::invalid_argument{"Invalid community URL: no valid server pubkey"}; @@ -230,8 +172,7 @@ LIBSESSION_C_API const size_t COMMUNITY_BASE_URL_MAX_LENGTH = LIBSESSION_C_API const size_t COMMUNITY_ROOM_MAX_LENGTH = session::config::community::ROOM_MAX_LENGTH; LIBSESSION_C_API const size_t COMMUNITY_FULL_URL_MAX_LENGTH = - COMMUNITY_BASE_URL_MAX_LENGTH + 3 /* '/r/' */ + COMMUNITY_ROOM_MAX_LENGTH + - session::config::qs_pubkey.size() + 64 /*pubkey hex*/ + 1 /*null terminator*/; + session::config::community::FULL_URL_MAX_LENGTH; LIBSESSION_C_API bool community_parse_full_url( const char* full_url, char* base_url, char* room_token, unsigned char* pubkey) { @@ -274,9 +215,8 @@ LIBSESSION_C_API bool community_parse_partial_url( LIBSESSION_C_API void community_make_full_url( const char* base_url, const char* room, const unsigned char* pubkey, char* full_url) { - auto full = - session::config::community::full_url(base_url, room, session::ustring_view{pubkey, 32}); + auto full = session::config::community::full_url( + base_url, room, std::span{pubkey, 32}); assert(full.size() <= COMMUNITY_FULL_URL_MAX_LENGTH); - size_t pos = 0; std::memcpy(full_url, full.data(), full.size() + 1); } diff --git a/src/config/contacts.cpp b/src/config/contacts.cpp index 6e973504..093c0a9c 100644 --- a/src/config/contacts.cpp +++ b/src/config/contacts.cpp @@ -14,7 +14,6 @@ using namespace std::literals; using namespace session::config; -using session::ustring_view; LIBSESSION_C_API const size_t CONTACT_MAX_NAME_LENGTH = contact_info::MAX_NAME_LENGTH; @@ -30,22 +29,6 @@ static_assert(CONVO_NOTIFY_ALL == static_cast(notify_mode::all)); static_assert(CONVO_NOTIFY_DISABLED == static_cast(notify_mode::disabled)); static_assert(CONVO_NOTIFY_MENTIONS_ONLY == static_cast(notify_mode::mentions_only)); -namespace { - -void check_session_id(std::string_view session_id) { - if (session_id.size() != 66 || !oxenc::is_hex(session_id)) - throw std::invalid_argument{ - "Invalid pubkey: expected 66 hex digits, got " + std::to_string(session_id.size()) + - " and/or not hex"}; -} - -std::string session_id_to_bytes(std::string_view session_id) { - check_session_id(session_id); - return oxenc::from_hex(session_id); -} - -} // namespace - LIBSESSION_C_API bool session_id_is_valid(const char* session_id) { return std::strlen(session_id) == 66 && oxenc::is_hex(session_id, session_id + 66); } @@ -56,8 +39,9 @@ contact_info::contact_info(std::string sid) : session_id{std::move(sid)} { void contact_info::set_name(std::string n) { if (n.size() > MAX_NAME_LENGTH) - throw std::invalid_argument{"Invalid contact name: exceeds maximum length"}; - name = std::move(n); + name = utf8_truncate(std::move(n), MAX_NAME_LENGTH); + else + name = std::move(n); } void contact_info::set_nickname(std::string n) { @@ -66,7 +50,13 @@ void contact_info::set_nickname(std::string n) { nickname = std::move(n); } -Contacts::Contacts(ustring_view ed25519_secretkey, std::optional dumped) : +void contact_info::set_nickname_truncated(std::string n) { + set_nickname(utf8_truncate(std::move(n), MAX_NAME_LENGTH)); +} + +Contacts::Contacts( + std::span ed25519_secretkey, + std::optional> dumped) : ConfigBase{dumped} { load_key(ed25519_secretkey); } @@ -85,7 +75,7 @@ void contact_info::load(const dict& info_dict) { nickname = maybe_string(info_dict, "N").value_or(""); auto url = maybe_string(info_dict, "p"); - auto key = maybe_ustring(info_dict, "q"); + auto key = maybe_vector(info_dict, "q"); if (url && key && !url->empty() && key->size() == 32) { profile_picture.url = std::move(*url); profile_picture.key = std::move(*key); @@ -107,7 +97,7 @@ void contact_info::load(const dict& info_dict) { } else { notifications = notify_mode::defaulted; } - mute_until = maybe_int(info_dict, "!").value_or(0); + mute_until = to_epoch_seconds(maybe_int(info_dict, "!").value_or(0)); int exp_mode_ = maybe_int(info_dict, "e").value_or(0); if (exp_mode_ >= static_cast(expiration_mode::none) && @@ -128,7 +118,7 @@ void contact_info::load(const dict& info_dict) { } } - created = maybe_int(info_dict, "j").value_or(0); + created = to_epoch_seconds(maybe_int(info_dict, "j").value_or(0)); } void contact_info::into(contacts_contact& c) const { @@ -146,11 +136,12 @@ void contact_info::into(contacts_contact& c) const { c.blocked = blocked; c.priority = priority; c.notifications = static_cast(notifications); + c.mute_until = to_epoch_seconds(mute_until); c.exp_mode = static_cast(exp_mode); c.exp_seconds = exp_timer.count(); if (c.exp_seconds <= 0 && c.exp_mode != CONVO_EXPIRATION_NONE) c.exp_mode = CONVO_EXPIRATION_NONE; - c.created = created; + c.created = to_epoch_seconds(created); } contact_info::contact_info(const contacts_contact& c) : session_id{c.session_id, 66} { @@ -161,18 +152,19 @@ contact_info::contact_info(const contacts_contact& c) : session_id{c.session_id, assert(std::strlen(c.profile_pic.url) <= profile_pic::MAX_URL_LENGTH); if (std::strlen(c.profile_pic.url)) { profile_picture.url = c.profile_pic.url; - profile_picture.key = {c.profile_pic.key, 32}; + profile_picture.key.assign(c.profile_pic.key, c.profile_pic.key + 32); } approved = c.approved; approved_me = c.approved_me; blocked = c.blocked; priority = c.priority; notifications = static_cast(c.notifications); + mute_until = to_epoch_seconds(c.mute_until); exp_mode = static_cast(c.exp_mode); exp_timer = exp_mode == expiration_mode::none ? 0s : std::chrono::seconds{c.exp_seconds}; if (exp_timer <= 0s && exp_mode != expiration_mode::none) exp_mode = expiration_mode::none; - created = c.created; + created = to_epoch_seconds(c.created); } std::optional Contacts::get(std::string_view pubkey_hex) const { @@ -189,17 +181,16 @@ std::optional Contacts::get(std::string_view pubkey_hex) const { LIBSESSION_C_API bool contacts_get( config_object* conf, contacts_contact* contact, const char* session_id) { - try { - conf->last_error = nullptr; - if (auto c = unbox(conf)->get(session_id)) { - c->into(*contact); - return true; - } - } catch (const std::exception& e) { - copy_c_str(conf->_error_buf, e.what()); - conf->last_error = conf->_error_buf; - } - return false; + return wrap_exceptions( + conf, + [&] { + if (auto c = unbox(conf)->get(session_id)) { + c->into(*contact); + return true; + } + return false; + }, + false); } contact_info Contacts::get_or_construct(std::string_view pubkey_hex) const { @@ -211,15 +202,13 @@ contact_info Contacts::get_or_construct(std::string_view pubkey_hex) const { LIBSESSION_C_API bool contacts_get_or_construct( config_object* conf, contacts_contact* contact, const char* session_id) { - try { - conf->last_error = nullptr; - unbox(conf)->get_or_construct(session_id).into(*contact); - return true; - } catch (const std::exception& e) { - copy_c_str(conf->_error_buf, e.what()); - conf->last_error = conf->_error_buf; - return false; - } + return wrap_exceptions( + conf, + [&] { + unbox(conf)->get_or_construct(session_id).into(*contact); + return true; + }, + false); } void Contacts::set(const contact_info& contact) { @@ -248,7 +237,7 @@ void Contacts::set(const contact_info& contact) { if (notify == notify_mode::mentions_only) notify = notify_mode::all; set_positive_int(info["@"], static_cast(notify)); - set_positive_int(info["!"], contact.mute_until); + set_positive_int(info["!"], to_epoch_seconds(contact.mute_until)); set_pair_if( contact.exp_mode != expiration_mode::none && contact.exp_timer > 0s, @@ -257,11 +246,17 @@ void Contacts::set(const contact_info& contact) { info["E"], contact.exp_timer.count()); - set_positive_int(info["j"], contact.created); + set_positive_int(info["j"], to_epoch_seconds(contact.created)); } -LIBSESSION_C_API void contacts_set(config_object* conf, const contacts_contact* contact) { - unbox(conf)->set(contact_info{*contact}); +LIBSESSION_C_API bool contacts_set(config_object* conf, const contacts_contact* contact) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set(contact_info{*contact}); + return true; + }, + false); } void Contacts::set_name(std::string_view session_id, std::string name) { @@ -274,6 +269,11 @@ void Contacts::set_nickname(std::string_view session_id, std::string nickname) { c.set_nickname(std::move(nickname)); set(c); } +void Contacts::set_nickname_truncated(std::string_view session_id, std::string nickname) { + auto c = get_or_construct(session_id); + c.set_nickname_truncated(std::move(nickname)); + set(c); +} void Contacts::set_profile_pic(std::string_view session_id, profile_pic pic) { auto c = get_or_construct(session_id); c.profile_picture = std::move(pic); @@ -317,7 +317,7 @@ void Contacts::set_expiry( void Contacts::set_created(std::string_view session_id, int64_t timestamp) { auto c = get_or_construct(session_id); - c.created = timestamp; + c.created = to_epoch_seconds(timestamp); set(c); } @@ -354,7 +354,6 @@ void Contacts::iterator::_load_info() { if (_it->first.size() == 33) { if (auto* info_dict = std::get_if(&_it->second)) { _val = std::make_shared(oxenc::to_hex(_it->first)); - auto hex = oxenc::to_hex(_it->first); _val->load(*info_dict); return; } @@ -372,6 +371,9 @@ bool Contacts::iterator::operator==(const iterator& other) const { if (!other._contacts) // other is an "end" tombstone: return whether we are at the end return _it == _contacts->end(); + if (!_contacts) + // we are an "end" tombstone: return whether the other one is at the end + return other._it == other._contacts->end(); return _it == other._it; } diff --git a/src/config/convo_info_volatile.cpp b/src/config/convo_info_volatile.cpp index 27d40b19..8d1206c7 100644 --- a/src/config/convo_info_volatile.cpp +++ b/src/config/convo_info_volatile.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -19,7 +18,6 @@ #include "session/util.hpp" using namespace std::literals; -using session::ustring_view; namespace session::config { @@ -31,7 +29,7 @@ namespace convo { one_to_one::one_to_one(std::string_view sid) : session_id{sid} { check_session_id(session_id); } - one_to_one::one_to_one(const struct convo_info_volatile_1to1& c) : + one_to_one::one_to_one(const convo_info_volatile_1to1& c) : base{c.last_read, c.unread}, session_id{c.session_id, 66} {} void one_to_one::into(convo_info_volatile_1to1& c) const { @@ -41,7 +39,7 @@ namespace convo { } community::community(const convo_info_volatile_community& c) : - config::community{c.base_url, c.room, ustring_view{c.pubkey, 32}}, + config::community{c.base_url, c.room, std::span{c.pubkey, 32}}, base{c.last_read, c.unread} {} void community::into(convo_info_volatile_community& c) const { @@ -54,13 +52,28 @@ namespace convo { c.unread = unread; } + group::group(std::string&& cgid) : id{std::move(cgid)} { + check_session_id(id, "03"); + } + group::group(std::string_view cgid) : id{cgid} { + check_session_id(id, "03"); + } + group::group(const convo_info_volatile_group& c) : + base{c.last_read, c.unread}, id{c.group_id, 66} {} + + void group::into(convo_info_volatile_group& c) const { + std::memcpy(c.group_id, id.c_str(), 67); + c.last_read = last_read; + c.unread = unread; + } + legacy_group::legacy_group(std::string&& cgid) : id{std::move(cgid)} { check_session_id(id); } legacy_group::legacy_group(std::string_view cgid) : id{cgid} { check_session_id(id); } - legacy_group::legacy_group(const struct convo_info_volatile_legacy_group& c) : + legacy_group::legacy_group(const convo_info_volatile_legacy_group& c) : base{c.last_read, c.unread}, id{c.group_id, 66} {} void legacy_group::into(convo_info_volatile_legacy_group& c) const { @@ -77,7 +90,8 @@ namespace convo { } // namespace convo ConvoInfoVolatile::ConvoInfoVolatile( - ustring_view ed25519_secretkey, std::optional dumped) : + std::span ed25519_secretkey, + std::optional> dumped) : ConfigBase{dumped} { load_key(ed25519_secretkey); } @@ -102,13 +116,12 @@ convo::one_to_one ConvoInfoVolatile::get_or_construct_1to1(std::string_view pubk } ConfigBase::DictFieldProxy ConvoInfoVolatile::community_field( - const convo::community& comm, ustring_view* get_pubkey) const { + const convo::community& comm, std::span* get_pubkey) const { auto record = data["o"][comm.base_url()]; if (get_pubkey) { auto pkrec = record["#"]; if (auto pk = pkrec.string_view_or(""); pk.size() == 32) - *get_pubkey = - ustring_view{reinterpret_cast(pk.data()), pk.size()}; + *get_pubkey = to_span(pk); } return record["R"][comm.room_norm()]; } @@ -117,7 +130,7 @@ std::optional ConvoInfoVolatile::get_community( std::string_view base_url, std::string_view room) const { convo::community og{base_url, community::canonical_room(room)}; - ustring_view pubkey; + std::span pubkey; if (auto* info_dict = community_field(og, &pubkey).dict()) { og.load(*info_dict); if (!pubkey.empty()) @@ -134,7 +147,9 @@ std::optional ConvoInfoVolatile::get_community( } convo::community ConvoInfoVolatile::get_or_construct_community( - std::string_view base_url, std::string_view room, ustring_view pubkey) const { + std::string_view base_url, + std::string_view room, + std::span pubkey) const { convo::community result{base_url, community::canonical_room(room), pubkey}; if (auto* info_dict = community_field(result).dict()) @@ -158,6 +173,25 @@ convo::community ConvoInfoVolatile::get_or_construct_community( return result; } +std::optional ConvoInfoVolatile::get_group(std::string_view pubkey_hex) const { + std::string pubkey = session_id_to_bytes(pubkey_hex, "03"); + + auto* info_dict = data["g"][pubkey].dict(); + if (!info_dict) + return std::nullopt; + + auto result = std::make_optional(std::string{pubkey_hex}); + result->load(*info_dict); + return result; +} + +convo::group ConvoInfoVolatile::get_or_construct_group(std::string_view pubkey_hex) const { + if (auto maybe = get_group(pubkey_hex)) + return *std::move(maybe); + + return convo::group{std::string{pubkey_hex}}; +} + std::optional ConvoInfoVolatile::get_legacy_group( std::string_view pubkey_hex) const { std::string pubkey = session_id_to_bytes(pubkey_hex); @@ -201,10 +235,9 @@ void ConvoInfoVolatile::set_base(const convo::base& c, DictFieldProxy& info) { } void ConvoInfoVolatile::prune_stale(std::chrono::milliseconds prune) { - const int64_t cutoff = - std::chrono::duration_cast( - (std::chrono::system_clock::now() - PRUNE_HIGH).time_since_epoch()) - .count(); + const int64_t cutoff = std::chrono::duration_cast( + (std::chrono::system_clock::now() - prune).time_since_epoch()) + .count(); std::vector stale; for (auto it = begin_1to1(); it != end(); ++it) @@ -228,7 +261,8 @@ void ConvoInfoVolatile::prune_stale(std::chrono::milliseconds prune) { erase_community(base, room); } -std::tuple> ConvoInfoVolatile::push() { +std::tuple>, std::vector> +ConvoInfoVolatile::push() { // Prune off any conversations with last_read timestamps more than PRUNE_HIGH ago (unless they // also have a `unread` flag set, in which case we keep them indefinitely). prune_stale(); @@ -242,6 +276,11 @@ void ConvoInfoVolatile::set(const convo::community& c) { set_base(c, info); } +void ConvoInfoVolatile::set(const convo::group& c) { + auto info = data["g"][session_id_to_bytes(c.id, "03")]; + set_base(c, info); +} + void ConvoInfoVolatile::set(const convo::legacy_group& c) { auto info = data["C"][session_id_to_bytes(c.id)]; set_base(c, info); @@ -270,12 +309,15 @@ bool ConvoInfoVolatile::erase(const convo::community& c) { } return gone; } +bool ConvoInfoVolatile::erase(const convo::group& c) { + return erase_impl(data["g"][session_id_to_bytes(c.id, "03")]); +} bool ConvoInfoVolatile::erase(const convo::legacy_group& c) { return erase_impl(data["C"][session_id_to_bytes(c.id)]); } bool ConvoInfoVolatile::erase(const convo::any& c) { - return var::visit([this](const auto& c) { return erase(c); }, c); + return std::visit([this](const auto& c) { return erase(c); }, c); } bool ConvoInfoVolatile::erase_1to1(std::string_view session_id) { return erase(convo::one_to_one{session_id}); @@ -283,6 +325,9 @@ bool ConvoInfoVolatile::erase_1to1(std::string_view session_id) { bool ConvoInfoVolatile::erase_community(std::string_view base_url, std::string_view room) { return erase(convo::community{base_url, room}); } +bool ConvoInfoVolatile::erase_group(std::string_view id) { + return erase(convo::group{id}); +} bool ConvoInfoVolatile::erase_legacy_group(std::string_view id) { return erase(convo::legacy_group{id}); } @@ -309,6 +354,12 @@ size_t ConvoInfoVolatile::size_communities() const { return count; } +size_t ConvoInfoVolatile::size_groups() const { + if (auto* d = data["g"].dict()) + return d->size(); + return 0; +} + size_t ConvoInfoVolatile::size_legacy_groups() const { if (auto* d = data["C"].dict()) return d->size(); @@ -316,11 +367,11 @@ size_t ConvoInfoVolatile::size_legacy_groups() const { } size_t ConvoInfoVolatile::size() const { - return size_1to1() + size_communities() + size_legacy_groups(); + return size_1to1() + size_communities() + size_legacy_groups() + size_groups(); } ConvoInfoVolatile::iterator::iterator( - const DictFieldRoot& data, bool oneto1, bool communities, bool legacy_groups) { + const DictFieldRoot& data, bool oneto1, bool communities, bool groups, bool legacy_groups) { if (oneto1) if (auto* d = data["1"].dict()) { _it_11 = d->begin(); @@ -329,6 +380,11 @@ ConvoInfoVolatile::iterator::iterator( if (communities) if (auto* d = data["o"].dict()) _it_comm.emplace(d->begin(), d->end()); + if (groups) + if (auto* d = data["g"].dict()) { + _it_group = d->begin(); + _end_group = d->end(); + } if (legacy_groups) if (auto* d = data["C"].dict()) { _it_lgroup = d->begin(); @@ -337,34 +393,51 @@ ConvoInfoVolatile::iterator::iterator( _load_val(); } +class val_loader { + public: + template + static bool load( + std::shared_ptr& val, + std::optional& it, + std::optional& end, + char prefix) { + while (it) { + if (*it == *end) { + it.reset(); + end.reset(); + return false; + } + + auto& [k, v] = **it; + + if (k.size() == 33 && k[0] == prefix) { + if (auto* info_dict = std::get_if(&v)) { + val = std::make_shared(ConvoType{oxenc::to_hex(k)}); + std::get(*val).load(*info_dict); + return true; + } + } + ++*it; + } + return false; + } +}; + /// Load _val from the current iterator position; if it is invalid, skip to the next key until we -/// find one that is valid (or hit the end). We also span across three different iterators: first -/// we exhaust _it_11, then _it_comm, then _it_lgroup. +/// find one that is valid (or hit the end). We also span across four different iterators: we +/// exhaust, in order: _it_11, _it_group, _it_comm, _it_lgroup. /// /// We *always* call this after incrementing the iterator (and after iterator initialization), and -/// this is responsible for making sure that _it_11, _it_comm, etc. are only set to non-nullopt if +/// this is responsible for making sure that _it_11, _it_group, etc. are only set to non-nullopt if /// the respective sub-iterator is *not* at the end (and resetting them when we hit the end). Thus, /// after calling this, our "end" condition will be simply that all of the three iterators are /// nullopt. void ConvoInfoVolatile::iterator::_load_val() { - while (_it_11) { - if (*_it_11 == *_end_11) { - _it_11.reset(); - _end_11.reset(); - break; - } + if (val_loader::load(_val, _it_11, _end_11, 0x05)) + return; - auto& [k, v] = **_it_11; - - if (k.size() == 33 && k[0] == 0x05) { - if (auto* info_dict = std::get_if(&v)) { - _val = std::make_shared(convo::one_to_one{oxenc::to_hex(k)}); - std::get(*_val).load(*info_dict); - return; - } - } - ++*_it_11; - } + if (val_loader::load(_val, _it_group, _end_group, 0x03)) + return; if (_it_comm) { if (_it_comm->load(_val)) @@ -373,37 +446,24 @@ void ConvoInfoVolatile::iterator::_load_val() { _it_comm.reset(); } - while (_it_lgroup) { - if (*_it_lgroup == *_end_lgroup) { - _it_lgroup.reset(); - _end_lgroup.reset(); - break; - } - - auto& [k, v] = **_it_lgroup; - - if (k.size() == 33 && k[0] == 0x05) { - if (auto* info_dict = std::get_if(&v)) { - _val = std::make_shared(convo::legacy_group{oxenc::to_hex(k)}); - std::get(*_val).load(*info_dict); - return; - } - } - ++*_it_lgroup; - } + if (val_loader::load(_val, _it_lgroup, _end_lgroup, 0x05)) + return; } bool ConvoInfoVolatile::iterator::operator==(const iterator& other) const { - return _it_11 == other._it_11 && _it_comm == other._it_comm && _it_lgroup == other._it_lgroup; + return _it_11 == other._it_11 && _it_group == other._it_group && _it_comm == other._it_comm && + _it_lgroup == other._it_lgroup; } bool ConvoInfoVolatile::iterator::done() const { - return !_it_11 && (!_it_comm || _it_comm->done()) && !_it_lgroup; + return !_it_11 && !_it_group && (!_it_comm || _it_comm->done()) && !_it_lgroup; } ConvoInfoVolatile::iterator& ConvoInfoVolatile::iterator::operator++() { if (_it_11) ++*_it_11; + else if (_it_group) + ++*_it_group; else if (_it_comm && !_it_comm->done()) _it_comm->advance(); else { @@ -437,30 +497,27 @@ int convo_info_volatile_init( LIBSESSION_C_API bool convo_info_volatile_get_1to1( config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) { - try { - conf->last_error = nullptr; - if (auto c = unbox(conf)->get_1to1(session_id)) { - c->into(*convo); - return true; - } - } catch (const std::exception& e) { - copy_c_str(conf->_error_buf, e.what()); - conf->last_error = conf->_error_buf; - } - return false; + return wrap_exceptions( + conf, + [&] { + if (auto c = unbox(conf)->get_1to1(session_id)) { + c->into(*convo); + return true; + } + return false; + }, + false); } LIBSESSION_C_API bool convo_info_volatile_get_or_construct_1to1( config_object* conf, convo_info_volatile_1to1* convo, const char* session_id) { - try { - conf->last_error = nullptr; - unbox(conf)->get_or_construct_1to1(session_id).into(*convo); - return true; - } catch (const std::exception& e) { - copy_c_str(conf->_error_buf, e.what()); - conf->last_error = conf->_error_buf; - return false; - } + return wrap_exceptions( + conf, + [&] { + unbox(conf)->get_or_construct_1to1(session_id).into(*convo); + return true; + }, + false); } LIBSESSION_C_API bool convo_info_volatile_get_community( @@ -468,17 +525,16 @@ LIBSESSION_C_API bool convo_info_volatile_get_community( convo_info_volatile_community* og, const char* base_url, const char* room) { - try { - conf->last_error = nullptr; - if (auto c = unbox(conf)->get_community(base_url, room)) { - c->into(*og); - return true; - } - } catch (const std::exception& e) { - copy_c_str(conf->_error_buf, e.what()); - conf->last_error = conf->_error_buf; - } - return false; + return wrap_exceptions( + conf, + [&] { + if (auto c = unbox(conf)->get_community(base_url, room)) { + c->into(*og); + return true; + } + return false; + }, + false); } LIBSESSION_C_API bool convo_info_volatile_get_or_construct_community( config_object* conf, @@ -486,82 +542,130 @@ LIBSESSION_C_API bool convo_info_volatile_get_or_construct_community( const char* base_url, const char* room, unsigned const char* pubkey) { - try { - conf->last_error = nullptr; - unbox(conf) - ->get_or_construct_community(base_url, room, ustring_view{pubkey, 32}) - .into(*convo); - return true; - } catch (const std::exception& e) { - copy_c_str(conf->_error_buf, e.what()); - conf->last_error = conf->_error_buf; - return false; - } + return wrap_exceptions( + conf, + [&] { + unbox(conf) + ->get_or_construct_community( + base_url, room, std::span{pubkey, 32}) + .into(*convo); + return true; + }, + false); +} + +LIBSESSION_C_API bool convo_info_volatile_get_group( + config_object* conf, convo_info_volatile_group* convo, const char* id) { + return wrap_exceptions( + conf, + [&] { + if (auto c = unbox(conf)->get_group(id)) { + c->into(*convo); + return true; + } + return false; + }, + false); +} + +LIBSESSION_C_API bool convo_info_volatile_get_or_construct_group( + config_object* conf, convo_info_volatile_group* convo, const char* id) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->get_or_construct_group(id).into(*convo); + return true; + }, + false); } LIBSESSION_C_API bool convo_info_volatile_get_legacy_group( config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) { - try { - conf->last_error = nullptr; - if (auto c = unbox(conf)->get_legacy_group(id)) { - c->into(*convo); - return true; - } - } catch (const std::exception& e) { - copy_c_str(conf->_error_buf, e.what()); - conf->last_error = conf->_error_buf; - } - return false; + return wrap_exceptions( + conf, + [&] { + if (auto c = unbox(conf)->get_legacy_group(id)) { + c->into(*convo); + return true; + } + return false; + }, + false); } LIBSESSION_C_API bool convo_info_volatile_get_or_construct_legacy_group( config_object* conf, convo_info_volatile_legacy_group* convo, const char* id) { - try { - conf->last_error = nullptr; - unbox(conf)->get_or_construct_legacy_group(id).into(*convo); - return true; - } catch (const std::exception& e) { - copy_c_str(conf->_error_buf, e.what()); - conf->last_error = conf->_error_buf; - return false; - } + return wrap_exceptions( + conf, + [&] { + unbox(conf)->get_or_construct_legacy_group(id).into(*convo); + return true; + }, + false); } -LIBSESSION_C_API void convo_info_volatile_set_1to1( +LIBSESSION_C_API bool convo_info_volatile_set_1to1( config_object* conf, const convo_info_volatile_1to1* convo) { - unbox(conf)->set(convo::one_to_one{*convo}); -} -LIBSESSION_C_API void convo_info_volatile_set_community( + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set(convo::one_to_one{*convo}); + return true; + }, + false); +} +LIBSESSION_C_API bool convo_info_volatile_set_community( config_object* conf, const convo_info_volatile_community* convo) { - unbox(conf)->set(convo::community{*convo}); -} -LIBSESSION_C_API void convo_info_volatile_set_legacy_group( + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set(convo::community{*convo}); + return true; + }, + false); +} +LIBSESSION_C_API bool convo_info_volatile_set_group( + config_object* conf, const convo_info_volatile_group* convo) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set(convo::group{*convo}); + return true; + }, + false); +} +LIBSESSION_C_API bool convo_info_volatile_set_legacy_group( config_object* conf, const convo_info_volatile_legacy_group* convo) { - unbox(conf)->set(convo::legacy_group{*convo}); + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set(convo::legacy_group{*convo}); + return true; + }, + false); } LIBSESSION_C_API bool convo_info_volatile_erase_1to1(config_object* conf, const char* session_id) { - try { - return unbox(conf)->erase_1to1(session_id); - } catch (...) { - return false; - } + return wrap_exceptions( + conf, [&] { return unbox(conf)->erase_1to1(session_id); }, false); } LIBSESSION_C_API bool convo_info_volatile_erase_community( config_object* conf, const char* base_url, const char* room) { - try { - return unbox(conf)->erase_community(base_url, room); - } catch (...) { - return false; - } + return wrap_exceptions( + conf, + [&] { return unbox(conf)->erase_community(base_url, room); }, + false); +} +LIBSESSION_C_API bool convo_info_volatile_erase_group(config_object* conf, const char* group_id) { + return wrap_exceptions( + conf, [&] { return unbox(conf)->erase_group(group_id); }, false); } LIBSESSION_C_API bool convo_info_volatile_erase_legacy_group( config_object* conf, const char* group_id) { - try { - return unbox(conf)->erase_legacy_group(group_id); - } catch (...) { - return false; - } + return wrap_exceptions( + conf, + [&] { return unbox(conf)->erase_legacy_group(group_id); }, + false); } LIBSESSION_C_API size_t convo_info_volatile_size(const config_object* conf) { @@ -573,6 +677,9 @@ LIBSESSION_C_API size_t convo_info_volatile_size_1to1(const config_object* conf) LIBSESSION_C_API size_t convo_info_volatile_size_communities(const config_object* conf) { return unbox(conf)->size_communities(); } +LIBSESSION_C_API size_t convo_info_volatile_size_groups(const config_object* conf) { + return unbox(conf)->size_groups(); +} LIBSESSION_C_API size_t convo_info_volatile_size_legacy_groups(const config_object* conf) { return unbox(conf)->size_legacy_groups(); } @@ -597,6 +704,13 @@ LIBSESSION_C_API convo_info_volatile_iterator* convo_info_volatile_iterator_new_ new ConvoInfoVolatile::iterator{unbox(conf)->begin_communities()}; return it; } +LIBSESSION_C_API convo_info_volatile_iterator* convo_info_volatile_iterator_new_groups( + const config_object* conf) { + auto* it = new convo_info_volatile_iterator{}; + it->_internals = + new ConvoInfoVolatile::iterator{unbox(conf)->begin_groups()}; + return it; +} LIBSESSION_C_API convo_info_volatile_iterator* convo_info_volatile_iterator_new_legacy_groups( const config_object* conf) { auto* it = new convo_info_volatile_iterator{}; @@ -641,6 +755,11 @@ LIBSESSION_C_API bool convo_info_volatile_it_is_community( return convo_info_volatile_it_is_impl(it, c); } +LIBSESSION_C_API bool convo_info_volatile_it_is_group( + convo_info_volatile_iterator* it, convo_info_volatile_group* c) { + return convo_info_volatile_it_is_impl(it, c); +} + LIBSESSION_C_API bool convo_info_volatile_it_is_legacy_group( convo_info_volatile_iterator* it, convo_info_volatile_legacy_group* c) { return convo_info_volatile_it_is_impl(it, c); diff --git a/src/config/encrypt.cpp b/src/config/encrypt.cpp index 0694fa09..23a87cd9 100644 --- a/src/config/encrypt.cpp +++ b/src/config/encrypt.cpp @@ -8,30 +8,27 @@ #include #include "session/export.h" +#include "session/util.hpp" using namespace std::literals; namespace session::config { -namespace { +// namespace { - // Helper function to go from char pointers to the unsigned char pointers sodium needs: - const unsigned char* to_unsigned(const char* x) { - return reinterpret_cast(x); - } - - ustring_view to_unsigned_sv(std::string_view v) { - return {to_unsigned(v.data()), v.size()}; - } +// // Helper function to go from char pointers to the unsigned char pointers sodium needs: +// const unsigned char* to_unsigned(const char* x) { +// return reinterpret_cast(x); +// } -} // namespace +// } // namespace static constexpr size_t DOMAIN_MAX_SIZE = 24; static constexpr auto NONCE_KEY_PREFIX = "libsessionutil-config-encrypted-"sv; static_assert(NONCE_KEY_PREFIX.size() + DOMAIN_MAX_SIZE < crypto_generichash_blake2b_KEYBYTES_MAX); static std::array make_encrypt_key( - ustring_view key_base, uint64_t message_size, std::string_view domain) { + std::span key_base, uint64_t message_size, std::string_view domain) { if (key_base.size() != 32) throw std::invalid_argument{"encrypt called with key_base != 32 bytes"}; if (domain.size() < 1 || domain.size() > DOMAIN_MAX_SIZE) @@ -40,7 +37,8 @@ static std::array ma // We hash the key because we're using a deterministic nonce: the `key_base` value is expected // to be a long-term value for which nonce reuse (via hash collision) would be bad: by // incorporating the domain and message size we at least vary the key to further restrict the - // nonce reuse concern to messages of identical sizes and identical domain. + // nonce reuse concern so that you would not only have to hash collide but also have it happen + // on messages of identical sizes and identical domain. std::array key{0}; crypto_generichash_blake2b_state state; crypto_generichash_blake2b_init(&state, nullptr, 0, key.size()); @@ -53,16 +51,20 @@ static std::array ma return key; } -ustring encrypt(ustring_view message, ustring_view key_base, std::string_view domain) { - ustring msg; - msg.reserve( - message.size() + crypto_aead_xchacha20poly1305_ietf_ABYTES + - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); - msg.assign(message); +std::vector encrypt( + std::span message, + std::span key_base, + std::string_view domain) { + std::vector msg; + msg.reserve(message.size() + ENCRYPT_DATA_OVERHEAD); + msg.assign(message.begin(), message.end()); encrypt_inplace(msg, key_base, domain); return msg; } -void encrypt_inplace(ustring& message, ustring_view key_base, std::string_view domain) { +void encrypt_inplace( + std::vector& message, + std::span key_base, + std::string_view domain) { auto key = make_encrypt_key(key_base, message.size(), domain); std::string nonce_key{NONCE_KEY_PREFIX}; @@ -78,9 +80,7 @@ void encrypt_inplace(ustring& message, ustring_view key_base, std::string_view d nonce_key.size()); size_t plaintext_len = message.size(); - message.resize( - plaintext_len + crypto_aead_xchacha20poly1305_ietf_ABYTES + - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + message.resize(plaintext_len + ENCRYPT_DATA_OVERHEAD); unsigned long long outlen = 0; crypto_aead_xchacha20poly1305_ietf_encrypt( @@ -102,18 +102,24 @@ static_assert( ENCRYPT_DATA_OVERHEAD == crypto_aead_xchacha20poly1305_IETF_ABYTES + crypto_aead_xchacha20poly1305_IETF_NPUBBYTES); -ustring decrypt(ustring_view ciphertext, ustring_view key_base, std::string_view domain) { - ustring x{ciphertext}; +std::vector decrypt( + std::span ciphertext, + std::span key_base, + std::string_view domain) { + std::vector x = session::to_vector(ciphertext); decrypt_inplace(x, key_base, domain); return x; } -void decrypt_inplace(ustring& ciphertext, ustring_view key_base, std::string_view domain) { - size_t message_len = ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES - - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES; + +void decrypt_inplace( + std::vector& ciphertext, + std::span key_base, + std::string_view domain) { + size_t message_len = ciphertext.size() - ENCRYPT_DATA_OVERHEAD; if (message_len > ciphertext.size()) // overflow throw decrypt_error{"Decryption failed: ciphertext is too short"}; - ustring_view nonce = ustring_view{ciphertext}.substr( + std::span nonce = std::span{ciphertext}.subspan( ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); auto key = make_encrypt_key(key_base, message_len, domain); @@ -134,18 +140,16 @@ void decrypt_inplace(ustring& ciphertext, ustring_view key_base, std::string_vie ciphertext.resize(mlen_wrote); } -void pad_message(ustring& data, size_t overhead) { +void pad_message(std::vector& data, size_t overhead) { size_t target_size = padded_size(data.size(), overhead); if (target_size > data.size()) - data.insert(0, target_size - data.size(), '\0'); + data.insert(data.begin(), target_size - data.size(), 0); } } // namespace session::config extern "C" { -using session::ustring; - LIBSESSION_EXPORT unsigned char* config_encrypt( const unsigned char* plaintext, size_t len, @@ -153,7 +157,7 @@ LIBSESSION_EXPORT unsigned char* config_encrypt( const char* domain, size_t* ciphertext_size) { - ustring ciphertext; + std::vector ciphertext; try { ciphertext = session::config::encrypt({plaintext, len}, {key_base, 32}, domain); } catch (...) { @@ -173,7 +177,7 @@ LIBSESSION_EXPORT unsigned char* config_decrypt( const char* domain, size_t* plaintext_size) { - ustring plaintext; + std::vector plaintext; try { plaintext = session::config::decrypt({ciphertext, clen}, {key_base, 32}, domain); } catch (const std::exception& e) { diff --git a/src/config/groups/info.cpp b/src/config/groups/info.cpp new file mode 100644 index 00000000..2025ca0a --- /dev/null +++ b/src/config/groups/info.cpp @@ -0,0 +1,398 @@ +#include "session/config/groups/info.hpp" + +#include +#include + +#include + +#include "../internal.hpp" +#include "session/config/error.h" +#include "session/config/groups/info.h" +#include "session/export.h" +#include "session/types.hpp" +#include "session/util.hpp" + +using namespace std::literals; + +namespace session::config::groups { + +Info::Info( + std::span ed25519_pubkey, + std::optional> ed25519_secretkey, + std::optional> dumped) : + ConfigBase{dumped, ed25519_pubkey, ed25519_secretkey}, + id{"03" + oxenc::to_hex(ed25519_pubkey.begin(), ed25519_pubkey.end())} {} + +std::optional Info::get_name() const { + if (auto* s = data["n"].string(); s && !s->empty()) + return *s; + return std::nullopt; +} + +void Info::set_name(std::string_view new_name) { + if (new_name.size() > NAME_MAX_LENGTH) + throw std::invalid_argument{"Invalid group name: exceeds maximum length"}; + set_nonempty_str(data["n"], new_name); +} + +void Info::set_name_truncated(std::string new_name) { + set_name(utf8_truncate(std::move(new_name), NAME_MAX_LENGTH)); +} + +std::optional Info::get_description() const { + if (auto* s = data["o"].string(); s && !s->empty()) + return *s; + return std::nullopt; +} + +void Info::set_description(std::string_view new_desc) { + if (new_desc.size() > DESCRIPTION_MAX_LENGTH) + throw std::invalid_argument{"Invalid group description: exceeds maximum length"}; + set_nonempty_str(data["o"], new_desc); +} + +void Info::set_description_truncated(std::string new_desc) { + set_description(utf8_truncate(std::move(new_desc), DESCRIPTION_MAX_LENGTH)); +} + +profile_pic Info::get_profile_pic() const { + profile_pic pic{}; + if (auto* url = data["p"].string(); url && !url->empty()) + pic.url = *url; + if (auto* key = data["q"].string(); key && key->size() == 32) + pic.key.assign( + reinterpret_cast(key->data()), + reinterpret_cast(key->data()) + 32); + return pic; +} + +void Info::set_profile_pic(std::string_view url, std::span key) { + set_pair_if(!url.empty() && key.size() == 32, data["p"], url, data["q"], key); +} + +void Info::set_profile_pic(profile_pic pic) { + set_profile_pic(pic.url, pic.key); +} + +std::optional Info::get_expiry_timer() const { + if (auto exp = data["E"].integer()) + return *exp * 1s; + return std::nullopt; +} + +void Info::set_expiry_timer(std::chrono::seconds expiration_timer) { + set_positive_int(data["E"], expiration_timer.count()); +} + +void Info::set_created(int64_t timestamp) { + set_positive_int(data["c"], to_epoch_seconds(timestamp)); +} + +std::optional Info::get_created() const { + if (auto* ts = data["c"].integer()) + return to_epoch_seconds(*ts); + return std::nullopt; +} + +void Info::set_delete_before(int64_t timestamp) { + set_positive_int(data["d"], to_epoch_seconds(timestamp)); +} + +std::optional Info::get_delete_before() const { + if (auto* ts = data["d"].integer()) + return to_epoch_seconds(*ts); + return std::nullopt; +} + +void Info::set_delete_attach_before(int64_t timestamp) { + set_positive_int(data["D"], to_epoch_seconds(timestamp)); +} + +std::optional Info::get_delete_attach_before() const { + if (auto* ts = data["D"].integer()) + return to_epoch_seconds(*ts); + return std::nullopt; +} + +void Info::destroy_group() { + set_flag(data["!"], true); +} + +bool Info::is_destroyed() const { + if (auto* ts = data["!"].integer(); ts && *ts > 0) + return true; + return false; +} + +} // namespace session::config::groups + +using namespace session; +using namespace session::config; + +LIBSESSION_C_API const size_t GROUP_INFO_NAME_MAX_LENGTH = groups::Info::NAME_MAX_LENGTH; +LIBSESSION_C_API const size_t GROUP_INFO_DESCRIPTION_MAX_LENGTH = + groups::Info::DESCRIPTION_MAX_LENGTH; + +LIBSESSION_C_API int groups_info_init( + config_object** conf, + const unsigned char* ed25519_pubkey, + const unsigned char* ed25519_secretkey, + const unsigned char* dump, + size_t dumplen, + char* error) { + return c_group_wrapper_init( + conf, ed25519_pubkey, ed25519_secretkey, dump, dumplen, error); +} + +/// API: groups_info/groups_info_get_name +/// +/// Returns a pointer to the currently-set name (null-terminated), or NULL if there is no name at +/// all. Should be copied right away as the pointer may not remain valid beyond other API calls. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `char*` -- Pointer to the currently-set name as a null-terminated string, or NULL if there is +/// no name +LIBSESSION_C_API const char* groups_info_get_name(const config_object* conf) { + if (auto s = unbox(conf)->get_name()) + return s->data(); + return nullptr; +} + +/// API: groups_info/groups_info_set_name +/// +/// Sets the group's name to the null-terminated C string. Returns 0 on success, non-zero on +/// error (and sets the config_object's error string). +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `name` -- [in] Pointer to the name as a null-terminated C string +/// +/// Outputs: +/// - `int` -- Returns 0 on success, non-zero on error +LIBSESSION_C_API int groups_info_set_name(config_object* conf, const char* name) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set_name(name); + return 0; + }, + static_cast(SESSION_ERR_BAD_VALUE)); +} + +/// API: groups_info/groups_info_get_description +/// +/// Returns a pointer to the currently-set description (null-terminated), or NULL if there is +/// no description at all. Should be copied right away as the pointer may not remain valid +/// beyond other API calls. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `char*` -- Pointer to the currently-set description as a null-terminated string, or NULL +/// if there is no description +LIBSESSION_C_API const char* groups_info_get_description(const config_object* conf) { + if (auto s = unbox(conf)->get_description()) + return s->data(); + return nullptr; +} + +/// API: groups_info/groups_info_set_description +/// +/// Sets the group's description to the null-terminated C string. Returns 0 on success, non-zero on +/// error (and sets the config_object's error string). +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `description` -- [in] Pointer to the description as a null-terminated C string +/// +/// Outputs: +/// - `int` -- Returns 0 on success, non-zero on error +LIBSESSION_C_API int groups_info_set_description(config_object* conf, const char* description) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set_description(description); + return 0; + }, + static_cast(SESSION_ERR_BAD_VALUE)); +} + +/// API: groups_info/groups_info_get_pic +/// +/// Obtains the current profile pic. The pointers in the returned struct will be NULL if a profile +/// pic is not currently set, and otherwise should be copied right away (they will not be valid +/// beyond other API calls on this config object). +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `user_profile_pic` -- Pointer to the currently-set profile pic (despite the "user_profile" in +/// the struct name, this is the group's profile pic). +LIBSESSION_C_API user_profile_pic groups_info_get_pic(const config_object* conf) { + user_profile_pic p; + if (auto pic = unbox(conf)->get_profile_pic(); pic) { + copy_c_str(p.url, pic.url); + std::memcpy(p.key, pic.key.data(), 32); + } else { + p.url[0] = 0; + } + return p; +} + +/// API: groups_info/groups_info_set_pic +/// +/// Sets a user profile +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `pic` -- [in] Pointer to the pic +/// +/// Outputs: +/// - `int` -- Returns 0 on success, non-zero on error +LIBSESSION_C_API int groups_info_set_pic(config_object* conf, user_profile_pic pic) { + std::string_view url{pic.url}; + std::span key; + if (!url.empty()) + key = {pic.key, 32}; + + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set_profile_pic(url, key); + return 0; + }, + static_cast(SESSION_ERR_BAD_VALUE)); +} + +/// API: groups_info/groups_info_get_expiry_timer +/// +/// Gets the group's message expiry timer (seconds). Returns 0 if not set. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `int` -- Returns the expiry timer in seconds. Returns 0 if not set +LIBSESSION_C_API int groups_info_get_expiry_timer(const config_object* conf) { + if (auto t = unbox(conf)->get_expiry_timer(); t && *t > 0s) + return t->count(); + return 0; +} + +/// API: groups_info/groups_info_set_expiry_timer +/// +/// Sets the group's message expiry timer (seconds). Setting 0 (or negative) will clear the current +/// timer. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `expiry` -- [in] Integer of the expiry timer in seconds +LIBSESSION_C_API void groups_info_set_expiry_timer(config_object* conf, int expiry) { + unbox(conf)->set_expiry_timer(std::max(0, expiry) * 1s); +} + +/// API: groups_info/groups_info_get_created +/// +/// Returns the timestamp (unix time, in seconds) when the group was created. Returns 0 if unset. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `int64_t` -- Unix timestamp when the group was created (if set by an admin). +LIBSESSION_C_API int64_t groups_info_get_created(const config_object* conf) { + return unbox(conf)->get_created().value_or(0); +} + +/// API: groups_info/groups_info_set_created +/// +/// Sets the creation time (unix timestamp, in seconds) when the group was created. Setting 0 +/// clears the value. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `ts` -- [in] the unix timestamp, or 0 to clear a current value. +LIBSESSION_C_API void groups_info_set_created(config_object* conf, int64_t ts) { + unbox(conf)->set_created(std::max(0, ts)); +} + +/// API: groups_info/groups_info_get_delete_before +/// +/// Returns the delete-before timestamp (unix time, in seconds); clients should deleted all messages +/// from the group with timestamps earlier than this value, if set. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `int64_t` -- Unix timestamp before which messages should be deleted. Returns 0 if not set. +LIBSESSION_C_API int64_t groups_info_get_delete_before(const config_object* conf) { + return unbox(conf)->get_delete_before().value_or(0); +} + +/// API: groups_info/groups_info_set_delete_before +/// +/// Sets the delete-before time (unix timestamp, in seconds) before which messages should be delete. +/// Setting 0 clears the value. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `ts` -- [in] the unix timestamp, or 0 to clear a current value. +LIBSESSION_C_API void groups_info_set_delete_before(config_object* conf, int64_t ts) { + unbox(conf)->set_delete_before(std::max(0, ts)); +} + +/// API: groups_info/groups_info_get_attach_delete_before +/// +/// Returns the delete-before timestamp (unix time, in seconds) for attachments; clients should drop +/// all attachments from messages from the group with timestamps earlier than this value, if set. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `int64_t` -- Unix timestamp before which messages should be deleted. Returns 0 if not set. +LIBSESSION_C_API int64_t groups_info_get_attach_delete_before(const config_object* conf) { + return unbox(conf)->get_delete_attach_before().value_or(0); +} + +/// API: groups_info/groups_info_set_attach_delete_before +/// +/// Sets the delete-before time (unix timestamp, in seconds) for attachments; attachments should be +/// dropped from messages older than this value. Setting 0 clears the value. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `ts` -- [in] the unix timestamp, or 0 to clear a current value. +LIBSESSION_C_API void groups_info_set_attach_delete_before(config_object* conf, int64_t ts) { + unbox(conf)->set_delete_attach_before(std::max(0, ts)); +} + +/// API: groups_info/groups_info_is_destroyed(const config_object* conf); +/// +/// Returns true if this group has been marked destroyed by an admin, which indicates to a receiving +/// client that they should destroy it locally. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `true` if the group has been nuked, `false` otherwise. +LIBSESSION_C_API bool groups_info_is_destroyed(const config_object* conf) { + return unbox(conf)->is_destroyed(); +} + +/// API: groups_info/groups_info_destroy_group(const config_object* conf); +/// +/// Nukes a group from orbit. This is permanent (i.e. there is no removing this setting once set). +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +LIBSESSION_C_API void groups_info_destroy_group(config_object* conf) { + unbox(conf)->destroy_group(); +} diff --git a/src/config/groups/keys.cpp b/src/config/groups/keys.cpp new file mode 100644 index 00000000..2fe6a156 --- /dev/null +++ b/src/config/groups/keys.cpp @@ -0,0 +1,1821 @@ +#include "session/config/groups/keys.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../internal.hpp" +#include "session/config/groups/info.hpp" +#include "session/config/groups/keys.h" +#include "session/config/groups/members.hpp" +#include "session/multi_encrypt.hpp" +#include "session/xed25519.hpp" + +using namespace std::literals; + +namespace session::config::groups { + +static auto sys_time_from_ms(int64_t milliseconds_since_epoch) { + return std::chrono::system_clock::time_point{milliseconds_since_epoch * 1ms}; +} + +Keys::Keys( + std::span user_ed25519_secretkey, + std::span group_ed25519_pubkey, + std::optional> group_ed25519_secretkey, + std::optional> dumped, + Info& info, + Members& members) { + + if (sodium_init() == -1) + throw std::runtime_error{"libsodium initialization failed!"}; + + if (user_ed25519_secretkey.size() != 64) + throw std::invalid_argument{"Invalid Keys construction: invalid user ed25519 secret key"}; + if (group_ed25519_pubkey.size() != 32) + throw std::invalid_argument{"Invalid Keys construction: invalid group ed25519 public key"}; + if (group_ed25519_secretkey && group_ed25519_secretkey->size() != 64) + throw std::invalid_argument{"Invalid Keys construction: invalid group ed25519 secret key"}; + + init_sig_keys(group_ed25519_pubkey, group_ed25519_secretkey); + + user_ed25519_sk.load(user_ed25519_secretkey.data(), 64); + + if (dumped) { + load_dump(*dumped); + auto key_list = group_keys(); + members.replace_keys(key_list, /*dirty=*/false); + info.replace_keys(key_list, /*dirty=*/false); + } +} + +bool Keys::needs_dump() const { + return needs_dump_; +} + +std::vector Keys::dump() { + auto dumped = make_dump(); + + needs_dump_ = false; + return dumped; +} + +std::vector Keys::make_dump() const { + oxenc::bt_dict_producer d; + { + auto active = d.append_list("A"); + for (const auto& [gen, hashes] : active_msgs_) { + auto lst = active.append_list(); + lst.append(gen); + for (const auto& h : hashes) + lst.append(h); + } + } + + { + auto keys = d.append_list("L"); + for (auto& k : keys_) { + auto ki = keys.append_dict(); + // NB: Keys must be in sorted order + ki.append("g", k.generation); + ki.append("k", to_string_view(k.key)); + ki.append( + "t", + std::chrono::duration_cast( + k.timestamp.time_since_epoch()) + .count()); + } + } + + if (!pending_key_config_.empty()) { + auto pending = d.append_dict("P"); + // NB: Keys must be in sorted order + pending.append("c", to_string_view(pending_key_config_)); + pending.append("g", pending_gen_); + pending.append("k", to_string_view(pending_key_)); + } + + return to_vector(d.view()); +} + +void Keys::load_dump(std::span dump) { + oxenc::bt_dict_consumer d{dump}; + + if (d.skip_until("A")) { + auto active = d.consume_list_consumer(); + while (!active.is_finished()) { + auto lst = active.consume_list_consumer(); + auto& hashes = active_msgs_[lst.consume_integer()]; + while (!lst.is_finished()) + hashes.insert(lst.consume_string()); + } + } else { + throw config_value_error{"Invalid Keys dump: `active` not found"}; + } + + if (d.skip_until("L")) { + auto keys = d.consume_list_consumer(); + while (!keys.is_finished()) { + auto kd = keys.consume_dict_consumer(); + auto& key = keys_.emplace_back(); + + if (!kd.skip_until("g")) + throw config_value_error{"Invalid Keys dump: found key without generation (g)"}; + key.generation = kd.consume_integer(); + + if (!kd.skip_until("k")) + throw config_value_error{"Invalid Keys dump: found key without key bytes (k)"}; + auto key_bytes = kd.consume_string_view(); + if (key_bytes.size() != key.key.size()) + throw config_value_error{ + "Invalid Keys dump: found key with invalid size (" + + std::to_string(key_bytes.size()) + ")"}; + std::memcpy(key.key.data(), key_bytes.data(), key.key.size()); + + if (!kd.skip_until("t")) + throw config_value_error{"Invalid Keys dump: found key without timestamp (t)"}; + key.timestamp = sys_time_from_ms(kd.consume_integer()); + + if (keys_.size() > 1 && *std::prev(keys_.end(), 2) >= key) + throw config_value_error{"Invalid Keys dump: keys are not in proper sorted order"}; + } + } else { + throw config_value_error{"Invalid Keys dump: `keys` not found"}; + } + + if (d.skip_until("P")) { + auto pending = d.consume_dict_consumer(); + + if (!pending.skip_until("c")) + throw config_value_error{"Invalid Keys dump: found pending without config (c)"}; + auto pc = pending.consume_string_view(); + pending_key_config_.clear(); + pending_key_config_.resize(pc.size()); + std::memcpy(pending_key_config_.data(), pc.data(), pc.size()); + + if (!pending.skip_until("g")) + throw config_value_error{"Invalid Keys dump: found pending without generation (g)"}; + pending_gen_ = pending.consume_integer(); + + if (!pending.skip_until("k")) + throw config_value_error{"Invalid Keys dump: found pending without key (k)"}; + auto pk = pending.consume_string_view(); + if (pk.size() != pending_key_.size()) + throw config_value_error{ + "Invalid Keys dump: found pending key (k) with invalid size (" + + std::to_string(pk.size()) + ")"}; + std::memcpy(pending_key_.data(), pk.data(), pending_key_.size()); + } +} + +size_t Keys::size() const { + return keys_.size() + !pending_key_config_.empty(); +} + +std::vector> Keys::group_keys() const { + std::vector> ret; + ret.reserve(size()); + + if (!pending_key_config_.empty()) + ret.emplace_back(pending_key_.data(), 32); + + for (auto it = keys_.rbegin(); it != keys_.rend(); ++it) + ret.emplace_back(it->key.data(), 32); + + return ret; +} + +std::span Keys::group_enc_key() const { + if (!pending_key_config_.empty()) + return {pending_key_.data(), 32}; + if (keys_.empty()) + throw std::runtime_error{"group_enc_key failed: Keys object has no keys at all!"}; + + auto& key = keys_.back().key; + return {key.data(), key.size()}; +} + +void Keys::load_admin_key(std::span seed, Info& info, Members& members) { + if (admin()) + return; + + if (seed.size() == 64) + seed = seed.subspan(0, seed.size() - 32); + else if (seed.size() != 32) + throw std::invalid_argument{ + "Failed to load admin key: invalid secret key (expected 32 or 64 bytes)"}; + + std::array pk; + sodium_cleared> sk; + crypto_sign_ed25519_seed_keypair(pk.data(), sk.data(), seed.data()); + + if (_sign_pk.has_value() && *_sign_pk != pk) + throw std::runtime_error{ + "Failed to load admin key: given secret key does not match group pubkey"}; + + auto seckey = to_span(sk); + set_sig_keys(seckey); + info.set_sig_keys(seckey); + members.set_sig_keys(seckey); +} + +namespace { + + std::array compute_xpk(const unsigned char* ed25519_pk) { + std::array xpk; + if (0 != crypto_sign_ed25519_pk_to_curve25519(xpk.data(), ed25519_pk)) + throw std::runtime_error{ + "An error occured while attempting to convert Ed25519 pubkey to X25519; " + "is the pubkey valid?"}; + return xpk; + } + + constexpr auto seed_hash_key = "SessionGroupKeySeed"sv; + const std::span enc_key_hash_key = to_span("SessionGroupKeyGen"); + constexpr auto enc_key_admin_hash_key = "SessionGroupKeyAdminKey"sv; + constexpr auto enc_key_member_hash_key = "SessionGroupKeyMemberKey"sv; + const std::span junk_seed_hash_key = to_span("SessionGroupJunkMembers"); + +} // namespace + +std::span Keys::rekey(Info& info, Members& members) { + if (!admin()) + throw std::logic_error{ + "Unable to issue a new group encryption key without the main group keys"}; + + // For members we calculate the outer encryption key as H(aB || A || B). But because we only + // have `B` (the session id) as an x25519 pubkey, we do this in x25519 space, which means we + // have to use the x25519 conversion of a/A rather than the group's ed25519 pubkey. + auto group_xpk = compute_xpk(_sign_pk->data()); + + sodium_cleared> group_xsk; + crypto_sign_ed25519_sk_to_curve25519(group_xsk.data(), _sign_sk.data()); + + // We need quasi-randomness: full secure random would be great, except that different admins + // encrypting for the same update would always create different keys, but we want it + // deterministic so that that doesn't happen. + // + // So we use: + // + // H1(member0 || member1 || ... || memberN || generation || H2(group_secret_key)) + // + // where: + // - H1(.) = 56-byte BLAKE2b keyed hash with key "SessionGroupKeyGen" + // - memberI is each members full session ID, expressed in hex (66 chars), in sorted order (note + // that this includes *all* members, not only non-admins). + // - generation is the new generation value, expressed as a base 10 string (e.g. "123") + // - H2(.) = 32-byte BLAKE2b keyed hash of the sodium group secret key seed (just the 32 byte, + // not the full 64 byte with the pubkey in the second half), key "SessionGroupKeySeed" + // + // And then from this 56-byte hash we use the first 32 bytes as the new group key and the last + // 24 bytes as the encryption nonce. + // + // If we have to append junk member keys (for padding) them we reuse H1 with H(H1 || a) to + // produce a sodium pseudo-RNG seed for deterministic junk value generation. + // + // To encrypt this we have one key encrypted for all admins, plus one encryption per non-admin + // member. For admins we encrypt using a 32-byte blake2b keyed hash of the group secret key + // seed, just like H2, but with key "SessionGroupKeyAdminKey". + + std::array h2 = seed_hash(seed_hash_key); + + std::array h1; + + crypto_generichash_blake2b_state st; + + crypto_generichash_blake2b_init( + &st, enc_key_hash_key.data(), enc_key_hash_key.size(), h1.size()); + for (const auto& m : members) + crypto_generichash_blake2b_update( + &st, to_unsigned(m.session_id.data()), m.session_id.size()); + + auto gen = keys_.empty() ? 0 : keys_.back().generation + 1; + auto gen_str = std::to_string(gen); + crypto_generichash_blake2b_update(&st, to_unsigned(gen_str.data()), gen_str.size()); + + crypto_generichash_blake2b_update(&st, h2.data(), 32); + + crypto_generichash_blake2b_final(&st, h1.data(), h1.size()); + + std::span enc_key{h1.data(), 32}; + std::span nonce{h1.data() + 32, 24}; + + oxenc::bt_dict_producer d{}; + + d.append("#", to_string_view(nonce)); + + static_assert(crypto_aead_xchacha20poly1305_ietf_KEYBYTES == 32); + static_assert(crypto_aead_xchacha20poly1305_ietf_ABYTES == 16); + std::array< + unsigned char, + crypto_aead_xchacha20poly1305_ietf_KEYBYTES + crypto_aead_xchacha20poly1305_ietf_ABYTES> + encrypted; + std::string_view enc_sv = to_string_view(encrypted); + + // Shared key for admins + auto member_k = seed_hash(enc_key_admin_hash_key); + static_assert(member_k.size() == crypto_aead_xchacha20poly1305_ietf_KEYBYTES); + crypto_aead_xchacha20poly1305_ietf_encrypt( + encrypted.data(), + nullptr, + enc_key.data(), + enc_key.size(), + nullptr, + 0, + nullptr, + nonce.data(), + member_k.data()); + + d.append("G", gen); + d.append("K", enc_sv); + + { + auto member_keys = d.append_list("k"); + int member_count = 0; + std::vector> member_xpk_raw; + std::vector> member_xpks; + member_xpk_raw.reserve(members.size()); + member_xpks.reserve(members.size()); + for (const auto& m : members) { + member_xpk_raw.push_back(session_id_pk(m.session_id)); + member_xpks.emplace_back(member_xpk_raw.back().data(), member_xpk_raw.back().size()); + } + + encrypt_for_multiple( + enc_key, + member_xpks, + nonce, + to_span(group_xsk), + to_span(group_xpk), + enc_key_member_hash_key, + [&](std::span enc_sv) { + member_keys.append(enc_sv); + member_count++; + }, + true // ignore invalid + ); + + // Pad it out with junk entries to the next MESSAGE_KEY_MULTIPLE + if (member_count % MESSAGE_KEY_MULTIPLE) { + int n_junk = MESSAGE_KEY_MULTIPLE - (member_count % MESSAGE_KEY_MULTIPLE); + std::vector junk_data; + junk_data.resize(encrypted.size() * n_junk); + + std::array rng_seed; + crypto_generichash_blake2b_init( + &st, junk_seed_hash_key.data(), junk_seed_hash_key.size(), rng_seed.size()); + crypto_generichash_blake2b_update(&st, h1.data(), h1.size()); + crypto_generichash_blake2b_update(&st, _sign_sk.data(), _sign_sk.size()); + crypto_generichash_blake2b_final(&st, rng_seed.data(), rng_seed.size()); + + randombytes_buf_deterministic(junk_data.data(), junk_data.size(), rng_seed.data()); + std::string_view junk_view = to_string_view(junk_data); + while (!junk_view.empty()) { + member_keys.append(junk_view.substr(0, encrypted.size())); + junk_view.remove_prefix(encrypted.size()); + } + } + } + + // Finally we sign the message at put it as the ~ key (which is 0x7e, and thus comes later than + // any other printable ascii key). + d.append_signature( + "~", [this](std::span to_sign) { return sign(to_sign); }); + + // Load this key/config/gen into our pending variables + pending_gen_ = gen; + std::memcpy(pending_key_.data(), enc_key.data(), pending_key_.size()); + pending_key_config_.clear(); + auto conf = d.view(); + pending_key_config_.resize(conf.size()); + std::memcpy(pending_key_config_.data(), conf.data(), conf.size()); + + auto new_key_list = group_keys(); + // We want to dirty the member/info lists so that they get re-encrypted and re-pushed with the + // new key: + members.replace_keys(new_key_list, /*dirty=*/true); + info.replace_keys(new_key_list, /*dirty=*/true); + + needs_dump_ = true; + + return std::span{pending_key_config_.data(), pending_key_config_.size()}; +} + +std::vector Keys::sign(std::span data) const { + auto sig = signer_(data); + if (sig.size() != 64) + throw std::logic_error{"Invalid signature: signing function did not return 64 bytes"}; + return sig; +} + +std::vector Keys::key_supplement(const std::vector& sids) const { + if (!admin()) + throw std::logic_error{ + "Unable to issue supplemental group encryption keys without the main group keys"}; + + if (keys_.empty()) + throw std::logic_error{ + "Unable to create supplemental keys: this object has no keys at all"}; + + // For members we calculate the outer encryption key as H(aB || A || B). But because we only + // have `B` (the session id) as an x25519 pubkey, we do this in x25519 space, which means we + // have to use the x25519 conversion of a/A rather than the group's ed25519 pubkey. + auto group_xpk = compute_xpk(_sign_pk->data()); + + sodium_cleared> group_xsk; + crypto_sign_ed25519_sk_to_curve25519(group_xsk.data(), _sign_sk.data()); + + // We need quasi-randomness here for the nonce: full secure random would be great, except that + // different admins encrypting for the same update would always create different keys, but we + // want it deterministic so that that doesn't happen. + // + // So we use a nonce of: + // + // H1(member0 || member1 || ... || memberN || keysdata || H2(group_secret_key)) + // + // where: + // - H1(.) = 24-byte BLAKE2b keyed hash with key "SessionGroupKeyGen" + // - memberI is the full session ID of each member included in this key update, expressed in hex + // (66 chars), in sorted order. + // - keysdata is the unencrypted inner value that we are encrypting for each supplemental member + // - H2(.) = 32-byte BLAKE2b keyed hash of the sodium group secret key seed (just the 32 byte, + // not the full 64 byte with the pubkey in the second half), key "SessionGroupKeySeed" + + std::string supp_keys; + { + oxenc::bt_list_producer supp; + for (auto& ki : keys_) { + auto d = supp.append_dict(); + d.append("g", ki.generation); + d.append("k", to_string_view(ki.key)); + d.append( + "t", + std::chrono::duration_cast( + ki.timestamp.time_since_epoch()) + .count()); + } + supp_keys = std::move(supp).str(); + } + + std::array h1; + + crypto_generichash_blake2b_state st; + + crypto_generichash_blake2b_init( + &st, enc_key_hash_key.data(), enc_key_hash_key.size(), h1.size()); + + for (const auto& sid : sids) + crypto_generichash_blake2b_update(&st, to_unsigned(sid.data()), sid.size()); + + crypto_generichash_blake2b_update(&st, to_unsigned(supp_keys.data()), supp_keys.size()); + + std::array h2 = seed_hash(seed_hash_key); + crypto_generichash_blake2b_update(&st, h2.data(), h2.size()); + + crypto_generichash_blake2b_final(&st, h1.data(), h1.size()); + + std::span nonce{h1.data(), h1.size()}; + + oxenc::bt_dict_producer d{}; + + d.append("#", to_string_view(nonce)); + + { + auto list = d.append_list("+"); + std::vector encrypted; + encrypted.resize(supp_keys.size() + crypto_aead_xchacha20poly1305_ietf_ABYTES); + + size_t member_count = 0; + + std::vector> member_xpk_raw; + std::vector> member_xpks; + member_xpk_raw.reserve(sids.size()); + member_xpks.reserve(sids.size()); + for (const auto& sid : sids) { + member_xpk_raw.push_back(session_id_pk(sid)); + member_xpks.emplace_back(member_xpk_raw.back().data(), member_xpk_raw.back().size()); + } + + encrypt_for_multiple( + supp_keys, + member_xpks, + nonce, + to_span(group_xsk), + to_span(group_xpk), + enc_key_member_hash_key, + [&](std::span encrypted) { + list.append(encrypted); + member_count++; + }, + true // ignore invalid + ); + + if (member_count == 0) + throw std::runtime_error{ + "Unable to construct supplemental messages: invalid session ids given"}; + } + + d.append("G", keys_.back().generation); + + // Finally we sign the message at put it as the ~ key (which is 0x7e, and thus comes later than + // any other printable ascii key). + d.append_signature( + "~", [this](std::span to_sign) { return sign(to_sign); }); + + return to_vector(d.view()); +} + +// Blinding factor for subaccounts: H(sessionid || groupid) mod L, where H is 64-byte blake2b, using +// a hash key derived from the group's seed. +std::array Keys::subaccount_blind_factor( + const std::array& session_xpk) const { + + auto mask = seed_hash("SessionGroupSubaccountMask"); + static_assert(mask.size() == crypto_generichash_blake2b_KEYBYTES); + + std::array h; + crypto_generichash_blake2b_state st; + crypto_generichash_blake2b_init(&st, mask.data(), mask.size(), h.size()); + crypto_generichash_blake2b_update(&st, to_unsigned("\x05"), 1); + crypto_generichash_blake2b_update(&st, session_xpk.data(), session_xpk.size()); + crypto_generichash_blake2b_update(&st, to_unsigned("\x03"), 1); + crypto_generichash_blake2b_update(&st, _sign_pk->data(), _sign_pk->size()); + crypto_generichash_blake2b_final(&st, h.data(), h.size()); + + std::array out; + crypto_core_ed25519_scalar_reduce(out.data(), h.data()); + return out; +} + +namespace { + + // These constants are defined and explains in more detail in oxen-storage-server + constexpr unsigned char SUBACC_FLAG_READ = 0b0001; + constexpr unsigned char SUBACC_FLAG_WRITE = 0b0010; + constexpr unsigned char SUBACC_FLAG_DEL = 0b0100; + constexpr unsigned char SUBACC_FLAG_ANY_PREFIX = 0b1000; + + constexpr unsigned char subacc_flags(bool write, bool del) { + return SUBACC_FLAG_READ | (write ? SUBACC_FLAG_WRITE : 0) | (del ? SUBACC_FLAG_DEL : 0); + } + +} // namespace + +std::vector Keys::swarm_make_subaccount( + std::string_view session_id, bool write, bool del) const { + if (!admin()) + throw std::logic_error{"Cannot make subaccount signature: admin keys required"}; + + // This gets a wee bit complicated because we only have a session_id, but we really need an + // Ed25519 pubkey. So we do the signal-style XEd25519 thing here where we start with the + // positive alternative behind their x25519 pubkey and work from there. This means, + // unfortunately, that making a signature needs to muck around since this is the proper public + // only half the time. + + // Terminology/variables (a/A indicates private/public keys) + // - s/S are the Ed25519 underlying Session keys (neither is observed in this context) + // - x/X are the X25519 conversions of s/S (x, similarly, is not observed, but X is: it's in the + // session_id). + // - T = |S|, i.e. the positive of the two alternatives we get from inverting the Ed -> X + // pubkey. + // - c/C is the group's Ed25519 + // - k is the blinding factor, which is: H(\x05...[sessionid]\x03...[groupid], key=M) mod L, + // where: H is 64-byte blake2b; M is `subaccount_blind_factor` (see above). + // - p is the account network prefix (03) + // - f are the flag bits, determined by `write` and `del` arguments + + auto X = session_id_pk(session_id); + auto& c = _sign_sk; + + auto k = subaccount_blind_factor(X); + + // T = |S| + auto T = xed25519::pubkey(std::span{X.data(), X.size()}); + + // kT is the user's Ed25519 blinded pubkey: + std::array kT; + + if (0 != crypto_scalarmult_ed25519_noclamp(kT.data(), k.data(), T.data())) + throw std::runtime_error{"scalarmult failed: perhaps an invalid session id?"}; + + std::vector out; + out.resize(4 + 32 + 64); + out[0] = 0x03; // network prefix + out[1] = subacc_flags(write, del); // permission flags + out[2] = 0; // reserved 1 + out[3] = 0; // reserved 2 + // The next 32 bytes are k (NOT kT; the user can go make kT themselves): + std::memcpy(&out[4], k.data(), k.size()); + + // And then finally, we append a group signature of: p || f || 0 || 0 || kT + std::array to_sign; + std::memcpy(&to_sign[0], out.data(), 4); // first 4 bytes are the same as out + std::memcpy(&to_sign[4], kT.data(), 32); // but then we have kT instead of k + crypto_sign_ed25519_detached(&out[36], nullptr, to_sign.data(), to_sign.size(), c.data()); + + return out; +} + +std::vector Keys::swarm_subaccount_token( + std::string_view session_id, bool write, bool del) const { + if (!admin()) + throw std::logic_error{"Cannot make subaccount signature: admin keys required"}; + + // Similar to the above, but we only care about getting flags || kT + + auto X = session_id_pk(session_id); + auto k = subaccount_blind_factor(X); + + // T = |S| + auto T = xed25519::pubkey(std::span{X.data(), X.size()}); + + std::vector out; + out.resize(4 + 32); + out[0] = 0x03; // network prefix + out[1] = subacc_flags(write, del); // permission flags + out[2] = 0; // reserved 1 + out[3] = 0; // reserved 2 + if (0 != crypto_scalarmult_ed25519_noclamp(&out[4], k.data(), T.data())) + throw std::runtime_error{"scalarmult failed: perhaps an invalid session id?"}; + return out; +} + +Keys::swarm_auth Keys::swarm_subaccount_sign( + std::span msg, + std::span sign_val, + bool binary) const { + if (sign_val.size() != 100) + throw std::logic_error{"Invalid signing value: size is wrong"}; + + if (!_sign_pk) + throw std::logic_error{"Unable to verify: group pubkey is not set (!?)"}; + + Keys::swarm_auth result; + auto& [token, sub_sig, sig] = result; + + // (see above for variable/crypto notation) + + std::span k = sign_val.subspan(4, 32); + + // our token is the first 4 bytes of `sign_val` (flags, etc.), followed by kT which we have to + // compute: + token.resize(36); + std::memcpy(token.data(), sign_val.data(), 4); + + // T = |S|, i.e. we have to clear the sign bit from our pubkey + std::array T; + crypto_sign_ed25519_sk_to_pk(T.data(), user_ed25519_sk.data()); + bool neg = T[31] & 0x80; + T[31] &= 0x7f; + if (0 != crypto_scalarmult_ed25519_noclamp(to_unsigned(token.data() + 4), k.data(), T.data())) + throw std::runtime_error{"scalarmult failed: perhaps an invalid session id or seed?"}; + + // token is now set: flags || kT + std::span kT{to_unsigned(token.data() + 4), 32}; + + // sub_sig is just the admin's signature, sitting at the end of sign_val (after 4f || k): + sub_sig = to_string_view(sign_val.subspan(36)); + + // Our signing private scalar is kt, where t = ±s according to whether we had to negate S to + // make T + std::array s, s_neg; + crypto_sign_ed25519_sk_to_curve25519(s.data(), user_ed25519_sk.data()); + crypto_core_ed25519_scalar_negate(s_neg.data(), s.data()); + xed25519::constant_time_conditional_assign(s, s_neg, neg); + + auto& t = s; + + std::array kt; + crypto_core_ed25519_scalar_mul(kt.data(), k.data(), t.data()); + + // We now have kt, kT, our privkey/public. (Note that kt is a scalar, not a seed). + + // We're going to get *close* to standard Ed25519 here, except: + // + // where Ed25519 uses + // + // r = SHA512(SHA512(seed)[32:64] || M) mod L + // + // we're instead going to use: + // + // r = H64(H32(seed, key="SubaccountSeed") || kT || M, key="SubaccountSig") mod L + // + // where H64 and H32 are BLAKE2b keyed hashes of 64 and 32 bytes, respectively, thus + // differentiating the signature for both different seeds and different blinded kT pubkeys. + // + // From there, we follow the standard EdDSA construction: + // + // R = rB + // S = r + H(R || kT || M) kt (mod L) + // + // (using the standard Ed25519 SHA-512 here for H) + + constexpr auto seed_hash_key = "SubaccountSeed"sv; + constexpr auto r_hash_key = "SubaccountSig"sv; + std::array hseed; + crypto_generichash_blake2b( + hseed.data(), + hseed.size(), + user_ed25519_sk.data(), + 32, + to_unsigned(seed_hash_key.data()), + seed_hash_key.size()); + + std::array tmp; + crypto_generichash_blake2b_state st; + crypto_generichash_blake2b_init( + &st, to_unsigned(r_hash_key.data()), r_hash_key.size(), tmp.size()); + crypto_generichash_blake2b_update(&st, hseed.data(), hseed.size()); + crypto_generichash_blake2b_update(&st, kT.data(), kT.size()); + crypto_generichash_blake2b_update(&st, msg.data(), msg.size()); + crypto_generichash_blake2b_final(&st, tmp.data(), tmp.size()); + + std::array r; + crypto_core_ed25519_scalar_reduce(r.data(), tmp.data()); + + sig.resize(64); + unsigned char* R = to_unsigned(sig.data()); + unsigned char* S = to_unsigned(sig.data() + 32); + // R = rB + crypto_scalarmult_ed25519_base_noclamp(R, r.data()); + + // Compute S = r + H(R || A || M) a mod L: (with A = kT, a = kt) + crypto_hash_sha512_state shast; + crypto_hash_sha512_init(&shast); + crypto_hash_sha512_update(&shast, R, 32); + crypto_hash_sha512_update(&shast, kT.data(), kT.size()); // A = pubkey, that is, kT + crypto_hash_sha512_update(&shast, msg.data(), msg.size()); + std::array hram; + crypto_hash_sha512_final(&shast, hram.data()); // S = H(R||A||M) + crypto_core_ed25519_scalar_reduce(S, hram.data()); // S %= L + crypto_core_ed25519_scalar_mul(S, S, kt.data()); // S *= a + crypto_core_ed25519_scalar_add(S, S, r.data()); // S += r + + // sig is now set to the desired R || S, with S = r + H(R || A || M)a (all mod L) + + if (!binary) { + token = oxenc::to_base64(token); + sub_sig = oxenc::to_base64(sub_sig); + sig = oxenc::to_base64(sig); + } + + return result; +} + +bool Keys::swarm_verify_subaccount( + std::span sign_val, bool write, bool del) const { + if (!_sign_pk) + return false; + return swarm_verify_subaccount( + "03" + oxenc::to_hex(_sign_pk->begin(), _sign_pk->end()), + std::span{user_ed25519_sk.data(), user_ed25519_sk.size()}, + sign_val, + write, + del); +} + +bool Keys::swarm_verify_subaccount( + std::string group_id, + std::span user_ed_sk, + std::span sign_val, + bool write, + bool del) { + auto group_pk = session_id_pk(group_id, "03"); + + if (sign_val.size() != 100) + return false; + + std::span prefix = sign_val.subspan(0, 4); + if (prefix[0] != 0x03 && !(prefix[1] & SUBACC_FLAG_ANY_PREFIX)) + return false; // require either 03 prefix match, or the "any prefix" flag + + if (!(prefix[1] & SUBACC_FLAG_READ)) + return false; // missing the read flag + + if (write && !(prefix[1] & SUBACC_FLAG_WRITE)) + return false; // we require write, but it isn't set + // + if (del && !(prefix[1] & SUBACC_FLAG_DEL)) + return false; // we require delete, but it isn't set + + std::span k = sign_val.subspan(4, 32); + std::span sig = sign_val.subspan(36); + + // T = |S|, i.e. we have to clear the sign bit from our pubkey + std::array T; + crypto_sign_ed25519_sk_to_pk(T.data(), user_ed_sk.data()); + T[31] &= 0x7f; + + // Compute kT, then reconstruct the `flags || kT` value the admin should have provided a + // signature for + std::array kT; + if (0 != crypto_scalarmult_ed25519_noclamp(kT.data(), k.data(), T.data())) + throw std::runtime_error{"scalarmult failed: perhaps an invalid session id or seed?"}; + + std::array to_verify; + std::memcpy(&to_verify[0], sign_val.data(), 4); // prefix, flags, 2x future use bytes + std::memcpy(&to_verify[4], kT.data(), 32); + + // Verify it! + return 0 == crypto_sign_ed25519_verify_detached( + sig.data(), to_verify.data(), to_verify.size(), group_pk.data()); +} + +std::optional> Keys::pending_config() const { + if (pending_key_config_.empty()) + return std::nullopt; + return std::span{pending_key_config_.data(), pending_key_config_.size()}; +} + +void Keys::insert_key(std::string_view msg_hash, key_info&& new_key) { + // Find all keys with the same generation and see if our key is in there (that is: we are + // deliberately ignoring timestamp so that we don't add the same key with slight timestamp + // variations). + const auto [gen_begin, gen_end] = + std::equal_range(keys_.begin(), keys_.end(), new_key, [](const auto& a, const auto& b) { + return a.generation < b.generation; + }); + for (auto it = gen_begin; it != gen_end; ++it) + if (it->key == new_key.key) { + active_msgs_[new_key.generation].emplace(msg_hash); + return; + } + + auto it = std::lower_bound(keys_.begin(), keys_.end(), new_key); + + if (keys_.size() >= 2 && it == keys_.begin() && new_key.generation < keys_.front().generation && + keys_.front().timestamp + KEY_EXPIRY < keys_.back().timestamp) + // The new one is older than the front one, and the front one is already more than + // KEY_EXPIRY before the last one, so this new one is stale. + return; + + active_msgs_[new_key.generation].emplace(msg_hash); + keys_.insert(it, std::move(new_key)); + remove_expired(); + needs_dump_ = true; +} + +// Attempts xchacha20 decryption. +// +// Preconditions: +// - `ciphertext` must be at least 16 [crypto_aead_xchacha20poly1305_ietf_ABYTES] +// - `out` must have enough space (ciphertext.size() - 16 +// [crypto_aead_xchacha20poly1305_ietf_ABYTES]) +// - `nonce` must be 24 bytes [crypto_aead_xchacha20poly1305_ietf_NPUBBYTES] +// - `key` must be 32 bytes [crypto_aead_xchacha20poly1305_ietf_KEYBYTES] +// +// The latter two are asserted in a debug build, but not otherwise checked. +// +// Returns true (after writing to `out`) if decryption succeeds, false if it fails. +namespace { + bool try_decrypting( + unsigned char* out, + std::span encrypted, + std::span nonce, + std::span key) { + assert(encrypted.size() >= crypto_aead_xchacha20poly1305_ietf_ABYTES); + assert(nonce.size() == crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + assert(key.size() == crypto_aead_xchacha20poly1305_ietf_KEYBYTES); + + return 0 == crypto_aead_xchacha20poly1305_ietf_decrypt( + out, + nullptr, + nullptr, + encrypted.data(), + encrypted.size(), + nullptr, + 0, + nonce.data(), + key.data()); + } + bool try_decrypting( + unsigned char* out, + std::span encrypted, + std::span nonce, + + const std::array& key) { + return try_decrypting( + out, encrypted, nonce, std::span{key.data(), key.size()}); + } +} // namespace + +bool Keys::load_key_message( + std::string_view hash, + std::span data, + int64_t timestamp_ms, + Info& info, + Members& members) { + + oxenc::bt_dict_consumer d{data}; + + if (!_sign_pk || !verifier_) + throw std::logic_error{"Group pubkey is not set; unable to load config message"}; + + auto group_xpk = compute_xpk(_sign_pk->data()); + + if (!d.skip_until("#")) + throw config_value_error{"Key message has no nonce"}; + auto nonce = to_span(d.consume_string_view()); + + sodium_vector new_keys; + std::optional max_gen; // If set then associate the message with this generation + // value, even if we didn't find a key for us. + + sodium_cleared> member_dec_key; + sodium_cleared> member_xsk; + std::array member_xpk; + if (!admin()) { + crypto_sign_ed25519_sk_to_curve25519(member_xsk.data(), user_ed25519_sk.data()); + member_xpk = compute_xpk(user_ed25519_sk.data() + 32); + } + + if (d.skip_until("+")) { + // This is a supplemental keys message, not a full one + auto supp = d.consume_list_consumer(); + + int member_key_pos = -1; + + auto next_ciphertext = [&]() -> std::optional> { + while (!supp.is_finished()) { + member_key_pos++; + auto encrypted = to_span(supp.consume_string_view()); + // Expect an encrypted message like this, which has a minimum valid size (if both g + // and t are 0 for some reason) of: + // d -- 1 + // 1:k 32:... -- +38 + // 1:g i1e -- + 6 + // 1:t iXe -- + 6 + // e + 1 + // --- + // 52 + if (encrypted.size() < 52 + crypto_aead_xchacha20poly1305_ietf_ABYTES) + throw config_value_error{ + "Supplemental key message has invalid key info size at " + "index " + + std::to_string(member_key_pos)}; + + if (!new_keys.empty() || admin()) + continue; // Keep parsing, to ensure validity of the whole message + + return encrypted; + } + return std::nullopt; + }; + + if (auto plaintext = decrypt_for_multiple( + next_ciphertext, + nonce, + to_span(member_xsk), + to_span(member_xpk), + to_span(group_xpk), + enc_key_member_hash_key)) { + + // Decryption success, we found our key list! + + oxenc::bt_list_consumer key_infos{to_string_view(*plaintext)}; + while (!key_infos.is_finished()) { + auto& new_key = new_keys.emplace_back(); + auto keyinf = key_infos.consume_dict_consumer(); + if (!keyinf.skip_until("g")) + throw config_value_error{"Invalid supplemental key message: no `g` generation"}; + new_key.generation = keyinf.consume_integer(); + if (!keyinf.skip_until("k")) + throw config_value_error{"Invalid supplemental key message: no `k` key data"}; + auto key_val = keyinf.consume_string_view(); + if (key_val.size() != 32) + throw config_value_error{ + "Invalid supplemental key message: `k` key has wrong size"}; + std::memcpy(new_key.key.data(), key_val.data(), 32); + if (!keyinf.skip_until("t")) + throw config_value_error{"Invalid supplemental key message: no `t` timestamp"}; + new_key.timestamp = sys_time_from_ms(keyinf.consume_integer()); + } + } + + // Ensure we consume all the ciphertexts (to ensure some message validity, even if we found + // one halfway through). + while (next_ciphertext()) { + } + + if (!d.skip_until("G")) + throw config_value_error{ + "Supplemental key message missing required max generation field (G)"}; + max_gen = d.consume_integer(); + + } else { // Full message (i.e. not supplemental) + + bool found_key = false; + auto& new_key = new_keys.emplace_back(); + new_key.timestamp = sys_time_from_ms(timestamp_ms); + + if (!d.skip_until("G")) + throw config_value_error{"Key message missing required generation (G) field"}; + + new_key.generation = d.consume_integer(); + if (new_key.generation < 0) + throw config_value_error{"Key message contains invalid negative generation"}; + + if (!d.skip_until("K")) + throw config_value_error{ + "Non-supplemental key message is missing required admin key (K)"}; + + auto admin_key = to_span(d.consume_string_view()); + if (admin_key.size() != 32 + crypto_aead_xchacha20poly1305_ietf_ABYTES) + throw config_value_error{"Key message has invalid admin key length"}; + + if (admin()) { + auto k = seed_hash(enc_key_admin_hash_key); + + if (!try_decrypting(new_key.key.data(), admin_key, nonce, k)) + throw config_value_error{"Failed to decrypt admin key from key message"}; + + found_key = true; + } + + // Even if we're already found a key we still parse these, so that admins and all users have + // the same error conditions for rejecting an invalid config message. + if (!d.skip_until("k")) + throw config_value_error{"Config is missing member keys list (k)"}; + + auto key_list = d.consume_list_consumer(); + + int member_key_pos = -1; + auto next_ciphertext = [&]() -> std::optional> { + while (!key_list.is_finished()) { + member_key_pos++; + auto member_key = to_span(key_list.consume_string_view()); + if (member_key.size() != 32 + crypto_aead_xchacha20poly1305_ietf_ABYTES) + throw config_value_error{ + "Key message has invalid member key length at index " + + std::to_string(member_key_pos)}; + + if (found_key) + continue; + + return member_key; + } + return std::nullopt; + }; + + if (auto plaintext = decrypt_for_multiple( + next_ciphertext, + nonce, + to_span(member_xsk), + to_span(member_xpk), + to_span(group_xpk), + enc_key_member_hash_key)) { + // Decryption success, we found our key! + assert(plaintext->size() == 32); + std::memcpy(new_key.key.data(), plaintext->data(), 32); + found_key = true; + } + + // Parse them all, even once we had a successful decryption, to properly count and fail on + // invalid input: + while (next_ciphertext()) { + } + + if (++member_key_pos % MESSAGE_KEY_MULTIPLE != 0) + throw config_value_error{"Member key list has wrong size (missing junk key padding?)"}; + + if (!found_key) { + max_gen = new_key.generation; + new_keys.pop_back(); + } + } + + verify_config_sig(d, verifier_); + + // If this is our pending config or this has a later generation than our pending config then + // drop our pending status. + if (admin() && !new_keys.empty() && !pending_key_config_.empty() && + (new_keys[0].generation > pending_gen_ || new_keys[0].key == pending_key_)) { + pending_key_config_.clear(); + needs_dump_ = true; + } + + if (!new_keys.empty()) { + for (auto& k : new_keys) + insert_key(hash, std::move(k)); + + auto new_key_list = group_keys(); + members.replace_keys(new_key_list, /*dirty=*/false); + info.replace_keys(new_key_list, /*dirty=*/false); + return true; + } else if (max_gen) { + active_msgs_[*max_gen].emplace(hash); + remove_expired(); + needs_dump_ = true; + } + + return false; +} + +std::unordered_set Keys::active_hashes() const { + std::unordered_set hashes; + for (const auto& [g, hash] : active_msgs_) + hashes.insert(hash.begin(), hash.end()); + return hashes; +} + +void Keys::remove_expired() { + if (keys_.size() >= 2) { + // When we're done, this will point at the first element we want to keep (i.e. we want to + // remove everything in `[ begin(), lapsed_end )`). + auto lapsed_end = keys_.begin(); + + for (auto it = keys_.begin(); it != keys_.end();) { + // Advance `it` if the next element is an alternate key (with a later timestamp) from + // the same generation. When we finish this little loop, `it` is the last element of + // this generation and `it2` is the first element of the next generation. + auto it2 = std::next(it); + while (it2 != keys_.end() && it2->generation == it->generation) + it = it2++; + if (it2 == keys_.end()) + break; + + // it2 points at the lowest-timestamp value of the next-largest generation: if there is + // something more than 30 days newer than it2, then that tells us that `it`'s generation + // is no longer needed since a newer generation passed it more than 30 days ago. (We + // actually use 60 days for paranoid safety, but the logic is the same). + // + // NB: We don't trust the local system clock here (and the `timestamp` values are + // swarm-provided), because devices are notoriously imprecise, which means that since we + // only invalidate keys when new keys come in, we can hold onto one obsolete generation + // indefinitely (but this is a tiny overhead and not worth trying to build a + // system-clock-is-broken workaround to avoid). + if (it2->timestamp + KEY_EXPIRY < keys_.back().timestamp) + lapsed_end = it2; + else + break; + it = it2; + } + + if (lapsed_end != keys_.begin()) + keys_.erase(keys_.begin(), lapsed_end); + } + + // Drop any active message hashes for generations we are no longer keeping around + if (!keys_.empty()) + active_msgs_.erase( + active_msgs_.begin(), active_msgs_.lower_bound(keys_.front().generation)); + else + // Keys is empty, which means we aren't keep *any* keys around (or they are all invalid or + // something) and so it isn't really up to us to keep them alive, since that's a history of + // the group we apparently don't have access to. + active_msgs_.clear(); +} + +bool Keys::needs_rekey() const { + if (!admin() || keys_.size() < 2) + return false; + + // We rekey if the max generation value is being used across multiple keys (which indicates some + // sort of rekey collision, somewhat analagous to merge configs in regular config messages). + auto last_it = std::prev(keys_.end()); + auto second_it = std::prev(last_it); + return last_it->generation == second_it->generation; +} + +std::optional> Keys::pending_key() const { + if (!pending_key_config_.empty()) + return std::span{pending_key_.data(), pending_key_.size()}; + return std::nullopt; +} + +static constexpr size_t ENCRYPT_OVERHEAD = + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + crypto_aead_xchacha20poly1305_ietf_ABYTES; + +std::vector Keys::encrypt_message( + std::span plaintext, bool compress, size_t padding) const { + if (plaintext.size() > MAX_PLAINTEXT_MESSAGE_SIZE) + throw std::runtime_error{"Cannot encrypt plaintext: message size is too large"}; + std::vector _compressed; + if (compress) { + _compressed = zstd_compress(plaintext); + if (_compressed.size() < plaintext.size()) + plaintext = _compressed; + else { + _compressed.clear(); + compress = false; + } + } + // `plaintext` is now pointing at either the original input data, or at `_compressed` local + // variable containing the compressed form of that data. + + oxenc::bt_dict_producer dict{}; + + // encoded data version (bump this if something changes in an incompatible way) + dict.append("", 1); + + // Sender ed pubkey, by which the message can be validated. Note that there are *two* + // components to this validation: first the regular signature validation of the "s" signature we + // add below, but then also validation that this Ed25519 converts to the Session ID of the + // claimed sender of the message inside the encoded message data. + dict.append( + "a", std::string_view{reinterpret_cast(user_ed25519_sk.data()) + 32, 32}); + + if (!compress) + dict.append("d", to_string_view(plaintext)); + + // We sign `plaintext || group_ed25519_pubkey` rather than just `plaintext` so that if this + // encrypted data will not validate if cross-posted to any other group. We don't actually + // include the pubkey alongside, because that is implicitly known by the group members that + // receive it. + assert(_sign_pk); + std::vector to_sign(plaintext.size() + _sign_pk->size()); + std::memcpy(to_sign.data(), plaintext.data(), plaintext.size()); + std::memcpy(to_sign.data() + plaintext.size(), _sign_pk->data(), _sign_pk->size()); + + std::array signature; + crypto_sign_ed25519_detached( + signature.data(), nullptr, to_sign.data(), to_sign.size(), user_ed25519_sk.data()); + dict.append("s", to_string_view(signature)); + + if (compress) + dict.append("z", to_string_view(plaintext)); + + auto encoded = std::move(dict).str(); + + // suppose size == 250, padding = 256 + // so size + overhead(40) == 290 + // need padding of (256 - (290 % 256)) = 256 - 34 = 222 + // thus 290 + 222 = 512 + size_t final_len = ENCRYPT_OVERHEAD + encoded.size(); + if (padding > 1 && final_len % padding != 0) { + size_t to_append = padding - (final_len % padding); + encoded.resize(encoded.size() + to_append); + } + + std::vector ciphertext; + ciphertext.resize(ENCRYPT_OVERHEAD + encoded.size()); + randombytes_buf(ciphertext.data(), crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + std::span nonce{ + ciphertext.data(), crypto_aead_xchacha20poly1305_ietf_NPUBBYTES}; + if (0 != crypto_aead_xchacha20poly1305_ietf_encrypt( + ciphertext.data() + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, + nullptr, + to_unsigned(encoded.data()), + encoded.size(), + nullptr, + 0, + nullptr, + nonce.data(), + group_enc_key().data())) + throw std::runtime_error{"Encryption failed"}; + + return ciphertext; +} + +std::pair> Keys::decrypt_message( + std::span ciphertext) const { + if (ciphertext.size() < ENCRYPT_OVERHEAD) + throw std::runtime_error{"ciphertext is too small to be encrypted data"}; + + std::vector plain; + + auto nonce = ciphertext.subspan(0, crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + ciphertext = ciphertext.subspan(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + plain.resize(ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES); + + // + // Decrypt, using all the possible keys, starting with a pending one (if we have one) + // + bool decrypt_success = false; + if (auto pending = pending_key(); + pending && try_decrypting(plain.data(), ciphertext, nonce, *pending)) { + decrypt_success = true; + } else { + for (auto& k : keys_) { + if (try_decrypting(plain.data(), ciphertext, nonce, k.key)) { + decrypt_success = true; + break; + } + } + } + + if (!decrypt_success) // none of the keys worked + throw std::runtime_error{"unable to decrypt ciphertext with any current group keys"}; + + // + // Removing any null padding bytes from the end + // + if (auto it = + std::find_if(plain.rbegin(), plain.rend(), [](unsigned char c) { return c != 0; }); + it != plain.rend()) + plain.resize(plain.size() - std::distance(plain.rbegin(), it)); + + // + // Now what we have less should be a bt_dict + // + if (plain.empty() || plain.front() != 'd' || plain.back() != 'e') + throw std::runtime_error{"decrypted data is not a bencoded dict"}; + + oxenc::bt_dict_consumer dict{to_string_view(plain)}; + + if (!dict.skip_until("")) + throw std::runtime_error{"group message version tag (\"\") is missing"}; + if (auto v = dict.consume_integer(); v != 1) + throw std::runtime_error{ + "group message version tag (" + std::to_string(v) + + ") is not compatible (we support v1)"}; + + if (!dict.skip_until("a")) + throw std::runtime_error{"missing message author pubkey"}; + auto ed_pk = to_span(dict.consume_string_view()); + if (ed_pk.size() != 32) + throw std::runtime_error{ + "message author pubkey size (" + std::to_string(ed_pk.size()) + ") is invalid"}; + + std::array x_pk; + if (0 != crypto_sign_ed25519_pk_to_curve25519(x_pk.data(), ed_pk.data())) + throw std::runtime_error{ + "author ed25519 pubkey is invalid (unable to convert it to a session id)"}; + + std::pair> result; + auto& [session_id, data] = result; + session_id.reserve(66); + session_id += "05"; + oxenc::to_hex(x_pk.begin(), x_pk.end(), std::back_inserter(session_id)); + + std::span raw_data; + if (dict.skip_until("d")) { + raw_data = to_span(dict.consume_string_view()); + if (raw_data.empty()) + throw std::runtime_error{"uncompressed message data (\"d\") cannot be empty"}; + } + + if (!dict.skip_until("s")) + throw std::runtime_error{"message signature is missing"}; + auto ed_sig = to_span(dict.consume_string_view()); + if (ed_sig.size() != 64) + throw std::runtime_error{ + "message signature size (" + std::to_string(ed_sig.size()) + ") is invalid"}; + + bool compressed = false; + if (dict.skip_until("z")) { + if (!raw_data.empty()) + throw std::runtime_error{ + "message signature cannot contain both compressed (z) and uncompressed (d) " + "data"}; + raw_data = to_span(dict.consume_string_view()); + if (raw_data.empty()) + throw std::runtime_error{"compressed message data (\"z\") cannot be empty"}; + + compressed = true; + } else if (raw_data.empty()) + throw std::runtime_error{"message must contain compressed (z) or uncompressed (d) data"}; + + // The value we verify is the raw data *followed by* the group Ed25519 pubkey. (See the comment + // in encrypt_message). + assert(_sign_pk); + std::vector to_verify(raw_data.size() + _sign_pk->size()); + std::memcpy(to_verify.data(), raw_data.data(), raw_data.size()); + std::memcpy(to_verify.data() + raw_data.size(), _sign_pk->data(), _sign_pk->size()); + if (0 != crypto_sign_ed25519_verify_detached( + ed_sig.data(), to_verify.data(), to_verify.size(), ed_pk.data())) + throw std::runtime_error{"message signature failed validation"}; + + if (compressed) { + if (auto decomp = zstd_decompress(raw_data, MAX_PLAINTEXT_MESSAGE_SIZE)) { + data = std::move(*decomp); + } else + throw std::runtime_error{"message decompression failed"}; + } else + data.assign(raw_data.begin(), raw_data.end()); + + return result; +} + +} // namespace session::config::groups + +using namespace session; +using namespace session::config; + +namespace { +groups::Keys& unbox(config_group_keys* conf) { + assert(conf && conf->internals); + return *static_cast(conf->internals); +} +const groups::Keys& unbox(const config_group_keys* conf) { + assert(conf && conf->internals); + return *static_cast(conf->internals); +} + +// Wraps a labmda and, if an exception is thrown, sets an error message in the internals.error +// string and updates the last_error pointer in the outer (C) config_object struct to point at it. +// +// No return value: accepts void and pointer returns; pointer returns will become nullptr on error +template +decltype(auto) wrap_exceptions(config_group_keys* conf, Call&& f) { + using Ret = std::invoke_result_t; + + try { + conf->last_error = nullptr; + return std::invoke(std::forward(f)); + } catch (const std::exception& e) { + copy_c_str(conf->_error_buf, e.what()); + conf->last_error = conf->_error_buf; + } + if constexpr (std::is_pointer_v) + return nullptr; + else + static_assert(std::is_void_v, "Don't know how to return an error value!"); +} + +// Same as above but accepts callbacks with value returns on errors: returns `f()` on success, +// `error_return` on exception +template +Ret wrap_exceptions(config_group_keys* conf, Call&& f, Ret error_return) { + try { + conf->last_error = nullptr; + return std::invoke(std::forward(f)); + } catch (const std::exception& e) { + copy_c_str(conf->_error_buf, e.what()); + conf->last_error = conf->_error_buf; + } + return error_return; +} +} // namespace + +LIBSESSION_C_API int groups_keys_init( + config_group_keys** conf, + const unsigned char* user_ed25519_secretkey, + const unsigned char* group_ed25519_pubkey, + const unsigned char* group_ed25519_secretkey, + config_object* cinfo, + config_object* cmembers, + const unsigned char* dump, + size_t dumplen, + char* error) { + + assert(user_ed25519_secretkey && group_ed25519_pubkey && cinfo && cmembers); + + std::span user_sk{user_ed25519_secretkey, 64}; + std::span group_pk{group_ed25519_pubkey, 32}; + std::optional> group_sk; + if (group_ed25519_secretkey) + group_sk.emplace(group_ed25519_secretkey, 64); + std::optional> dumped; + if (dump && dumplen) + dumped.emplace(dump, dumplen); + + auto& info = *unbox(cinfo); + auto& members = *unbox(cmembers); + auto c_conf = std::make_unique(); + + try { + c_conf->internals = new groups::Keys{user_sk, group_pk, group_sk, dumped, info, members}; + } catch (const std::exception& e) { + if (error) { + std::string msg = e.what(); + if (msg.size() > 255) + msg.resize(255); + std::memcpy(error, msg.c_str(), msg.size() + 1); + } + return SESSION_ERR_INVALID_DUMP; + } + + c_conf->last_error = nullptr; + *conf = c_conf.release(); + return SESSION_ERR_NONE; +} + +LIBSESSION_C_API void groups_keys_free(config_group_keys* conf) { + delete static_cast(conf->internals); + delete conf; +} + +LIBSESSION_EXPORT int16_t groups_keys_storage_namespace(const config_group_keys* conf) { + return static_cast(unbox(conf).storage_namespace()); +} + +LIBSESSION_C_API size_t groups_keys_size(const config_group_keys* conf) { + return unbox(conf).size(); +} + +LIBSESSION_C_API const unsigned char* group_keys_get_key(const config_group_keys* conf, size_t N) { + auto keys = unbox(conf).group_keys(); + if (N >= keys.size()) + return nullptr; + return keys[N].data(); +} + +LIBSESSION_C_API bool groups_keys_is_admin(const config_group_keys* conf) { + return unbox(conf).admin(); +} + +LIBSESSION_C_API bool groups_keys_load_admin_key( + config_group_keys* conf, + const unsigned char* secret, + config_object* info, + config_object* members) { + return wrap_exceptions( + conf, + [&] { + unbox(conf).load_admin_key( + std::span{secret, 32}, + *unbox(info), + *unbox(members)); + return true; + }, + false); +} + +LIBSESSION_C_API bool groups_keys_rekey( + config_group_keys* conf, + config_object* info, + config_object* members, + const unsigned char** out, + size_t* outlen) { + assert(info && members); + auto& keys = unbox(conf); + std::span to_push; + + return wrap_exceptions( + conf, + [&] { + to_push = keys.rekey(*unbox(info), *unbox(members)); + if (out && outlen) { + *out = to_push.data(); + *outlen = to_push.size(); + } + return true; + }, + false); +} + +LIBSESSION_C_API bool groups_keys_pending_config( + const config_group_keys* conf, const unsigned char** out, size_t* outlen) { + assert(out && outlen); + if (auto pending = unbox(conf).pending_config()) { + *out = pending->data(); + *outlen = pending->size(); + return true; + } + return false; +} + +LIBSESSION_C_API bool groups_keys_load_message( + config_group_keys* conf, + const char* msg_hash, + const unsigned char* data, + size_t datalen, + int64_t timestamp_ms, + config_object* info, + config_object* members) { + assert(data && info && members); + return wrap_exceptions( + conf, + [&] { + unbox(conf).load_key_message( + msg_hash, + std::span{data, datalen}, + timestamp_ms, + *unbox(info), + *unbox(members)); + return true; + }, + false); +} + +LIBSESSION_C_API config_string_list* groups_keys_active_hashes(const config_group_keys* conf) { + return make_string_list(unbox(conf).active_hashes()); +} + +LIBSESSION_C_API bool groups_keys_needs_rekey(const config_group_keys* conf) { + return unbox(conf).needs_rekey(); +} + +LIBSESSION_C_API bool groups_keys_needs_dump(const config_group_keys* conf) { + return unbox(conf).needs_dump(); +} + +LIBSESSION_C_API void groups_keys_dump( + config_group_keys* conf, unsigned char** out, size_t* outlen) { + assert(out && outlen); + auto dump = unbox(conf).dump(); + *out = static_cast(std::malloc(dump.size())); + std::memcpy(*out, dump.data(), dump.size()); + *outlen = dump.size(); +} + +LIBSESSION_C_API void groups_keys_encrypt_message( + const config_group_keys* conf, + const unsigned char* plaintext_in, + size_t plaintext_len, + unsigned char** ciphertext_out, + size_t* ciphertext_len) { + assert(plaintext_in && ciphertext_out && ciphertext_len); + + std::vector ciphertext; + try { + ciphertext = unbox(conf).encrypt_message( + std::span{plaintext_in, plaintext_len}); + *ciphertext_out = static_cast(std::malloc(ciphertext.size())); + std::memcpy(*ciphertext_out, ciphertext.data(), ciphertext.size()); + *ciphertext_len = ciphertext.size(); + } catch (...) { + *ciphertext_out = nullptr; + *ciphertext_len = 0; + } +} + +LIBSESSION_C_API bool groups_keys_decrypt_message( + config_group_keys* conf, + const unsigned char* ciphertext_in, + size_t ciphertext_len, + char* session_id, + unsigned char** plaintext_out, + size_t* plaintext_len) { + assert(ciphertext_in && plaintext_out && plaintext_len); + + return wrap_exceptions( + conf, + [&] { + auto [sid, plaintext] = unbox(conf).decrypt_message( + std::span{ciphertext_in, ciphertext_len}); + std::memcpy(session_id, sid.c_str(), sid.size() + 1); + *plaintext_out = static_cast(std::malloc(plaintext.size())); + std::memcpy(*plaintext_out, plaintext.data(), plaintext.size()); + *plaintext_len = plaintext.size(); + return true; + }, + false); +} + +LIBSESSION_C_API bool groups_keys_key_supplement( + config_group_keys* conf, + const char* const* sids, + size_t sids_len, + unsigned char** message, + size_t* message_len) { + assert(sids && message && message_len); + + std::vector session_ids; + for (size_t i = 0; i < sids_len; i++) + session_ids.emplace_back(sids[i]); + + return wrap_exceptions( + conf, + [&] { + auto msg = unbox(conf).key_supplement(session_ids); + *message = static_cast(malloc(msg.size())); + *message_len = msg.size(); + std::memcpy(*message, msg.data(), msg.size()); + return true; + }, + false); +} + +LIBSESSION_EXPORT int groups_keys_current_generation(config_group_keys* conf) { + return unbox(conf).current_generation(); +} + +LIBSESSION_C_API bool groups_keys_swarm_make_subaccount_flags( + config_group_keys* conf, + const char* session_id, + bool write, + bool del, + unsigned char* sign_value) { + assert(sign_value); + return wrap_exceptions( + conf, + [&] { + auto val = unbox(conf).swarm_make_subaccount(session_id, write, del); + assert(val.size() == 100); + std::memcpy(sign_value, val.data(), val.size()); + return true; + }, + false); +} + +LIBSESSION_C_API bool groups_keys_swarm_make_subaccount( + config_group_keys* conf, const char* session_id, unsigned char* sign_value) { + return groups_keys_swarm_make_subaccount_flags(conf, session_id, true, false, sign_value); +} + +LIBSESSION_C_API bool groups_keys_swarm_verify_subaccount_flags( + const char* group_id, + const unsigned char* session_ed25519_secretkey, + const unsigned char* signing_value, + bool write, + bool del) { + try { + return groups::Keys::swarm_verify_subaccount( + group_id, + std::span{session_ed25519_secretkey, 64}, + std::span{signing_value, 100}, + write, + del); + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool groups_keys_swarm_verify_subaccount( + const char* group_id, + const unsigned char* session_ed25519_secretkey, + const unsigned char* signing_value) { + try { + return groups::Keys::swarm_verify_subaccount( + group_id, + std::span{session_ed25519_secretkey, 64}, + std::span{signing_value, 100}); + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool groups_keys_swarm_subaccount_sign( + config_group_keys* conf, + const unsigned char* msg, + size_t msg_len, + const unsigned char* signing_value, + + char* subaccount, + char* subaccount_sig, + char* signature) { + assert(msg && signing_value && subaccount && subaccount_sig && signature); + return wrap_exceptions( + conf, + [&] { + auto auth = unbox(conf).swarm_subaccount_sign( + std::span{msg, msg_len}, + std::span{signing_value, 100}); + assert(auth.subaccount.size() == 48); + assert(auth.subaccount_sig.size() == 88); + assert(auth.signature.size() == 88); + std::memcpy(subaccount, auth.subaccount.c_str(), auth.subaccount.size() + 1); + std::memcpy( + subaccount_sig, + auth.subaccount_sig.c_str(), + auth.subaccount_sig.size() + 1); + std::memcpy(signature, auth.signature.c_str(), auth.signature.size() + 1); + return true; + }, + false); +} + +LIBSESSION_C_API bool groups_keys_swarm_subaccount_sign_binary( + config_group_keys* conf, + const unsigned char* msg, + size_t msg_len, + const unsigned char* signing_value, + + unsigned char* subaccount, + unsigned char* subaccount_sig, + unsigned char* signature) { + assert(msg && signing_value && subaccount && subaccount_sig && signature); + return wrap_exceptions( + conf, + [&] { + auto auth = unbox(conf).swarm_subaccount_sign( + std::span{msg, msg_len}, + std::span{signing_value, 100}, + true); + assert(auth.subaccount.size() == 36); + assert(auth.subaccount_sig.size() == 64); + assert(auth.signature.size() == 64); + std::memcpy(subaccount, auth.subaccount.data(), 36); + std::memcpy(subaccount_sig, auth.subaccount_sig.data(), 64); + std::memcpy(signature, auth.signature.data(), 64); + return true; + }, + false); +} + +LIBSESSION_C_API bool groups_keys_swarm_subaccount_token_flags( + config_group_keys* conf, + const char* session_id, + bool write, + bool del, + unsigned char* token) { + return wrap_exceptions( + conf, + [&] { + auto tok = unbox(conf).swarm_subaccount_token(session_id, write, del); + assert(tok.size() == 36); + std::memcpy(token, tok.data(), 36); + return true; + }, + false); +} + +LIBSESSION_C_API bool groups_keys_swarm_subaccount_token( + config_group_keys* conf, const char* session_id, unsigned char* token) { + return groups_keys_swarm_subaccount_token_flags(conf, session_id, true, false, token); +} diff --git a/src/config/groups/members.cpp b/src/config/groups/members.cpp new file mode 100644 index 00000000..ca515e66 --- /dev/null +++ b/src/config/groups/members.cpp @@ -0,0 +1,497 @@ +#include "session/config/groups/members.hpp" + +#include + +#include "../internal.hpp" +#include "session/config/groups/members.h" + +namespace session::config::groups { + +Members::Members( + std::span ed25519_pubkey, + std::optional> ed25519_secretkey, + std::optional> dumped) { + init(dumped, ed25519_pubkey, ed25519_secretkey); +} + +void Members::extra_data(oxenc::bt_dict_producer&& extra) const { + if (pending_send_ids.empty()) + return; + + extra.append_list("pending_send_ids").extend(pending_send_ids.begin(), pending_send_ids.end()); +} + +void Members::load_extra_data(oxenc::bt_dict_consumer&& extra) { + if (extra.skip_until("pending_send_ids")) { + auto lst = extra.consume_list_consumer(); + while (!lst.is_finished()) + pending_send_ids.insert(lst.consume_string()); + } +} + +std::optional Members::get(std::string_view pubkey_hex) const { + std::string pubkey = session_id_to_bytes(pubkey_hex); + + auto* info_dict = data["m"][pubkey].dict(); + if (!info_dict) + return std::nullopt; + + auto sid = std::string{pubkey_hex}; + auto result = std::make_optional(sid); + result->load(*info_dict); + + return result; +} + +member Members::get_or_construct(std::string_view pubkey_hex) const { + if (auto maybe = get(pubkey_hex)) + return *std::move(maybe); + + return member{std::string{pubkey_hex}}; +} + +void Members::set(const member& mem) { + + std::string pk = session_id_to_bytes(mem.session_id); + auto info = data["m"][pk]; + + // Always set the name, even if empty, to keep the dict from getting pruned if there are no + // other entries. + info["n"] = mem.name.substr(0, member::MAX_NAME_LENGTH); + + set_pair_if( + mem.profile_picture, + info["p"], + mem.profile_picture.url, + info["q"], + mem.profile_picture.key); + + set_flag(info["A"], mem.admin); + set_positive_int(info["P"], mem.promotion_status); + set_positive_int(info["I"], mem.admin ? 0 : mem.invite_status); + set_flag(info["s"], mem.supplement); + set_positive_int(info["R"], mem.removed_status); + + // When adding a new member, if their `invite_status` is `STATUS_NOT_SENT` then we should + // add them to the `pending_send_ids` until they are given a new status + if ((!mem.admin && mem.invite_status == STATUS_NOT_SENT) || + (mem.admin && mem.promotion_status == STATUS_NOT_SENT)) + set_pending_send(mem.session_id, true); + else if ( + (!mem.admin && mem.invite_status != STATUS_NOT_SENT) || + (mem.admin && mem.promotion_status != STATUS_NOT_SENT)) + set_pending_send(mem.session_id, false); +} + +void member::load(const dict& info_dict) { + name = maybe_string(info_dict, "n").value_or(""); + + auto url = maybe_string(info_dict, "p"); + auto key = maybe_vector(info_dict, "q"); + if (url && key && !url->empty() && key->size() == 32) { + profile_picture.url = std::move(*url); + profile_picture.key = std::move(*key); + } else { + profile_picture.clear(); + } + + admin = maybe_int(info_dict, "A").value_or(0); + invite_status = admin ? 0 : maybe_int(info_dict, "I").value_or(0); + promotion_status = maybe_int(info_dict, "P").value_or(0); + removed_status = maybe_int(info_dict, "R").value_or(0); + supplement = invite_status > 0 && !(admin || promotion_status > 0) + ? maybe_int(info_dict, "s").value_or(0) + : 0; +} + +/// Load _val from the current iterator position; if it is invalid, skip to the next key until we +/// find one that is valid (or hit the end). +void Members::iterator::_load_info() { + while (_it != _members->end()) { + if (_it->first.size() == 33) { + if (auto* info_dict = std::get_if(&_it->second)) { + _val = std::make_shared(oxenc::to_hex(_it->first)); + _val->load(*info_dict); + return; + } + } + + // We found something we don't understand (wrong pubkey size, or not a dict value) so skip + // it. + ++_it; + } +} + +bool Members::iterator::operator==(const iterator& other) const { + if (!_members && !other._members) + return true; // Both are end tombstones + if (!other._members) + // other is an "end" tombstone: return whether we are at the end + return _it == _members->end(); + if (!_members) + // we are an "end" tombstone: return whether the other one is at the end + return other._it == other._members->end(); + return _it == other._it; +} + +bool Members::iterator::done() const { + return !_members || _it == _members->end(); +} + +Members::iterator& Members::iterator::operator++() { + ++_it; + _load_info(); + return *this; +} + +bool Members::erase(std::string_view session_id) { + std::string pk = session_id_to_bytes(session_id); + auto info = data["m"][pk]; + bool ret = info.exists(); + info.erase(); + + set_pending_send(std::string(session_id), false); + + return ret; +} + +size_t Members::size() const { + if (auto d = data["m"].dict()) + return d->size(); + return 0; +} + +bool Members::has_pending_send(std::string pubkey_hex) const { + return pending_send_ids.count(pubkey_hex); +} + +void Members::set_pending_send(std::string pubkey_hex, bool pending) { + bool changed = false; + if (pending) + changed = pending_send_ids.insert(pubkey_hex).second; + else + changed = pending_send_ids.erase(pubkey_hex); + if (changed) + _needs_dump = true; +} + +member::member(std::string sid) : session_id{std::move(sid)} { + check_session_id(session_id); +} + +member::member(const config_group_member& m) : session_id{m.session_id, 66} { + assert(std::strlen(m.name) <= MAX_NAME_LENGTH); + name = m.name; + assert(std::strlen(m.profile_pic.url) <= profile_pic::MAX_URL_LENGTH); + if (std::strlen(m.profile_pic.url)) { + profile_picture.url = m.profile_pic.url; + profile_picture.key.assign(m.profile_pic.key, m.profile_pic.key + 32); + } + admin = m.admin; + invite_status = + (m.invited == STATUS_SENT || m.invited == STATUS_FAILED || m.invited == STATUS_NOT_SENT) + ? m.invited + : 0; + promotion_status = (m.promoted == STATUS_SENT || m.promoted == STATUS_FAILED || + m.promoted == STATUS_NOT_SENT) + ? m.promoted + : 0; + removed_status = (m.removed == REMOVED_MEMBER || m.removed == REMOVED_MEMBER_AND_MESSAGES) + ? m.removed + : 0; + supplement = m.supplement; +} + +void member::into(config_group_member& m) const { + std::memcpy(m.session_id, session_id.data(), 67); + copy_c_str(m.name, name); + if (profile_picture) { + copy_c_str(m.profile_pic.url, profile_picture.url); + std::memcpy(m.profile_pic.key, profile_picture.key.data(), 32); + } else { + copy_c_str(m.profile_pic.url, ""); + } + m.admin = admin; + static_assert(groups::STATUS_SENT == ::STATUS_SENT); + static_assert(groups::STATUS_FAILED == ::STATUS_FAILED); + static_assert(groups::STATUS_NOT_SENT == ::STATUS_NOT_SENT); + static_assert( + static_cast(groups::member::Status::invite_unknown) == + ::GROUP_MEMBER_STATUS_INVITE_UNKNOWN); + static_assert( + static_cast(groups::member::Status::invite_not_sent) == + ::GROUP_MEMBER_STATUS_INVITE_NOT_SENT); + static_assert( + static_cast(groups::member::Status::invite_sending) == + ::GROUP_MEMBER_STATUS_INVITE_SENDING); + static_assert( + static_cast(groups::member::Status::invite_failed) == + ::GROUP_MEMBER_STATUS_INVITE_FAILED); + static_assert( + static_cast(groups::member::Status::invite_sent) == + ::GROUP_MEMBER_STATUS_INVITE_SENT); + static_assert( + static_cast(groups::member::Status::invite_accepted) == + ::GROUP_MEMBER_STATUS_INVITE_ACCEPTED); + static_assert( + static_cast(groups::member::Status::promotion_unknown) == + ::GROUP_MEMBER_STATUS_PROMOTION_UNKNOWN); + static_assert( + static_cast(groups::member::Status::promotion_not_sent) == + ::GROUP_MEMBER_STATUS_PROMOTION_NOT_SENT); + static_assert( + static_cast(groups::member::Status::promotion_sending) == + ::GROUP_MEMBER_STATUS_PROMOTION_SENDING); + static_assert( + static_cast(groups::member::Status::promotion_failed) == + ::GROUP_MEMBER_STATUS_PROMOTION_FAILED); + static_assert( + static_cast(groups::member::Status::promotion_sent) == + ::GROUP_MEMBER_STATUS_PROMOTION_SENT); + static_assert( + static_cast(groups::member::Status::promotion_accepted) == + ::GROUP_MEMBER_STATUS_PROMOTION_ACCEPTED); + static_assert( + static_cast(groups::member::Status::removed_unknown) == + ::GROUP_MEMBER_STATUS_REMOVED_UNKNOWN); + static_assert( + static_cast(groups::member::Status::removed) == ::GROUP_MEMBER_STATUS_REMOVED); + static_assert( + static_cast(groups::member::Status::removed_including_messages) == + ::GROUP_MEMBER_STATUS_REMOVED_MEMBER_AND_MESSAGES); + m.invited = invite_status; + m.promoted = promotion_status; + m.removed = removed_status; + m.supplement = supplement; +} + +void member::set_name(std::string n) { + if (n.size() > MAX_NAME_LENGTH) + throw std::invalid_argument{"Invalid member name: exceeds maximum length"}; + name = std::move(n); +} + +void member::set_name_truncated(std::string n) { + set_name(utf8_truncate(std::move(n), MAX_NAME_LENGTH)); +} + +} // namespace session::config::groups + +using namespace session; +using namespace session::config; + +LIBSESSION_C_API int groups_members_init( + config_object** conf, + const unsigned char* ed25519_pubkey, + const unsigned char* ed25519_secretkey, + const unsigned char* dump, + size_t dumplen, + char* error) { + return c_group_wrapper_init( + conf, ed25519_pubkey, ed25519_secretkey, dump, dumplen, error); +} + +LIBSESSION_C_API bool groups_members_get( + config_object* conf, config_group_member* member, const char* session_id) { + return wrap_exceptions( + conf, + [&] { + if (auto c = unbox(conf)->get(session_id)) { + c->into(*member); + return true; + } + return false; + }, + false); +} + +LIBSESSION_C_API bool groups_members_get_or_construct( + config_object* conf, config_group_member* member, const char* session_id) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->get_or_construct(session_id).into(*member); + return true; + }, + false); +} + +LIBSESSION_C_API bool groups_members_set(config_object* conf, const config_group_member* member) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set(groups::member{*member}); + return true; + }, + false); +} + +LIBSESSION_C_API GROUP_MEMBER_STATUS +groups_members_get_status(const config_object* conf, const config_group_member* member) { + try { + auto m = groups::member{*member}; + return static_cast(unbox(conf)->get_status(m)); + } catch (...) { + return GROUP_MEMBER_STATUS_INVITE_NOT_SENT; + } +} + +LIBSESSION_C_API bool groups_members_set_invite_sent(config_object* conf, const char* session_id) { + try { + if (auto m = unbox(conf)->get(session_id)) { + m->set_invite_sent(); + unbox(conf)->set(*m); + return true; + } + return false; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool groups_members_set_invite_not_sent( + config_object* conf, const char* session_id) { + try { + if (auto m = unbox(conf)->get(session_id)) { + m->set_invite_not_sent(); + unbox(conf)->set(*m); + return true; + } + return false; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool groups_members_set_invite_failed( + config_object* conf, const char* session_id) { + try { + if (auto m = unbox(conf)->get(session_id)) { + m->set_invite_failed(); + unbox(conf)->set(*m); + return true; + } + return false; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool groups_members_set_invite_accepted( + config_object* conf, const char* session_id) { + try { + if (auto m = unbox(conf)->get(session_id)) { + m->set_invite_accepted(); + unbox(conf)->set(*m); + return true; + } + return false; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool groups_members_set_promoted(config_object* conf, const char* session_id) { + try { + if (auto m = unbox(conf)->get(session_id)) { + m->set_promoted(); + unbox(conf)->set(*m); + return true; + } + return false; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool groups_members_set_promotion_sent( + config_object* conf, const char* session_id) { + try { + if (auto m = unbox(conf)->get(session_id)) { + m->set_promotion_sent(); + unbox(conf)->set(*m); + return true; + } + return false; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool groups_members_set_promotion_failed( + config_object* conf, const char* session_id) { + try { + if (auto m = unbox(conf)->get(session_id)) { + m->set_promotion_failed(); + unbox(conf)->set(*m); + return true; + } + return false; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool groups_members_set_promotion_accepted( + config_object* conf, const char* session_id) { + try { + if (auto m = unbox(conf)->get(session_id)) { + m->set_promotion_accepted(); + unbox(conf)->set(*m); + return true; + } + return false; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool groups_members_set_removed( + config_object* conf, const char* session_id, bool messages) { + try { + if (auto m = unbox(conf)->get(session_id)) { + m->set_removed(messages); + unbox(conf)->set(*m); + return true; + } + return false; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool groups_members_erase(config_object* conf, const char* session_id) { + try { + return unbox(conf)->erase(session_id); + } catch (...) { + return false; + } +} + +LIBSESSION_C_API size_t groups_members_size(const config_object* conf) { + return unbox(conf)->size(); +} + +LIBSESSION_C_API groups_members_iterator* groups_members_iterator_new(const config_object* conf) { + auto* it = new groups_members_iterator{}; + it->_internals = new groups::Members::iterator{unbox(conf)->begin()}; + return it; +} + +LIBSESSION_C_API void groups_members_iterator_free(groups_members_iterator* it) { + delete static_cast(it->_internals); + delete it; +} + +LIBSESSION_C_API bool groups_members_iterator_done( + groups_members_iterator* it, config_group_member* c) { + auto& real = *static_cast(it->_internals); + if (real.done()) + return true; + real->into(*c); + return false; +} + +LIBSESSION_C_API void groups_members_iterator_advance(groups_members_iterator* it) { + ++*static_cast(it->_internals); +} diff --git a/src/config/internal.cpp b/src/config/internal.cpp index 48ba1ec2..a81446ef 100644 --- a/src/config/internal.cpp +++ b/src/config/internal.cpp @@ -2,26 +2,36 @@ #include #include +#include #include +#include #include #include namespace session::config { -void check_session_id(std::string_view session_id) { - if (!(session_id.size() == 66 && oxenc::is_hex(session_id) && session_id[0] == '0' && - session_id[1] == '5')) +void check_session_id(std::string_view session_id, std::string_view prefix) { + if (!(session_id.size() == 64 + prefix.size() && oxenc::is_hex(session_id) && + session_id.substr(0, prefix.size()) == prefix)) throw std::invalid_argument{ - "Invalid session ID: expected 66 hex digits starting with 05; got " + - std::string{session_id}}; + "Invalid session ID: expected 66 hex digits starting with " + std::string{prefix} + + "; got " + std::string{session_id}}; } -std::string session_id_to_bytes(std::string_view session_id) { - check_session_id(session_id); +std::string session_id_to_bytes(std::string_view session_id, std::string_view prefix) { + check_session_id(session_id, prefix); return oxenc::from_hex(session_id); } +std::array session_id_pk(std::string_view session_id, std::string_view prefix) { + check_session_id(session_id, prefix); + std::array pk; + session_id.remove_prefix(2); + oxenc::from_hex(session_id.begin(), session_id.end(), pk.begin()); + return pk; +} + void check_encoded_pubkey(std::string_view pk) { if (!((pk.size() == 64 && oxenc::is_hex(pk)) || ((pk.size() == 43 || (pk.size() == 44 && pk.back() == '=')) && oxenc::is_base64(pk)) || @@ -29,8 +39,8 @@ void check_encoded_pubkey(std::string_view pk) { throw std::invalid_argument{"Invalid encoded pubkey: expected hex, base32z or base64"}; } -ustring decode_pubkey(std::string_view pk) { - session::ustring pubkey; +std::vector decode_pubkey(std::string_view pk) { + std::vector pubkey; pubkey.reserve(32); if (pk.size() == 64 && oxenc::is_hex(pk)) oxenc::from_hex(pk.begin(), pk.end(), std::back_inserter(pubkey)); @@ -83,10 +93,13 @@ std::optional maybe_sv(const session::config::dict& d, const c return std::nullopt; } -std::optional maybe_ustring(const session::config::dict& d, const char* key) { - std::optional result; +std::optional> maybe_vector( + const session::config::dict& d, const char* key) { + std::optional> result; if (auto* s = maybe_scalar(d, key)) - result.emplace(reinterpret_cast(s->data()), s->size()); + result.emplace( + reinterpret_cast(s->data()), + reinterpret_cast(s->data()) + s->size()); return result; } @@ -125,4 +138,101 @@ void set_nonempty_str(ConfigBase::DictFieldProxy&& field, std::string_view val) field.erase(); } +/// Writes all the dict elements in `[it, E)` into `out`; E is whichever of `end` or an element with +/// a key >= `until` comes first. +oxenc::bt_dict::iterator append_unknown( + oxenc::bt_dict_producer& out, + oxenc::bt_dict::iterator it, + oxenc::bt_dict::iterator end, + std::string_view until) { + for (; it != end && it->first < until; ++it) + out.append_bt(it->first, it->second); + + assert(!(it != end && it->first == until)); + return it; +} + +/// Extracts and unknown keys in the top-level dict into `unknown` that have keys (strictly) +/// between previous and until. +void load_unknowns( + oxenc::bt_dict& unknown, + oxenc::bt_dict_consumer& in, + std::string_view previous, + std::string_view until) { + while (!in.is_finished() && in.key() < until) { + std::string key{in.key()}; + if (key <= previous || (!unknown.empty() && key <= unknown.rbegin()->first)) + throw oxenc::bt_deserialize_invalid{"top-level keys are out of order"}; + if (in.is_string()) + unknown.emplace_hint(unknown.end(), std::move(key), in.consume_string()); + else if (in.is_negative_integer()) + unknown.emplace_hint(unknown.end(), std::move(key), in.consume_integer()); + else if (in.is_integer()) + unknown.emplace_hint(unknown.end(), std::move(key), in.consume_integer()); + else if (in.is_list()) + unknown.emplace_hint(unknown.end(), std::move(key), in.consume_list()); + else if (in.is_dict()) + unknown.emplace_hint(unknown.end(), std::move(key), in.consume_dict()); + else + throw oxenc::bt_deserialize_invalid{"invalid bencoded value type"}; + } +} + +namespace { + struct zstd_decomp_freer { + void operator()(ZSTD_DStream* z) const { ZSTD_freeDStream(z); } + }; + + using zstd_decomp_ptr = std::unique_ptr; +} // namespace + +std::vector zstd_compress( + std::span data, int level, std::span prefix) { + std::vector compressed; + if (prefix.empty()) + compressed.resize(ZSTD_compressBound(data.size())); + else { + compressed.resize(prefix.size() + ZSTD_compressBound(data.size())); + std::copy(prefix.begin(), prefix.end(), compressed.begin()); + } + auto size = ZSTD_compress( + compressed.data() + prefix.size(), + compressed.size() - prefix.size(), + data.data(), + data.size(), + level); + if (ZSTD_isError(size)) + throw std::runtime_error{"Compression failed: " + std::string{ZSTD_getErrorName(size)}}; + + compressed.resize(prefix.size() + size); + return compressed; +} + +std::optional> zstd_decompress( + std::span data, size_t max_size) { + zstd_decomp_ptr z_decompressor{ZSTD_createDStream()}; + auto* zds = z_decompressor.get(); + + ZSTD_initDStream(zds); + ZSTD_inBuffer input{/*.src=*/data.data(), /*.size=*/data.size(), /*.pos=*/0}; + std::array out_buf; + ZSTD_outBuffer output{/*.dst=*/out_buf.data(), /*.size=*/out_buf.size(), /*.pos=*/0}; + + std::vector decompressed; + + size_t ret; + do { + output.pos = 0; + if (ret = ZSTD_decompressStream(zds, &output, &input); ZSTD_isError(ret)) + return std::nullopt; + + if (max_size > 0 && decompressed.size() + output.pos > max_size) + return std::nullopt; + + decompressed.insert(decompressed.end(), out_buf.begin(), out_buf.begin() + output.pos); + } while (ret > 0 || input.pos < input.size); + + return decompressed; +} + } // namespace session::config diff --git a/src/config/internal.hpp b/src/config/internal.hpp index 5bbab837..74cc31fd 100644 --- a/src/config/internal.hpp +++ b/src/config/internal.hpp @@ -1,31 +1,26 @@ #pragma once +#include + #include #include +#include +#include +#include "session/config/base.h" #include "session/config/base.hpp" #include "session/config/error.h" #include "session/types.hpp" namespace session::config { -template -[[nodiscard]] int c_wrapper_init( - config_object** conf, - const unsigned char* ed25519_secretkey_bytes, - const unsigned char* dumpstr, - size_t dumplen, - char* error) { - assert(ed25519_secretkey_bytes); - ustring_view ed25519_secretkey{ed25519_secretkey_bytes, 32}; - auto c_conf = std::make_unique(); +template +[[nodiscard]] int c_wrapper_init_generic(config_object** conf, char* error, Args&&... args) { auto c = std::make_unique>(); - std::optional dump; - if (dumpstr && dumplen) - dump.emplace(dumpstr, dumplen); + auto c_conf = std::make_unique(); try { - c->config = std::make_unique(ed25519_secretkey, dump); + c->config = std::make_unique(std::forward(args)...); } catch (const std::exception& e) { if (error) { std::string msg = e.what(); @@ -42,19 +37,98 @@ template return SESSION_ERR_NONE; } -template -void copy_c_str(char (&dest)[N], std::string_view src) { - if (src.size() >= N) - src.remove_suffix(src.size() - N - 1); - std::memcpy(dest, src.data(), src.size()); - dest[src.size()] = 0; +template +[[nodiscard]] int c_wrapper_init( + config_object** conf, + const unsigned char* ed25519_secretkey_bytes, + const unsigned char* dumpstr, + size_t dumplen, + char* error) { + assert(ed25519_secretkey_bytes); + std::span ed25519_secretkey{ed25519_secretkey_bytes, 64}; + std::optional> dump; + if (dumpstr && dumplen) + dump.emplace(dumpstr, dumplen); + return c_wrapper_init_generic(conf, error, ed25519_secretkey, dump); } -// Throws std::invalid_argument if session_id doesn't look valid -void check_session_id(std::string_view session_id); +template +[[nodiscard]] int c_group_wrapper_init( + config_object** conf, + const unsigned char* ed25519_pubkey_bytes, + const unsigned char* ed25519_secretkey_bytes, + const unsigned char* dump_bytes, + size_t dumplen, + char* error) { + + assert(ed25519_pubkey_bytes); + + std::span ed25519_pubkey{ed25519_pubkey_bytes, 32}; + std::optional> ed25519_secretkey; + if (ed25519_secretkey_bytes) + ed25519_secretkey.emplace(ed25519_secretkey_bytes, 64); + std::optional> dump; + if (dump_bytes && dumplen) + dump.emplace(dump_bytes, dumplen); + + return c_wrapper_init_generic(conf, error, ed25519_pubkey, ed25519_secretkey, dump); +} + +// Copies a container of std::strings into a self-contained malloc'ed config_string_list for +// returning to C code with the strings and pointers of the string list in the same malloced space, +// hanging off the end (so that everything, including string values, is freed by a single `free()`). +template < + typename Container, + typename = std::enable_if_t>> +config_string_list* make_string_list(Container vals) { + // We malloc space for the config_string_list struct itself, plus the required number of string + // pointers to store its strings, and the space to actually contain a copy of the string data. + // When we're done, the malloced memory we grab is going to look like this: + // + // {config_string_list} + // {pointer1}{pointer2}... + // {string data 1\0}{string data 2\0}... + // + // where config_string_list.value points at the beginning of {pointer1}, and each pointerN + // points at the beginning of the {string data N\0} c string. + // + // Since we malloc it all at once, when the user frees it, they also free the entire thing. + size_t sz = sizeof(config_string_list) + vals.size() * sizeof(char*); + // plus, for each string, the space to store it (including the null) + for (auto& v : vals) + sz += v.size() + 1; + + auto* ret = static_cast(std::malloc(sz)); + ret->len = vals.size(); + + static_assert(alignof(config_string_list) >= alignof(char*)); + + // value points at the space immediately after the struct itself, which is the first element in + // the array of c string pointers. + ret->value = reinterpret_cast(ret + 1); + char** next_ptr = ret->value; + char* next_str = reinterpret_cast(next_ptr + ret->len); + + for (const auto& v : vals) { + *(next_ptr++) = next_str; + std::memcpy(next_str, v.c_str(), v.size() + 1); + next_str += v.size() + 1; + } + + return ret; +} + +// Throws std::invalid_argument if session_id doesn't look valid. Can optionally be passed a prefix +// byte for id's that aren't starting with 0x05 (e.g. 0x03 for non-legacy group ids). +void check_session_id(std::string_view session_id, std::string_view prefix = "05"); // Checks the session_id (throwing if invalid) then returns it as bytes -std::string session_id_to_bytes(std::string_view session_id); +std::string session_id_to_bytes(std::string_view session_id, std::string_view prefix = "05"); + +// Checks the session_id (throwing if invalid) then returns it as bytes, omitting the 05 (or +// whatever) prefix, which is a pubkey (x25519 for 05 session_ids, ed25519 for other prefixes). +std::array session_id_pk( + std::string_view session_id, std::string_view prefix = "05"); // Validates an open group pubkey; we accept it in hex, base32z, or base64 (padded or unpadded). // Throws std::invalid_argument if invalid. @@ -62,7 +136,7 @@ void check_encoded_pubkey(std::string_view pk); // Takes a 32-byte pubkey value encoded as hex, base32z, or base64 and returns the decoded 32 bytes. // Throws if invalid. -ustring decode_pubkey(std::string_view pk); +std::vector decode_pubkey(std::string_view pk); // Modifies a string to be (ascii) lowercase. void make_lc(std::string& s); @@ -76,8 +150,10 @@ std::optional maybe_int(const session::config::dict& d, const char* key // Digs into a config `dict` to get out a string; nullopt if not there (or not string) std::optional maybe_string(const session::config::dict& d, const char* key); -// Digs into a config `dict` to get out a ustring; nullopt if not there (or not string) -std::optional maybe_ustring(const session::config::dict& d, const char* key); +// Digs into a config `dict` to get out a std::vector; nullopt if not there (or not +// string) +std::optional> maybe_vector( + const session::config::dict& d, const char* key); // Digs into a config `dict` to get out a string view; nullopt if not there (or not string). The // string view is only valid as long as the dict stays unchanged. @@ -113,4 +189,30 @@ void set_pair_if( } } +oxenc::bt_dict::iterator append_unknown( + oxenc::bt_dict_producer& out, + oxenc::bt_dict::iterator it, + oxenc::bt_dict::iterator end, + std::string_view until); + +/// Extracts and unknown keys in the top-level dict into `unknown` that have keys (strictly) +/// between previous and until. +void load_unknowns( + oxenc::bt_dict& unknown, + oxenc::bt_dict_consumer& in, + std::string_view previous, + std::string_view until); + +/// ZSTD-compresses a value. `prefix` can be prepended on the returned value, if needed. Throws on +/// serious error. +std::vector zstd_compress( + std::span data, + int level = 1, + std::span prefix = {}); + +/// ZSTD-decompresses a value. Returns nullopt if decompression fails. If max_size is non-zero +/// then this returns nullopt if the decompressed size would exceed that limit. +std::optional> zstd_decompress( + std::span data, size_t max_size = 0); + } // namespace session::config diff --git a/src/config/protos.cpp b/src/config/protos.cpp new file mode 100644 index 00000000..bfd5d7af --- /dev/null +++ b/src/config/protos.cpp @@ -0,0 +1,186 @@ +#include "session/config/protos.hpp" + +#include +#include + +#include +#include +#include + +#include "SessionProtos.pb.h" +#include "WebSocketResources.pb.h" +#include "session/session_encrypt.hpp" + +namespace session::config::protos { + +namespace { + + SessionProtos::SharedConfigMessage_Kind encode_namespace(session::config::Namespace t) { + switch (t) { + case session::config::Namespace::UserProfile: + return SessionProtos::SharedConfigMessage_Kind_USER_PROFILE; + case session::config::Namespace::Contacts: + return SessionProtos::SharedConfigMessage_Kind_CONTACTS; + case session::config::Namespace::ConvoInfoVolatile: + return SessionProtos::SharedConfigMessage_Kind_CONVO_INFO_VOLATILE; + case session::config::Namespace::UserGroups: + return SessionProtos::SharedConfigMessage_Kind_USER_GROUPS; + default: + throw std::invalid_argument{ + "Error: cannot encode invalid SharedConfigMessage type"}; + } + } + +} // namespace + +std::vector wrap_config( + std::span ed25519_sk, + std::span data, + int64_t seqno, + config::Namespace t) { + std::array tmp_sk; + if (ed25519_sk.size() == 32) { + std::array ignore_pk; + crypto_sign_ed25519_seed_keypair(ignore_pk.data(), tmp_sk.data(), ed25519_sk.data()); + ed25519_sk = {tmp_sk.data(), 64}; + } else if (ed25519_sk.size() != 64) + throw std::invalid_argument{ + "Error: ed25519_sk is not the expected 64-byte Ed25519 secret key"}; + + std::array my_xpk; + if (0 != crypto_sign_ed25519_pk_to_curve25519(my_xpk.data(), ed25519_sk.data() + 32)) + throw std::invalid_argument{ + "Failed to convert Ed25519 pubkey to X25519; invalid secret key?"}; + + if (static_cast(t) > 5) + throw std::invalid_argument{"Error: received invalid outgoing SharedConfigMessage type"}; + + // Wrap in a SharedConfigMessage inside a Content + SessionProtos::Content config{}; + auto& shconf = *config.mutable_sharedconfigmessage(); + shconf.set_kind(encode_namespace(t)); + shconf.set_seqno(seqno); + *shconf.mutable_data() = to_string_view(data); + + // Then we serialize that, pad it, and encrypt it. Copying this relevant comment from the + // Session codebase (the comment itself git blames to Signal): + // NOTE: This is dumb. + auto shared_conf = config.SerializeAsString(); + // Okay now let's talk about padding. Remember, though: + // NOTE: This is dumb. + // Okay so to be more specific, padding adds a 0x80 byte followed by any number (including 0) of + // 0x00 bytes. The 0x80 byte, however, is always required (so there is always at least one + // padding byte); for DMs, this gets pushed up to the next multiple of 160, hence the final + // padded length we want, mathematically, is ⌈(x+1)/160⌉ * 160. With integer division, + // ceil(a/b) is (a+b-1)/b, so for a=x+1 we get: (x+1+b-1)/b*b = (x+b)/b*b = (x/b + 1)*b. + // +#if 0 + const size_t unpadded_size = shared_conf.size(); + const size_t padded_size = (unpadded_size / 160 + 1) * 160; + assert(padded_size > shared_conf.size()); + shared_conf.resize(padded_size); + shared_conf[unpadded_size] = 0x80; +#else + // But this is all moot for a config message which is *already* padded to a multiple of 256, so + // just tack on the 0x80 and no 0x00s rather than making it bigger still. + shared_conf += '\x80'; +#endif + + // Now we encrypt using the session protocol encryption, but with sender == recipient == + // ourself. This is unnecessary because the inner content is already encrypted with a value + // derived from our private key, but old Session clients expect this. + // NOTE: This is dumb. + auto enc_shared_conf = encrypt_for_recipient_deterministic( + ed25519_sk, {my_xpk.data(), my_xpk.size()}, to_span(shared_conf)); + + // This is the point in session client code where this value got base64-encoded, passed to + // another function, which then base64-decoded that value to put into the envelope. We're going + // to skip that step here: fingers crossed!!! + // enc_shared_conf = oxenc::from_base64(oxenc::to_base64(enc_shared_conf)); + // NOTE: This is dumb. + + // Now we just keep on trucking with more protobuf: + auto envelope = SessionProtos::Envelope(); + *envelope.mutable_content() = to_string_view(enc_shared_conf); + envelope.set_timestamp(1); // Old session clients with their own unwrapping require this > 0 + envelope.set_type(SessionProtos::Envelope_Type::Envelope_Type_SESSION_MESSAGE); + + // And more protobuf (even though this no one cares about anything other than the body in this + // one): + // NOTE: This is dumb. + auto webreq = WebSocketProtos::WebSocketRequestMessage(); + webreq.set_verb(""); + webreq.set_path(""); + webreq.set_requestid(0); + *webreq.mutable_body() = envelope.SerializeAsString(); + + // And then yet more protobuf (even though this no one cares about anything other than the body + // in this one, again): + // NOTE: This is dumb. + auto msg = WebSocketProtos::WebSocketMessage(); + msg.set_type(WebSocketProtos::WebSocketMessage_Type_REQUEST); + *msg.mutable_request() = webreq; + + return to_vector(msg.SerializeAsString()); +} + +std::vector unwrap_config( + std::span ed25519_sk, + std::span data, + config::Namespace ns) { + // Hurray, we get to undo everything from the above! + + std::array tmp_sk; + if (ed25519_sk.size() == 32) { + std::array ignore_pk; + crypto_sign_ed25519_seed_keypair(ignore_pk.data(), tmp_sk.data(), ed25519_sk.data()); + ed25519_sk = {tmp_sk.data(), 64}; + } else if (ed25519_sk.size() != 64) + throw std::invalid_argument{ + "Error: ed25519_sk is not the expected 64-byte Ed25519 secret key"}; + auto ed25519_pk = ed25519_sk.subspan(32); + + WebSocketProtos::WebSocketMessage req{}; + + if (!req.ParseFromArray(data.data(), data.size())) + throw std::runtime_error{"Failed to parse WebSocketMessage"}; + + if (req.type() != WebSocketProtos::WebSocketMessage_Type_REQUEST) + throw std::runtime_error{"Error: received invalid WebSocketRequest"}; + + SessionProtos::Envelope envelope{}; + if (!envelope.ParseFromString(req.request().body())) + throw std::runtime_error{"Failed to parse Envelope"}; + + auto [content, sender] = decrypt_incoming(ed25519_sk, to_span(envelope.content())); + if (to_string_view(sender) != to_string_view(ed25519_pk)) + throw std::runtime_error{"Incoming config data was not from us; ignoring"}; + + if (content.empty()) + throw std::runtime_error{"Incoming config data decrypted to empty string"}; + + if (!(content.back() == 0x00 || content.back() == 0x80)) + throw std::runtime_error{"Incoming config data doesn't have required padding"}; + + if (auto it = std::find_if( + content.rbegin(), content.rend(), [](unsigned char c) { return c != 0; }); + it != content.rend() && *it == 0x80) + content.resize(content.size() - std::distance(content.rbegin(), it) - 1); + else + throw std::runtime_error{"Incoming config data has invalid padding"}; + + SessionProtos::Content config{}; + if (!config.ParseFromArray(content.data(), content.size())) + throw std::runtime_error{"Failed to parse SharedConfig"}; + + if (!config.has_sharedconfigmessage()) + throw std::runtime_error{"Content is missing a SharedConfigMessage"}; + auto& shconf = config.sharedconfigmessage(); + if (shconf.kind() != encode_namespace(ns)) + throw std::runtime_error{"SharedConfig has wrong kind for config namespace"}; + + // if ParseFromString fails, we have a raw (not protobuf encoded) message + return to_vector(shconf.data()); +} + +} // namespace session::config::protos diff --git a/src/config/user_groups.cpp b/src/config/user_groups.cpp index aa689653..a93da2a5 100644 --- a/src/config/user_groups.cpp +++ b/src/config/user_groups.cpp @@ -3,8 +3,8 @@ #include #include #include -#include #include +#include #include #include @@ -19,7 +19,6 @@ #include "session/util.hpp" using namespace std::literals; -using session::ustring_view; LIBSESSION_C_API const size_t GROUP_NAME_MAX_LENGTH = session::config::legacy_group_info::NAME_MAX_LENGTH; @@ -35,17 +34,23 @@ namespace session::config { template static void base_into(const base_group_info& self, T& c) { c.priority = self.priority; - c.joined_at = self.joined_at; + c.joined_at = to_epoch_seconds(self.joined_at); c.notifications = static_cast(self.notifications); - c.mute_until = self.mute_until; + c.mute_until = to_epoch_seconds(self.mute_until); + c.invited = self.invited; } template static void base_from(base_group_info& self, const T& c) { self.priority = c.priority; - self.joined_at = c.joined_at; + self.joined_at = to_epoch_seconds(c.joined_at); self.notifications = static_cast(c.notifications); - self.mute_until = c.mute_until; + self.mute_until = to_epoch_seconds(c.mute_until); + self.invited = c.invited; +} + +group_info::group_info(std::string sid) : id{std::move(sid)} { + check_session_id(id, "03"); } legacy_group_info::legacy_group_info(std::string sid) : session_id{std::move(sid)} { @@ -53,7 +58,7 @@ legacy_group_info::legacy_group_info(std::string sid) : session_id{std::move(sid } community_info::community_info(const ugroups_community_info& c) : - community_info{c.base_url, c.room, ustring_view{c.pubkey, 32}} { + community_info{c.base_url, c.room, std::span{c.pubkey, 32}} { base_from(*this, c); } @@ -66,15 +71,17 @@ void community_info::into(ugroups_community_info& c) const { std::memcpy(c.pubkey, pubkey().data(), 32); } -static_assert(sizeof(ugroups_legacy_group_info::name) == legacy_group_info::NAME_MAX_LENGTH + 1); +static_assert(sizeof(ugroups_legacy_group_info::name) == base_group_info::NAME_MAX_LENGTH + 1); +static_assert(sizeof(ugroups_group_info::name) == base_group_info::NAME_MAX_LENGTH + 1); legacy_group_info::legacy_group_info(const ugroups_legacy_group_info& c, impl_t) : - session_id{c.session_id, 66}, name{c.name}, disappearing_timer{c.disappearing_timer} { + session_id{c.session_id, 66}, disappearing_timer{c.disappearing_timer} { + name = c.name; assert(name.size() <= NAME_MAX_LENGTH); // Otherwise the caller messed up base_from(*this, c); if (c.have_enc_keys) { - enc_pubkey.assign(c.enc_pubkey, 32); - enc_seckey.assign(c.enc_seckey, 32); + enc_pubkey.assign(c.enc_pubkey, c.enc_pubkey + 32); + enc_seckey.assign(c.enc_seckey, c.enc_seckey + 32); } } @@ -120,7 +127,7 @@ void legacy_group_info::into(ugroups_legacy_group_info& c) && { void base_group_info::load(const dict& info_dict) { priority = maybe_int(info_dict, "+").value_or(0); - joined_at = std::max(0, maybe_int(info_dict, "j").value_or(0)); + joined_at = to_epoch_seconds(std::max(0, maybe_int(info_dict, "j").value_or(0))); int notify = maybe_int(info_dict, "@").value_or(0); if (notify >= 0 && notify <= 3) @@ -128,18 +135,21 @@ void base_group_info::load(const dict& info_dict) { else notifications = notify_mode::defaulted; - mute_until = maybe_int(info_dict, "!").value_or(0); + mute_until = to_epoch_seconds(maybe_int(info_dict, "!").value_or(0)); + + invited = maybe_int(info_dict, "i").value_or(0); } void legacy_group_info::load(const dict& info_dict) { base_group_info::load(info_dict); if (auto n = maybe_string(info_dict, "n")) - name = *n; - // otherwise leave the current `name` alone at whatever the object was constructed with + name = std::move(*n); + else + name.clear(); - auto enc_pub = maybe_ustring(info_dict, "k"); - auto enc_sec = maybe_ustring(info_dict, "K"); + auto enc_pub = maybe_vector(info_dict, "k"); + auto enc_sec = maybe_vector(info_dict, "K"); if (enc_pub && enc_sec && enc_pub->size() == 32 && enc_sec->size() == 32) { enc_pubkey = std::move(*enc_pub); enc_seckey = std::move(*enc_sec); @@ -190,26 +200,103 @@ bool legacy_group_info::erase(const std::string& session_id) { return members_.erase(session_id); } +group_info::group_info(const ugroups_group_info& c) : id{c.id, 66} { + base_from(*this, c); + + name = c.name; + removed_status = c.removed_status; + assert(name.size() <= NAME_MAX_LENGTH); // Otherwise the caller messed up + + if (c.have_secretkey) + secretkey.assign(c.secretkey, c.secretkey + 64); + if (c.have_auth_data) + auth_data.assign(c.auth_data, c.auth_data + sizeof(c.auth_data)); +} + +void group_info::into(ugroups_group_info& c) const { + assert(id.size() == 66); + base_into(*this, c); + copy_c_str(c.id, id); + copy_c_str(c.name, name); + c.removed_status = removed_status; + if ((c.have_secretkey = secretkey.size() == 64)) + std::memcpy(c.secretkey, secretkey.data(), 64); + if ((c.have_auth_data = auth_data.size() == 100)) + std::memcpy(c.auth_data, auth_data.data(), 100); +} + +void group_info::load(const dict& info_dict) { + base_group_info::load(info_dict); + + if (auto n = maybe_string(info_dict, "n")) + name = std::move(*n); + else + name.clear(); + + if (auto seed = maybe_vector(info_dict, "K"); seed && seed->size() == 32) { + std::array pk; + pk[0] = 0x03; + secretkey.resize(64); + crypto_sign_seed_keypair(pk.data() + 1, secretkey.data(), seed->data()); + if (id != oxenc::to_hex(pk.begin(), pk.end())) + secretkey.clear(); + } + if (auto sig = maybe_vector(info_dict, "s"); sig && sig->size() == 100) + auth_data = std::move(*sig); + + removed_status = maybe_int(info_dict, "r").value_or(0); +} + +void group_info::mark_kicked() { + secretkey.clear(); + auth_data.clear(); + if (removed_status != GROUP_DESTROYED) { + removed_status = KICKED_FROM_GROUP; + } +} + +void group_info::mark_invited() { + if (removed_status == KICKED_FROM_GROUP) { + removed_status = NOT_REMOVED; + } +} + +bool group_info::kicked() const { + return removed_status == KICKED_FROM_GROUP; +} + +void group_info::mark_destroyed() { + secretkey.clear(); + auth_data.clear(); + removed_status = GROUP_DESTROYED; +} + +bool group_info::is_destroyed() const { + return removed_status == GROUP_DESTROYED; +} + void community_info::load(const dict& info_dict) { base_group_info::load(info_dict); if (auto n = maybe_string(info_dict, "n")) - set_room(*n); + set_room(std::move(*n)); } -UserGroups::UserGroups(ustring_view ed25519_secretkey, std::optional dumped) : +UserGroups::UserGroups( + std::span ed25519_secretkey, + std::optional> dumped) : ConfigBase{dumped} { load_key(ed25519_secretkey); } ConfigBase::DictFieldProxy UserGroups::community_field( - const community_info& og, ustring_view* get_pubkey) const { + const community_info& og, std::span* get_pubkey) const { auto record = data["o"][og.base_url()]; if (get_pubkey) { auto pkrec = record["#"]; if (auto pk = pkrec.string_view_or(""); pk.size() == 32) - *get_pubkey = - ustring_view{reinterpret_cast(pk.data()), pk.size()}; + *get_pubkey = std::span{ + reinterpret_cast(pk.data()), pk.size()}; } return record["R"][og.room_norm()]; } @@ -218,12 +305,12 @@ std::optional UserGroups::get_community( std::string_view base_url, std::string_view room) const { community_info og{base_url, room}; - ustring_view pubkey; + std::span pubkey; if (auto* info_dict = community_field(og, &pubkey).dict()) { og.load(*info_dict); if (!pubkey.empty()) og.set_pubkey(pubkey); - return std::move(og); + return og; } return std::nullopt; } @@ -234,7 +321,9 @@ std::optional UserGroups::get_community(std::string_view partial } community_info UserGroups::get_or_construct_community( - std::string_view base_url, std::string_view room, ustring_view pubkey) const { + std::string_view base_url, + std::string_view room, + std::span pubkey) const { community_info result{base_url, room, pubkey}; if (auto* info_dict = community_field(result).dict()) @@ -277,6 +366,40 @@ legacy_group_info UserGroups::get_or_construct_legacy_group(std::string_view pub return legacy_group_info{std::string{pubkey_hex}}; } +std::optional UserGroups::get_group(std::string_view pubkey_hex) const { + std::string pubkey = session_id_to_bytes(pubkey_hex, "03"); + + auto* info_dict = data["g"][pubkey].dict(); + if (!info_dict) + return std::nullopt; + + auto result = std::make_optional(std::string{pubkey_hex}); + result->load(*info_dict); + return result; +} + +group_info UserGroups::get_or_construct_group(std::string_view pubkey_hex) const { + if (auto maybe = get_group(pubkey_hex)) + return *std::move(maybe); + + return group_info{std::string{pubkey_hex}}; +} + +group_info UserGroups::create_group() const { + std::array pk; + std::vector sk; + sk.resize(64); + crypto_sign_keypair(pk.data(), sk.data()); + std::string pk_hex; + pk_hex.reserve(66); + pk_hex += "03"; + oxenc::to_hex(pk.begin(), pk.end(), std::back_inserter(pk_hex)); + + group_info gr{std::move(pk_hex)}; + gr.secretkey = std::move(sk); + return gr; +} + void UserGroups::set(const community_info& c) { data["o"][c.base_url()]["#"] = c.pubkey(); auto info = community_field(c); // data["o"][base]["R"][lc_room] @@ -286,18 +409,17 @@ void UserGroups::set(const community_info& c) { void UserGroups::set_base(const base_group_info& bg, DictFieldProxy& info) const { set_nonzero_int(info["+"], bg.priority); - set_positive_int(info["j"], bg.joined_at); + set_positive_int(info["j"], to_epoch_seconds(bg.joined_at)); set_positive_int(info["@"], static_cast(bg.notifications)); - set_positive_int(info["!"], bg.mute_until); + set_positive_int(info["!"], to_epoch_seconds(bg.mute_until)); + set_flag(info["i"], bg.invited); + // We don't set n here because it's subtly different in the three group types } void UserGroups::set(const legacy_group_info& g) { auto info = data["C"][session_id_to_bytes(g.session_id)]; set_base(g, info); - if (g.name.size() > legacy_group_info::NAME_MAX_LENGTH) - info["n"] = g.name.substr(0, legacy_group_info::NAME_MAX_LENGTH); - else - info["n"] = g.name; + info["n"] = std::string_view{g.name}.substr(0, legacy_group_info::NAME_MAX_LENGTH); set_pair_if( g.enc_pubkey.size() == 32 && g.enc_seckey.size() == 32, @@ -316,6 +438,31 @@ void UserGroups::set(const legacy_group_info& g) { set_positive_int(info["E"], g.disappearing_timer.count()); } +void UserGroups::set(const group_info& g) { + auto pk_bytes = session_id_to_bytes(g.id, "03"); + auto info = data["g"][pk_bytes]; + set_base(g, info); + + set_nonempty_str( + info["n"], std::string_view{g.name}.substr(0, legacy_group_info::NAME_MAX_LENGTH)); + set_positive_int(info["r"], g.removed_status); + + if (g.secretkey.size() == 64 && + // Make sure the secretkey's embedded pubkey matches the group id: + to_string_view(std::span{g.secretkey.data() + 32, 32}) == + to_string_view(std::span{ + reinterpret_cast(pk_bytes.data() + 1), + pk_bytes.size() - 1})) + info["K"] = std::span{g.secretkey.data(), 32}; + else { + info["K"] = std::span{}; + if (g.auth_data.size() == 100) + info["s"] = g.auth_data; + else + info["s"].erase(); + } +} + template static bool erase_impl(Field convo) { bool ret = convo.exists(); @@ -337,12 +484,15 @@ bool UserGroups::erase(const community_info& c) { } return gone; } +bool UserGroups::erase(const group_info& c) { + return erase_impl(data["g"][session_id_to_bytes(c.id, "03")]); +} bool UserGroups::erase(const legacy_group_info& c) { return erase_impl(data["C"][session_id_to_bytes(c.session_id)]); } bool UserGroups::erase(const any_group_info& c) { - return var::visit([this](const auto& c) { return erase(c); }, c); + return std::visit([this](const auto& c) { return erase(c); }, c); } bool UserGroups::erase_community(std::string_view base_url, std::string_view room) { return erase(community_info{base_url, room}); @@ -350,6 +500,9 @@ bool UserGroups::erase_community(std::string_view base_url, std::string_view roo bool UserGroups::erase_legacy_group(std::string_view id) { return erase(legacy_group_info{std::string{id}}); } +bool UserGroups::erase_group(std::string_view id) { + return erase(group_info{std::string{id}}); +} size_t UserGroups::size_communities() const { size_t count = 0; @@ -373,11 +526,23 @@ size_t UserGroups::size_legacy_groups() const { return 0; } +size_t UserGroups::size_groups() const { + if (auto* d = data["g"].dict()) + return d->size(); + return 0; +} + size_t UserGroups::size() const { - return size_communities() + size_legacy_groups(); + return size_communities() + size_legacy_groups() + size_groups(); } -UserGroups::iterator::iterator(const DictFieldRoot& data, bool communities, bool legacy_groups) { +UserGroups::iterator::iterator( + const DictFieldRoot& data, bool groups, bool communities, bool legacy_groups) { + if (groups) + if (auto* d = data["g"].dict()) { + _it_group = d->begin(); + _end_group = d->end(); + } if (communities) if (auto* d = data["o"].dict()) _it_comm.emplace(d->begin(), d->end()); @@ -389,50 +554,70 @@ UserGroups::iterator::iterator(const DictFieldRoot& data, bool communities, bool _load_val(); } -/// Load _val from the current iterator position; if it is invalid, skip to the next key until we -/// find one that is valid (or hit the end). We also span across three different iterators: first -/// we exhaust communities, then legacy groups. -/// -/// We *always* call this after incrementing the iterators (and after iterator initialization), and -/// this is responsible for making sure that the the _it variables are set up as required. -void UserGroups::iterator::_load_val() { - if (_it_comm) { - if (_it_comm->load(_val)) - return; - else - _it_comm.reset(); - } +template +bool UserGroups::iterator::check_it() { + static_assert( + std::is_same_v || std::is_same_v); + constexpr bool legacy = std::is_same_v; + auto& it = legacy ? _it_legacy : _it_group; + auto& end = legacy ? _end_legacy : _end_group; - while (_it_legacy) { - if (*_it_legacy == *_end_legacy) { - _it_legacy.reset(); - _end_legacy.reset(); + constexpr char prefix = legacy ? 0x05 : 0x03; + while (it) { + if (*it == *end) { + it.reset(); + end.reset(); break; } - auto& [k, v] = **_it_legacy; + auto& [k, v] = **it; - if (k.size() == 33 && k[0] == 0x05) { + if (k.size() == 33 && k[0] == prefix) { if (auto* info_dict = std::get_if(&v)) { - _val = std::make_shared(legacy_group_info{oxenc::to_hex(k)}); - std::get(*_val).load(*info_dict); - return; + _val = std::make_shared(GroupInfo{oxenc::to_hex(k)}); + std::get(*_val).load(*info_dict); + return true; } } - ++*_it_legacy; + ++*it; + } + return false; +} + +/// Load _val from the current iterator position; if it is invalid, skip to the next key until +/// we find one that is valid (or hit the end). We also span across three different iterators: +/// first we exhaust communities, then legacy groups. +/// +/// We *always* call this after incrementing the iterators (and after iterator initialization), +/// and this is responsible for making sure that the the _it variables are set up as required. +void UserGroups::iterator::_load_val() { + if (check_it()) + return; + + if (_it_comm) { + if (_it_comm->load(_val)) + return; + else + _it_comm.reset(); } + + if (check_it()) + return; } bool UserGroups::iterator::operator==(const iterator& other) const { - return _it_comm == other._it_comm && _it_legacy == other._it_legacy; + return _it_group == other._it_group && _it_comm == other._it_comm && + _it_legacy == other._it_legacy; } bool UserGroups::iterator::done() const { - return !_it_comm && !_it_legacy; + return !_it_group && !_it_comm && !_it_legacy; } UserGroups::iterator& UserGroups::iterator::operator++() { - if (_it_comm) + if (_it_group) + ++*_it_group; + else if (_it_comm) _it_comm->advance(); else { assert(_it_legacy); @@ -464,17 +649,16 @@ int user_groups_init( LIBSESSION_C_API bool user_groups_get_community( config_object* conf, ugroups_community_info* comm, const char* base_url, const char* room) { - try { - conf->last_error = nullptr; - if (auto c = unbox(conf)->get_community(base_url, room)) { - c->into(*comm); - return true; - } - } catch (const std::exception& e) { - copy_c_str(conf->_error_buf, e.what()); - conf->last_error = conf->_error_buf; - } - return false; + return wrap_exceptions( + conf, + [&] { + if (auto c = unbox(conf)->get_community(base_url, room)) { + c->into(*comm); + return true; + } + return false; + }, + false); } LIBSESSION_C_API bool user_groups_get_or_construct_community( config_object* conf, @@ -482,17 +666,39 @@ LIBSESSION_C_API bool user_groups_get_or_construct_community( const char* base_url, const char* room, unsigned const char* pubkey) { - try { - conf->last_error = nullptr; - unbox(conf) - ->get_or_construct_community(base_url, room, ustring_view{pubkey, 32}) - .into(*comm); - return true; - } catch (const std::exception& e) { - copy_c_str(conf->_error_buf, e.what()); - conf->last_error = conf->_error_buf; - return false; - } + return wrap_exceptions( + conf, + [&] { + unbox(conf) + ->get_or_construct_community( + base_url, room, std::span{pubkey, 32}) + .into(*comm); + return true; + }, + false); +} +LIBSESSION_C_API bool user_groups_get_group( + config_object* conf, ugroups_group_info* group, const char* group_id) { + return wrap_exceptions( + conf, + [&] { + if (auto g = unbox(conf)->get_group(group_id)) { + g->into(*group); + return true; + } + return false; + }, + false); +} +LIBSESSION_C_API bool user_groups_get_or_construct_group( + config_object* conf, ugroups_group_info* group, const char* group_id) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->get_or_construct_group(group_id).into(*group); + return true; + }, + false); } LIBSESSION_C_API void ugroups_legacy_group_free(ugroups_legacy_group_info* group) { @@ -504,47 +710,59 @@ LIBSESSION_C_API void ugroups_legacy_group_free(ugroups_legacy_group_info* group LIBSESSION_C_API ugroups_legacy_group_info* user_groups_get_legacy_group( config_object* conf, const char* id) { - try { - conf->last_error = nullptr; + return wrap_exceptions(conf, [&] { auto group = std::make_unique(); group->_internal = nullptr; if (auto c = unbox(conf)->get_legacy_group(id)) { std::move(c)->into(*group); return group.release(); } - } catch (const std::exception& e) { - copy_c_str(conf->_error_buf, e.what()); - conf->last_error = conf->_error_buf; - } - return nullptr; + return static_cast(nullptr); + }); } LIBSESSION_C_API ugroups_legacy_group_info* user_groups_get_or_construct_legacy_group( config_object* conf, const char* id) { - try { - conf->last_error = nullptr; + return wrap_exceptions(conf, [&] { auto group = std::make_unique(); group->_internal = nullptr; unbox(conf)->get_or_construct_legacy_group(id).into(*group); return group.release(); - } catch (const std::exception& e) { - copy_c_str(conf->_error_buf, e.what()); - conf->last_error = conf->_error_buf; - return nullptr; - } + }); } LIBSESSION_C_API void user_groups_set_community( config_object* conf, const ugroups_community_info* comm) { unbox(conf)->set(community_info{*comm}); } -LIBSESSION_C_API void user_groups_set_legacy_group( - config_object* conf, const ugroups_legacy_group_info* group) { - unbox(conf)->set(legacy_group_info{*group}); +LIBSESSION_C_API bool user_groups_set_group(config_object* conf, const ugroups_group_info* group) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set(group_info{*group}); + return true; + }, + false); } -LIBSESSION_C_API void user_groups_set_free_legacy_group( +LIBSESSION_C_API bool user_groups_set_legacy_group( + config_object* conf, const ugroups_legacy_group_info* group) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set(legacy_group_info{*group}); + return true; + }, + false); +} +LIBSESSION_C_API bool user_groups_set_free_legacy_group( config_object* conf, ugroups_legacy_group_info* group) { - unbox(conf)->set(legacy_group_info{std::move(*group)}); + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set(legacy_group_info{std::move(*group)}); + return true; + }, + false); } LIBSESSION_C_API bool user_groups_erase_community( @@ -555,6 +773,13 @@ LIBSESSION_C_API bool user_groups_erase_community( return false; } } +LIBSESSION_C_API bool user_groups_erase_group(config_object* conf, const char* group_id) { + try { + return unbox(conf)->erase_group(group_id); + } catch (...) { + return false; + } +} LIBSESSION_C_API bool user_groups_erase_legacy_group(config_object* conf, const char* group_id) { try { return unbox(conf)->erase_legacy_group(group_id); @@ -563,6 +788,32 @@ LIBSESSION_C_API bool user_groups_erase_legacy_group(config_object* conf, const } } +LIBSESSION_C_API void ugroups_group_set_invited(ugroups_group_info* group) { + if (group->removed_status == KICKED_FROM_GROUP) { + group->removed_status = NOT_REMOVED; + } +} + +LIBSESSION_C_API void ugroups_group_set_kicked(ugroups_group_info* group) { + assert(group); + group->have_auth_data = false; + group->have_secretkey = false; + group->removed_status = KICKED_FROM_GROUP; +} +LIBSESSION_C_API bool ugroups_group_is_kicked(const ugroups_group_info* group) { + return group->removed_status == KICKED_FROM_GROUP; +} + +LIBSESSION_C_API void ugroups_group_set_destroyed(ugroups_group_info* group) { + assert(group); + group->have_auth_data = false; + group->have_secretkey = false; + group->removed_status = GROUP_DESTROYED; +} +LIBSESSION_C_API bool ugroups_group_is_destroyed(const ugroups_group_info* group) { + return group->removed_status == GROUP_DESTROYED; +} + struct ugroups_legacy_members_iterator { using map_t = std::map; map_t& members; @@ -653,6 +904,9 @@ LIBSESSION_C_API size_t user_groups_size(const config_object* conf) { LIBSESSION_C_API size_t user_groups_size_communities(const config_object* conf) { return unbox(conf)->size_communities(); } +LIBSESSION_C_API size_t user_groups_size_groups(const config_object* conf) { + return unbox(conf)->size_groups(); +} LIBSESSION_C_API size_t user_groups_size_legacy_groups(const config_object* conf) { return unbox(conf)->size_legacy_groups(); } @@ -665,6 +919,9 @@ LIBSESSION_C_API user_groups_iterator* user_groups_iterator_new_communities( const config_object* conf) { return new user_groups_iterator{{unbox(conf)->begin_communities()}}; } +LIBSESSION_C_API user_groups_iterator* user_groups_iterator_new_groups(const config_object* conf) { + return new user_groups_iterator{{unbox(conf)->begin_groups()}}; +} LIBSESSION_C_API user_groups_iterator* user_groups_iterator_new_legacy_groups( const config_object* conf) { return new user_groups_iterator{{unbox(conf)->begin_legacy_groups()}}; @@ -699,6 +956,10 @@ LIBSESSION_C_API bool user_groups_it_is_community( return user_groups_it_is_impl(it, c); } +LIBSESSION_C_API bool user_groups_it_is_group(user_groups_iterator* it, ugroups_group_info* g) { + return user_groups_it_is_impl(it, g); +} + LIBSESSION_C_API bool user_groups_it_is_legacy_group( user_groups_iterator* it, ugroups_legacy_group_info* g) { return user_groups_it_is_impl(it, g); diff --git a/src/config/user_profile.cpp b/src/config/user_profile.cpp index 1c5a3df2..4547386d 100644 --- a/src/config/user_profile.cpp +++ b/src/config/user_profile.cpp @@ -3,17 +3,19 @@ #include #include "internal.hpp" +#include "session/config/contacts.hpp" #include "session/config/error.h" #include "session/config/user_profile.hpp" #include "session/export.h" #include "session/types.hpp" using namespace session::config; -using session::ustring_view; LIBSESSION_C_API const size_t PROFILE_PIC_MAX_URL_LENGTH = profile_pic::MAX_URL_LENGTH; -UserProfile::UserProfile(ustring_view ed25519_secretkey, std::optional dumped) : +UserProfile::UserProfile( + std::span ed25519_secretkey, + std::optional> dumped) : ConfigBase{dumped} { load_key(ed25519_secretkey); } @@ -39,15 +41,21 @@ LIBSESSION_C_API const char* user_profile_get_name(const config_object* conf) { } void UserProfile::set_name(std::string_view new_name) { + if (new_name.size() > contact_info::MAX_NAME_LENGTH) + throw std::invalid_argument{"Invalid profile name: exceeds maximum length"}; set_nonempty_str(data["n"], new_name); } +void UserProfile::set_name_truncated(std::string new_name) { + set_name(utf8_truncate(std::move(new_name), contact_info::MAX_NAME_LENGTH)); +} LIBSESSION_C_API int user_profile_set_name(config_object* conf, const char* name) { - try { - unbox(conf)->set_name(name); - } catch (const std::exception& e) { - return set_error(conf, SESSION_ERR_BAD_VALUE, e); - } - return 0; + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set_name(name); + return 0; + }, + static_cast(SESSION_ERR_BAD_VALUE)); } profile_pic UserProfile::get_profile_pic() const { @@ -55,7 +63,9 @@ profile_pic UserProfile::get_profile_pic() const { if (auto* url = data["p"].string(); url && !url->empty()) pic.url = *url; if (auto* key = data["q"].string(); key && key->size() == 32) - pic.key = {reinterpret_cast(key->data()), 32}; + pic.key.assign( + reinterpret_cast(key->data()), + reinterpret_cast(key->data()) + 32); return pic; } @@ -70,7 +80,7 @@ LIBSESSION_C_API user_profile_pic user_profile_get_pic(const config_object* conf return p; } -void UserProfile::set_profile_pic(std::string_view url, ustring_view key) { +void UserProfile::set_profile_pic(std::string_view url, std::span key) { set_pair_if(!url.empty() && key.size() == 32, data["p"], url, data["q"], key); } @@ -80,17 +90,17 @@ void UserProfile::set_profile_pic(profile_pic pic) { LIBSESSION_C_API int user_profile_set_pic(config_object* conf, user_profile_pic pic) { std::string_view url{pic.url}; - ustring_view key; + std::span key; if (!url.empty()) key = {pic.key, 32}; - try { - unbox(conf)->set_profile_pic(url, key); - } catch (const std::exception& e) { - return set_error(conf, SESSION_ERR_BAD_VALUE, e); - } - - return 0; + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set_profile_pic(url, key); + return 0; + }, + static_cast(SESSION_ERR_BAD_VALUE)); } void UserProfile::set_nts_priority(int priority) { diff --git a/src/curve25519.cpp b/src/curve25519.cpp new file mode 100644 index 00000000..a9daea6c --- /dev/null +++ b/src/curve25519.cpp @@ -0,0 +1,89 @@ +#include "session/curve25519.hpp" + +#include +#include + +#include + +#include "session/export.h" +#include "session/util.hpp" + +namespace session::curve25519 { + +std::pair, std::array> curve25519_key_pair() { + std::array curve_pk; + std::array curve_sk; + crypto_box_keypair(curve_pk.data(), curve_sk.data()); + + return {curve_pk, curve_sk}; +} + +std::array to_curve25519_pubkey(std::span ed25519_pubkey) { + if (ed25519_pubkey.size() != 32) { + throw std::invalid_argument{"Invalid ed25519_pubkey: expected 32 bytes"}; + } + + std::array curve_pk; + + if (0 != crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed25519_pubkey.data())) + throw std::runtime_error{ + "An error occured while attempting to convert Ed25519 pubkey to curve25519; " + "is the pubkey valid?"}; + + return curve_pk; +} + +std::array to_curve25519_seckey(std::span ed25519_seckey) { + if (ed25519_seckey.size() != 64) { + throw std::invalid_argument{"Invalid ed25519_seckey: expected 64 bytes"}; + } + + std::array curve_sk; + if (0 != crypto_sign_ed25519_sk_to_curve25519(curve_sk.data(), ed25519_seckey.data())) + throw std::runtime_error{ + "An error occured while attempting to convert Ed25519 pubkey to curve25519; " + "is the seckey valid?"}; + + return curve_sk; +} + +} // namespace session::curve25519 + +using namespace session; + +LIBSESSION_C_API bool session_curve25519_key_pair( + unsigned char* curve25519_pk_out, unsigned char* curve25519_sk_out) { + try { + auto result = session::curve25519::curve25519_key_pair(); + auto [curve_pk, curve_sk] = result; + std::memcpy(curve25519_pk_out, curve_pk.data(), curve_pk.size()); + std::memcpy(curve25519_sk_out, curve_sk.data(), curve_sk.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_to_curve25519_pubkey( + const unsigned char* ed25519_pubkey, unsigned char* curve25519_pk_out) { + try { + auto curve_pk = session::curve25519::to_curve25519_pubkey( + std::span{ed25519_pubkey, 32}); + std::memcpy(curve25519_pk_out, curve_pk.data(), curve_pk.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_to_curve25519_seckey( + const unsigned char* ed25519_seckey, unsigned char* curve25519_sk_out) { + try { + auto curve_sk = session::curve25519::to_curve25519_seckey( + std::span{ed25519_seckey, 64}); + std::memcpy(curve25519_sk_out, curve_sk.data(), curve_sk.size()); + return true; + } catch (...) { + return false; + } +} diff --git a/src/ed25519.cpp b/src/ed25519.cpp new file mode 100644 index 00000000..0cde00e3 --- /dev/null +++ b/src/ed25519.cpp @@ -0,0 +1,159 @@ +#include "session/ed25519.hpp" + +#include +#include + +#include + +#include "session/export.h" +#include "session/sodium_array.hpp" + +namespace session::ed25519 { + +template +using cleared_array = sodium_cleared>; + +using uc32 = std::array; +using cleared_uc64 = cleared_array<64>; + +std::pair, std::array> ed25519_key_pair() { + std::array ed_pk; + std::array ed_sk; + crypto_sign_ed25519_keypair(ed_pk.data(), ed_sk.data()); + + return {ed_pk, ed_sk}; +} + +std::pair, std::array> ed25519_key_pair( + std::span ed25519_seed) { + if (ed25519_seed.size() != 32) { + throw std::invalid_argument{"Invalid ed25519_seed: expected 32 bytes"}; + } + + std::array ed_pk; + std::array ed_sk; + + crypto_sign_ed25519_seed_keypair(ed_pk.data(), ed_sk.data(), ed25519_seed.data()); + + return {ed_pk, ed_sk}; +} + +std::array seed_for_ed_privkey(std::span ed25519_privkey) { + std::array seed; + + if (ed25519_privkey.size() == 32 || ed25519_privkey.size() == 64) + // The first 32 bytes of a 64 byte ed25519 private key are the seed, otherwise + // if the provided value is 32 bytes we just assume we were given a seed + std::memcpy(seed.data(), ed25519_privkey.data(), 32); + else + throw std::invalid_argument{"Invalid ed25519_privkey: expected 32 or 64 bytes"}; + + return seed; +} + +std::vector sign( + std::span ed25519_privkey, std::span msg) { + cleared_uc64 ed_sk_from_seed; + if (ed25519_privkey.size() == 32) { + uc32 ignore_pk; + crypto_sign_ed25519_seed_keypair( + ignore_pk.data(), ed_sk_from_seed.data(), ed25519_privkey.data()); + ed25519_privkey = {ed_sk_from_seed.data(), ed_sk_from_seed.size()}; + } else if (ed25519_privkey.size() != 64) { + throw std::invalid_argument{"Invalid ed25519_privkey: expected 32 or 64 bytes"}; + } + + std::vector sig; + sig.resize(64); + + if (0 != crypto_sign_ed25519_detached( + sig.data(), nullptr, msg.data(), msg.size(), ed25519_privkey.data())) + throw std::runtime_error{"Failed to sign; perhaps the secret key is invalid?"}; + + return sig; +} + +bool verify( + std::span sig, + std::span pubkey, + std::span msg) { + if (sig.size() != 64) + throw std::invalid_argument{"Invalid sig: expected 64 bytes"}; + if (pubkey.size() != 32) + throw std::invalid_argument{"Invalid pubkey: expected 32 bytes"}; + + return (0 == + crypto_sign_ed25519_verify_detached(sig.data(), msg.data(), msg.size(), pubkey.data())); +} + +} // namespace session::ed25519 + +using namespace session; + +LIBSESSION_C_API bool session_ed25519_key_pair( + unsigned char* ed25519_pk_out, unsigned char* ed25519_sk_out) { + try { + auto result = session::ed25519::ed25519_key_pair(); + auto [ed_pk, ed_sk] = result; + std::memcpy(ed25519_pk_out, ed_pk.data(), ed_pk.size()); + std::memcpy(ed25519_sk_out, ed_sk.data(), ed_sk.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_ed25519_key_pair_seed( + const unsigned char* ed25519_seed, + unsigned char* ed25519_pk_out, + unsigned char* ed25519_sk_out) { + try { + auto result = session::ed25519::ed25519_key_pair( + std::span{ed25519_seed, 32}); + auto [ed_pk, ed_sk] = result; + std::memcpy(ed25519_pk_out, ed_pk.data(), ed_pk.size()); + std::memcpy(ed25519_sk_out, ed_sk.data(), ed_sk.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_seed_for_ed_privkey( + const unsigned char* ed25519_privkey, unsigned char* ed25519_seed_out) { + try { + auto result = session::ed25519::seed_for_ed_privkey( + std::span{ed25519_privkey, 64}); + std::memcpy(ed25519_seed_out, result.data(), result.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_ed25519_sign( + const unsigned char* ed25519_privkey, + const unsigned char* msg, + size_t msg_len, + unsigned char* ed25519_sig_out) { + try { + auto result = session::ed25519::sign( + std::span{ed25519_privkey, 64}, + std::span{msg, msg_len}); + std::memcpy(ed25519_sig_out, result.data(), result.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_ed25519_verify( + const unsigned char* sig, + const unsigned char* pubkey, + const unsigned char* msg, + size_t msg_len) { + return session::ed25519::verify( + std::span{sig, 64}, + std::span{pubkey, 32}, + std::span{msg, msg_len}); +} diff --git a/src/file.cpp b/src/file.cpp new file mode 100644 index 00000000..8f0ee656 --- /dev/null +++ b/src/file.cpp @@ -0,0 +1,38 @@ +#include +#include + +namespace session { + +std::ofstream open_for_writing(const fs::path& filename) { + std::ofstream out; + out.exceptions(std::ios_base::failbit | std::ios_base::badbit); + out.open(filename, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); + out.exceptions(std::ios_base::badbit); + return out; +} + +std::ifstream open_for_reading(const fs::path& filename) { + std::ifstream in; + in.exceptions(std::ios_base::failbit | std::ios_base::badbit); + in.open(filename, std::ios::binary | std::ios::in); + in.exceptions(std::ios_base::badbit); + return in; +} + +std::string read_whole_file(const fs::path& filename) { + auto in = open_for_reading(filename); + std::string contents; + in.seekg(0, std::ios::end); + auto size = in.tellg(); + in.seekg(0, std::ios::beg); + contents.resize(size); + in.read(contents.data(), size); + return contents; +} + +void write_whole_file(const fs::path& filename, std::string_view contents) { + auto out = open_for_writing(filename); + out.write(contents.data(), static_cast(contents.size())); +} + +} // namespace session diff --git a/src/hash.cpp b/src/hash.cpp new file mode 100644 index 00000000..b698f6b3 --- /dev/null +++ b/src/hash.cpp @@ -0,0 +1,66 @@ +#include "session/hash.hpp" + +#include + +#include "session/export.h" +#include "session/util.hpp" + +namespace session::hash { + +void hash( + std::span hash, + std::span msg, + std::optional> key) { + const auto size = hash.size(); + if (size < crypto_generichash_blake2b_BYTES_MIN || size > crypto_generichash_blake2b_BYTES_MAX) + throw std::invalid_argument{"Invalid size: expected between 16 and 64 bytes (inclusive)"}; + + if (key && key->size() > crypto_generichash_blake2b_BYTES_MAX) + throw std::invalid_argument{"Invalid key: expected less than 65 bytes"}; + + crypto_generichash_blake2b( + hash.data(), + size, + msg.data(), + msg.size(), + key ? key->data() : nullptr, + key ? key->size() : 0); +} + +std::vector hash( + const size_t size, + std::span msg, + std::optional> key) { + std::vector result; + result.resize(size); + hash(result, msg, key); + + return result; +} + +} // namespace session::hash + +extern "C" { + +LIBSESSION_C_API bool session_hash( + size_t size, + const unsigned char* msg_in, + size_t msg_len, + const unsigned char* key_in, + size_t key_len, + unsigned char* hash_out) { + try { + std::optional> key; + + if (key_in && key_len) + key = {key_in, key_len}; + + std::vector result = session::hash::hash(size, {msg_in, msg_len}, key); + std::memcpy(hash_out, result.data(), size); + return true; + } catch (...) { + return false; + } +} + +} // extern "C" diff --git a/src/logging.cpp b/src/logging.cpp new file mode 100644 index 00000000..779b1f66 --- /dev/null +++ b/src/logging.cpp @@ -0,0 +1,106 @@ +#include "session/logging.hpp" + +#include + +#include +#include +#include + +#include "oxen/log/level.hpp" +#include "session/export.h" + +namespace session { + +namespace log = oxen::log; + +LogLevel::LogLevel(spdlog::level::level_enum lvl) : level{static_cast(lvl)} {} + +spdlog::level::level_enum LogLevel::spdlog_level() const { + return static_cast(level); +} + +std::string_view LogLevel::to_string() const { + return log::to_string(spdlog_level()); +} + +void add_logger(std::function cb) { + log::add_sink(std::make_shared(std::move(cb))); +} +void add_logger( + std::function cb) { + log::add_sink(std::make_shared(std::move(cb))); +} + +void manual_log(std::string_view msg) { + log::info(oxen::log::Cat("manual"), "{}", msg); +} + +void logger_reset_level(LogLevel level) { + log::reset_level(level.spdlog_level()); +} +void logger_set_level_default(LogLevel level) { + log::set_level_default(level.spdlog_level()); +} +LogLevel logger_get_level_default() { + return log::get_level_default(); +} +void logger_set_level(std::string cat_name, LogLevel level) { + log::set_level(std::move(cat_name), level.spdlog_level()); +} +LogLevel logger_get_level(std::string cat_name) { + return log::get_level(std::move(cat_name)); +} + +void clear_loggers() { + log::clear_sinks(); +} + +} // namespace session + +extern "C" { + +LIBSESSION_C_API void session_add_logger_simple(void (*callback)(const char* msg, size_t msglen)) { + assert(callback); + session::add_logger( + [cb = std::move(callback)](std::string_view msg) { cb(msg.data(), msg.size()); }); +} + +LIBSESSION_C_API void session_add_logger_full(void (*callback)( + const char* msg, size_t msglen, const char* cat, size_t cat_len, LOG_LEVEL level)) { + assert(callback); + session::add_logger( + [cb = std::move(callback)]( + std::string_view msg, std::string_view category, session::LogLevel level) { + cb(msg.data(), + msg.size(), + category.data(), + category.size(), + static_cast(level.level)); + }); +} + +LIBSESSION_C_API void session_logger_reset_level(LOG_LEVEL level) { + oxen::log::reset_level(static_cast(level)); +} +LIBSESSION_C_API void session_logger_set_level_default(LOG_LEVEL level) { + oxen::log::set_level_default(static_cast(level)); +} +LIBSESSION_C_API LOG_LEVEL session_logger_get_level_default() { + return static_cast(oxen::log::get_level_default()); +} +LIBSESSION_C_API void session_logger_set_level(const char* cat_name, LOG_LEVEL level) { + oxen::log::set_level(cat_name, static_cast(level)); +} +LIBSESSION_C_API LOG_LEVEL session_logger_get_level(const char* cat_name) { + return static_cast(oxen::log::get_level(cat_name)); +} + +LIBSESSION_C_API void session_manual_log(const char* msg) { + session::manual_log(msg); +} + +LIBSESSION_C_API void session_clear_loggers() { + session::clear_loggers(); +} + +} // extern "C" \ No newline at end of file diff --git a/src/multi_encrypt.cpp b/src/multi_encrypt.cpp new file mode 100644 index 00000000..93c7f72c --- /dev/null +++ b/src/multi_encrypt.cpp @@ -0,0 +1,397 @@ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace session { + +const size_t encrypt_multiple_message_overhead = crypto_aead_xchacha20poly1305_ietf_ABYTES; + +namespace detail { + + void encrypt_multi_key( + std::array& key, + const unsigned char* a, + const unsigned char* A, + const unsigned char* B, + bool encrypting, + std::string_view domain) { + + std::array buf; + if (0 != crypto_scalarmult_curve25519(buf.data(), a, B)) + throw std::invalid_argument{"Unable to compute shared encrypted key: invalid pubkey?"}; + + static_assert(crypto_aead_xchacha20poly1305_ietf_KEYBYTES == 32); + + crypto_generichash_blake2b_state st; + crypto_generichash_blake2b_init( + &st, + reinterpret_cast(domain.data()), + std::min(domain.size(), crypto_generichash_blake2b_KEYBYTES_MAX), + 32); + + crypto_generichash_blake2b_update(&st, buf.data(), buf.size()); + + // If we're encrypting then a/A == sender, B = recipient + // If we're decrypting then a/A = recipient, B = sender + // We always need the same sR || S || R or rS || S || R, so if we're decrypting we need to + // put B before A in the hash; + const auto* S = encrypting ? A : B; + const auto* R = encrypting ? B : A; + crypto_generichash_blake2b_update(&st, S, 32); + crypto_generichash_blake2b_update(&st, R, 32); + crypto_generichash_blake2b_final(&st, key.data(), 32); + } + + void encrypt_multi_impl( + std::vector& out, + std::span msg, + const unsigned char* key, + const unsigned char* nonce) { + + // auto key = encrypt_multi_key(a, A, B, true, domain); + + out.resize(msg.size() + crypto_aead_xchacha20poly1305_ietf_ABYTES); + if (0 != + crypto_aead_xchacha20poly1305_ietf_encrypt( + out.data(), nullptr, msg.data(), msg.size(), nullptr, 0, nullptr, nonce, key)) + throw std::runtime_error{"XChaCha20 encryption failed!"}; + } + + bool decrypt_multi_impl( + std::vector& out, + std::span ciphertext, + const unsigned char* key, + const unsigned char* nonce) { + + if (ciphertext.size() < crypto_aead_xchacha20poly1305_ietf_ABYTES) + return false; + + out.resize(ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES); + return 0 == crypto_aead_xchacha20poly1305_ietf_decrypt( + out.data(), + nullptr, + nullptr, + ciphertext.data(), + ciphertext.size(), + nullptr, + 0, + nonce, + key); + } + + std::pair>, std::array> x_keys( + std::span ed25519_secret_key) { + if (ed25519_secret_key.size() != 64) + throw std::invalid_argument{"Ed25519 secret key is not the expected 64 bytes"}; + + std::pair>, std::array> ret; + auto& [x_priv, x_pub] = ret; + + crypto_sign_ed25519_sk_to_curve25519(x_priv.data(), ed25519_secret_key.data()); + if (0 != crypto_sign_ed25519_pk_to_curve25519(x_pub.data(), ed25519_secret_key.data() + 32)) + throw std::runtime_error{"Failed to convert Ed25519 key to X25519: invalid secret key"}; + + return ret; + } + +} // namespace detail + +std::optional> decrypt_for_multiple( + const std::vector>& ciphertexts, + std::span nonce, + std::span privkey, + std::span pubkey, + std::span sender_pubkey, + std::string_view domain) { + + auto it = ciphertexts.begin(); + return decrypt_for_multiple( + [&]() -> std::optional> { + if (it == ciphertexts.end()) + return std::nullopt; + return *it++; + }, + nonce, + privkey, + pubkey, + sender_pubkey, + domain); +} + +std::vector encrypt_for_multiple_simple( + const std::vector>& messages, + const std::vector>& recipients, + std::span privkey, + std::span pubkey, + std::string_view domain, + std::optional> nonce, + int pad) { + + oxenc::bt_dict_producer d; + + std::array random_nonce; + if (!nonce) { + randombytes_buf(random_nonce.data(), random_nonce.size()); + nonce.emplace(random_nonce.data(), random_nonce.size()); + } else if (nonce->size() != 24) { + throw std::invalid_argument{"Invalid nonce: nonce must be 24 bytes"}; + } + + d.append("#", *nonce); + { + auto enc_list = d.append_list("e"); + + int msg_count = 0; + encrypt_for_multiple( + messages, + recipients, + *nonce, + privkey, + pubkey, + domain, + [&](std::span encrypted) { + enc_list.append(encrypted); + msg_count++; + }); + + if (int pad_size = pad > 1 && !messages.empty() ? messages.front().size() : 0) { + std::vector junk; + junk.resize(pad_size); + for (; msg_count % pad != 0; msg_count++) { + randombytes_buf(junk.data(), pad_size); + enc_list.append(to_string(junk)); + } + } + } + + return to_vector(d.span()); +} + +std::vector encrypt_for_multiple_simple( + const std::vector>& messages, + const std::vector>& recipients, + std::span ed25519_secret_key, + std::string_view domain, + std::span nonce, + int pad) { + + auto [x_privkey, x_pubkey] = detail::x_keys(ed25519_secret_key); + + return encrypt_for_multiple_simple( + messages, recipients, to_span(x_privkey), to_span(x_pubkey), domain, nonce, pad); +} + +std::optional> decrypt_for_multiple_simple( + std::span encoded, + std::span privkey, + std::span pubkey, + std::span sender_pubkey, + std::string_view domain) { + try { + oxenc::bt_dict_consumer d{encoded}; + auto nonce = d.require>("#"); + if (nonce.size() != 24) + return std::nullopt; + auto enc_list = d.require("e"); + + return decrypt_for_multiple( + [&]() -> std::optional> { + if (enc_list.is_finished()) + return std::nullopt; + return enc_list.consume>(); + }, + nonce, + privkey, + pubkey, + sender_pubkey, + domain); + } catch (...) { + return std::nullopt; + } +} + +std::optional> decrypt_for_multiple_simple( + std::span encoded, + std::span ed25519_secret_key, + std::span sender_pubkey, + std::string_view domain) { + + auto [x_privkey, x_pubkey] = detail::x_keys(ed25519_secret_key); + + return decrypt_for_multiple_simple( + encoded, to_span(x_privkey), to_span(x_pubkey), sender_pubkey, domain); +} + +std::optional> decrypt_for_multiple_simple_ed25519( + std::span encoded, + std::span ed25519_secret_key, + std::span sender_ed25519_pubkey, + std::string_view domain) { + + std::array sender_pub; + if (sender_ed25519_pubkey.size() != 32) + throw std::invalid_argument{"Invalid sender Ed25519 pubkey: expected 32 bytes"}; + if (0 != crypto_sign_ed25519_pk_to_curve25519(sender_pub.data(), sender_ed25519_pubkey.data())) + throw std::runtime_error{"Failed to convert Ed25519 key to X25519: invalid secret key"}; + + return decrypt_for_multiple_simple(encoded, ed25519_secret_key, to_span(sender_pub), domain); +} + +} // namespace session + +using namespace session; + +static unsigned char* to_c_buffer(std::span x, size_t* out_len) { + auto* ret = static_cast(malloc(x.size())); + *out_len = x.size(); + std::memcpy(ret, x.data(), x.size()); + return ret; +} + +LIBSESSION_C_API unsigned char* session_encrypt_for_multiple_simple( + size_t* out_len, + const unsigned char* const* messages, + const size_t* message_lengths, + size_t n_messages, + const unsigned char* const* recipients, + size_t n_recipients, + const unsigned char* x25519_privkey, + const unsigned char* x25519_pubkey, + const char* domain, + const unsigned char* nonce, + int pad) { + + std::vector> msgs, recips; + msgs.reserve(n_messages); + recips.reserve(n_recipients); + for (size_t i = 0; i < n_messages; i++) + msgs.emplace_back(messages[i], message_lengths[i]); + for (size_t i = 0; i < n_recipients; i++) + recips.emplace_back(recipients[i], 32); + std::optional> maybe_nonce; + if (nonce) + maybe_nonce.emplace(nonce, 24); + + try { + auto encoded = session::encrypt_for_multiple_simple( + msgs, + recips, + std::span{x25519_privkey, 32}, + std::span{x25519_pubkey, 32}, + domain, + std::move(maybe_nonce), + pad); + return to_c_buffer(encoded, out_len); + } catch (...) { + return nullptr; + } +} + +LIBSESSION_C_API unsigned char* session_encrypt_for_multiple_simple_ed25519( + size_t* out_len, + const unsigned char* const* messages, + const size_t* message_lengths, + size_t n_messages, + const unsigned char* const* recipients, + size_t n_recipients, + const unsigned char* ed25519_secret_key, + const char* domain, + const unsigned char* nonce, + int pad) { + + try { + auto [priv, pub] = + session::detail::x_keys(std::span{ed25519_secret_key, 64}); + return session_encrypt_for_multiple_simple( + out_len, + messages, + message_lengths, + n_messages, + recipients, + n_recipients, + priv.data(), + pub.data(), + domain, + nonce, + pad); + } catch (...) { + return nullptr; + } +} + +LIBSESSION_C_API unsigned char* session_decrypt_for_multiple_simple( + size_t* out_len, + const unsigned char* encoded, + size_t encoded_len, + const unsigned char* x25519_privkey, + const unsigned char* x25519_pubkey, + const unsigned char* sender_x25519_pubkey, + const char* domain) { + + try { + if (auto decrypted = session::decrypt_for_multiple_simple( + std::span{encoded, encoded_len}, + std::span{x25519_privkey, 32}, + std::span{x25519_pubkey, 32}, + std::span{sender_x25519_pubkey, 32}, + domain)) { + return to_c_buffer(*decrypted, out_len); + } + } catch (...) { + } + + return nullptr; +} + +LIBSESSION_C_API unsigned char* session_decrypt_for_multiple_simple_ed25519_from_x25519( + size_t* out_len, + const unsigned char* encoded, + size_t encoded_len, + const unsigned char* ed25519_secret, + const unsigned char* sender_x25519_pubkey, + const char* domain) { + + try { + if (auto decrypted = session::decrypt_for_multiple_simple( + std::span{encoded, encoded_len}, + std::span{ed25519_secret, 64}, + std::span{sender_x25519_pubkey, 32}, + domain)) { + return to_c_buffer(*decrypted, out_len); + } + } catch (...) { + } + + return nullptr; +} + +LIBSESSION_C_API unsigned char* session_decrypt_for_multiple_simple_ed25519( + size_t* out_len, + const unsigned char* encoded, + size_t encoded_len, + const unsigned char* ed25519_secret, + const unsigned char* sender_ed25519_pubkey, + const char* domain) { + + try { + if (auto decrypted = session::decrypt_for_multiple_simple_ed25519( + std::span{encoded, encoded_len}, + std::span{ed25519_secret, 64}, + std::span{sender_ed25519_pubkey, 32}, + domain)) { + return to_c_buffer(*decrypted, out_len); + } + } catch (...) { + } + + return nullptr; +} diff --git a/src/onionreq/builder.cpp b/src/onionreq/builder.cpp new file mode 100644 index 00000000..bea6688c --- /dev/null +++ b/src/onionreq/builder.cpp @@ -0,0 +1,418 @@ +#include "session/onionreq/builder.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "session/export.h" +#include "session/onionreq/builder.h" +#include "session/onionreq/hop_encryption.hpp" +#include "session/onionreq/key_types.hpp" +#include "session/session_network.hpp" +#include "session/util.hpp" +#include "session/xed25519.hpp" + +using namespace std::literals; +using namespace oxen::log::literals; + +namespace session::onionreq { + +namespace detail { + session::onionreq::x25519_pubkey pubkey_for_destination(network_destination destination) { + if (auto* dest = std::get_if(&destination)) + return compute_x25519_pubkey(dest->view_remote_key()); + + if (auto* dest = std::get_if(&destination)) + return dest->x25519_pubkey; + + throw std::runtime_error{"Invalid destination."}; + } +} // namespace detail + +namespace { + + std::vector encode_size(uint32_t s) { + std::vector result; + result.resize(4); + oxenc::write_host_as_little(s, result.data()); + return result; + } +} // namespace + +EncryptType parse_enc_type(std::string_view enc_type) { + if (enc_type == "xchacha20" || enc_type == "xchacha20-poly1305") + return EncryptType::xchacha20; + if (enc_type == "aes-gcm" || enc_type == "gcm") + return EncryptType::aes_gcm; + throw std::runtime_error{"Invalid encryption type " + std::string{enc_type}}; +} + +Builder Builder::make( + const network_destination& destination, + const std::vector& nodes, + const EncryptType enc_type_) { + return Builder{destination, nodes, enc_type_}; +} + +Builder::Builder( + const network_destination& destination, + const std::vector& nodes, + const EncryptType enc_type_) : + enc_type{enc_type_}, + destination_x25519_public_key{detail::pubkey_for_destination(destination)} { + set_destination(destination); + for (auto& n : nodes) + add_hop(n.view_remote_key()); +} + +void Builder::add_hop(std::span remote_key) { + hops_.push_back({ed25519_pubkey::from_bytes(remote_key), compute_x25519_pubkey(remote_key)}); +} + +void Builder::set_destination(network_destination destination) { + ed25519_public_key_.reset(); + + if (auto* dest = std::get_if(&destination)) + ed25519_public_key_.emplace(ed25519_pubkey::from_bytes(dest->view_remote_key())); + else if (auto* dest = std::get_if(&destination)) { + host_.emplace(dest->host); + endpoint_.emplace(dest->endpoint); + method_.emplace(dest->method); + + // Remove the '://' from the protocol if it was given + size_t pos = dest->protocol.find("://"); + if (pos != std::string::npos) + protocol_.emplace(dest->protocol.substr(0, pos)); + else + protocol_.emplace(dest->protocol); + + if (dest->port) + port_.emplace(*dest->port); + + if (dest->headers) + headers_.emplace(*dest->headers); + } else + throw std::invalid_argument{"Invalid destination type."}; +} + +void Builder::set_destination_pubkey(session::onionreq::x25519_pubkey x25519_pubkey) { + destination_x25519_public_key.reset(); + destination_x25519_public_key.emplace(x25519_pubkey); +} + +void Builder::generate(network::request_info& info) { + info.body = build(_generate_payload(info.original_body)); +} + +std::vector Builder::_generate_payload( + std::optional> body) const { + // If we don't have the data required for a server request, then assume it's targeting a + // service node and, therefore, the `body` is the payload + if (!host_ || !endpoint_ || !protocol_ || !method_ || !destination_x25519_public_key) + return body.value_or(std::vector{}); + + // Otherwise generate the payload for a server request + auto headers_json = nlohmann::json::object(); + + if (headers_) + for (const auto& [key, value] : *headers_) { + // Some platforms might automatically add this header, but we don't want to include it + if (key != "User-Agent") + headers_json[key] = value; + } + + if (body && !headers_json.contains("Content-Type")) + headers_json["Content-Type"] = "application/json"; + + // Structure the request information + nlohmann::json request_info{ + {"method", *method_}, {"endpoint", *endpoint_}, {"headers", headers_json}}; + std::vector payload{request_info.dump()}; + + // If we were given a body, add it to the payload + if (body.has_value()) + payload.emplace_back(session::to_string(*body)); + + auto result = oxenc::bt_serialize(payload); + return to_vector(result); +} + +std::vector Builder::build(std::vector payload) { + std::vector blob; + + // First hop: + // + // [N][ENCRYPTED]{json} + // + // where json has the ephemeral_key indicating how we encrypted ENCRYPTED for this first hop. + // The first hop decrypts ENCRYPTED into: + // + // [N][BLOB]{json} + // + // where [N] is the length of the blob and {json} now contains either: + // - a "headers" key with an empty value. This is how we indicate that the request is for this + // node as the final hop, and means that the BLOB is actually JSON it should parse to get the + // request info (which has "method", "params", etc. in it). + // - "host"/"target"/"port"/"protocol" asking for an HTTP or HTTPS proxy request to be made + // (though "target" must start with /loki/ or /oxen/ and end with /lsrpc). (There is still a + // blob here, but it is not used and typically empty). + // - "destination" and "ephemeral_key" to forward the request to the next hop. + // + // This later case continues onion routing by giving us something like: + // + // {"destination":"ed25519pubkey","ephemeral_key":"x25519-eph-pubkey-for-decryption","enc_type":"xchacha20"} + // + // (enc_type can also be aes-gcm, and defaults to that if not specified). We forward this via + // oxenmq to the given ed25519pubkey (but since oxenmq uses x25519 pubkeys we first have to go + // look it up), sending an oxenmq request to sn.onion_req_v2 of the following (but bencoded, not + // json): + // + // { "d": "BLOB", "ek": "ephemeral-key-in-binary", "et": "xchacha20", "nh": N } + // + // where BLOB is the opaque data received from the previous hop and N is the hop number which + // gets incremented at each hop (and terminates if it exceeds 15). That next hop decrypts BLOB, + // giving it a value interpreted as the same [N][BLOB]{json} as above, and we recurse. + // + // On the *return* trip, the message gets encrypted (once!) at the final destination using the + // derived key from the pubkey given to the final hop, base64-encoded, then passed back without + // any onion encryption at all all the way back to the client. + + // Ephemeral keypair: + x25519_pubkey A; + x25519_seckey a; + nlohmann::json final_route; + + { + crypto_box_keypair(A.data(), a.data()); + HopEncryption e{a, A, false}; + + // The data we send to the destination differs depending on whether the destination is a + // server or a service node + if (host_ && protocol_ && destination_x25519_public_key) { + final_route = { + {"host", *host_}, + {"target", "/oxen/v4/lsrpc"}, // All servers support V4 onion requests + {"method", "POST"}, + {"protocol", *protocol_}, + {"port", port_.value_or(*protocol_ == "https" ? 443 : 80)}, + {"ephemeral_key", A.hex()}, // The x25519 ephemeral_key here is the key for the + // *next* hop to use + {"enc_type", to_string(enc_type)}, + }; + + blob = e.encrypt(enc_type, payload, *destination_x25519_public_key); + } else if (ed25519_public_key_ && destination_x25519_public_key) { + nlohmann::json control{{"headers", ""}}; + final_route = { + {"destination", ed25519_public_key_.value().hex()}, // Next hop's ed25519 key + {"ephemeral_key", A.hex()}, // The x25519 ephemeral_key here is the key for the + // *next* hop to use + {"enc_type", to_string(enc_type)}, + }; + + auto control_dump = control.dump(); + auto control_span = to_span(control_dump); + auto data = encode_size(payload.size()); + data.insert(data.end(), payload.begin(), payload.end()); + data.insert(data.end(), control_span.begin(), control_span.end()); + blob = e.encrypt(enc_type, data, *destination_x25519_public_key); + } else { + if (!destination_x25519_public_key.has_value()) + throw std::runtime_error{"Destination not set: No destination x25519 public key"}; + if (!ed25519_public_key_.has_value()) + throw std::runtime_error{"Destination not set: No destination ed25519 public key"}; + throw std::runtime_error{ + "Destination not set: " + host_.value_or("N/A") + ", " + + protocol_.value_or("N/A")}; + } + + // Save these because we need them again to decrypt the final response: + final_hop_x25519_keypair.reset(); + final_hop_x25519_keypair.emplace(A, a); + } + + for (auto it = hops_.rbegin(); it != hops_.rend(); ++it) { + // Routing data for this hop: + nlohmann::json routing; + + if (it == hops_.rbegin()) { + routing = final_route; + } else { + routing = { + {"destination", std::prev(it)->first.hex()}, // Next hop's ed25519 key + {"ephemeral_key", A.hex()}, // The x25519 ephemeral_key here is the key for the + // *next* hop to use + {"enc_type", to_string(enc_type)}, + }; + } + + auto routing_dump = routing.dump(); + auto routing_span = to_span(routing_dump); + auto data = encode_size(blob.size()); + data.insert(data.end(), blob.begin(), blob.end()); + data.insert(data.end(), routing_span.begin(), routing_span.end()); + + // Generate eph key for *this* request and encrypt it: + crypto_box_keypair(A.data(), a.data()); + HopEncryption e{a, A, false}; + blob = e.encrypt(enc_type, data, it->second); + } + + // The data going to the first hop needs to be wrapped in one more layer to tell the first hop + // how to decrypt the initial payload: + auto wrapper_dump = + nlohmann::json{{"ephemeral_key", A.hex()}, {"enc_type", to_string(enc_type)}}.dump(); + auto wrapper_span = to_span(wrapper_dump); + auto result = encode_size(blob.size()); + result.insert(result.end(), blob.begin(), blob.end()); + result.insert(result.end(), wrapper_span.begin(), wrapper_span.end()); + + return result; +} +} // namespace session::onionreq + +namespace { + +session::onionreq::Builder& unbox(onion_request_builder_object* builder) { + assert(builder && builder->internals); + return *static_cast(builder->internals); +} + +} // namespace + +extern "C" { + +LIBSESSION_C_API void onion_request_builder_init(onion_request_builder_object** builder) { + auto c_builder = std::make_unique(); + c_builder->internals = new session::onionreq::Builder{}; + *builder = c_builder.release(); +} + +LIBSESSION_C_API void onion_request_builder_free(onion_request_builder_object* builder) { + delete static_cast(builder->internals); + delete builder; +} + +LIBSESSION_C_API void onion_request_builder_set_enc_type( + onion_request_builder_object* builder, ENCRYPT_TYPE enc_type) { + assert(builder); + + switch (enc_type) { + case ENCRYPT_TYPE::ENCRYPT_TYPE_AES_GCM: + unbox(builder).set_enc_type(session::onionreq::EncryptType::aes_gcm); + break; + + case ENCRYPT_TYPE::ENCRYPT_TYPE_X_CHA_CHA_20: + unbox(builder).set_enc_type(session::onionreq::EncryptType::xchacha20); + break; + + default: throw std::runtime_error{"Invalid encryption type"}; + } +} + +LIBSESSION_C_API void onion_request_builder_set_snode_destination( + onion_request_builder_object* builder, + const uint8_t ip[4], + const uint16_t quic_port, + const char* ed25519_pubkey) { + assert(builder && ip && ed25519_pubkey); + + std::array target_ip; + std::memcpy(target_ip.data(), ip, target_ip.size()); + + unbox(builder).set_destination(session::network::service_node( + oxenc::from_hex({ed25519_pubkey, 64}), + {0}, + session::network::INVALID_SWARM_ID, + "{}"_format(fmt::join(target_ip, ".")), + quic_port)); +} + +LIBSESSION_C_API void onion_request_builder_set_server_destination( + onion_request_builder_object* builder, + const char* protocol, + const char* host, + const char* endpoint, + const char* method, + uint16_t port, + const char* x25519_pubkey) { + assert(builder && protocol && host && endpoint && protocol && x25519_pubkey); + + unbox(builder).set_destination(session::onionreq::ServerDestination{ + protocol, + host, + endpoint, + session::onionreq::x25519_pubkey::from_hex({x25519_pubkey, 64}), + port, + std::nullopt, + method}); +} + +LIBSESSION_C_API void onion_request_builder_set_destination_pubkey( + onion_request_builder_object* builder, const char* x25519_pubkey) { + assert(builder && x25519_pubkey); + + unbox(builder).set_destination_pubkey( + session::onionreq::x25519_pubkey::from_hex({x25519_pubkey, 64})); +} + +LIBSESSION_C_API void onion_request_builder_add_hop( + onion_request_builder_object* builder, + const char* ed25519_pubkey, + const char* x25519_pubkey) { + assert(builder && ed25519_pubkey && x25519_pubkey); + + unbox(builder).add_hop( + {session::onionreq::ed25519_pubkey::from_hex({ed25519_pubkey, 64}), + session::onionreq::x25519_pubkey::from_hex({x25519_pubkey, 64})}); +} + +LIBSESSION_C_API bool onion_request_builder_build( + onion_request_builder_object* builder, + const unsigned char* payload_in, + size_t payload_in_len, + unsigned char** payload_out, + size_t* payload_out_len, + unsigned char* final_x25519_pubkey_out, + unsigned char* final_x25519_seckey_out) { + assert(builder && payload_in); + + try { + auto& unboxed_builder = unbox(builder); + auto payload = unboxed_builder.build({payload_in, payload_in + payload_in_len}); + + if (unboxed_builder.final_hop_x25519_keypair) { + auto key_pair = unboxed_builder.final_hop_x25519_keypair.value(); + std::memcpy(final_x25519_pubkey_out, key_pair.first.data(), key_pair.first.size()); + std::memcpy(final_x25519_seckey_out, key_pair.second.data(), key_pair.second.size()); + } else { + throw std::runtime_error{"Final keypair not generated"}; + } + + *payload_out = static_cast(malloc(payload.size())); + *payload_out_len = payload.size(); + std::memcpy(*payload_out, payload.data(), payload.size()); + + return true; + } catch (...) { + return false; + } +} +} diff --git a/src/onionreq/hop_encryption.cpp b/src/onionreq/hop_encryption.cpp new file mode 100644 index 00000000..611ce0bd --- /dev/null +++ b/src/onionreq/hop_encryption.cpp @@ -0,0 +1,245 @@ +#include "session/onionreq/hop_encryption.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "session/export.h" +#include "session/onionreq/builder.hpp" +#include "session/onionreq/key_types.hpp" +#include "session/util.hpp" +#include "session/xed25519.hpp" + +namespace session::onionreq { + +namespace { + + // Derive shared secret from our (ephemeral) `seckey` and the other party's `pubkey` + std::array calculate_shared_secret( + const x25519_seckey& seckey, const x25519_pubkey& pubkey) { + std::array secret; + if (crypto_scalarmult(secret.data(), seckey.data(), pubkey.data()) != 0) + throw std::runtime_error("Shared key derivation failed (crypto_scalarmult)"); + return secret; + } + + constexpr std::string_view salt{"LOKI"}; + + std::array derive_symmetric_key( + const x25519_seckey& seckey, const x25519_pubkey& pubkey) { + auto key = calculate_shared_secret(seckey, pubkey); + + auto usalt = to_span(salt); + + crypto_auth_hmacsha256_state state; + + crypto_auth_hmacsha256_init(&state, usalt.data(), usalt.size()); + crypto_auth_hmacsha256_update(&state, key.data(), key.size()); + crypto_auth_hmacsha256_final(&state, key.data()); + + return key; + } + + // More robust shared secret calculation, used when using xchacha20-poly1305 encryption. (This + // could be used for AES-GCM as well, but would break backwards compatibility with existing + // Session clients). + std::array xchacha20_shared_key( + const x25519_pubkey& local_pub, + const x25519_seckey& local_sec, + const x25519_pubkey& remote_pub, + bool local_first) { + std::array key; + static_assert(crypto_aead_xchacha20poly1305_ietf_KEYBYTES >= crypto_scalarmult_BYTES); + if (0 != crypto_scalarmult( + key.data(), + local_sec.data(), + remote_pub.data())) // Use key as tmp storage for aB + throw std::runtime_error{"Failed to compute shared key for xchacha20"}; + crypto_generichash_state h; + crypto_generichash_init(&h, nullptr, 0, key.size()); + crypto_generichash_update(&h, key.data(), crypto_scalarmult_BYTES); + crypto_generichash_update( + &h, (local_first ? local_pub : remote_pub).data(), local_pub.size()); + crypto_generichash_update( + &h, (local_first ? remote_pub : local_pub).data(), local_pub.size()); + crypto_generichash_final(&h, key.data(), key.size()); + return key; + } + +} // namespace + +bool HopEncryption::response_long_enough(EncryptType type, size_t response_size) { + switch (type) { + case EncryptType::xchacha20: + return (response_size >= crypto_aead_xchacha20poly1305_ietf_ABYTES); + case EncryptType::aes_gcm: return (response_size >= GCM_IV_SIZE + GCM_DIGEST_SIZE); + } + return false; +} + +std::vector HopEncryption::encrypt( + EncryptType type, std::vector plaintext, const x25519_pubkey& pubkey) const { + switch (type) { + case EncryptType::xchacha20: return encrypt_xchacha20(plaintext, pubkey); + case EncryptType::aes_gcm: return encrypt_aesgcm(plaintext, pubkey); + } + throw std::runtime_error{"Invalid encryption type"}; +} + +std::vector HopEncryption::decrypt( + EncryptType type, + std::vector ciphertext, + const x25519_pubkey& pubkey) const { + switch (type) { + case EncryptType::xchacha20: return decrypt_xchacha20(ciphertext, pubkey); + case EncryptType::aes_gcm: return decrypt_aesgcm(ciphertext, pubkey); + } + throw std::runtime_error{"Invalid decryption type"}; +} + +std::vector HopEncryption::encrypt_aesgcm( + std::vector plaintext, const x25519_pubkey& pubKey) const { + auto key = derive_symmetric_key(private_key_, pubKey); + + // Initialise cipher context with the key + struct gcm_aes256_ctx ctx; + static_assert(key.size() == AES256_KEY_SIZE); + gcm_aes256_set_key(&ctx, key.data()); + + std::vector output; + output.resize(GCM_IV_SIZE + plaintext.size() + GCM_DIGEST_SIZE); + + // Start the output with the random IV, and load it into ctx + auto* o = output.data(); + randombytes_buf(o, GCM_IV_SIZE); + gcm_aes256_set_iv(&ctx, GCM_IV_SIZE, o); + o += GCM_IV_SIZE; + + // Append encrypted data + gcm_aes256_encrypt(&ctx, plaintext.size(), o, plaintext.data()); + o += plaintext.size(); + + // Append digest + gcm_aes256_digest(&ctx, GCM_DIGEST_SIZE, o); + o += GCM_DIGEST_SIZE; + + assert(o == output.data() + output.size()); + + return output; +} + +std::vector HopEncryption::decrypt_aesgcm( + std::vector ciphertext_, const x25519_pubkey& pubKey) const { + std::span ciphertext = to_span(ciphertext_); + + if (!response_long_enough(EncryptType::aes_gcm, ciphertext_.size())) + throw std::invalid_argument{ + "Ciphertext data is too short: " + session::to_string(ciphertext_)}; + + auto key = derive_symmetric_key(private_key_, pubKey); + + // Initialise cipher context with the key + struct gcm_aes256_ctx ctx; + static_assert(key.size() == AES256_KEY_SIZE); + gcm_aes256_set_key(&ctx, key.data()); + + gcm_aes256_set_iv(&ctx, GCM_IV_SIZE, ciphertext.data()); + + ciphertext = ciphertext.subspan(GCM_IV_SIZE); + auto digest_in = ciphertext.subspan(ciphertext.size() - GCM_DIGEST_SIZE); + ciphertext = ciphertext.subspan(0, ciphertext.size() - GCM_DIGEST_SIZE); + + std::vector plaintext; + plaintext.resize(ciphertext.size()); + + gcm_aes256_decrypt(&ctx, ciphertext.size(), plaintext.data(), ciphertext.data()); + + std::array digest_out; + gcm_aes256_digest(&ctx, digest_out.size(), digest_out.data()); + + if (sodium_memcmp(digest_out.data(), digest_in.data(), GCM_DIGEST_SIZE) != 0) + throw std::runtime_error{"Decryption failed (AES256-GCM)"}; + + return plaintext; +} + +std::vector HopEncryption::encrypt_xchacha20( + std::vector plaintext, const x25519_pubkey& pubKey) const { + + std::vector ciphertext; + ciphertext.resize( + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + plaintext.size() + + crypto_aead_xchacha20poly1305_ietf_ABYTES); + + const auto key = xchacha20_shared_key(public_key_, private_key_, pubKey, !server_); + + // Generate random nonce, and stash it at the beginning of ciphertext: + randombytes_buf(ciphertext.data(), crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + + auto* c = reinterpret_cast(ciphertext.data()) + + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES; + unsigned long long clen; + + crypto_aead_xchacha20poly1305_ietf_encrypt( + c, + &clen, + plaintext.data(), + plaintext.size(), + nullptr, + 0, // additional data + nullptr, // nsec (always unused) + reinterpret_cast(ciphertext.data()), + key.data()); + assert(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + clen <= ciphertext.size()); + ciphertext.resize(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + clen); + return ciphertext; +} + +std::vector HopEncryption::decrypt_xchacha20( + std::vector ciphertext_, const x25519_pubkey& pubKey) const { + std::span ciphertext = to_span(ciphertext_); + + // Extract nonce from the beginning of the ciphertext: + auto nonce = ciphertext.subspan(0, crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + ciphertext = ciphertext.subspan(nonce.size()); + + if (!response_long_enough(EncryptType::xchacha20, ciphertext_.size())) + throw std::invalid_argument{ + "Ciphertext data is too short: " + + std::string(reinterpret_cast(ciphertext_.data()))}; + + const auto key = xchacha20_shared_key(public_key_, private_key_, pubKey, !server_); + + std::vector plaintext; + plaintext.resize(ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES); + auto* m = reinterpret_cast(plaintext.data()); + unsigned long long mlen; + if (0 != crypto_aead_xchacha20poly1305_ietf_decrypt( + m, + &mlen, + nullptr, // nsec (always unused) + ciphertext.data(), + ciphertext.size(), + nullptr, + 0, // additional data + nonce.data(), + key.data())) + throw std::runtime_error{"Could not decrypt (XChaCha20-Poly1305)"}; + assert(mlen <= plaintext.size()); + plaintext.resize(mlen); + return plaintext; +} + +} // namespace session::onionreq diff --git a/src/onionreq/key_types.cpp b/src/onionreq/key_types.cpp new file mode 100644 index 00000000..f99174a2 --- /dev/null +++ b/src/onionreq/key_types.cpp @@ -0,0 +1,93 @@ +#include "session/onionreq/key_types.hpp" + +#include +#include +#include +#include + +#include +#include + +namespace session::onionreq { + +namespace detail { + + void load_from_hex(void* buffer, size_t length, std::string_view hex) { + if (!oxenc::is_hex(hex)) + throw std::runtime_error{"Hex key data is invalid: data is not hex"}; + if (hex.size() != 2 * length) + throw std::runtime_error{ + "Hex key data is invalid: expected " + std::to_string(length) + + " hex digits, received " + std::to_string(hex.size())}; + oxenc::from_hex(hex.begin(), hex.end(), reinterpret_cast(buffer)); + } + + void load_from_bytes(void* buffer, size_t length, std::string_view bytes) { + if (bytes.size() != length) + throw std::runtime_error{ + "Key data is invalid: expected " + std::to_string(length) + + " bytes, received " + std::to_string(bytes.size())}; + std::memmove(buffer, bytes.data(), length); + } + +} // namespace detail + +std::string ed25519_pubkey::snode_address() const { + auto addr = oxenc::to_base32z(begin(), end()); + addr += ".snode"; + return addr; +} + +legacy_pubkey legacy_seckey::pubkey() const { + legacy_pubkey pk; + crypto_scalarmult_ed25519_base_noclamp(pk.data(), data()); + return pk; +}; +ed25519_pubkey ed25519_seckey::pubkey() const { + ed25519_pubkey pk; + crypto_sign_ed25519_sk_to_pk(pk.data(), data()); + return pk; +}; +x25519_pubkey x25519_seckey::pubkey() const { + x25519_pubkey pk; + crypto_scalarmult_curve25519_base(pk.data(), data()); + return pk; +}; + +template +static T parse_pubkey(std::string_view pubkey_in) { + T pk{}; + static_assert(pk.size() == 32); + if (pubkey_in.size() == 32) + detail::load_from_bytes(pk.data(), 32, pubkey_in); + else if (pubkey_in.size() == 64 && oxenc::is_hex(pubkey_in)) + oxenc::from_hex(pubkey_in.begin(), pubkey_in.end(), pk.begin()); + else if ( + (pubkey_in.size() == 43 || (pubkey_in.size() == 44 && pubkey_in.back() == '=')) && + oxenc::is_base64(pubkey_in)) + oxenc::from_base64(pubkey_in.begin(), pubkey_in.end(), pk.begin()); + else if (pubkey_in.size() == 52 && oxenc::is_base32z(pubkey_in)) + oxenc::from_base32z(pubkey_in.begin(), pubkey_in.end(), pk.begin()); + + return pk; +} + +legacy_pubkey parse_legacy_pubkey(std::string_view pubkey_in) { + return parse_pubkey(pubkey_in); +} +ed25519_pubkey parse_ed25519_pubkey(std::string_view pubkey_in) { + return parse_pubkey(pubkey_in); +} +x25519_pubkey parse_x25519_pubkey(std::string_view pubkey_in) { + return parse_pubkey(pubkey_in); +} +x25519_pubkey compute_x25519_pubkey(std::span ed25519_pk) { + std::array xpk; + if (0 != crypto_sign_ed25519_pk_to_curve25519(xpk.data(), ed25519_pk.data())) + throw std::runtime_error{ + "An error occured while attempting to convert Ed25519 pubkey to X25519; " + "is the pubkey valid?"}; + return x25519_pubkey::from_bytes({xpk.data(), 32}); +} + +} // namespace session::onionreq diff --git a/src/onionreq/parser.cpp b/src/onionreq/parser.cpp new file mode 100644 index 00000000..766150db --- /dev/null +++ b/src/onionreq/parser.cpp @@ -0,0 +1,50 @@ +#include "session/onionreq/parser.hpp" + +#include +#include + +#include +#include + +namespace session::onionreq { + +OnionReqParser::OnionReqParser( + std::span x25519_pk, + std::span x25519_sk, + std::span req, + size_t max_size) : + keys{x25519_pubkey::from_bytes(x25519_pk), x25519_seckey::from_bytes(x25519_sk)}, + enc{keys.second, keys.first} { + if (sodium_init() == -1) + throw std::runtime_error{"Failed to initialize libsodium!"}; + if (req.size() < sizeof(uint32_t)) + throw std::invalid_argument{"onion request data too small"}; + if (req.size() > max_size) + throw std::invalid_argument{"onion request data too big"}; + auto size = oxenc::load_little_to_host(req.data()); + req = req.subspan(sizeof(size)); + + if (req.size() < size) + throw std::invalid_argument{"encrypted onion request data segment too small"}; + auto ciphertext = req.subspan(0, size); + req = req.subspan(size); + auto metadata = nlohmann::json::parse(req); + + if (auto encit = metadata.find("enc_type"); encit != metadata.end()) + enc_type = parse_enc_type(encit->get()); + // else leave it at the backwards-compat AES-GCM default + + if (auto itr = metadata.find("ephemeral_key"); itr != metadata.end()) + remote_pk = parse_x25519_pubkey(itr->get()); + else + throw std::invalid_argument{"metadata does not have 'ephemeral_key' entry"}; + + payload_ = enc.decrypt(enc_type, to_vector(ciphertext), remote_pk); +} + +std::vector OnionReqParser::encrypt_reply( + std::span reply) const { + return enc.encrypt(enc_type, to_vector(reply), remote_pk); +} + +} // namespace session::onionreq diff --git a/src/onionreq/response_parser.cpp b/src/onionreq/response_parser.cpp new file mode 100644 index 00000000..64c0d3b1 --- /dev/null +++ b/src/onionreq/response_parser.cpp @@ -0,0 +1,122 @@ +#include "session/onionreq/response_parser.hpp" + +#include +#include + +#include + +#include "session/export.h" +#include "session/onionreq/builder.h" +#include "session/onionreq/builder.hpp" +#include "session/onionreq/hop_encryption.hpp" + +namespace session::onionreq { + +ResponseParser::ResponseParser(session::onionreq::Builder builder) { + if (!builder.destination_x25519_public_key.has_value()) + throw std::runtime_error{"Builder does not contain destination x25519 public key"}; + if (!builder.final_hop_x25519_keypair.has_value()) + throw std::runtime_error{"Builder does not contain final keypair"}; + + enc_type_ = builder.enc_type; + destination_x25519_public_key_ = builder.destination_x25519_public_key.value(); + x25519_keypair_ = builder.final_hop_x25519_keypair.value(); +} + +bool ResponseParser::response_long_enough(EncryptType enc_type, size_t response_size) { + return HopEncryption::response_long_enough(enc_type, response_size); +} + +std::vector ResponseParser::decrypt(std::vector ciphertext) const { + HopEncryption d{x25519_keypair_.second, x25519_keypair_.first, false}; + + // FIXME: The legacy PN server doesn't support 'xchacha20' onion requests so would return an + // error encrypted with 'aes_gcm' so try to decrypt in case that is what happened - this + // workaround can be removed once the legacy PN server is removed + try { + return d.decrypt(enc_type_, ciphertext, destination_x25519_public_key_); + } catch (const std::exception& e) { + if (enc_type_ == session::onionreq::EncryptType::xchacha20) { + try { + return d.decrypt( + session::onionreq::EncryptType::aes_gcm, + ciphertext, + destination_x25519_public_key_); + } catch (...) { + throw std::runtime_error{std::string(decryption_failed_error)}; + } + } else + throw; + } +} + +} // namespace session::onionreq + +extern "C" { + +LIBSESSION_C_API bool onion_request_decrypt( + const unsigned char* ciphertext_, + size_t ciphertext_len, + ENCRYPT_TYPE enc_type_, + unsigned char* destination_x25519_pubkey, + unsigned char* final_x25519_pubkey, + unsigned char* final_x25519_seckey, + unsigned char** plaintext_out, + size_t* plaintext_out_len) { + assert(ciphertext_ && destination_x25519_pubkey && final_x25519_pubkey && final_x25519_seckey && + ciphertext_len > 0); + + try { + auto enc_type = session::onionreq::EncryptType::xchacha20; + + switch (enc_type_) { + case ENCRYPT_TYPE::ENCRYPT_TYPE_AES_GCM: + enc_type = session::onionreq::EncryptType::aes_gcm; + break; + + case ENCRYPT_TYPE::ENCRYPT_TYPE_X_CHA_CHA_20: + enc_type = session::onionreq::EncryptType::xchacha20; + break; + + default: + throw std::runtime_error{"Invalid decryption type " + std::to_string(enc_type_)}; + } + + session::onionreq::HopEncryption d{ + session::onionreq::x25519_seckey::from_bytes({final_x25519_seckey, 32}), + session::onionreq::x25519_pubkey::from_bytes({final_x25519_pubkey, 32}), + false}; + + std::vector result; + std::vector ciphertext; + ciphertext.reserve(ciphertext_len); + ciphertext.assign(ciphertext_, ciphertext_ + ciphertext_len); + + // FIXME: The legacy PN server doesn't support 'xchacha20' onion requests so would return an + // error encrypted with 'aes_gcm' so try to decrypt in case that is what happened - this + // workaround can be removed once the legacy PN server is removed + try { + result = d.decrypt( + enc_type, + ciphertext, + session::onionreq::x25519_pubkey::from_bytes({destination_x25519_pubkey, 32})); + } catch (...) { + if (enc_type == session::onionreq::EncryptType::xchacha20) + result = d.decrypt( + session::onionreq::EncryptType::aes_gcm, + ciphertext, + session::onionreq::x25519_pubkey::from_bytes( + {destination_x25519_pubkey, 32})); + else + return false; + } + + *plaintext_out = static_cast(malloc(result.size())); + *plaintext_out_len = result.size(); + std::memcpy(*plaintext_out, result.data(), result.size()); + return true; + } catch (...) { + return false; + } +} +} diff --git a/src/random.cpp b/src/random.cpp new file mode 100644 index 00000000..ac06bb2f --- /dev/null +++ b/src/random.cpp @@ -0,0 +1,49 @@ +#include "session/random.hpp" + +#include + +#include + +#include "session/export.h" +#include "session/util.hpp" + +namespace session { +// make this once, and only once, and use it where needed +CSRNG csrng = CSRNG{}; +} // namespace session + +namespace session::random { + +constexpr char base32_charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +std::vector random(size_t size) { + std::vector result; + result.resize(size); + randombytes_buf(result.data(), size); + + return result; +} + +std::string random_base32(size_t size) { + std::string result; + result.reserve(size); + auto n_chars = sizeof(base32_charset) - 1; + + for (size_t i = 0; i < size; ++i) + result.push_back(base32_charset[csrng() % n_chars]); + + return result; +} + +} // namespace session::random + +extern "C" { + +LIBSESSION_C_API unsigned char* session_random(size_t size) { + auto result = session::random::random(size); + auto* ret = static_cast(malloc(size)); + std::memcpy(ret, result.data(), result.size()); + return ret; +} + +} // extern "C" diff --git a/src/session_encrypt.cpp b/src/session_encrypt.cpp new file mode 100644 index 00000000..b1003d66 --- /dev/null +++ b/src/session_encrypt.cpp @@ -0,0 +1,1036 @@ +#include "session/session_encrypt.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "session/blinding.hpp" +#include "session/sodium_array.hpp" + +using namespace std::literals; + +namespace session { + +namespace detail { + inline int64_t to_epoch_ms(std::chrono::system_clock::time_point t) { + return std::chrono::duration_cast(t.time_since_epoch()).count(); + } + + // detail::to_hashable takes either an integral type, system_clock::time_point, or a string + // type and converts it to a string_view by writing an integer value (using std::to_chars) + // into the buffer space (which should be at least 20 bytes), and returning a string_view + // into the written buffer space. For strings/string_views the string_view is returned + // directly from the argument. system_clock::time_points are converted into integral + // milliseconds since epoch then treated as an integer value. + template , int> = 0> + std::string_view to_hashable(const T& val, char*& buffer) { + std::ostringstream ss; + ss << val; + + std::string str = ss.str(); + std::copy(str.begin(), str.end(), buffer); + std::string_view s(buffer, str.length()); + buffer += str.length(); + return s; + } + inline std::string_view to_hashable( + const std::chrono::system_clock::time_point& val, char*& buffer) { + return to_hashable(to_epoch_ms(val), buffer); + } + template , int> = 0> + std::string_view to_hashable(const T& value, char*&) { + return value; + } + +} // namespace detail + +// Version tag we prepend to encrypted-for-blinded-user messages. This is here so we can detect if +// some future version changes the format (and if not even try to load it). +inline constexpr unsigned char BLINDED_ENCRYPT_VERSION = 0; + +std::vector sign_for_recipient( + std::span ed25519_privkey, + std::span recipient_pubkey, + std::span message) { + cleared_uc64 ed_sk_from_seed; + if (ed25519_privkey.size() == 32) { + uc32 ignore_pk; + crypto_sign_ed25519_seed_keypair( + ignore_pk.data(), ed_sk_from_seed.data(), ed25519_privkey.data()); + ed25519_privkey = {ed_sk_from_seed.data(), ed_sk_from_seed.size()}; + } else if (ed25519_privkey.size() != 64) { + throw std::invalid_argument{"Invalid ed25519_privkey: expected 32 or 64 bytes"}; + } + // If prefixed, drop it (and do this for the caller, too) so that everything after this + // doesn't need to worry about whether it is prefixed or not. + if (recipient_pubkey.size() == 33 && recipient_pubkey.front() == 0x05) + recipient_pubkey = recipient_pubkey.subspan(1); + else if (recipient_pubkey.size() != 32) + throw std::invalid_argument{ + "Invalid recipient_pubkey: expected 32 bytes (33 with 05 prefix)"}; + + std::vector buf; + buf.reserve(message.size() + 96); // 32+32 now, but 32+64 when we reuse it for the sealed box + buf.insert(buf.end(), message.begin(), message.end()); + buf.insert( + buf.end(), + ed25519_privkey.begin() + 32, + ed25519_privkey.end()); // [32:] of a libsodium full seed value is the *pubkey* + buf.insert(buf.end(), recipient_pubkey.begin(), recipient_pubkey.end()); + + uc64 sig; + if (0 != crypto_sign_ed25519_detached( + sig.data(), nullptr, buf.data(), buf.size(), ed25519_privkey.data())) + throw std::runtime_error{"Failed to sign; perhaps the secret key is invalid?"}; + + // We have M||A||Y for the sig, but now we want M||A||SIG so drop Y then append SIG: + buf.resize(buf.size() - 32); + buf.insert(buf.end(), sig.begin(), sig.end()); + + return buf; +} + +static const std::span BOX_HASHKEY = to_span("SessionBoxEphemeralHashKey"); + +std::vector encrypt_for_recipient( + std::span ed25519_privkey, + std::span recipient_pubkey, + std::span message) { + + auto signed_msg = sign_for_recipient(ed25519_privkey, recipient_pubkey, message); + + if (recipient_pubkey.size() == 33) + recipient_pubkey = + recipient_pubkey.subspan(1); // sign_for_recipient already checked that this is the + // proper 0x05 prefix when present. + + std::vector result; + result.resize(signed_msg.size() + crypto_box_SEALBYTES); + if (0 != crypto_box_seal( + result.data(), signed_msg.data(), signed_msg.size(), recipient_pubkey.data())) + throw std::runtime_error{"Sealed box encryption failed"}; + + return result; +} + +std::vector encrypt_for_recipient_deterministic( + std::span ed25519_privkey, + std::span recipient_pubkey, + std::span message) { + + auto signed_msg = sign_for_recipient(ed25519_privkey, recipient_pubkey, message); + + if (recipient_pubkey.size() == 33) + recipient_pubkey = recipient_pubkey.subspan(1); // sign_for_recipient already checked that + // this is the proper 0x05 when present. + + // To make our ephemeral seed we're going to hash: SENDER_SEED || RECIPIENT_PK || MESSAGE with a + // keyed blake2b hash. + cleared_array seed; + crypto_generichash_blake2b_state st; + crypto_generichash_blake2b_init(&st, BOX_HASHKEY.data(), BOX_HASHKEY.size(), seed.size()); + crypto_generichash_blake2b_update(&st, ed25519_privkey.data(), 32); + crypto_generichash_blake2b_update(&st, recipient_pubkey.data(), 32); + crypto_generichash_blake2b_update(&st, message.data(), message.size()); + crypto_generichash_blake2b_final(&st, seed.data(), seed.size()); + + cleared_array eph_sk; + cleared_array eph_pk; + + crypto_box_seed_keypair(eph_pk.data(), eph_sk.data(), seed.data()); + + // The nonce for a sealed box is not passed but is implicitly defined as the (unkeyed) blake2b + // hash of: + // EPH_PUBKEY || RECIPIENT_PUBKEY + cleared_array nonce; + crypto_generichash_blake2b_init(&st, nullptr, 0, nonce.size()); + crypto_generichash_blake2b_update(&st, eph_pk.data(), eph_pk.size()); + crypto_generichash_blake2b_update(&st, recipient_pubkey.data(), recipient_pubkey.size()); + crypto_generichash_blake2b_final(&st, nonce.data(), nonce.size()); + + // A sealed box is a regular box (using the ephermal keys and nonce), but with the ephemeral + // pubkey prepended: + static_assert(crypto_box_SEALBYTES == crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES); + + std::vector result; + result.resize(crypto_box_SEALBYTES + signed_msg.size()); + std::memcpy(result.data(), eph_pk.data(), crypto_box_PUBLICKEYBYTES); + if (0 != crypto_box_easy( + result.data() + crypto_box_PUBLICKEYBYTES, + signed_msg.data(), + signed_msg.size(), + nonce.data(), + recipient_pubkey.data(), + eph_sk.data())) + throw std::runtime_error{"Crypto box encryption failed"}; + + return result; +} + +// Calculate the shared encryption key, sending from blinded sender kS (k = S's blinding factor) to +// blinded receiver jR (j = R's blinding factor). +// +// The sender knows s, k, S, and jR, but not j/R individually. +// The receiver knows r, j, R, and kS, but not k/S individually. +// +// From the sender's perspective, then, we compute: +// +// BLAKE2b(s k[jR] || kS || [jR]) +// +// The receiver can calulate the same value via: +// +// BLAKE2b(r j[kS] || [kS] || jR) +// +// (which will be the same because sR = rS, and so skjR = kjsR = kjrS = rjkS). +// +// For 15 blinding, however, the blinding factor depended only on the SOGS server pubkey, and so `j +// = k`, and so for *15* keys we don't do the double-blinding (i.e. the first terms above drop the +// double-blinding factors and become just sjR and rkS). +// +// Arguments. "A" and "B" here are either sender and receiver, or receiver and sender, depending on +// the value of `sending`. +// +// seed -- A's 32-byte secret key (can also be 64 bytes; only the first 32 are used). +// kA -- A's 33-byte blinded id, beginning with 0x15 or 0x25 +// jB -- A's 33-byte blinded id, beginning with 0x15 or 0x25 (must be the same prefix as kA). +// server_pk -- the server's pubkey (needed to compute A's `k` value) +// sending -- true if this for a message from A to B, false if this is from B to A. +static cleared_uc32 blinded_shared_secret( + std::span seed, + std::span kA, + std::span jB, + std::span server_pk, + bool sending) { + + // Because we're doing this generically, we use notation a/A/k for ourselves and b/jB for the + // other person; this notion keeps everything exactly as above *except* for the concatenation in + // the BLAKE2b hashed value: there we have to use kA || jB if we are the sender, but reverse the + // order to jB || kA if we are the receiver. + + std::pair blinded_key_pair; + cleared_uc32 k; + + if (seed.size() != 64 && seed.size() != 32) + throw std::invalid_argument{"Invalid ed25519_privkey: expected 32 or 64 bytes"}; + if (server_pk.size() != 32) + throw std::invalid_argument{"Invalid server_pk: expected 32 bytes"}; + if (kA.size() != 33) + throw std::invalid_argument{"Invalid local blinded id: expected 33 bytes"}; + if (jB.size() != 33) + throw std::invalid_argument{"Invalid remote blinded id: expected 33 bytes"}; + if (kA[0] == 0x15 && jB[0] == 0x15) + blinded_key_pair = blind15_key_pair(seed, server_pk, &k); + else if (kA[0] == 0x25 && jB[0] == 0x25) + blinded_key_pair = blind25_key_pair(seed, server_pk, &k); + else + throw std::invalid_argument{"Both ids must start with the same 0x15 or 0x25 prefix"}; + + bool blind25 = kA[0] == 0x25; + + kA = kA.subspan(1); + jB = jB.subspan(1); + + cleared_uc32 ka; + // Not really switching to x25519 here, this is just an easy way to compute `a` + crypto_sign_ed25519_sk_to_curve25519(ka.data(), seed.data()); + + if (blind25) + // Multiply a by k, so that we end up computing kajB = kjaB, which the other side can + // compute as jkbA. + crypto_core_ed25519_scalar_mul(ka.data(), ka.data(), k.data()); + // Else for 15 blinding we leave "ka" as just a, because j=k and so we don't need the + // double-blind. + + cleared_uc32 shared_secret; + if (0 != crypto_scalarmult_ed25519_noclamp(shared_secret.data(), ka.data(), jB.data())) + throw std::runtime_error{"Shared secret generation failed"}; + + auto& sender = sending ? kA : jB; + auto& recipient = sending ? jB : kA; + + // H(kjsR || kS || jR): + crypto_generichash_blake2b_state st; + crypto_generichash_blake2b_init(&st, nullptr, 0, 32); + crypto_generichash_blake2b_update(&st, shared_secret.data(), shared_secret.size()); + crypto_generichash_blake2b_update(&st, sender.data(), sender.size()); + crypto_generichash_blake2b_update(&st, recipient.data(), recipient.size()); + crypto_generichash_blake2b_final(&st, shared_secret.data(), shared_secret.size()); + + return shared_secret; +} + +std::vector encrypt_for_blinded_recipient( + std::span ed25519_privkey, + std::span server_pk, + std::span recipient_blinded_id, + std::span message) { + if (ed25519_privkey.size() != 64 && ed25519_privkey.size() != 32) + throw std::invalid_argument{"Invalid ed25519_privkey: expected 32 or 64 bytes"}; + if (server_pk.size() != 32) + throw std::invalid_argument{"Invalid server_pk: expected 32 bytes"}; + if (recipient_blinded_id.size() != 33) + throw std::invalid_argument{"Invalid recipient_blinded_id: expected 33 bytes"}; + + // Generate the blinded key pair & shared encryption key + std::pair blinded_key_pair; + switch (recipient_blinded_id[0]) { + case 0x15: blinded_key_pair = blind15_key_pair(ed25519_privkey, server_pk); break; + + case 0x25: blinded_key_pair = blind25_key_pair(ed25519_privkey, server_pk); break; + + default: + throw std::invalid_argument{ + "Invalid recipient_blinded_id: must start with 0x15 or 0x25"}; + } + std::vector blinded_id; + blinded_id.reserve(33); + blinded_id.insert( + blinded_id.end(), recipient_blinded_id.begin(), recipient_blinded_id.begin() + 1); + blinded_id.insert( + blinded_id.end(), blinded_key_pair.first.begin(), blinded_key_pair.first.end()); + + auto enc_key = blinded_shared_secret( + ed25519_privkey, blinded_id, recipient_blinded_id, server_pk, true); + + // Inner data: msg || A (i.e. the sender's ed25519 master pubkey, *not* kA blinded pubkey) + std::vector buf; + buf.reserve(message.size() + 32); + buf.insert(buf.end(), message.begin(), message.end()); + + // append A (pubkey) + if (ed25519_privkey.size() == 64) { + buf.insert(buf.end(), ed25519_privkey.begin() + 32, ed25519_privkey.end()); + } else { + cleared_uc64 ed_sk_from_seed; + uc32 ed_pk_buf; + crypto_sign_ed25519_seed_keypair( + ed_pk_buf.data(), ed_sk_from_seed.data(), ed25519_privkey.data()); + buf.insert(buf.end(), ed_pk_buf.begin(), ed_pk_buf.end()); + } + + // Encrypt using xchacha20-poly1305 + cleared_array nonce; + randombytes_buf(nonce.data(), nonce.size()); + + std::vector ciphertext; + unsigned long long outlen = 0; + ciphertext.resize( + 1 + buf.size() + crypto_aead_xchacha20poly1305_ietf_ABYTES + + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + + // Prepend with a version byte, so that the recipient can reliably detect if a future version is + // no longer encrypting things the way it expects. + ciphertext[0] = BLINDED_ENCRYPT_VERSION; + + if (0 != crypto_aead_xchacha20poly1305_ietf_encrypt( + ciphertext.data() + 1, + &outlen, + buf.data(), + buf.size(), + nullptr, + 0, + nullptr, + nonce.data(), + enc_key.data())) + throw std::runtime_error{"Crypto aead encryption failed"}; + + assert(outlen == ciphertext.size() - 1 - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + + // append the nonce, so that we have: data = b'\x00' + ciphertext + nonce + std::memcpy(ciphertext.data() + (1 + outlen), nonce.data(), nonce.size()); + + return ciphertext; +} + +std::pair, std::string> decrypt_incoming_session_id( + std::span ed25519_privkey, std::span ciphertext) { + auto [buf, sender_ed_pk] = decrypt_incoming(ed25519_privkey, ciphertext); + + // Convert the sender_ed_pk to the sender's session ID + std::array sender_x_pk; + + if (0 != crypto_sign_ed25519_pk_to_curve25519(sender_x_pk.data(), sender_ed_pk.data())) + throw std::runtime_error{"Sender ed25519 pubkey to x25519 pubkey conversion failed"}; + + // Everything is good, so just drop A and Y off the message and prepend the '05' prefix to + // the sender session ID + std::string sender_session_id; + sender_session_id.reserve(66); + sender_session_id += "05"; + oxenc::to_hex(sender_x_pk.begin(), sender_x_pk.end(), std::back_inserter(sender_session_id)); + + return {buf, sender_session_id}; +} + +std::pair, std::string> decrypt_incoming_session_id( + std::span x25519_pubkey, + std::span x25519_seckey, + std::span ciphertext) { + auto [buf, sender_ed_pk] = decrypt_incoming(x25519_pubkey, x25519_seckey, ciphertext); + + // Convert the sender_ed_pk to the sender's session ID + std::array sender_x_pk; + + if (0 != crypto_sign_ed25519_pk_to_curve25519(sender_x_pk.data(), sender_ed_pk.data())) + throw std::runtime_error{"Sender ed25519 pubkey to x25519 pubkey conversion failed"}; + + // Everything is good, so just drop A and Y off the message and prepend the '05' prefix to + // the sender session ID + std::string sender_session_id; + sender_session_id.reserve(66); + sender_session_id += "05"; + oxenc::to_hex(sender_x_pk.begin(), sender_x_pk.end(), std::back_inserter(sender_session_id)); + + return {buf, sender_session_id}; +} + +std::pair, std::vector> decrypt_incoming( + std::span ed25519_privkey, std::span ciphertext) { + cleared_uc64 ed_sk_from_seed; + if (ed25519_privkey.size() == 32) { + uc32 ignore_pk; + crypto_sign_ed25519_seed_keypair( + ignore_pk.data(), ed_sk_from_seed.data(), ed25519_privkey.data()); + ed25519_privkey = {ed_sk_from_seed.data(), ed_sk_from_seed.size()}; + } else if (ed25519_privkey.size() != 64) { + throw std::invalid_argument{"Invalid ed25519_privkey: expected 32 or 64 bytes"}; + } + + cleared_uc32 x_sec; + uc32 x_pub; + crypto_sign_ed25519_sk_to_curve25519(x_sec.data(), ed25519_privkey.data()); + crypto_scalarmult_base(x_pub.data(), x_sec.data()); + + return decrypt_incoming(x_pub, x_sec, ciphertext); +} + +std::pair, std::vector> decrypt_incoming( + std::span x25519_pubkey, + std::span x25519_seckey, + std::span ciphertext) { + + if (ciphertext.size() < crypto_box_SEALBYTES + 32 + 64) + throw std::runtime_error{"Invalid incoming message: ciphertext is too small"}; + const size_t outer_size = ciphertext.size() - crypto_box_SEALBYTES; + const size_t msg_size = outer_size - 32 - 64; + + std::pair, std::vector> result; + auto& [buf, sender_ed_pk] = result; + + buf.resize(outer_size); + if (0 != crypto_box_seal_open( + buf.data(), + ciphertext.data(), + ciphertext.size(), + x25519_pubkey.data(), + x25519_seckey.data())) + throw std::runtime_error{"Decryption failed"}; + + uc64 sig; + sender_ed_pk.assign(buf.begin() + msg_size, buf.begin() + msg_size + 32); + std::memcpy(sig.data(), buf.data() + msg_size + 32, 64); + buf.resize(buf.size() - 64); // Remove SIG, then append Y so that we get M||A||Y to verify + buf.insert(buf.end(), x25519_pubkey.begin(), x25519_pubkey.begin() + 32); + + if (0 != crypto_sign_ed25519_verify_detached( + sig.data(), buf.data(), buf.size(), sender_ed_pk.data())) + throw std::runtime_error{"Signature verification failed"}; + + // Everything is good, so just drop A and Y off the message + buf.resize(buf.size() - 32 - 32); + + return result; +} + +std::pair, std::string> decrypt_from_blinded_recipient( + std::span ed25519_privkey, + std::span server_pk, + std::span sender_id, + std::span recipient_id, + std::span ciphertext) { + uc32 ed_pk_from_seed; + cleared_uc64 ed_sk_from_seed; + if (ed25519_privkey.size() == 32) { + crypto_sign_ed25519_seed_keypair( + ed_pk_from_seed.data(), ed_sk_from_seed.data(), ed25519_privkey.data()); + ed25519_privkey = {ed_sk_from_seed.data(), ed_sk_from_seed.size()}; + } else if (ed25519_privkey.size() == 64) + std::memcpy(ed_pk_from_seed.data(), ed25519_privkey.data() + 32, 32); + else + throw std::invalid_argument{"Invalid ed25519_privkey: expected 32 or 64 bytes"}; + if (ciphertext.size() < crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + 1 + + crypto_aead_xchacha20poly1305_ietf_ABYTES) + throw std::invalid_argument{ + "Invalid ciphertext: too short to contain valid encrypted data"}; + + cleared_uc32 dec_key; + auto blinded_id = recipient_id[0] == 0x25 + ? blinded25_id_from_ed(to_span(ed_pk_from_seed), server_pk) + : blinded15_id_from_ed(to_span(ed_pk_from_seed), server_pk); + + if (to_string_view(sender_id) == to_string_view(blinded_id)) + dec_key = blinded_shared_secret(ed25519_privkey, sender_id, recipient_id, server_pk, true); + else + dec_key = blinded_shared_secret(ed25519_privkey, recipient_id, sender_id, server_pk, false); + + std::pair, std::string> result; + auto& [buf, sender_session_id] = result; + + // v, ct, nc = data[0], data[1:-24], data[-24:] + if (ciphertext[0] != BLINDED_ENCRYPT_VERSION) + throw std::invalid_argument{ + "Invalid ciphertext: version is not " + std::to_string(BLINDED_ENCRYPT_VERSION)}; + + std::vector nonce; + const size_t msg_size = + (ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES - 1 - + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + + if (msg_size < 32) + throw std::invalid_argument{"Invalid ciphertext: innerBytes too short"}; + buf.resize(msg_size); + + unsigned long long buf_len = 0; + + nonce.resize(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + std::memcpy( + nonce.data(), + ciphertext.data() + msg_size + 1 + crypto_aead_xchacha20poly1305_ietf_ABYTES, + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + + if (0 != crypto_aead_xchacha20poly1305_ietf_decrypt( + buf.data(), + &buf_len, + nullptr, + ciphertext.data() + 1, + msg_size + crypto_aead_xchacha20poly1305_ietf_ABYTES, + nullptr, + 0, + nonce.data(), + dec_key.data())) + throw std::invalid_argument{"Decryption failed"}; + + assert(buf_len == buf.size()); + + // Split up: the last 32 bytes are the sender's *unblinded* ed25519 key + uc32 sender_ed_pk; + std::memcpy(sender_ed_pk.data(), buf.data() + (buf.size() - 32), 32); + + // Convert the sender_ed_pk to the sender's session ID + uc32 sender_x_pk; + if (0 != crypto_sign_ed25519_pk_to_curve25519(sender_x_pk.data(), sender_ed_pk.data())) + throw std::runtime_error{"Sender ed25519 pubkey to x25519 pubkey conversion failed"}; + + std::vector session_id; // Gets populated by the following ..._from_ed calls + + // Verify that the inner sender_ed_pk (A) yields the same outer kA we got with the message + auto extracted_sender = + recipient_id[0] == 0x25 + ? blinded25_id_from_ed(to_span(sender_ed_pk), server_pk, &session_id) + : blinded15_id_from_ed(to_span(sender_ed_pk), server_pk, &session_id); + + bool matched = to_string_view(sender_id) == to_string_view(extracted_sender); + if (!matched && extracted_sender[0] == 0x15) { + // With 15-blinding we might need the negative instead: + extracted_sender[31] ^= 0x80; + matched = to_string_view(sender_id) == to_string_view(extracted_sender); + } + if (!matched) + throw std::runtime_error{"Blinded sender id does not match the actual sender"}; + + // Everything is good, so just drop the sender_ed_pk off the message and prepend the '05' prefix + // to the sender session ID + buf.resize(buf.size() - 32); + sender_session_id.reserve(66); + sender_session_id += "05"; + oxenc::to_hex(sender_x_pk.begin(), sender_x_pk.end(), std::back_inserter(sender_session_id)); + + return result; +} + +std::string decrypt_ons_response( + std::string_view lowercase_name, + std::span ciphertext, + std::optional> nonce) { + // Handle old Argon2-based encryption used before HF16 + if (!nonce) { + if (ciphertext.size() < crypto_secretbox_MACBYTES) + throw std::invalid_argument{"Invalid ciphertext: expected to be greater than 16 bytes"}; + + uc32 key; + std::array salt = {0}; + + if (0 != crypto_pwhash( + key.data(), + key.size(), + lowercase_name.data(), + lowercase_name.size(), + salt.data(), + crypto_pwhash_OPSLIMIT_MODERATE, + crypto_pwhash_MEMLIMIT_MODERATE, + crypto_pwhash_ALG_ARGON2ID13)) + throw std::runtime_error{"Failed to generate key"}; + + std::vector msg; + msg.resize(ciphertext.size() - crypto_secretbox_MACBYTES); + std::array nonce = {0}; + + if (0 != + crypto_secretbox_open_easy( + msg.data(), ciphertext.data(), ciphertext.size(), nonce.data(), key.data())) + throw std::runtime_error{"Failed to decrypt"}; + + std::string session_id = oxenc::to_hex(msg.begin(), msg.end()); + return session_id; + } + + if (ciphertext.size() < crypto_aead_xchacha20poly1305_ietf_ABYTES) + throw std::invalid_argument{"Invalid ciphertext: expected to be greater than 16 bytes"}; + if (nonce->size() != crypto_aead_xchacha20poly1305_ietf_NPUBBYTES) + throw std::invalid_argument{"Invalid nonce: expected to be 24 bytes"}; + + // Hash the ONS name using BLAKE2b + // + // xchacha-based encryption + // key = H(name, key=H(name)) + uc32 key; + uc32 name_hash; + auto name_bytes = to_unsigned(lowercase_name.data()); + crypto_generichash_blake2b( + name_hash.data(), name_hash.size(), name_bytes, lowercase_name.size(), nullptr, 0); + crypto_generichash_blake2b( + key.data(), + key.size(), + name_bytes, + lowercase_name.size(), + name_hash.data(), + name_hash.size()); + + std::vector buf; + unsigned long long buf_len = 0; + buf.resize(ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES); + + if (0 != crypto_aead_xchacha20poly1305_ietf_decrypt( + buf.data(), + &buf_len, + nullptr, + ciphertext.data(), + ciphertext.size(), + nullptr, + 0, + nonce->data(), + key.data())) + throw std::runtime_error{"Failed to decrypt"}; + + if (buf_len != 33) + throw std::runtime_error{"Invalid decrypted value: expected to be 33 bytes"}; + + std::string session_id = oxenc::to_hex(buf.begin(), buf.end()); + return session_id; +} + +std::vector decrypt_push_notification( + std::span payload, std::span enc_key) { + if (payload.size() < + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + crypto_aead_xchacha20poly1305_ietf_ABYTES) + throw std::invalid_argument{"Invalid payload: too short to contain valid encrypted data"}; + if (enc_key.size() != 32) + throw std::invalid_argument{"Invalid enc_key: expected 32 bytes"}; + + std::vector buf; + std::vector nonce; + const size_t msg_size = + (payload.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES - + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + unsigned long long buf_len = 0; + buf.resize(msg_size); + nonce.resize(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + std::memcpy(nonce.data(), payload.data(), crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + + if (0 != crypto_aead_xchacha20poly1305_ietf_decrypt( + buf.data(), + &buf_len, + nullptr, + payload.data() + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, + payload.size() - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, + nullptr, + 0, + nonce.data(), + enc_key.data())) + throw std::runtime_error{"Failed to decrypt; perhaps the secret key is invalid?"}; + + // Removing any null padding bytes from the end + if (auto it = std::find_if(buf.rbegin(), buf.rend(), [](unsigned char c) { return c != 0; }); + it != buf.rend()) + buf.resize(buf.size() - std::distance(buf.rbegin(), it)); + + return buf; +} + +template +std::string compute_hash(Func hasher, const T&... args) { + // Allocate a buffer of 20 bytes per integral value (which is the largest the any integral + // value can be when stringified). + std::array< + char, + (0 + ... + + (std::is_integral_v || std::is_same_v + ? 20 + : 0))> + buffer; + auto* b = buffer.data(); + return hasher({detail::to_hashable(args, b)...}); +} + +std::string compute_hash_blake2b_b64(std::vector parts) { + constexpr size_t HASH_SIZE = 32; + crypto_generichash_state state; + crypto_generichash_init(&state, nullptr, 0, HASH_SIZE); + for (const auto& s : parts) + crypto_generichash_update( + &state, reinterpret_cast(s.data()), s.size()); + std::array hash; + crypto_generichash_final(&state, hash.data(), HASH_SIZE); + + std::string b64hash = oxenc::to_base64(hash.begin(), hash.end()); + // Trim padding: + while (!b64hash.empty() && b64hash.back() == '=') + b64hash.pop_back(); + return b64hash; +} + +std::string compute_message_hash( + const std::string_view pubkey_hex, int16_t ns, std::string_view data) { + if (pubkey_hex.size() != 66) + throw std::invalid_argument{ + "Invalid pubkey_hex: Expecting 66 character hex-encoded pubkey"}; + + // This function is based on the `computeMessageHash` function on the storage-server used to + // generate a message hash: + // https://github.com/oxen-io/oxen-storage-server/blob/dev/oxenss/rpc/request_handler.cpp + auto pubkey = oxenc::from_hex(pubkey_hex.substr(2)); + uint8_t netid_raw; + oxenc::from_hex(pubkey_hex.begin(), pubkey_hex.begin() + 2, &netid_raw); + char netid = static_cast(netid_raw); + + std::array ns_buf; + char* ns_buf_ptr = ns_buf.data(); + std::string_view ns_for_hash = ns != 0 ? detail::to_hashable(ns, ns_buf_ptr) : ""sv; + + auto decoded_data = oxenc::from_base64(data); + + return compute_hash( + compute_hash_blake2b_b64, + std::string_view{&netid, 1}, + pubkey, + ns_for_hash, + decoded_data); +} + +std::vector encrypt_xchacha20( + std::span plaintext, std::span enc_key) { + if (enc_key.size() != 32) + throw std::invalid_argument{"Invalid enc_key: expected 32 bytes"}; + + std::vector ciphertext; + ciphertext.resize( + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + plaintext.size() + + crypto_aead_xchacha20poly1305_ietf_ABYTES); + + // Generate random nonce, and stash it at the beginning of ciphertext: + randombytes_buf(ciphertext.data(), crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + + auto* c = reinterpret_cast(ciphertext.data()) + + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES; + unsigned long long clen; + + crypto_aead_xchacha20poly1305_ietf_encrypt( + c, + &clen, + plaintext.data(), + plaintext.size(), + nullptr, + 0, // additional data + nullptr, // nsec (always unused) + reinterpret_cast(ciphertext.data()), + enc_key.data()); + assert(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + clen <= ciphertext.size()); + ciphertext.resize(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + clen); + return ciphertext; +} + +std::vector decrypt_xchacha20( + std::span ciphertext, std::span enc_key) { + if (ciphertext.size() < + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES + crypto_aead_xchacha20poly1305_ietf_ABYTES) + throw std::invalid_argument{ + "Invalid ciphertext: too short to contain valid encrypted data"}; + if (enc_key.size() != 32) + throw std::invalid_argument{"Invalid enc_key: expected 32 bytes"}; + + // Extract nonce from the beginning of the ciphertext: + auto nonce = ciphertext.subspan(0, crypto_aead_xchacha20poly1305_ietf_NPUBBYTES); + ciphertext = ciphertext.subspan(nonce.size()); + + std::vector plaintext; + plaintext.resize(ciphertext.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES); + auto* m = reinterpret_cast(plaintext.data()); + unsigned long long mlen; + if (0 != crypto_aead_xchacha20poly1305_ietf_decrypt( + m, + &mlen, + nullptr, // nsec (always unused) + ciphertext.data(), + ciphertext.size(), + nullptr, + 0, // additional data + nonce.data(), + enc_key.data())) + throw std::runtime_error{"Could not decrypt (XChaCha20-Poly1305)"}; + assert(mlen <= plaintext.size()); + plaintext.resize(mlen); + return plaintext; +} + +} // namespace session + +extern "C" { + +using namespace session; + +LIBSESSION_C_API bool session_encrypt_for_recipient_deterministic( + const unsigned char* plaintext_in, + size_t plaintext_len, + const unsigned char* ed25519_privkey, + const unsigned char* recipient_pubkey, + unsigned char** ciphertext_out, + size_t* ciphertext_len) { + try { + auto ciphertext = session::encrypt_for_recipient_deterministic( + std::span{ed25519_privkey, 64}, + std::span{recipient_pubkey, 32}, + std::span{plaintext_in, plaintext_len}); + + *ciphertext_out = static_cast(malloc(ciphertext.size())); + *ciphertext_len = ciphertext.size(); + std::memcpy(*ciphertext_out, ciphertext.data(), ciphertext.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_encrypt_for_blinded_recipient( + const unsigned char* plaintext_in, + size_t plaintext_len, + const unsigned char* ed25519_privkey, + const unsigned char* open_group_pubkey, + const unsigned char* recipient_blinded_id, + unsigned char** ciphertext_out, + size_t* ciphertext_len) { + try { + auto ciphertext = session::encrypt_for_blinded_recipient( + std::span{ed25519_privkey, 64}, + std::span{open_group_pubkey, 32}, + std::span{recipient_blinded_id, 33}, + std::span{plaintext_in, plaintext_len}); + + *ciphertext_out = static_cast(malloc(ciphertext.size())); + *ciphertext_len = ciphertext.size(); + std::memcpy(*ciphertext_out, ciphertext.data(), ciphertext.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_decrypt_incoming( + const unsigned char* ciphertext_in, + size_t ciphertext_len, + const unsigned char* ed25519_privkey, + char* session_id_out, + unsigned char** plaintext_out, + size_t* plaintext_len) { + try { + auto result = session::decrypt_incoming_session_id( + std::span{ed25519_privkey, 64}, + std::span{ciphertext_in, ciphertext_len}); + auto [plaintext, session_id] = result; + + std::memcpy(session_id_out, session_id.c_str(), session_id.size() + 1); + *plaintext_out = static_cast(malloc(plaintext.size())); + *plaintext_len = plaintext.size(); + std::memcpy(*plaintext_out, plaintext.data(), plaintext.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_decrypt_incoming_legacy_group( + const unsigned char* ciphertext_in, + size_t ciphertext_len, + const unsigned char* x25519_pubkey, + const unsigned char* x25519_seckey, + char* session_id_out, + unsigned char** plaintext_out, + size_t* plaintext_len) { + try { + auto result = session::decrypt_incoming_session_id( + std::span{x25519_pubkey, 32}, + std::span{x25519_seckey, 32}, + std::span{ciphertext_in, ciphertext_len}); + auto [plaintext, session_id] = result; + + std::memcpy(session_id_out, session_id.c_str(), session_id.size() + 1); + *plaintext_out = static_cast(malloc(plaintext.size())); + *plaintext_len = plaintext.size(); + std::memcpy(*plaintext_out, plaintext.data(), plaintext.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_decrypt_for_blinded_recipient( + const unsigned char* ciphertext_in, + size_t ciphertext_len, + const unsigned char* ed25519_privkey, + const unsigned char* open_group_pubkey, + const unsigned char* sender_id, + const unsigned char* recipient_id, + char* session_id_out, + unsigned char** plaintext_out, + size_t* plaintext_len) { + try { + auto result = session::decrypt_from_blinded_recipient( + std::span{ed25519_privkey, 64}, + std::span{open_group_pubkey, 32}, + std::span{sender_id, 33}, + std::span{recipient_id, 33}, + std::span{ciphertext_in, ciphertext_len}); + auto [plaintext, session_id] = result; + + std::memcpy(session_id_out, session_id.c_str(), session_id.size() + 1); + *plaintext_out = static_cast(malloc(plaintext.size())); + *plaintext_len = plaintext.size(); + std::memcpy(*plaintext_out, plaintext.data(), plaintext.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_decrypt_ons_response( + const char* name_in, + const unsigned char* ciphertext_in, + size_t ciphertext_len, + const unsigned char* nonce_in, + char* session_id_out) { + try { + std::optional> nonce; + if (nonce_in) + nonce = std::span{ + nonce_in, crypto_aead_xchacha20poly1305_ietf_NPUBBYTES}; + + auto session_id = session::decrypt_ons_response( + name_in, std::span{ciphertext_in, ciphertext_len}, nonce); + + std::memcpy(session_id_out, session_id.c_str(), session_id.size() + 1); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_decrypt_push_notification( + const unsigned char* payload_in, + size_t payload_len, + const unsigned char* enc_key_in, + unsigned char** plaintext_out, + size_t* plaintext_len) { + try { + auto plaintext = session::decrypt_push_notification( + std::span{payload_in, payload_len}, + std::span{enc_key_in, 32}); + + *plaintext_out = static_cast(malloc(plaintext.size())); + *plaintext_len = plaintext.size(); + std::memcpy(*plaintext_out, plaintext.data(), plaintext.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_compute_message_hash( + const char* pubkey_hex_in, int16_t ns, const char* base64_data_in, char* hash_out) { + try { + auto hash = session::compute_message_hash(pubkey_hex_in, ns, base64_data_in); + + std::memcpy(hash_out, hash.c_str(), hash.size() + 1); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_encrypt_xchacha20( + const unsigned char* plaintext_in, + size_t plaintext_len, + const unsigned char* enc_key_in, + unsigned char** ciphertext_out, + size_t* ciphertext_len) { + try { + auto ciphertext = session::encrypt_xchacha20( + std::span{plaintext_in, plaintext_len}, + std::span{enc_key_in, 32}); + + *ciphertext_out = static_cast(malloc(ciphertext.size())); + *ciphertext_len = ciphertext.size(); + std::memcpy(*ciphertext_out, ciphertext.data(), ciphertext.size()); + return true; + } catch (...) { + return false; + } +} + +LIBSESSION_C_API bool session_decrypt_xchacha20( + const unsigned char* ciphertext_in, + size_t ciphertext_len, + const unsigned char* enc_key_in, + unsigned char** plaintext_out, + size_t* plaintext_len) { + try { + auto plaintext = session::decrypt_xchacha20( + std::span{ciphertext_in, ciphertext_len}, + std::span{enc_key_in, 32}); + + *plaintext_out = static_cast(malloc(plaintext.size())); + *plaintext_len = plaintext.size(); + std::memcpy(*plaintext_out, plaintext.data(), plaintext.size()); + return true; + } catch (...) { + return false; + } +} + +} // extern "C" \ No newline at end of file diff --git a/src/session_network.cpp b/src/session_network.cpp new file mode 100644 index 00000000..ee59703e --- /dev/null +++ b/src/session_network.cpp @@ -0,0 +1,3284 @@ +#include "session/session_network.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "session/blinding.hpp" +#include "session/ed25519.hpp" +#include "session/export.h" +#include "session/file.hpp" +#include "session/onionreq/builder.h" +#include "session/onionreq/builder.hpp" +#include "session/onionreq/key_types.hpp" +#include "session/onionreq/response_parser.hpp" +#include "session/session_network.h" +#include "session/util.hpp" + +using namespace oxen; +using namespace session::onionreq; +using namespace std::literals; +using namespace oxen::log::literals; + +namespace session::network { + +namespace { + + inline auto cat = log::Cat("network"); + + class load_cache_exception : public std::runtime_error { + public: + load_cache_exception(std::string message) : std::runtime_error(message) {} + }; + class status_code_exception : public std::runtime_error { + public: + int16_t status_code; + std::vector> headers; + + status_code_exception( + int16_t status_code, + std::vector> headers, + std::string message) : + std::runtime_error(message), status_code{status_code}, headers{headers} {} + }; + + constexpr int16_t error_network_suspended = -10001; + constexpr int16_t error_building_onion_request = -10002; + constexpr int16_t error_path_build_timeout = -10003; + + const std::pair content_type_plain_text = { + "Content-Type", "text/plain; charset=UTF-8"}; + const std::pair content_type_json = { + "Content-Type", "application/json"}; + + // The amount of time the snode cache can be used before it needs to be refreshed/ + constexpr auto snode_cache_expiration_duration = 2h; + + // The smallest size the snode cache can get to before we need to fetch more. + constexpr size_t min_snode_cache_count = 12; + + // The number of snodes to use to refresh the cache. + constexpr int num_snodes_to_refresh_cache_from = 3; + + // The number of snodes (including the guard snode) in a path. + constexpr uint8_t path_size = 3; + + // The number of times a path can fail before it's replaced. + constexpr uint16_t path_failure_threshold = 3; + + // The number of times a snode can fail before it's replaced. + constexpr uint16_t snode_failure_threshold = 3; + + // The frequency to check if queued requests have timed out due to a pending path build + constexpr auto queued_request_path_build_timeout_frequency = 250ms; + + const fs::path default_cache_path{u8"."}, file_testnet{u8"testnet"}, + file_snode_pool{u8"snode_pool"}; + const std::vector legacy_files{ + u8"snode_pool_updated", u8"swarm", u8"snode_failure_counts"}; + + constexpr auto node_not_found_prefix = "502 Bad Gateway\n\nNext node not found: "sv; + constexpr auto node_not_found_prefix_no_status = "Next node not found: "sv; + constexpr auto ALPN = "oxenstorage"; + constexpr auto ONION = "onion_req"; + + enum class PathSelectionBehaviour { + random, + new_or_least_busy, + }; + + std::string path_type_name(PathType path_type, bool single_path_mode) { + if (single_path_mode) + return "single_path"; + + switch (path_type) { + case PathType::standard: return "standard"; + case PathType::upload: return "upload"; + case PathType::download: return "download"; + } + return "standard"; // Default + } + + // The mininum number of paths we want to maintain + uint8_t min_path_count(PathType path_type, bool single_path_mode) { + if (single_path_mode) + return 1; + + switch (path_type) { + case PathType::standard: return 2; + case PathType::upload: return 2; + case PathType::download: return 2; + } + return 2; // Default + } + + PathSelectionBehaviour path_selection_behaviour(PathType path_type) { + switch (path_type) { + case PathType::standard: return PathSelectionBehaviour::random; + case PathType::upload: return PathSelectionBehaviour::new_or_least_busy; + case PathType::download: return PathSelectionBehaviour::new_or_least_busy; + } + return PathSelectionBehaviour::random; // Default + } + + /// Converts a string such as "1.2.3" to a vector of ints {1,2,3}. Throws if something + /// in/around the .'s isn't parseable as an integer. + std::vector parse_version(std::string_view vers, bool trim_trailing_zero = true) { + auto v_s = session::split(vers, "."); + std::vector result; + for (const auto& piece : v_s) + if (!quic::parse_int(piece, result.emplace_back())) + throw std::invalid_argument{"Invalid version"}; + + // Remove any trailing `0` values (but ensure we at least end up with a "0" version) + if (trim_trailing_zero) + while (result.size() > 1 && result.back() == 0) + result.pop_back(); + + return result; + } + + service_node node_from_json(nlohmann::json json) { + auto pk_ed = json["pubkey_ed25519"].get(); + if (pk_ed.size() != 64 || !oxenc::is_hex(pk_ed)) + throw std::invalid_argument{ + "Invalid service node json: pubkey_ed25519 is not a valid, hex pubkey"}; + + // When parsing a node from JSON it'll generally be from the 'get_swarm` endpoint or a 421 + // error neither of which contain the `storage_server_version` - luckily we don't need the + // version for these two cases so can just default it to `0` + std::vector storage_server_version = {0}; + if (json.contains("storage_server_version")) { + if (json["storage_server_version"].is_array()) { + if (json["storage_server_version"].size() > 0) { + // Convert the version to a string and parse it back into a version code to + // ensure the version formats remain consistent throughout + storage_server_version = json["storage_server_version"].get>(); + storage_server_version = + parse_version("{}"_format(fmt::join(storage_server_version, "."))); + } + } else + storage_server_version = + parse_version(json["storage_server_version"].get()); + } + + std::string ip; + if (json.contains("public_ip")) + ip = json["public_ip"].get(); + else + ip = json["ip"].get(); + + uint16_t port; + if (json.contains("storage_lmq_port")) + port = json["storage_lmq_port"].get(); + else + port = json["port_omq"].get(); + + swarm_id_t swarm_id = INVALID_SWARM_ID; + if (json.contains("swarm_id")) + swarm_id = json["swarm_id"].get(); + + return {oxenc::from_hex(pk_ed), storage_server_version, swarm_id, ip, port}; + } + + service_node node_from_disk(std::string_view str, bool can_ignore_version = false) { + // Format is "{ip}|{port}|{version}|{ed_pubkey}|{swarm_id}" + auto parts = split(str, "|"); + if (parts.size() != 5) + throw std::invalid_argument("Invalid service node serialisation: {}"_format(str)); + if (parts[3].size() != 64 || !oxenc::is_hex(parts[3])) + throw std::invalid_argument{ + "Invalid service node serialisation: pubkey is not hex or has wrong size"}; + + uint16_t port; + if (!quic::parse_int(parts[1], port)) + throw std::invalid_argument{"Invalid service node serialization: invalid port"}; + + std::vector storage_server_version = parse_version(parts[2]); + if (!can_ignore_version && storage_server_version == std::vector{0}) + throw std::invalid_argument{"Invalid service node serialization: invalid version"}; + + swarm_id_t swarm_id = INVALID_SWARM_ID; + quic::parse_int(parts[4], swarm_id); + + return { + oxenc::from_hex(parts[3]), // ed25519_pubkey + storage_server_version, // storage_server_version + swarm_id, // swarm_id + std::string(parts[0]), // ip + port, // port + }; + } + + const std::vector seed_nodes_testnet{ + node_from_disk("144.76.164.202|35400|2.8.0|" + "decaf007f26d3d6f9b845ad031ffdf6d04638c25bb10b8fffbbe99135303c4b9|"sv)}; + const std::vector seed_nodes_mainnet{ + node_from_disk("144.76.164.202|20200|2.8.0|" + "1f000f09a7b07828dcb72af7cd16857050c10c02bd58afb0e38111fb6cda1fef|"sv), + node_from_disk("88.99.102.229|20201|2.8.0|" + "1f101f0acee4db6f31aaa8b4df134e85ca8a4878efaef7f971e88ab144c1a7ce|"sv), + node_from_disk("195.16.73.17|20202|2.8.0|" + "1f202f00f4d2d4acc01e20773999a291cf3e3136c325474d159814e06199919f|"sv), + node_from_disk("104.194.11.120|20203|2.8.0|" + "1f303f1d7523c46fa5398826740d13282d26b5de90fbae5749442f66afb6d78b|"sv), + node_from_disk("104.194.8.115|20204|2.8.0|" + "1f604f1c858a121a681d8f9b470ef72e6946ee1b9c5ad15a35e16b50c28db7b0|"sv)}; + constexpr auto file_server = "filev2.getsession.org"sv; + constexpr auto file_server_pubkey = + "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59"sv; + + std::string node_to_disk(service_node node) { + // Format is "{ip}|{port}|{version}|{ed_pubkey}|{swarm_id}" + auto ed25519_pubkey_hex = oxenc::to_hex(node.view_remote_key()); + + return fmt::format( + "{}|{}|{}|{}|{}", + node.host(), + node.port(), + "{}"_format(fmt::join(node.storage_server_version, ".")), + ed25519_pubkey_hex, + node.swarm_id); + } + + session::onionreq::x25519_pubkey compute_xpk(std::span ed25519_pk) { + std::array xpk; + if (0 != crypto_sign_ed25519_pk_to_curve25519(xpk.data(), ed25519_pk.data())) + throw std::runtime_error{ + "An error occured while attempting to convert Ed25519 pubkey to X25519; " + "is the pubkey valid?"}; + return session::onionreq::x25519_pubkey::from_bytes({xpk.data(), 32}); + } + + std::string consume_string(oxenc::bt_dict_consumer dict, std::string_view key) { + if (!dict.skip_until(key)) + throw std::invalid_argument{ + "Unable to find entry in dict for key '" + std::string(key) + "'"}; + return dict.consume_string(); + } + + template + auto consume_integer(oxenc::bt_dict_consumer dict, std::string_view key) { + if (!dict.skip_until(key)) + throw std::invalid_argument{ + "Unable to find entry in dict for key '" + std::string(key) + "'"}; + return dict.next_integer().second; + } +} // namespace + +namespace detail { + swarm_id_t pubkey_to_swarm_space(const session::onionreq::x25519_pubkey& pk) { + swarm_id_t res = 0; + for (size_t i = 0; i < 4; i++) { + swarm_id_t buf; + std::memcpy(&buf, pk.data() + i * 8, 8); + res ^= buf; + } + oxenc::big_to_host_inplace(res); + + return res; + } + + std::vector>> generate_swarms( + std::vector nodes) { + std::vector>> result; + std::unordered_map> _grouped_nodes; + + for (const auto& node : nodes) + _grouped_nodes[node.swarm_id].push_back(node); + + for (auto& [swarm_id, nodes] : _grouped_nodes) + result.emplace_back(swarm_id, std::move(nodes)); + + std::sort(result.begin(), result.end(), [](const auto& a, const auto& b) { + return a.first < b.first; + }); + return result; + } + + std::optional node_for_destination(network_destination destination) { + if (auto* dest = std::get_if(&destination)) + return *dest; + + return std::nullopt; + } + + nlohmann::json get_service_nodes_params(std::optional limit) { + nlohmann::json params{ + {"active_only", true}, + {"fields", + {{"public_ip", true}, + {"pubkey_ed25519", true}, + {"storage_lmq_port", true}, + {"storage_server_version", true}, + {"swarm_id", true}}}}; + + if (limit) + params["limit"] = *limit; + + return params; + } + + std::vector process_get_service_nodes_response( + oxenc::bt_list_consumer result_bencode) { + std::vector result; + result_bencode.skip_value(); // Skip the status code (already validated) + auto response_dict = result_bencode.consume_dict_consumer(); + response_dict.skip_until("result"); + + auto result_dict = response_dict.consume_dict_consumer(); + result_dict.skip_until("service_node_states"); + + // Process the node list + auto node = result_dict.consume_list_consumer(); + + while (!node.is_finished()) { + auto node_consumer = node.consume_dict_consumer(); + auto pubkey_ed25519 = oxenc::from_hex(consume_string(node_consumer, "pubkey_ed25519")); + auto public_ip = consume_string(node_consumer, "public_ip"); + auto storage_lmq_port = consume_integer(node_consumer, "storage_lmq_port"); + + std::vector storage_server_version; + node_consumer.skip_until("storage_server_version"); + auto version_consumer = node_consumer.consume_list_consumer(); + auto swarm_id = consume_integer(node_consumer, "swarm_id"); + + while (!version_consumer.is_finished()) { + storage_server_version.emplace_back(version_consumer.consume_integer()); + } + + result.emplace_back( + pubkey_ed25519, storage_server_version, swarm_id, public_ip, storage_lmq_port); + } + + return result; + } + + std::vector process_get_service_nodes_response(nlohmann::json response_json) { + if (!response_json.contains("result") || !response_json["result"].is_object()) + throw std::runtime_error{"JSON missing result field."}; + + nlohmann::json result_json = response_json["result"]; + if (!result_json.contains("service_node_states") || + !result_json["service_node_states"].is_array()) + throw std::runtime_error{"JSON missing service_node_states field."}; + + std::vector result; + for (auto& snode : result_json["service_node_states"]) + result.emplace_back(node_from_json(snode)); + + return result; + } + + void log_retry_result_if_needed(request_info info, bool single_path_mode) { + if (!info.retry_reason) + return; + + // For debugging purposes if the error was a redirect retry then + // we want to log that the retry was successful as this will + // help identify how often we are receiving incorrect errors + auto reason = "unknown retry"; + + switch (*info.retry_reason) { + case request_info::RetryReason::none: reason = "unknown retry"; break; + case request_info::RetryReason::redirect: reason = "421 retry"; break; + case request_info::RetryReason::decryption_failure: reason = "decryption error"; break; + case request_info::RetryReason::redirect_swarm_refresh: + reason = "421 swarm refresh retry"; + break; + } + + log::info( + cat, + "Received valid response after {} in request {} for {}.", + reason, + info.request_id, + path_type_name(info.path_type, single_path_mode)); + } + + std::vector convert_service_nodes( + std::vector nodes) { + std::vector converted_nodes; + for (auto& node : nodes) { + auto ed25519_pubkey_hex = oxenc::to_hex(node.view_remote_key()); + auto ipv4 = node.to_ipv4(); + network_service_node converted_node; + converted_node.ip[0] = (ipv4.addr >> 24) & 0xFF; + converted_node.ip[1] = (ipv4.addr >> 16) & 0xFF; + converted_node.ip[2] = (ipv4.addr >> 8) & 0xFF; + converted_node.ip[3] = ipv4.addr & 0xFF; + strncpy(converted_node.ed25519_pubkey_hex, ed25519_pubkey_hex.c_str(), 64); + converted_node.ed25519_pubkey_hex[64] = '\0'; // Ensure null termination + converted_node.quic_port = node.port(); + converted_nodes.push_back(converted_node); + } + + return converted_nodes; + } + + ServerDestination convert_server_destination(const network_server_destination server) { + std::optional>> headers; + if (server.headers_size > 0) { + headers = std::vector>{}; + + for (size_t i = 0; i < server.headers_size; i++) + headers->emplace_back(server.headers[i], server.header_values[i]); + } + + return ServerDestination{ + server.protocol, + server.host, + server.endpoint, + x25519_pubkey::from_hex({server.x25519_pubkey, 64}), + server.port, + headers, + server.method}; + } +} // namespace detail + +request_info request_info::make( + onionreq::network_destination _dest, + std::optional> _original_body, + std::optional _swarm_pk, + std::chrono::milliseconds _request_timeout, + std::optional _request_and_path_build_timeout, + PathType _type, + std::optional _req_id, + std::optional _ep, + std::optional> _body) { + return request_info{ + _req_id.value_or("R-{}"_format(random::random_base32(4))), + std::move(_dest), + _ep.value_or(ONION), + std::move(_body), + std::move(_original_body), + std::move(_swarm_pk), + _type, + _request_timeout, + _request_and_path_build_timeout}; +} + +std::string onion_path::to_string() const { + std::vector node_descriptions; + std::transform( + nodes.begin(), + nodes.end(), + std::back_inserter(node_descriptions), + [](const service_node& node) { return node.to_string(); }); + + return "{}"_format(fmt::join(node_descriptions, ", ")); +} + +bool onion_path::contains_node(const service_node& sn) const { + for (auto& n : nodes) { + if (n == sn) + return true; + } + + return false; +} + +// MARK: Initialization + +Network::Network( + std::optional cache_path, + bool use_testnet, + bool single_path_mode, + bool pre_build_paths) : + use_testnet{use_testnet}, + should_cache_to_disk{cache_path}, + single_path_mode{single_path_mode}, + cache_path{cache_path.value_or(default_cache_path)} { + loop = std::make_shared(); + + // Load the cache from disk and start the disk write thread + if (should_cache_to_disk) { + load_cache_from_disk(); + disk_write_thread = std::thread{&Network::disk_write_thread_loop, this}; + } + + // Kick off a separate thread to build paths (may as well kick this off early) + if (pre_build_paths) + for (int i = 0; i < min_path_count(PathType::standard, single_path_mode); ++i) { + auto path_id = "P-{}"_format(random::random_base32(4)); + in_progress_path_builds[path_id] = PathType::standard; + loop->call_soon([this, path_id] { build_path(path_id, PathType::standard); }); + } +} + +Network::~Network() { + // Flag the network as suspended when we start destroying to ensure no new requests get started + // (which could result in additional calls being added to the `loop` incorrectly and cause bad + // memory crashes) + suspended = true; + + // Trigger a 'call_get' to block until the endpoint has been destroyed + loop->call_get([this]() mutable { _close_connections(); }); + + { + std::lock_guard lock{snode_cache_mutex}; + shut_down_disk_thread = true; + } + update_disk_cache_throttled(true); + if (disk_write_thread.joinable()) + disk_write_thread.join(); +} + +// MARK: Cache Management + +void Network::load_cache_from_disk() { + try { + // If the cache is for the wrong network then delete everything + auto testnet_stub = cache_path / file_testnet; + if (use_testnet != fs::exists(testnet_stub) && fs::exists(testnet_stub)) + fs::remove_all(cache_path); + + // Remove any legacy files (don't want to leave old data around) + for (const auto& path : legacy_files) { + auto path_to_remove = cache_path / path; + fs::remove_all(path_to_remove); + } + + // If we are using testnet then create a file to indicate that + if (use_testnet) + write_whole_file(testnet_stub); + + // Load the snode pool + if (auto pool_path = cache_path / file_snode_pool; fs::exists(pool_path)) { + auto ftime = fs::last_write_time(pool_path); + last_snode_cache_update = + std::chrono::time_point_cast( + ftime - fs::file_time_type::clock::now() + + std::chrono::system_clock::now()); + + auto file = open_for_reading(pool_path); + std::vector loaded_cache; + std::string line; + auto invalid_entries = 0; + + while (std::getline(file, line)) { + try { + loaded_cache.push_back(node_from_disk(line)); + } catch (...) { + ++invalid_entries; + } + } + + if (invalid_entries > 0) + log::warning(cat, "Skipped {} invalid entries in snode cache.", invalid_entries); + + snode_cache = loaded_cache; + all_swarms = detail::generate_swarms(loaded_cache); + } + + log::info( + cat, + "Loaded cache of {} snodes, {} swarms.", + snode_cache.size(), + all_swarms.size()); + } catch (const std::exception& e) { + log::error(cat, "Failed to load snode cache, will rebuild ({}).", e.what()); + + if (fs::exists(cache_path)) + fs::remove_all(cache_path); + } +} + +void Network::update_disk_cache_throttled(bool force_immediate_write) { + // If we are forcing an immediate write then just notify the disk write thread and reset the + // pending write flag + if (force_immediate_write) { + snode_cache_cv.notify_one(); + has_pending_disk_write = false; + return; + } + + if (has_pending_disk_write) + return; + + has_pending_disk_write = true; + loop->call_later(1s, [this]() { + snode_cache_cv.notify_one(); + has_pending_disk_write = false; + }); +} + +void Network::disk_write_thread_loop() { + std::unique_lock lock{snode_cache_mutex}; + while (true) { + snode_cache_cv.wait( + lock, [this] { return need_write || need_clear_cache || shut_down_disk_thread; }); + + if (need_write) { + // Make a local copy so that we can release the lock and not + // worry about other threads wanting to change things + auto snode_cache_write = snode_cache; + + lock.unlock(); + { + try { + // Create the cache directories if needed + fs::create_directories(cache_path); + + // If we are using testnet then create a file to indicate that + if (use_testnet) { + auto testnet_stub = cache_path / file_testnet; + write_whole_file(testnet_stub); + } + + // Save the snode pool to disk + auto pool_path = cache_path / file_snode_pool, pool_tmp = pool_path; + pool_tmp += u8"_new"; + + { + std::stringstream ss; + for (auto& snode : snode_cache_write) + ss << node_to_disk(snode) << '\n'; + + std::ofstream file(pool_tmp, std::ios::binary); + file << ss.rdbuf(); + } + + fs::rename(pool_tmp, pool_path); + need_write = false; + + log::debug(cat, "Finished writing snode cache to disk."); + } catch (const std::exception& e) { + log::error(cat, "Failed to write snode cache: {}", e.what()); + } + } + lock.lock(); + } + if (need_clear_cache) { + snode_cache = {}; + + lock.unlock(); + if (fs::exists(cache_path)) + fs::remove_all(cache_path); + lock.lock(); + need_clear_cache = false; + } + if (shut_down_disk_thread) + return; + } +} + +void Network::clear_cache() { + loop->call([this] { + { + std::lock_guard lock{snode_cache_mutex}; + need_clear_cache = true; + } + update_disk_cache_throttled(true); + }); +} + +size_t Network::snode_cache_size() { + return loop->call_get([this]() -> size_t { return snode_cache.size(); }); +} + +// MARK: Connection + +void Network::suspend() { + loop->call([this] { + suspended = true; + close_connections(); + log::info(cat, "Suspended."); + }); +} + +void Network::resume() { + loop->call([this] { + suspended = false; + log::info(cat, "Resumed."); + }); +} + +void Network::close_connections() { + loop->call([this] { _close_connections(); }); +} + +void Network::_close_connections() { + // Explicitly close all connections then reset the endpoint + if (endpoint) + endpoint->close_conns(); + endpoint.reset(); + + // Cancel any pending requests (they can't succeed once the connection is closed) + for (const auto& [path_type, path_type_requests] : request_queue) + for (const auto& [info, callback] : path_type_requests) + callback( + false, + false, + error_network_suspended, + {content_type_plain_text}, + "Network is suspended."); + + // Clear all storage of requests, paths and connections so that we are in a fresh state on + // relaunch + request_queue.clear(); + paths.clear(); + path_build_queue.clear(); + paths_pending_drop.clear(); + unused_connections.clear(); + in_progress_connections.clear(); + snode_refresh_results.reset(); + current_snode_cache_refresh_request_id = std::nullopt; + + update_status(ConnectionStatus::disconnected); + log::info(cat, "Closed all connections."); +} + +void Network::update_status(ConnectionStatus updated_status) { + // Ignore updates which don't change the status + if (status == updated_status) + return; + + // If we are already 'connected' then ignore 'connecting' status changes (if we drop one path + // and build another in the background this can happen) + if (status == ConnectionStatus::connected && updated_status == ConnectionStatus::connecting) + return; + + // Store the updated status + status = updated_status; + + if (!status_changed) + return; + + status_changed(updated_status); +} + +std::chrono::milliseconds Network::retry_delay( + int num_failures, std::chrono::milliseconds max_delay) { + return std::chrono::milliseconds(std::min( + max_delay.count(), + static_cast(100 * std::pow(2, num_failures)))); +} + +std::shared_ptr Network::get_endpoint() { + return loop->call_get([this]() mutable { + if (!endpoint) + endpoint = quic::Endpoint::endpoint( + *loop, + quic::Address{"0.0.0.0", 0}, + quic::opt::alpns{ALPN}, + quic::opt::disable_mtu_discovery{}); + + return endpoint; + }); +} + +// MARK: Request Queues and Path Building + +size_t Network::min_snode_cache_size() const { + if (!seed_node_cache_size) + return min_snode_cache_count; + + // If the seed node cache size is somehow smaller than `min_snode_cache_count` (ie. Testnet + // having issues) then the minimum size should be the full cache size (minus enough to build a + // path) or at least the size of a path + auto min_path_size = static_cast(path_size); + return std::min( + std::max(min_path_size, *seed_node_cache_size - min_path_size), min_snode_cache_count); +} + +std::vector Network::get_unused_nodes() { + if (snode_cache.size() < min_snode_cache_size()) + return {}; + + // Exclude any IPs that are already in use from existing paths + std::vector node_ips_to_exlude = all_path_ips(); + + // Exclude unused connections + for (const auto& conn_info : unused_connections) + node_ips_to_exlude.emplace_back(conn_info.node.to_ipv4()); + + // Exclude in progress connections + for (const auto& [request_id, node] : in_progress_connections) + node_ips_to_exlude.emplace_back(node.to_ipv4()); + + // Exclude pending requests + for (const auto& [path_type, path_type_requests] : request_queue) + for (const auto& [info, callback] : path_type_requests) + if (auto* dest = std::get_if(&info.destination)) + node_ips_to_exlude.emplace_back(dest->to_ipv4()); + + // Exclude any nodes which have surpassed the failure threshold + for (const auto& [node_string, failure_count] : snode_failure_counts) + if (failure_count >= snode_failure_threshold) { + size_t colon_pos = node_string.find(':'); + + if (colon_pos != std::string::npos) + node_ips_to_exlude.emplace_back(quic::ipv4{node_string.substr(0, colon_pos)}); + else + node_ips_to_exlude.emplace_back(quic::ipv4{node_string}); + } + + // Populate the unused nodes with any nodes in the cache which shouldn't be excluded + std::vector result; + + if (node_ips_to_exlude.empty()) + result = snode_cache; + else + std::copy_if( + snode_cache.begin(), + snode_cache.end(), + std::back_inserter(result), + [&node_ips_to_exlude](const auto& node) { + return std::find( + node_ips_to_exlude.begin(), + node_ips_to_exlude.end(), + node.to_ipv4()) == node_ips_to_exlude.end(); + }); + + // Shuffle the `result` so anything that uses it would get random nodes + std::shuffle(result.begin(), result.end(), csrng); + + return result; +} + +void Network::establish_connection( + std::string id, + service_node target, + std::optional timeout, + std::function error)> callback) { + log::trace(cat, "{} called for {}.", __PRETTY_FUNCTION__, id); + auto currently_suspended = loop->call_get([this]() -> bool { return suspended; }); + + // If the network is currently suspended then don't try to open a connection + if (currently_suspended) + return callback( + {target, std::make_shared(0), nullptr, nullptr}, "Network is suspended."); + + auto conn_key_pair = ed25519::ed25519_key_pair(); + auto creds = quic::GNUTLSCreds::make_from_ed_seckey(to_string_view(conn_key_pair.second)); + auto cb_called = std::make_shared(); + auto cb = std::make_shared)>>( + std::move(callback)); + auto conn_promise = std::promise>(); + auto conn_future = conn_promise.get_future().share(); + auto handshake_timeout = + timeout ? std::optional{quic::opt::handshake_timeout{ + std::chrono::duration_cast(*timeout)}} + : std::nullopt; + + auto c = get_endpoint()->connect( + target, + creds, + quic::opt::keep_alive{10s}, + handshake_timeout, + [this, id, target, cb, cb_called, conn_future](quic::Connection&) mutable { + log::trace(cat, "Connection established for {}.", id); + + // Just in case, call it within a `loop->call` + loop->call([&] { + std::call_once(*cb_called, [&]() { + if (cb) { + auto conn = conn_future.get(); + (*cb)({target, + std::make_shared(0), + conn, + conn->open_stream()}, + std::nullopt); + cb.reset(); + } + }); + }); + }, + [this, target, id, cb, cb_called, conn_future]( + quic::Connection& conn, uint64_t error_code) mutable { + if (error_code == static_cast(NGTCP2_ERR_HANDSHAKE_TIMEOUT)) + log::info( + cat, + "Unable to establish connection to {} for {}.", + target.to_string(), + id); + else + log::info(cat, "Connection to {} closed for {}.", target.to_string(), id); + + // Just in case, call it within a `loop->call` + loop->call([&] { + // Trigger the callback first before updating the paths in case this was + // triggered when try to establish a connection + std::call_once(*cb_called, [&]() { + if (cb) { + (*cb)({target, std::make_shared(0), nullptr, nullptr}, + std::nullopt); + cb.reset(); + } + }); + + // Remove the connection from `unused_connection` if present + std::erase_if(unused_connections, [&conn, &target](auto& unused_conn) { + return (unused_conn.node == target && unused_conn.conn && + unused_conn.conn->reference_id() == conn.reference_id()); + }); + + // If this connection is being used in an existing path then we should drop it + // (as the path is no longer valid) + for (const auto& [path_type, paths_for_type] : paths) { + for (const auto& path : paths_for_type) { + if (!path.nodes.empty() && path.nodes.front() == target && + path.conn_info.conn && + conn.reference_id() == path.conn_info.conn->reference_id()) { + drop_path_when_empty(id, path_type, path); + break; + } + } + } + + // Since a connection was closed we should also clear any pending path drops + // in case this connection was one of those + clear_empty_pending_path_drops(); + + // If the connection failed with a handshake timeout then the node is + // unreachable, either due to a device network issue or because the node + // is down so set the failure count to the failure threshold so it won't + // be used for subsequent requests + if (error_code == static_cast(NGTCP2_ERR_HANDSHAKE_TIMEOUT)) + snode_failure_counts[target.to_string()] = snode_failure_threshold; + }); + }); + + conn_promise.set_value(c); +} + +void Network::establish_and_store_connection(std::string path_id) { + // If we are suspended then don't try to establish a new connection + if (suspended) + return; + + // If we haven't set a connection status yet then do so now + if (status == ConnectionStatus::unknown) + update_status(ConnectionStatus::connecting); + + // Re-populate the unused nodes if it ends up being empty + if (unused_nodes.empty()) + unused_nodes = get_unused_nodes(); + + // If there aren't enough unused nodes then trigger a cache refresh + if (unused_nodes.size() < min_snode_cache_size()) { + log::trace( + cat, + "Unable to establish new connection due to lack of unused nodes, refreshing snode " + "cache ({}).", + path_id); + return loop->call_soon([this, path_id]() { refresh_snode_cache(path_id); }); + } + + // Otherwise check if it's been too long since the last cache update and, if so, trigger a + // refresh + auto cache_lifetime = std::chrono::duration_cast( + std::chrono::system_clock::now() - last_snode_cache_update); + + if (cache_lifetime < 0s || cache_lifetime > snode_cache_expiration_duration) + loop->call_soon([this]() { refresh_snode_cache(); }); + + // If there are no in progress connections then reset the failure count + if (in_progress_connections.empty()) + connection_failures = 0; + + // Grab a node from the `unused_nodes` list to establish a connection to + auto target_node = unused_nodes.back(); + unused_nodes.pop_back(); + + // Try to establish a new connection to the target (this has a 3s handshake timeout as we + // wouldn't want to use any nodes which take longer than that anyway) + log::info(cat, "Establishing connection to {} for {}.", target_node.to_string(), path_id); + in_progress_connections.emplace(path_id, target_node); + + establish_connection( + path_id, + target_node, + 3s, + [this, target_node, path_id](connection_info info, std::optional) { + // If we failed to get a connection then try again after a delay (may as well try + // indefinitely because there is no way to recover from this issue) + if (!info.is_valid()) { + connection_failures++; + auto connection_retry_delay = retry_delay(connection_failures); + log::error( + cat, + "Failed to connect to {}, will try another after {}ms.", + target_node.to_string(), + connection_retry_delay.count()); + return loop->call_later(connection_retry_delay, [this, path_id]() { + establish_and_store_connection(path_id); + }); + } + + // We were able to connect to the node so add it to the unused_connections queue + log::info(cat, "Connection to {} valid for {}.", target_node.to_string(), path_id); + unused_connections.emplace_back(info); + + // Kick off the next pending path build since we now have a valid connection + if (!path_build_queue.empty()) { + in_progress_path_builds[path_id] = path_build_queue.front(); + loop->call_soon([this, path_type = path_build_queue.front(), path_id]() { + build_path(path_id, path_type); + }); + path_build_queue.pop_front(); + } + + // If there are still pending path builds but no in progress connections then kick + // off enough additional connections for remaining builds (this shouldn't happen but + // better to be safe and avoid a situation where a path build gets orphaned) + if (!path_build_queue.empty() && in_progress_connections.empty()) + for ([[maybe_unused]] const auto& _ : path_build_queue) + loop->call_soon([this]() { + auto conn_id = "EC-{}"_format(random::random_base32(4)); + establish_and_store_connection(conn_id); + }); + }); +} + +void Network::refresh_snode_cache_complete(std::vector nodes) { + // Shuffle the nodes so we don't have a specific order + std::shuffle(nodes.begin(), nodes.end(), csrng); + + // Update the disk cache if the snode pool was updated + { + std::lock_guard lock{snode_cache_mutex}; + snode_cache = nodes; + last_snode_cache_update = std::chrono::system_clock::now(); + need_write = true; + } + update_disk_cache_throttled(); + + // Reset the cache refresh state + current_snode_cache_refresh_request_id = std::nullopt; + snode_cache_refresh_failure_count = 0; + in_progress_snode_cache_refresh_count = 0; + unused_snode_refresh_nodes = std::nullopt; + snode_refresh_results.reset(); + + // Reset the snode failure counts (assume if the snode refresh includes + // nodes then they are valid) + snode_failure_counts.clear(); + + // Since we've updated the snode cache the swarm cache could be invalid + // so we need to regenerate it (the resulting `all_swarms` needs to be + // stored in ascending order as it is required for the logic to find the + // appropriate swarm for a given pubkey) + all_swarms.clear(); + swarm_cache.clear(); + all_swarms = detail::generate_swarms(nodes); + + // Run any post-refresh processes + for (const auto& callback : after_snode_cache_refresh) + loop->call_soon([cb = std::move(callback)]() { cb(); }); + after_snode_cache_refresh.clear(); + + // Resume any queued path builds + for (const auto& path_type : path_build_queue) { + auto path_id = "P-{}"_format(random::random_base32(4)); + in_progress_path_builds[path_id] = path_type; + loop->call_soon([this, path_type, path_id]() { build_path(path_id, path_type); }); + } + path_build_queue.clear(); +} + +void Network::refresh_snode_cache_from_seed_nodes(std::string request_id, bool reset_unused_nodes) { + if (suspended) { + log::info(cat, "Ignoring snode cache refresh as network is suspended ({}).", request_id); + return; + } + + // Only allow a single cache refresh at a time (this gets cleared in `_close_connections` so if + // it happens to loop after going to, and returning from, the background a subsequent refresh + // won't be blocked) + if (current_snode_cache_refresh_request_id && + current_snode_cache_refresh_request_id != request_id) { + log::info( + cat, + "Snode cache refresh from seed node {} ignored as it doesn't match the current " + "refresh id ({}).", + request_id, + current_snode_cache_refresh_request_id.value_or("NULL")); + return; + } + + // If the unused nodes is empty then reset it (if we are refreshing from seed nodes it means the + // local cache is not usable so we are just going to have to call this endlessly until it works) + if (reset_unused_nodes || !unused_snode_refresh_nodes || unused_snode_refresh_nodes->empty()) { + log::info( + cat, + "Existing cache is insufficient, refreshing from seed nodes ({}).", + request_id); + + // Shuffle to ensure we pick random nodes to fetch from + unused_snode_refresh_nodes = (use_testnet ? seed_nodes_testnet : seed_nodes_mainnet); + std::shuffle(unused_snode_refresh_nodes->begin(), unused_snode_refresh_nodes->end(), csrng); + } + + auto target_node = unused_snode_refresh_nodes->back(); + unused_snode_refresh_nodes->pop_back(); + + establish_connection( + request_id, + target_node, + 3s, + [this, request_id](connection_info info, std::optional) { + // If we failed to get a connection then try again after a delay (may as well try + // indefinitely because there is no way to recover from this issue) + if (!info.is_valid()) { + snode_cache_refresh_failure_count++; + auto cache_refresh_retry_delay = retry_delay(snode_cache_refresh_failure_count); + log::error( + cat, + "Failed to connect to seed node to refresh snode cache, will retry " + "after {}ms ({}).", + cache_refresh_retry_delay.count(), + request_id); + return loop->call_later(cache_refresh_retry_delay, [this, request_id]() { + refresh_snode_cache_from_seed_nodes(request_id, false); + }); + } + + get_service_nodes( + request_id, + info, + std::nullopt, + [this, request_id]( + std::vector nodes, std::optional error) { + // If we got no nodes then we will need to try again + if (nodes.empty()) { + snode_cache_refresh_failure_count++; + auto cache_refresh_retry_delay = + retry_delay(snode_cache_refresh_failure_count); + log::error( + cat, + "Failed to retrieve nodes from seed node to refresh cache " + "due to error: {}, will retry after {}ms ({}).", + error.value_or("Unknown Error"), + cache_refresh_retry_delay.count(), + request_id); + return loop->call_later( + cache_refresh_retry_delay, [this, request_id]() { + refresh_snode_cache_from_seed_nodes(request_id, false); + }); + } + + log::info( + cat, + "Refreshing snode cache from seed nodes completed with {} " + "nodes ({}).", + nodes.size(), + request_id); + seed_node_cache_size = nodes.size(); + refresh_snode_cache_complete(nodes); + }); + }); +} + +void Network::refresh_snode_cache(std::optional existing_request_id) { + auto request_id = existing_request_id.value_or("RSC-{}"_format(random::random_base32(4))); + + if (suspended) { + log::info(cat, "Ignoring snode cache refresh as network is suspended ({}).", request_id); + return; + } + + // Only allow a single cache refresh at a time (this gets cleared in `_close_connections` so if + // it happens to loop after going to, and returning from, the background a subsequent refresh + // won't be blocked) + if (current_snode_cache_refresh_request_id && + current_snode_cache_refresh_request_id != request_id) { + log::info( + cat, + "Snode cache refresh {} ignored due to in progress refresh ({}).", + request_id, + current_snode_cache_refresh_request_id.value_or("NULL")); + return; + } + + // We are starting a new cache refresh so store an identifier for it (we also initialise + // `snode_refresh_results` so we can use it to track the results from the different requests) + if (!current_snode_cache_refresh_request_id) { + log::info(cat, "Refreshing snode cache ({}).", request_id); + snode_cache_refresh_failure_count = 0; + in_progress_snode_cache_refresh_count = 0; + current_snode_cache_refresh_request_id = request_id; + snode_refresh_results = std::make_shared>>(); + } + + // If we don't have enough nodes in the unused nodes then refresh it + if (unused_nodes.size() < min_snode_cache_size()) + unused_nodes = get_unused_nodes(); + + // If we still don't have enough nodes in the unused nodes it likely means we didn't + // have enough nodes in the cache so instead just fetch from the seed nodes (which is + // a trusted source so we can update the cache from a single response) + if (unused_nodes.size() < min_snode_cache_size()) + return refresh_snode_cache_from_seed_nodes(request_id, true); + + // Target an unused node and increment the in progress refresh counter + auto target_node = unused_nodes.back(); + unused_nodes.pop_back(); + in_progress_snode_cache_refresh_count++; + + // If there are still more concurrent refresh_snode_cache requests we want to trigger then + // trigger the next one to run in the next run loop + if (in_progress_snode_cache_refresh_count < num_snodes_to_refresh_cache_from) + loop->call_soon([this, request_id]() { refresh_snode_cache(request_id); }); + + // Prepare and send the request to retrieve service nodes + nlohmann::json payload{ + {"method", "oxend_request"}, + {"params", + {{"endpoint", "get_service_nodes"}, + {"params", detail::get_service_nodes_params(std::nullopt)}}}, + }; + auto info = request_info::make( + target_node, + to_vector(payload.dump()), + std::nullopt, + quic::DEFAULT_TIMEOUT, + std::nullopt, + PathType::standard, + request_id); + _send_onion_request( + info, + [this, request_id]( + bool success, + bool timeout, + int16_t, + std::vector>, + std::optional response) { + // If the 'snode_refresh_results' value doesn't exist it means we have already + // completed/cancelled this snode cache refresh and have somehow gotten into an + // invalid state, so just ignore this request + if (!snode_refresh_results) { + log::warning( + cat, + "Ignoring snode cache response after cache update already completed " + "({}).", + request_id); + return; + } + + try { + if (!success || timeout || !response) + throw std::runtime_error{response.value_or("Unknown error.")}; + + nlohmann::json response_json = nlohmann::json::parse(*response); + std::vector result = + detail::process_get_service_nodes_response(response_json); + snode_refresh_results->emplace_back(result); + + // Update the in progress request count + in_progress_snode_cache_refresh_count--; + } catch (const std::exception& e) { + // The request failed so increment the failure counter and retry after a short + // delay + snode_cache_refresh_failure_count++; + + auto cache_refresh_retry_delay = retry_delay(snode_cache_refresh_failure_count); + log::error( + cat, + "Failed to retrieve nodes from one target when refreshing cache due to " + "error: {}, Will try another target after {}ms ({}).", + e.what(), + cache_refresh_retry_delay.count(), + request_id); + return loop->call_later(cache_refresh_retry_delay, [this, request_id]() { + refresh_snode_cache(request_id); + }); + } + + // If we haven't received all results then do nothing + if (snode_refresh_results->size() != num_snodes_to_refresh_cache_from) { + log::info( + cat, + "Received snode cache refresh result {}/{} ({}).", + snode_refresh_results->size(), + num_snodes_to_refresh_cache_from, + request_id); + return; + } + + auto any_nodes_request_failed = std::any_of( + snode_refresh_results->begin(), + snode_refresh_results->end(), + [](const auto& n) { return n.empty(); }); + + // If the current cache is still usable just send a warning and don't bother + // retrying + if (any_nodes_request_failed) { + log::warning(cat, "Failed to refresh snode cache ({}).", request_id); + current_snode_cache_refresh_request_id = std::nullopt; + snode_cache_refresh_failure_count = 0; + in_progress_snode_cache_refresh_count = 0; + snode_refresh_results.reset(); + return; + } + + // Sort the vectors (so make it easier to find the intersection) + auto compare_service_nodes = [](const service_node& a, const service_node& b) { + if (auto cmp = quic::Address(a) <=> quic::Address(b); cmp != 0) + return cmp < 0; + + return std::tie(a.get_remote_key(), a.swarm_id, a.storage_server_version) < + std::tie(b.get_remote_key(), b.swarm_id, b.storage_server_version); + }; + + for (auto& nodes : *snode_refresh_results) + std::stable_sort(nodes.begin(), nodes.end(), compare_service_nodes); + + auto nodes = (*snode_refresh_results)[0]; + + // If we triggered multiple requests then get the intersection of all vectors + if (snode_refresh_results->size() > 1) { + for (size_t i = 1; i < snode_refresh_results->size(); ++i) { + std::vector temp; + std::set_intersection( + nodes.begin(), + nodes.end(), + (*snode_refresh_results)[i].begin(), + (*snode_refresh_results)[i].end(), + std::back_inserter(temp), + compare_service_nodes); + nodes = std::move(temp); + } + } + + log::info( + cat, + "Refreshing snode cache completed with {} nodes ({}).", + nodes.size(), + request_id); + refresh_snode_cache_complete(nodes); + }); +} + +void Network::build_path(std::string path_id, PathType path_type) { + if (suspended) { + log::info(cat, "Ignoring build_path call as network is suspended."); + return; + } + + auto path_name = path_type_name(path_type, single_path_mode); + + // If we don't have an unused connection for the first hop then enqueue the path build and + // establish a new connection + if (unused_connections.empty()) { + log::info( + cat, + "No unused connections available to build {} path, creating new connection for {}.", + path_name, + path_id); + path_build_queue.emplace_back(path_type); + in_progress_path_builds.erase(path_id); + return loop->call_soon([this, path_id]() { establish_and_store_connection(path_id); }); + } + + // Reset the unused nodes list if it's too small + if (unused_nodes.size() < path_size) + unused_nodes = get_unused_nodes(); + + // If we still don't have enough unused nodes then we need to refresh the cache + if (unused_nodes.size() < path_size) { + log::info( + cat, "Re-queing {} path build due to insufficient nodes ({}).", path_name, path_id); + path_build_failures = 0; + path_build_queue.emplace_back(path_type); + in_progress_path_builds.erase(path_id); + return loop->call_soon([this]() { refresh_snode_cache(); }); + } + + // Build the path + log::info(cat, "Building {} path ({}).", path_name, path_id); + in_progress_path_builds[path_id] = path_type; + + auto conn_info = std::move(unused_connections.front()); + unused_connections.pop_front(); + std::vector path_nodes = {conn_info.node}; + + while (path_nodes.size() < path_size) { + if (unused_nodes.empty()) { + // Log the error and try build again after a slight delay + log::info( + cat, + "Unable to build {} path due to lack of suitable unused nodes ({}).", + path_name, + path_id); + + // Delay the next path build attempt based on the error we received + path_build_failures++; + unused_connections.push_front(std::move(conn_info)); + auto delay = retry_delay(path_build_failures); + loop->call_later( + delay, [this, path_id, path_type]() { build_path(path_id, path_type); }); + return; + } + + // Grab the next unused node to continue building the path + auto node = unused_nodes.back(); + unused_nodes.pop_back(); + + // Ensure we don't put two nodes with the same IP into the same path + auto snode_with_ip_it = std::find_if( + path_nodes.begin(), path_nodes.end(), [&node](const auto& existing_node) { + return existing_node.to_ipv4() == node.to_ipv4(); + }); + + if (snode_with_ip_it == path_nodes.end()) + path_nodes.push_back(node); + } + + // Store the new path + auto path = onion_path{path_id, std::move(conn_info), path_nodes, 0}; + paths[path_type].emplace_back(path); + in_progress_path_builds.erase(path_id); + + // Log that a path was built + log::info( + cat, + "Built new onion request path [{}], now have {} {} path(s) ({}).", + path.to_string(), + paths[path_type].size(), + path_name, + path_id); + + // If the connection info is valid and it's a standard path then update the + // connection status to connected + if (path_type == PathType::standard) { + update_status(ConnectionStatus::connected); + + // If a paths_changed callback was provided then call it + if (paths_changed) { + std::vector> raw_paths; + for (const auto& path : paths[path_type]) + raw_paths.emplace_back(path.nodes); + + paths_changed(raw_paths); + } + } + + // Remove the nodes from unused_nodes which have the same IPs as nodes in + // the final path + std::vector path_ips; + for (const auto& node : path_nodes) + path_ips.emplace_back(node.to_ipv4()); + + std::erase_if(unused_nodes, [&path_ips](const auto& node) { + return std::find(path_ips.begin(), path_ips.end(), node.to_ipv4()) != path_ips.end(); + }); + + // If there are pending requests which this path is valid for then resume them + std::erase_if(request_queue[path_type], [this, &path](const auto& request) { + if (!find_valid_path(request.first, {path})) + return false; + + loop->call_soon([this, info = request.first, cb = std::move(request.second)]() { + _send_onion_request(std::move(info), std::move(cb)); + }); + return true; + }); + + // If there are still pending requests and there are no pending path builds for them then kick + // off a subsequent path build in an effort to resume the remaining requests + if (!request_queue[path_type].empty()) { + auto additional_path_id = "P-{}"_format(random::random_base32(4)); + in_progress_path_builds[additional_path_id] = path_type; + loop->call_soon([this, path_type, additional_path_id] { + build_path(additional_path_id, path_type); + }); + } else + request_queue.erase(path_type); +} + +std::optional Network::find_valid_path( + const request_info info, const std::vector paths) { + if (paths.empty()) + return std::nullopt; + + // Only include paths with valid connections as options + std::vector possible_paths; + std::copy_if( + paths.begin(), paths.end(), std::back_inserter(possible_paths), [&](const auto& path) { + return path.is_valid(); + }); + + // If the request destination is a node then only select a path that doesn't include the IP of + // the destination + if (auto target = detail::node_for_destination(info.destination)) { + std::vector ip_excluded_paths; + std::copy_if( + possible_paths.begin(), + possible_paths.end(), + std::back_inserter(ip_excluded_paths), + [&](const onion_path& p) { return not p.contains_node(*target); }); + + if (single_path_mode && ip_excluded_paths.empty()) + log::warning( + cat, + "Path should have been excluded due to matching IP for {} but network is in " + "single path mode.", + info.request_id); + else + possible_paths = ip_excluded_paths; + } + + if (possible_paths.empty()) + return std::nullopt; + + // Randomise the possible paths (if all paths are equal for the PathSelectionBehaviour then we + // want a random one to be selected) + std::shuffle(possible_paths.begin(), possible_paths.end(), csrng); + + // Select from the possible paths based on the desired behaviour + auto behaviour = path_selection_behaviour(info.path_type); + switch (behaviour) { + case PathSelectionBehaviour::new_or_least_busy: { + auto min_num_paths = min_path_count(info.path_type, single_path_mode); + std::sort( + possible_paths.begin(), possible_paths.end(), [](const auto& a, const auto& b) { + return a.num_pending_requests() < b.num_pending_requests(); + }); + + // If we have already have the min number of paths for this path type, or there is + // a path with no pending requests then return the first path + if (paths.size() >= min_num_paths || possible_paths.front().num_pending_requests() == 0) + return possible_paths.front(); + + // Otherwise we want to build a new path (for this PathSelectionBehaviour the assuption + // is that it'd be faster to build a new path and send the request along that rather + // than use an existing path) + return std::nullopt; + } + + // Random is the default behaviour + case PathSelectionBehaviour::random: return possible_paths.front(); + default: return possible_paths.front(); + } +}; + +void Network::build_path_if_needed(PathType path_type, bool found_path) { + const auto current_paths = paths[path_type]; + + // In `single_path_mode` we never build additional paths + if (current_paths.size() > 0 && single_path_mode) + return; + + // We only want to enqueue a new path build if: + // - We don't have the minimum number of paths for the specified type + // - We don't have any pending builds + // - The current paths are unsuitable for the request + auto min_paths = min_path_count(path_type, single_path_mode); + + // If we have enough existing paths and found a valid path then no need to build more paths + if (found_path && current_paths.size() >= min_paths) + return; + + // Get the number pending paths + auto queued = std::count(path_build_queue.begin(), path_build_queue.end(), path_type); + auto in_progress = std::count_if( + in_progress_path_builds.begin(), + in_progress_path_builds.end(), + [&path_type](const auto& build) { return build.second == path_type; }); + auto pending_paths = (queued + in_progress); + + // If we don't have enough current + pending paths, or the request couldn't be sent then + // kick off a new path build + if ((current_paths.size() + pending_paths) < min_paths || (!found_path && pending_paths == 0)) { + auto path_id = "P-{}"_format(random::random_base32(4)); + build_path(path_id, path_type); + } +} + +// MARK: Direct Requests + +void Network::get_service_nodes( + std::string request_id, + connection_info conn_info, + std::optional limit, + std::function nodes, std::optional error)> + callback) { + log::trace(cat, "{} called for {}.", __PRETTY_FUNCTION__, request_id); + + if (!conn_info.is_valid()) + return callback({}, "Connection is not valid."); + + oxenc::bt_dict_producer payload; + payload.append("endpoint", "get_service_nodes"); + payload.append("params", detail::get_service_nodes_params(limit).dump()); + + conn_info.add_pending_request(); + conn_info.stream->command( + "oxend_request", + payload.view(), + [this, request_id, conn_info, cb = std::move(callback)](quic::message resp) { + log::trace(cat, "{} got response for {}.", __PRETTY_FUNCTION__, request_id); + std::vector result; + conn_info.remove_pending_request(); + + try { + auto [status_code, body] = validate_response(resp, true); + oxenc::bt_list_consumer result_bencode{body}; + result = detail::process_get_service_nodes_response(result_bencode); + } catch (const std::exception& e) { + return cb({}, e.what()); + } + + // Output the result + cb(result, std::nullopt); + + // After completing a request we should try to clear any pending path drops (just in + // case this request was the final one on a pending path drop) + if (!conn_info.has_pending_requests()) + clear_empty_pending_path_drops(); + }); +} + +// MARK: Swarm Management + +void Network::get_swarm( + session::onionreq::x25519_pubkey swarm_pubkey, + std::function swarm)> callback) { + log::trace(cat, "{} called for {}.", __PRETTY_FUNCTION__, swarm_pubkey.hex()); + + loop->call([this, swarm_pubkey, cb = std::move(callback)]() { + // If we have a cached swarm then return it + auto cached_swarm = swarm_cache[swarm_pubkey.hex()]; + if (!cached_swarm.second.empty()) + return cb(cached_swarm.first, cached_swarm.second); + + // If we have no snode cache or no swarms then we need to rebuild the cache (which will also + // rebuild the swarms) and run this request again + if (snode_cache.empty() || all_swarms.empty()) { + after_snode_cache_refresh.emplace_back([this, swarm_pubkey, cb = std::move(cb)]() { + get_swarm(swarm_pubkey, std::move(cb)); + }); + return loop->call_soon([this]() { refresh_snode_cache(); }); + } + + // If there is only a single swarm then return it + if (all_swarms.size() == 1) + return cb(all_swarms.front().first, all_swarms.front().second); + + // Generate a swarm_id for the pubkey + const swarm_id_t swarm_id = detail::pubkey_to_swarm_space(swarm_pubkey); + + // Find the right boundary, i.e. first swarm with swarm_id >= res + auto right_it = std::lower_bound( + all_swarms.begin(), all_swarms.end(), swarm_id, [](const auto& s, uint64_t v) { + return s.first < v; + }); + + if (right_it == all_swarms.end()) + // res is > the top swarm_id, meaning it is big and in the wrapping space between last + // and first elements. + right_it = all_swarms.begin(); + + // Our "left" is the one just before that (with wraparound, if right is the first swarm) + auto left_it = std::prev(right_it == all_swarms.begin() ? all_swarms.end() : right_it); + + uint64_t dright = right_it->first - swarm_id; + uint64_t dleft = swarm_id - left_it->first; + auto swarm = &*(dright < dleft ? right_it : left_it); + + // Update the cache with the result + log::info( + cat, + "Found swarm with {} nodes for {}, adding to cache.", + swarm->second.size(), + swarm_pubkey.hex()); + swarm_cache[swarm_pubkey.hex()] = *swarm; + cb(swarm->first, swarm->second); + }); +} + +// MARK: Node Retrieval + +void Network::get_random_nodes( + uint16_t count, std::function nodes)> callback) { + auto request_id = "R-{}"_format(random::random_base32(4)); + log::trace(cat, "{} called for {}.", __PRETTY_FUNCTION__, request_id); + + loop->call([this, request_id, count, cb = std::move(callback)]() mutable { + // If we don't have sufficient unused nodes then regenerate it + if (unused_nodes.size() < count) + unused_nodes = get_unused_nodes(); + + // If we still don't have sufficient nodes then we need to refresh the snode cache + if (unused_nodes.size() < count) { + after_snode_cache_refresh.emplace_back( + [this, count, cb = std::move(cb)]() { get_random_nodes(count, cb); }); + return loop->call_soon([this]() { refresh_snode_cache(); }); + } + + // Otherwise callback with the requested random number of nodes + auto random_nodes = + std::vector(unused_nodes.begin(), unused_nodes.begin() + count); + unused_nodes.erase(unused_nodes.begin(), unused_nodes.begin() + count); + cb(random_nodes); + }); +} + +// MARK: Request Handling + +void Network::check_request_queue_timeouts(std::optional request_timeout_id_) { + // If the network is suspended then don't bother checking for timeouts + if (suspended) + return; + + // If there is an existing timeout checking loop then we don't want to start a second + if (request_timeout_id != request_timeout_id_) + return; + + // If there wasn't an existing loop id then set it here + if (!request_timeout_id) + request_timeout_id = "RT-{}"_format(random::random_base32(4)); + + // Timeout and remove any pending requests which should timeout based on path build time + auto has_remaining_timeout_requests = false; + auto time_now = std::chrono::system_clock::now(); + + for (auto& [path_type, requests_for_path] : request_queue) + std::erase_if( + requests_for_path, + [&has_remaining_timeout_requests, &time_now](const auto& request) { + // If the request doesn't have a path build timeout then ignore it + if (!request.first.request_and_path_build_timeout) + return false; + + auto duration = std::chrono::duration_cast( + time_now - request.first.creation_time); + + if (duration > *request.first.request_and_path_build_timeout) { + request.second( + false, + true, + error_path_build_timeout, + {content_type_plain_text}, + "Timed out waiting for path build."); + return true; + } + + has_remaining_timeout_requests = true; + return false; + }); + + // If there are no more timeout requests then stop looping here + if (!has_remaining_timeout_requests) { + request_timeout_id = std::nullopt; + return; + } + + // Otherwise schedule the next check + loop->call_later(queued_request_path_build_timeout_frequency, [this]() { + check_request_queue_timeouts(request_timeout_id); + }); +} + +void Network::send_request( + request_info info, connection_info conn_info, network_response_callback_t handle_response) { + log::trace(cat, "{} called for {}.", __PRETTY_FUNCTION__, info.request_id); + + if (!conn_info.is_valid()) + return handle_response( + false, false, -1, {content_type_plain_text}, "Network is unreachable."); + + std::span payload{}; + + if (info.body) + payload = to_span(*info.body); + + // Calculate the remaining timeout + std::chrono::milliseconds timeout = info.request_timeout; + + if (info.request_and_path_build_timeout) { + auto elapsed_time = std::chrono::duration_cast( + std::chrono::system_clock::now() - info.creation_time); + + timeout = *info.request_and_path_build_timeout - elapsed_time; + + // If the timeout was somehow negative then just fail the request (no point continuing if + // we have already timed out) + if (timeout < std::chrono::milliseconds(0)) + return handle_response( + false, + true, + error_path_build_timeout, + {content_type_plain_text}, + "Path Build Timed Out."); + } + + conn_info.add_pending_request(); + conn_info.stream->command( + info.endpoint, + payload, + timeout, + [this, info, conn_info, cb = std::move(handle_response)](quic::message resp) { + log::trace(cat, "{} got response for {}.", __PRETTY_FUNCTION__, info.request_id); + + std::pair result; + auto& [status_code, body] = result; + conn_info.remove_pending_request(); + + try { + result = validate_response(resp, false); + } catch (const status_code_exception& e) { + return handle_errors( + info, + conn_info, + resp.timed_out, + e.status_code, + e.headers, + e.what(), + cb); + } catch (const std::exception& e) { + return handle_errors( + info, + conn_info, + resp.timed_out, + -1, + {content_type_plain_text}, + e.what(), + cb); + } + + cb(true, false, status_code, {}, body); + + // After completing a request we should try to clear any pending path drops (just in + // case this request was the final one on a pending path drop) + if (!conn_info.has_pending_requests()) + clear_empty_pending_path_drops(); + }); +} + +void Network::send_onion_request( + onionreq::network_destination destination, + std::optional> body, + std::optional swarm_pubkey, + network_response_callback_t handle_response, + std::chrono::milliseconds request_timeout, + std::optional request_and_path_build_timeout, + PathType type) { + _send_onion_request( + request_info::make( + std::move(destination), + std::move(body), + std::move(swarm_pubkey), + request_timeout, + request_and_path_build_timeout, + type), + std::move(handle_response)); +} + +void Network::_send_onion_request(request_info info, network_response_callback_t handle_response) { + auto path_name = path_type_name(info.path_type, single_path_mode); + log::trace(cat, "{} called for {} path ({}).", __PRETTY_FUNCTION__, path_name, info.request_id); + + // Try to retrieve a valid path for this request, if we can't get one then add the request to + // the queue to be run once a path for it has successfully been built + auto path = loop->call_get([this, info]() { + auto result = find_valid_path(info, paths[info.path_type]); + loop->call_soon([this, path_type = info.path_type, found_path = result.has_value()]() { + build_path_if_needed(path_type, found_path); + }); + return result; + }); + + if (!path) { + return loop->call([this, info = std::move(info), cb = std::move(handle_response)]() { + // If the network is suspended then fail immediately + if (suspended) + return cb( + false, + false, + error_network_suspended, + {content_type_plain_text}, + "Network is suspended."); + + request_queue[info.path_type].emplace_back(std::move(info), std::move(cb)); + + // If the request has a path_build_timeout then start the timeout check loop + if (info.request_and_path_build_timeout) + loop->call_later(queued_request_path_build_timeout_frequency, [this]() { + check_request_queue_timeouts(); + }); + }); + } + + log::trace(cat, "{} got {} path for {}.", __PRETTY_FUNCTION__, path_name, info.request_id); + + // Construct the onion request + auto builder = Builder::make(info.destination, path->nodes); + try { + builder.generate(info); + } catch (const std::exception& e) { + log::warning(cat, "Builder exception: {}", e.what()); + return handle_response( + false, false, error_building_onion_request, {content_type_plain_text}, e.what()); + } + + // Actually send the request + send_request( + info, + path->conn_info, + [this, + builder = std::move(builder), + info, + path = *path, + cb = std::move(handle_response)]( + bool success, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response) { + log::trace(cat, "{} got response for {}.", __PRETTY_FUNCTION__, info.request_id); + + // If the request was reported as a failure or a timeout then we + // will have already handled the errors so just trigger the callback + if (!success || timeout) + return cb(success, timeout, status_code, headers, response); + + try { + // Ensure the response is long enough to be processed, if not + // then handle it as an error + if (!ResponseParser::response_long_enough(builder.enc_type, response->size())) + throw status_code_exception{ + status_code, + {content_type_plain_text}, + "Response is too short to be an onion request response: " + + *response}; + + // Otherwise, process the onion request response + std::tuple< + int16_t, + std::vector>, + std::optional> + processed_response; + + // The SnodeDestination runs via V3 onion requests and the + // ServerDestination runs via V4 + if (std::holds_alternative(info.destination)) + processed_response = process_v3_onion_response(builder, *response); + else if (std::holds_alternative(info.destination)) + processed_response = process_v4_onion_response(builder, *response); + + // If we got a non 2xx status code, return the error + auto& [processed_status_code, processed_headers, processed_body] = + processed_response; + if (processed_status_code < 200 || processed_status_code > 299) + throw status_code_exception{ + processed_status_code, + {content_type_plain_text}, + processed_body.value_or("Request returned " + "non-success status " + "code.")}; + + // For debugging purposes we want to add a log if this was a successful request + // after we did an automatic retry + detail::log_retry_result_if_needed(info, single_path_mode); + + // Try process the body in case it was a batch request which + // failed + std::optional results; + if (processed_body) { + try { + auto processed_body_json = nlohmann::json::parse(*processed_body); + + // If it wasn't a batch/sequence request then assume it + // was successful and return no error + if (processed_body_json.contains("results")) + results = processed_body_json["results"]; + } catch (...) { + } + } + + // If there was no 'results' array then it wasn't a batch + // request so we can stop here and return + if (!results) + return cb( + true, + false, + processed_status_code, + processed_headers, + processed_body); + + // Otherwise we want to check if all of the results have the + // same status code and, if so, handle that failure case + // (default the 'error_body' to the 'processed_body' in case we + // don't get an explicit error) + int16_t single_status_code = -1; + std::vector> single_headers = { + content_type_plain_text}; + std::optional error_body = processed_body; + for (const auto& result : results->items()) { + if (result.value().contains("code") && result.value()["code"].is_number() && + (single_status_code == -1 || + result.value()["code"].get() != single_status_code)) + single_status_code = result.value()["code"].get(); + else { + // Either there was no code, or the code was different + // from a former code in which case there wasn't an + // individual detectable error (ie. it needs specific + // handling) so return no error + single_status_code = 200; + break; + } + + if (result.value().contains("headers")) { + single_headers = {}; + auto header_vals = result.value()["headers"]; + + for (auto it = header_vals.begin(); it != header_vals.end(); ++it) + single_headers.emplace_back(it.key(), it.value()); + } + + if (result.value().contains("body") && result.value()["body"].is_string()) + error_body = result.value()["body"].get(); + } + + // If all results contained the same error then handle it as a + // single error + if (single_status_code < 200 || single_status_code > 299) + throw status_code_exception{ + single_status_code, + single_headers, + error_body.value_or("Sub-request returned " + "non-success status code.")}; + + // Otherwise some requests succeeded and others failed so + // succeed with the processed data + return cb( + true, false, processed_status_code, processed_headers, processed_body); + } catch (const status_code_exception& e) { + handle_errors( + info, path.conn_info, false, e.status_code, e.headers, e.what(), cb); + } catch (const std::exception& e) { + handle_errors( + info, + path.conn_info, + false, + -1, + {content_type_plain_text}, + e.what(), + cb); + } + }); +} + +void Network::upload_file_to_server( + std::vector data, + onionreq::ServerDestination server, + std::optional file_name, + network_response_callback_t handle_response, + std::chrono::milliseconds request_timeout, + std::optional request_and_path_build_timeout) { + std::vector> headers; + std::unordered_set existing_keys; + + if (server.headers) + for (auto& [key, value] : *server.headers) { + headers.emplace_back(key, value); + existing_keys.insert(key); + } + + // Add the required headers if they weren't provided + if (existing_keys.find("Content-Disposition") == existing_keys.end()) + headers.emplace_back( + "Content-Disposition", + (file_name ? "attachment; filename=\"{}\""_format(*file_name) : "attachment")); + + if (existing_keys.find("Content-Type") == existing_keys.end()) + headers.emplace_back("Content-Type", "application/octet-stream"); + + send_onion_request( + ServerDestination{ + server.protocol, + server.host, + server.endpoint, + server.x25519_pubkey, + server.port, + headers, + server.method}, + data, + std::nullopt, + handle_response, + request_timeout, + request_and_path_build_timeout, + PathType::upload); +} + +void Network::download_file( + std::string_view download_url, + session::onionreq::x25519_pubkey x25519_pubkey, + network_response_callback_t handle_response, + std::chrono::milliseconds request_timeout, + std::optional request_and_path_build_timeout) { + const auto& [proto, host, port, path] = parse_url(download_url); + + if (!path) + throw std::invalid_argument{"Invalid URL provided: Missing path"}; + + download_file( + ServerDestination{proto, host, *path, x25519_pubkey, port, std::nullopt, "GET"}, + handle_response, + request_timeout, + request_and_path_build_timeout); +} + +void Network::download_file( + onionreq::ServerDestination server, + network_response_callback_t handle_response, + std::chrono::milliseconds request_timeout, + std::optional request_and_path_build_timeout) { + send_onion_request( + server, + std::nullopt, + std::nullopt, + handle_response, + request_timeout, + request_and_path_build_timeout, + PathType::download); +} + +void Network::get_client_version( + Platform platform, + onionreq::ed25519_seckey seckey, + network_response_callback_t handle_response, + std::chrono::milliseconds request_timeout, + std::optional request_and_path_build_timeout) { + std::string endpoint; + + switch (platform) { + case Platform::android: endpoint = "/session_version?platform=android"; break; + case Platform::desktop: endpoint = "/session_version?platform=desktop"; break; + case Platform::ios: endpoint = "/session_version?platform=ios"; break; + } + + // Generate the auth signature + auto blinded_keys = blind_version_key_pair(to_span(seckey.view())); + auto timestamp = std::chrono::duration_cast( + (std::chrono::system_clock::now()).time_since_epoch()) + .count(); + auto signature = blind_version_sign(to_span(seckey.view()), platform, timestamp); + auto pubkey = x25519_pubkey::from_hex(file_server_pubkey); + std::string blinded_pk_hex; + blinded_pk_hex.reserve(66); + blinded_pk_hex += "07"; + oxenc::to_hex( + blinded_keys.first.begin(), + blinded_keys.first.end(), + std::back_inserter(blinded_pk_hex)); + + auto headers = std::vector>{}; + headers.emplace_back("X-FS-Pubkey", blinded_pk_hex); + headers.emplace_back("X-FS-Timestamp", "{}"_format(timestamp)); + headers.emplace_back("X-FS-Signature", oxenc::to_base64(signature.begin(), signature.end())); + + send_onion_request( + ServerDestination{ + "http", std::string(file_server), endpoint, pubkey, 80, headers, "GET"}, + std::nullopt, + pubkey, + handle_response, + request_timeout, + request_and_path_build_timeout, + PathType::standard); +} + +// MARK: Response Handling + +std::tuple>, std::optional> +Network::process_v3_onion_response(Builder builder, std::string response) { + std::string base64_iv_and_ciphertext; + try { + nlohmann::json response_json = nlohmann::json::parse(response); + + if (!response_json.contains("result") || !response_json["result"].is_string()) + throw std::runtime_error{"JSON missing result field."}; + + base64_iv_and_ciphertext = response_json["result"].get(); + } catch (...) { + base64_iv_and_ciphertext = response; + } + + if (!oxenc::is_base64(base64_iv_and_ciphertext)) + throw std::runtime_error{"Invalid base64 encoded IV and ciphertext."}; + + std::vector iv_and_ciphertext; + oxenc::from_base64( + base64_iv_and_ciphertext.begin(), + base64_iv_and_ciphertext.end(), + std::back_inserter(iv_and_ciphertext)); + auto parser = ResponseParser(builder); + auto result = parser.decrypt(iv_and_ciphertext); + auto result_json = nlohmann::json::parse(result); + int16_t status_code; + std::vector> headers; + std::string body; + + if (result_json.contains("status_code") && result_json["status_code"].is_number()) + status_code = result_json["status_code"].get(); + else if (result_json.contains("status") && result_json["status"].is_number()) + status_code = result_json["status"].get(); + else + throw std::runtime_error{"Invalid JSON response, missing required status_code field."}; + + if (result_json.contains("headers")) { + auto header_vals = result_json["headers"]; + + for (auto it = header_vals.begin(); it != header_vals.end(); ++it) + headers.emplace_back(it.key(), it.value()); + } + + if (result_json.contains("body") && result_json["body"].is_string()) + body = result_json["body"].get(); + else + body = result_json.dump(); + + return {status_code, headers, body}; +} + +std::tuple>, std::optional> +Network::process_v4_onion_response(Builder builder, std::string response) { + auto response_data = to_vector(response); + auto parser = ResponseParser(builder); + auto result = parser.decrypt(response_data); + + // Process the bencoded response + oxenc::bt_list_consumer result_bencode{to_span(result)}; + + if (result_bencode.is_finished() || !result_bencode.is_string()) + throw std::runtime_error{"Invalid bencoded response"}; + + auto response_info_string = result_bencode.consume_string(); + int16_t status_code; + std::vector> headers; + nlohmann::json response_info_json = nlohmann::json::parse(response_info_string); + + if (response_info_json.contains("code") && response_info_json["code"].is_number()) + status_code = response_info_json["code"].get(); + else + throw std::runtime_error{"Invalid JSON response, missing required code field."}; + + if (response_info_json.contains("headers")) { + auto header_vals = response_info_json["headers"]; + + for (auto it = header_vals.begin(); it != header_vals.end(); ++it) + headers.emplace_back(it.key(), it.value()); + } + + if (result_bencode.is_finished()) + return {status_code, headers, std::nullopt}; + + return {status_code, headers, result_bencode.consume_string()}; +} + +// MARK: Error Handling + +std::pair Network::validate_response(quic::message resp, bool is_bencoded) { + std::string body = std::string(resp.body()); + + if (resp.timed_out) + throw std::runtime_error{"Timed out"}; + if (resp.is_error()) + throw std::runtime_error{body.empty() ? "Unknown error" : body}; + + if (is_bencoded) { + // Process the bencoded response + oxenc::bt_list_consumer result_bencode{body}; + + if (result_bencode.is_finished() || !result_bencode.is_integer()) + throw std::runtime_error{"Invalid bencoded response"}; + + // If we have a status code that is not in the 2xx range, return the error + auto status_code = result_bencode.consume_integer(); + + if (status_code < 200 || status_code > 299) { + if (result_bencode.is_finished() || !result_bencode.is_string()) + throw status_code_exception{ + status_code, + {content_type_plain_text}, + "Request failed with status code: " + std::to_string(status_code)}; + + throw status_code_exception{ + status_code, {content_type_plain_text}, result_bencode.consume_string()}; + } + + // Can't convert the data to a string so just return the response body itself + return {status_code, body}; + } + + // Default to a 200 success if the response is empty but didn't timeout or error + int16_t status_code = 200; + std::pair content_type; + std::string response_string; + + try { + nlohmann::json response_json = nlohmann::json::parse(body); + content_type = content_type_json; + + if (response_json.is_array() && response_json.size() == 2) { + status_code = response_json[0].get(); + response_string = response_json[1].dump(); + } else + response_string = body; + } catch (...) { + response_string = body; + content_type = content_type_plain_text; + } + + if (status_code < 200 || status_code > 299) + throw status_code_exception{status_code, {content_type}, response_string}; + + return {status_code, response_string}; +} + +void Network::drop_path_when_empty(std::string id, PathType path_type, onion_path path) { + paths_pending_drop.emplace_back(path, path_type); + paths[path_type].erase( + std::remove(paths[path_type].begin(), paths[path_type].end(), path), + paths[path_type].end()); + + std::string reason; + if (id == path.id) + reason = "connection being closed"; + else + reason = "failure threshold passed with {} failure"_format(id); + + log::info( + cat, + "Flagging path {} [{}] to be dropped due to {}, now have {} {} paths(s).", + path.id, + path.to_string(), + reason, + paths[path_type].size(), + path_type_name(path_type, single_path_mode)); + + // Clear any paths which are waiting to be dropped + clear_empty_pending_path_drops(); +} + +void Network::clear_empty_pending_path_drops() { + auto remaining_standard_paths = 0; + std::erase_if(paths_pending_drop, [this, &remaining_standard_paths](const auto& path_info) { + // If the path is no longer valid then we can drop it + if (!path_info.first.has_pending_requests()) { + log::info( + cat, + "Removing flagged {} path {} that {}: [{}].", + path_type_name(path_info.second, single_path_mode), + path_info.first.id, + (path_info.first.is_valid() ? "has no remaining requests" + : "is no longer valid"), + path_info.first.to_string()); + return true; + } + remaining_standard_paths++; + return false; + }); + + // Update the network status if we've removed all standard paths + if (remaining_standard_paths == 0 && paths[PathType::standard].empty()) + update_status(ConnectionStatus::disconnected); +} + +void Network::handle_errors( + request_info info, + connection_info conn_info, + bool timeout_, + int16_t status_code_, + std::vector> headers_, + std::optional response, + std::optional handle_response) { + bool timeout = timeout_; + auto status_code = status_code_; + auto headers = headers_; + auto path_name = path_type_name(info.path_type, single_path_mode); + + // There is an issue which can occur where we get invalid data back and are unable to decrypt + // it, if we do see this behaviour then we want to retry the request on the off chance it + // resolves itself + // + // When testing this case the retry always resulted in a 421 error, if that occurs we want to go + // through the standard 421 behaviour (which, in this case, would involve a 3rd retry against + // another node in the swarm to confirm the redirect) + if (!info.retry_reason && response && *response == session::onionreq::decryption_failed_error) { + log::info( + cat, + "Received decryption failure in request {} on {} path, retrying.", + info.request_id, + path_name); + auto updated_info = info; + updated_info.retry_reason = request_info::RetryReason::decryption_failure; + return loop->call_soon([this, updated_info, cb = std::move(*handle_response)]() { + _send_onion_request(updated_info, std::move(cb)); + }); + } + + // A number of server errors can return HTML data but no status code, we want to extract those + // cases so they can be handled properly below + if (status_code == -1 && response) { + const std::unordered_map> response_map = { + {"400 Bad Request", {400, false}}, + {"403 Forbidden", {403, false}}, + {"500 Internal Server Error", {500, false}}, + {"502 Bad Gateway", {502, false}}, + {"503 Service Unavailable", {503, false}}, + {"504 Gateway Timeout", {504, true}}, + }; + + for (const auto& [prefix, result] : response_map) { + if (response->starts_with(prefix)) { + status_code = result.first; + timeout = (timeout || result.second); + } + } + } + + // In trace mode log all error info + log::trace( + cat, + "Received network error in request {} on {} path, status_code: {}, timeout: {}, " + "response: {}", + info.request_id, + path_name, + status_code, + timeout, + response.value_or("(No Response)")); + + // A timeout could be caused because the destination is unreachable rather than the the path + // (eg. if a user has an old SOGS which is no longer running on their device they will get a + // timeout) so if we timed out while sending a proxied request we assume something is wrong on + // the server side and don't update the path/snode state + if (!info.node_destination && timeout) { + if (handle_response) + return (*handle_response)(false, true, status_code, headers, response); + return; + } + + switch (status_code) { + // A 404 or a 400 is likely due to a bad/missing SOGS or file so + // shouldn't mark a path or snode as invalid + case 400: + case 404: + if (handle_response) + return (*handle_response)(false, false, status_code, headers, response); + return; + + // The user's clock is out of sync with the service node network (a + // snode will return 406, but V4 onion requests returns a 425) + case 406: + case 425: + if (handle_response) + return (*handle_response)(false, false, status_code, headers, response); + return; + + // The snode is reporting that it isn't associated with the given public key anymore. If + // this is the first 421 then we want to try another node in the swarm (just in case it + // was reported incorrectly). If this is the second occurrence of the 421 then the + // client needs to update the swarm (if the response contains updated swarm data), or + // increment the path failure count. + case 421: + try { + // If there is no response handler or no swarm information was provided then we + // should just replace the swarm + auto target = detail::node_for_destination(info.destination); + + if (!handle_response || !info.swarm_pubkey || !target) + throw std::invalid_argument{"Unable to handle redirect."}; + + switch (info.retry_reason.value_or(request_info::RetryReason::none)) { + // If this was the first 421 then we want to retry using another node in the + // swarm to get confirmation that we should switch to a different swarm + case request_info::RetryReason::none: + case request_info::RetryReason::decryption_failure: { + auto cached_swarm = swarm_cache[info.swarm_pubkey->hex()]; + + if (cached_swarm.second.empty()) + throw std::invalid_argument{ + "Unable to handle redirect due to lack of swarm."}; + + std::vector swarm_copy; + std::copy_if( + cached_swarm.second.begin(), + cached_swarm.second.end(), + std::back_inserter(swarm_copy), + [&target = *target](const auto& node) { return node != target; }); + std::shuffle(swarm_copy.begin(), swarm_copy.end(), csrng); + + if (swarm_copy.empty()) + throw std::invalid_argument{"No other nodes in the swarm."}; + + log::info( + cat, + "Received 421 error in request {} on {} path, retrying once before " + "updating swarm.", + info.request_id, + path_name); + auto updated_info = info; + updated_info.destination = swarm_copy.front(); + updated_info.retry_reason = request_info::RetryReason::redirect; + return loop->call_soon( + [this, updated_info, cb = std::move(*handle_response)]() { + _send_onion_request(updated_info, std::move(cb)); + }); + } + + // If we got a second 421 then it's likely that our cached swarm is out of date + // so we need to refresh our snode cache, regenerate our swarm and try one more + // time + case request_info::RetryReason::redirect: + log::info( + cat, + "Received second 421 error in request {} on {} path, refreshing " + "snode cache before trying one final time.", + info.request_id, + path_name); + after_snode_cache_refresh.emplace_back([this, + swarm_pubkey = info.swarm_pubkey, + info, + status_code, + headers, + response, + cb = std::move( + *handle_response)]() { + get_swarm( + *swarm_pubkey, + [this, + info, + status_code, + headers, + response, + cb = std::move(cb)]( + swarm_id_t, std::vector swarm) { + auto target = + detail::node_for_destination(info.destination); + + std::vector swarm_copy; + std::copy_if( + swarm.begin(), + swarm.end(), + std::back_inserter(swarm_copy), + [&target = *target](const auto& node) { + return node != target; + }); + std::shuffle(swarm_copy.begin(), swarm_copy.end(), csrng); + + // If there are no nodes in the swarm then don't bother + // trying again + if (swarm_copy.empty()) { + log::info( + cat, + "Second 421 retry for request {} resulted in " + "another 421 and had no other nodes in the " + "swarm.", + info.request_id); + return cb(false, false, status_code, headers, response); + } + + auto updated_info = info; + updated_info.retry_reason = + request_info::RetryReason::redirect_swarm_refresh; + updated_info.destination = swarm_copy.front(); + loop->call_soon([this, updated_info, cb = std::move(cb)]() { + _send_onion_request(updated_info, std::move(cb)); + }); + }); + }); + return loop->call_soon([this, request_id = info.request_id]() { + refresh_snode_cache(request_id); + }); + + // If we got a 421 after refreshing the swarm then there is some bigger issue + // (ie. our local swarm generation logic differs from the server or we are + // getting invalid swarm ids back when updating our cache) so the best we can + // do is handle this like any other error + case request_info::RetryReason::redirect_swarm_refresh: + log::info( + cat, + "Received another 421 for request {} after refreshing the snode " + "cache, failing request.", + info.request_id); + break; + + default: break; // Unhandled case should just behave like any other error + } + } catch (...) { + } + + // If we weren't able to retry or redirect the swarm then handle this like any other + // error + break; + + case 500: + case 504: + // If we are making a proxied request to a server then assume 500 errors are occurring + // on the server rather than in the service node network and don't update the path/snode + // state + if (!info.node_destination) { + if (handle_response) + return (*handle_response)(false, timeout, status_code, headers, response); + return; + } + break; + + default: break; + } + + // Retrieve the path for the connection_info (no paths share the same guard node so we can use + // that to find it) + std::optional path; + auto is_active_path = true; + + auto path_it = std::find_if( + paths[info.path_type].begin(), + paths[info.path_type].end(), + [guard_node = conn_info.node](const auto& path) { + return !path.nodes.empty() && path.nodes.front() == guard_node; + }); + + // Try to retrieve the path this request was on, if it's not in an active or pending drop path + // then log a warning (as this shouldn't be possible) and call the callback + if (path_it != paths[info.path_type].end()) + path = *path_it; + else { + auto path_pending_drop_it = std::find_if( + paths_pending_drop.begin(), + paths_pending_drop.end(), + [guard_node = conn_info.node](const auto& path_info) { + return !path_info.first.nodes.empty() && + path_info.first.nodes.front() == guard_node; + }); + + if (path_pending_drop_it == paths_pending_drop.end()) { + log::warning( + cat, + "Request {} failed but {} path with guard {} already dropped.", + info.request_id, + path_name, + conn_info.node.to_string()); + + if (handle_response) + (*handle_response)(false, timeout, status_code, headers, response); + return; + } + path = path_pending_drop_it->first; + is_active_path = false; + } + + // Update the failure counts and paths + auto updated_path = *path; + bool found_invalid_node = false; + + if (response) { + std::optional ed25519PublicKey; + + // Check if the response has one of the 'node_not_found' prefixes + if (response->starts_with(node_not_found_prefix)) + ed25519PublicKey = {response->data() + node_not_found_prefix.size()}; + else if (response->starts_with(node_not_found_prefix_no_status)) + ed25519PublicKey = {response->data() + node_not_found_prefix_no_status.size()}; + + // If we found a result then try to extract the pubkey and process it + if (ed25519PublicKey && ed25519PublicKey->size() == 64 && + oxenc::is_hex(*ed25519PublicKey)) { + session::onionreq::ed25519_pubkey edpk = + session::onionreq::ed25519_pubkey::from_hex(*ed25519PublicKey); + auto edpk_view = to_span(edpk.view()); + + auto snode_it = std::find_if( + updated_path.nodes.begin(), + updated_path.nodes.end(), + [&edpk_view](const auto& node) { + return to_string_view(node.view_remote_key()) == to_string_view(edpk_view); + }); + + if (snode_it != updated_path.nodes.end()) { + found_invalid_node = true; + + // If we get an explicit node failure then we should just immediately drop it and + // try to repair the existing path by replacing the bad node with another one + snode_failure_counts[snode_it->to_string()] = snode_failure_threshold; + + try { + // If the node that's gone bad is the guard node then we just have to + // drop the path + if (snode_it == updated_path.nodes.begin()) + throw std::runtime_error{"Cannot recover if guard node is bad"}; + + if (unused_nodes.empty()) + throw std::runtime_error{"No remaining nodes"}; + + auto target_node = unused_nodes.back(); + unused_nodes.pop_back(); + + std::replace( + updated_path.nodes.begin(), + updated_path.nodes.end(), + *snode_it, + target_node); + log::info( + cat, + "Found bad node ({}) in {} path, replacing node ({}).", + *ed25519PublicKey, + path_name, + updated_path.id); + } catch (...) { + // There aren't enough unused nodes remaining so we need to drop the + // path + updated_path.failure_count = path_failure_threshold; + log::info( + cat, + "Unable to replace bad node ({}) in {} path ({}).", + *ed25519PublicKey, + path_name, + updated_path.id); + } + } + } + } + + // If we didn't find the specific node or the paths connection was closed then increment the + // path failure count + if (!found_invalid_node || !updated_path.conn_info.is_valid()) { + updated_path.failure_count += 1; + + // If the path has failed too many times we want to drop the guard snode (marking it as + // invalid) and increment the failure count of each node in the path) + if (updated_path.failure_count >= path_failure_threshold) { + for (auto& it : updated_path.nodes) + ++snode_failure_counts[it.to_string()]; + + // Set the failure count of the guard node to match the threshold so we don't use it + // again until we refresh the cache + snode_failure_counts[updated_path.nodes[0].to_string()] = snode_failure_threshold; + } else if (updated_path.nodes.size() < path_size) + // triggered when trying to establish a new path and, as such, we should increase + // the failure count of the guard node since it is probably invalid + ++snode_failure_counts[updated_path.nodes[0].to_string()]; + } + + // Drop the path if invalid (and currently an active path) + if (is_active_path) { + if (updated_path.failure_count >= path_failure_threshold) + drop_path_when_empty(info.request_id, info.path_type, *path_it); + else + std::replace( + paths[info.path_type].begin(), + paths[info.path_type].end(), + *path_it, + updated_path); + } + + if (handle_response) + (*handle_response)(false, timeout, status_code, headers, response); +} + +} // namespace session::network + +// MARK: C API + +namespace { + +inline session::network::Network& unbox(network_object* network_) { + assert(network_ && network_->internals); + return *static_cast(network_->internals); +} + +inline bool set_error(char* error, const std::exception& e) { + if (!error) + return false; + + std::string msg = e.what(); + if (msg.size() > 255) + msg.resize(255); + std::memcpy(error, msg.c_str(), msg.size() + 1); + return false; +} + +} // namespace + +extern "C" { + +using namespace session; +using namespace session::network; + +LIBSESSION_C_API bool network_init( + network_object** network, + const char* cache_path_, + bool use_testnet, + bool single_path_mode, + bool pre_build_paths, + char* error) { + try { + std::optional cache_path; + if (cache_path_) + cache_path = cache_path_; + + auto n = std::make_unique( + cache_path, use_testnet, single_path_mode, pre_build_paths); + auto n_object = std::make_unique(); + + n_object->internals = n.release(); + *network = n_object.release(); + return true; + } catch (const std::exception& e) { + return set_error(error, e); + } +} + +LIBSESSION_C_API void network_free(network_object* network) { + delete static_cast(network->internals); + delete network; +} + +LIBSESSION_C_API void network_suspend(network_object* network) { + unbox(network).suspend(); +} + +LIBSESSION_C_API void network_resume(network_object* network) { + unbox(network).resume(); +} + +LIBSESSION_C_API void network_close_connections(network_object* network) { + unbox(network).close_connections(); +} + +LIBSESSION_C_API void network_clear_cache(network_object* network) { + unbox(network).clear_cache(); +} + +LIBSESSION_C_API size_t network_get_snode_cache_size(network_object* network) { + return unbox(network).snode_cache_size(); +} + +LIBSESSION_C_API void network_set_status_changed_callback( + network_object* network, void (*callback)(CONNECTION_STATUS status, void* ctx), void* ctx) { + if (!callback) + unbox(network).status_changed = nullptr; + else + unbox(network).status_changed = [cb = std::move(callback), ctx](ConnectionStatus status) { + cb(static_cast(status), ctx); + }; +} + +LIBSESSION_C_API void network_set_paths_changed_callback( + network_object* network, + void (*callback)(onion_request_path* paths, size_t paths_len, void* ctx), + void* ctx) { + if (!callback) + unbox(network).paths_changed = nullptr; + else + unbox(network).paths_changed = [cb = std::move(callback), + ctx](std::vector> paths) { + size_t paths_mem_size = 0; + for (auto& nodes : paths) + paths_mem_size += + sizeof(onion_request_path) + (sizeof(network_service_node) * nodes.size()); + + // Allocate the memory for the onion_request_paths* array + auto* c_paths_array = static_cast(std::malloc(paths_mem_size)); + for (size_t i = 0; i < paths.size(); ++i) { + auto c_nodes = network::detail::convert_service_nodes(paths[i]); + + // Allocate memory that persists outside the loop + size_t node_array_size = sizeof(network_service_node) * c_nodes.size(); + auto* c_nodes_array = + static_cast(std::malloc(node_array_size)); + std::copy(c_nodes.begin(), c_nodes.end(), c_nodes_array); + new (c_paths_array + i) onion_request_path{c_nodes_array, c_nodes.size()}; + } + + cb(c_paths_array, paths.size(), ctx); + }; +} + +LIBSESSION_C_API void network_get_swarm( + network_object* network, + const char* swarm_pubkey_hex, + void (*callback)(network_service_node* nodes, size_t nodes_len, void*), + void* ctx) { + assert(swarm_pubkey_hex && callback); + unbox(network).get_swarm( + x25519_pubkey::from_hex({swarm_pubkey_hex, 64}), + [cb = std::move(callback), ctx](swarm_id_t, std::vector nodes) { + auto c_nodes = network::detail::convert_service_nodes(nodes); + cb(c_nodes.data(), c_nodes.size(), ctx); + }); +} + +LIBSESSION_C_API void network_get_random_nodes( + network_object* network, + uint16_t count, + void (*callback)(network_service_node*, size_t, void*), + void* ctx) { + assert(callback); + unbox(network).get_random_nodes( + count, [cb = std::move(callback), ctx](std::vector nodes) { + auto c_nodes = network::detail::convert_service_nodes(nodes); + cb(c_nodes.data(), c_nodes.size(), ctx); + }); +} + +LIBSESSION_C_API void network_send_onion_request_to_snode_destination( + network_object* network, + const network_service_node node, + const unsigned char* body_, + size_t body_size, + const char* swarm_pubkey_hex, + int64_t request_timeout_ms, + int64_t request_and_path_build_timeout_ms, + network_onion_response_callback_t callback, + void* ctx) { + assert(callback); + + try { + std::optional> body; + if (body_size > 0) + body.emplace(body_, body_ + body_size); + + std::optional swarm_pubkey; + if (swarm_pubkey_hex) + swarm_pubkey = x25519_pubkey::from_hex({swarm_pubkey_hex, 64}); + + std::optional request_and_path_build_timeout; + if (request_and_path_build_timeout_ms > 0) + request_and_path_build_timeout = + std::chrono::milliseconds{request_and_path_build_timeout_ms}; + + std::array ip; + std::memcpy(ip.data(), node.ip, ip.size()); + + unbox(network).send_onion_request( + service_node{ + oxenc::from_hex({node.ed25519_pubkey_hex, 64}), + {0}, + INVALID_SWARM_ID, + "{}"_format(fmt::join(ip, ".")), + node.quic_port}, + body, + swarm_pubkey, + [cb = std::move(callback), ctx]( + bool success, + bool timeout, + int status_code, + std::vector> headers, + std::optional response) { + std::vector cHeaders; + std::vector cHeaderValues; + cHeaders.reserve(headers.size()); + cHeaderValues.reserve(headers.size()); + + for (const auto& [header, value] : headers) { + cHeaders.push_back(header.c_str()); + cHeaderValues.push_back(value.c_str()); + } + + if (response) + cb(success, + timeout, + status_code, + cHeaders.data(), + cHeaderValues.data(), + headers.size(), + (*response).c_str(), + (*response).size(), + ctx); + else + cb(success, + timeout, + status_code, + cHeaders.data(), + cHeaderValues.data(), + headers.size(), + nullptr, + 0, + ctx); + }, + std::chrono::milliseconds{request_timeout_ms}, + request_and_path_build_timeout); + } catch (const std::exception& e) { + callback(false, false, -1, nullptr, nullptr, 0, e.what(), std::strlen(e.what()), ctx); + } +} + +LIBSESSION_C_API void network_send_onion_request_to_server_destination( + network_object* network, + const network_server_destination server, + const unsigned char* body_, + size_t body_size, + int64_t request_timeout_ms, + int64_t request_and_path_build_timeout_ms, + network_onion_response_callback_t callback, + void* ctx) { + assert(server.method && server.protocol && server.host && server.endpoint && + server.x25519_pubkey && callback); + + try { + std::optional> body; + if (body_size > 0) + body.emplace(body_, body_ + body_size); + + std::optional request_and_path_build_timeout; + if (request_and_path_build_timeout_ms > 0) + request_and_path_build_timeout = + std::chrono::milliseconds{request_and_path_build_timeout_ms}; + + unbox(network).send_onion_request( + network::detail::convert_server_destination(server), + body, + std::nullopt, + [cb = std::move(callback), ctx]( + bool success, + bool timeout, + int status_code, + std::vector> headers, + std::optional response) { + std::vector cHeaders; + std::vector cHeaderValues; + cHeaders.reserve(headers.size()); + cHeaderValues.reserve(headers.size()); + + for (const auto& [header, value] : headers) { + cHeaders.push_back(header.c_str()); + cHeaderValues.push_back(value.c_str()); + } + + if (response) + cb(success, + timeout, + status_code, + cHeaders.data(), + cHeaderValues.data(), + headers.size(), + (*response).c_str(), + (*response).size(), + ctx); + else + cb(success, + timeout, + status_code, + cHeaders.data(), + cHeaderValues.data(), + headers.size(), + nullptr, + 0, + ctx); + }, + std::chrono::milliseconds{request_timeout_ms}, + request_and_path_build_timeout); + } catch (const std::exception& e) { + callback(false, false, -1, nullptr, nullptr, 0, e.what(), std::strlen(e.what()), ctx); + } +} + +LIBSESSION_C_API void network_upload_to_server( + network_object* network, + const network_server_destination server, + const unsigned char* data, + size_t data_len, + const char* file_name_, + int64_t request_timeout_ms, + int64_t request_and_path_build_timeout_ms, + network_onion_response_callback_t callback, + void* ctx) { + assert(data && server.method && server.protocol && server.host && server.endpoint && + server.x25519_pubkey && callback); + + try { + std::optional file_name; + if (file_name_) + file_name = file_name_; + + std::optional request_and_path_build_timeout; + if (request_and_path_build_timeout_ms > 0) + request_and_path_build_timeout = + std::chrono::milliseconds{request_and_path_build_timeout_ms}; + + unbox(network).upload_file_to_server( + {data, data + data_len}, + network::detail::convert_server_destination(server), + file_name, + [cb = std::move(callback), ctx]( + bool success, + bool timeout, + int status_code, + std::vector> headers, + std::optional response) { + std::vector cHeaders; + std::vector cHeaderValues; + cHeaders.reserve(headers.size()); + cHeaderValues.reserve(headers.size()); + + for (const auto& [header, value] : headers) { + cHeaders.push_back(header.c_str()); + cHeaderValues.push_back(value.c_str()); + } + + if (response) + cb(success, + timeout, + status_code, + cHeaders.data(), + cHeaderValues.data(), + headers.size(), + (*response).c_str(), + (*response).size(), + ctx); + else + cb(success, + timeout, + status_code, + cHeaders.data(), + cHeaderValues.data(), + headers.size(), + nullptr, + 0, + ctx); + }, + std::chrono::milliseconds{request_timeout_ms}, + request_and_path_build_timeout); + } catch (const std::exception& e) { + callback(false, false, -1, nullptr, nullptr, 0, e.what(), std::strlen(e.what()), ctx); + } +} + +LIBSESSION_C_API void network_download_from_server( + network_object* network, + const network_server_destination server, + int64_t request_timeout_ms, + int64_t request_and_path_build_timeout_ms, + network_onion_response_callback_t callback, + void* ctx) { + assert(server.method && server.protocol && server.host && server.endpoint && + server.x25519_pubkey && callback); + + try { + std::optional request_and_path_build_timeout; + if (request_and_path_build_timeout_ms > 0) + request_and_path_build_timeout = + std::chrono::milliseconds{request_and_path_build_timeout_ms}; + + unbox(network).download_file( + network::detail::convert_server_destination(server), + [cb = std::move(callback), ctx]( + bool success, + bool timeout, + int status_code, + std::vector> headers, + std::optional response) { + std::vector cHeaders; + std::vector cHeaderValues; + cHeaders.reserve(headers.size()); + cHeaderValues.reserve(headers.size()); + + for (const auto& [header, value] : headers) { + cHeaders.push_back(header.c_str()); + cHeaderValues.push_back(value.c_str()); + } + + if (response) + cb(success, + timeout, + status_code, + cHeaders.data(), + cHeaderValues.data(), + headers.size(), + (*response).c_str(), + (*response).size(), + ctx); + else + cb(success, + timeout, + status_code, + cHeaders.data(), + cHeaderValues.data(), + headers.size(), + nullptr, + 0, + ctx); + }, + std::chrono::milliseconds{request_timeout_ms}, + request_and_path_build_timeout); + } catch (const std::exception& e) { + callback(false, false, -1, nullptr, nullptr, 0, e.what(), std::strlen(e.what()), ctx); + } +} + +LIBSESSION_C_API void network_get_client_version( + network_object* network, + CLIENT_PLATFORM platform, + const unsigned char* ed25519_secret, + int64_t request_timeout_ms, + int64_t request_and_path_build_timeout_ms, + network_onion_response_callback_t callback, + void* ctx) { + assert(platform && callback); + + try { + std::optional request_and_path_build_timeout; + if (request_and_path_build_timeout_ms > 0) + request_and_path_build_timeout = + std::chrono::milliseconds{request_and_path_build_timeout_ms}; + + unbox(network).get_client_version( + static_cast(platform), + onionreq::ed25519_seckey::from_bytes({ed25519_secret, 64}), + [cb = std::move(callback), ctx]( + bool success, + bool timeout, + int status_code, + std::vector> headers, + std::optional response) { + std::vector cHeaders; + std::vector cHeaderValues; + cHeaders.reserve(headers.size()); + cHeaderValues.reserve(headers.size()); + + for (const auto& [header, value] : headers) { + cHeaders.push_back(header.c_str()); + cHeaderValues.push_back(value.c_str()); + } + + if (response) + cb(success, + timeout, + status_code, + cHeaders.data(), + cHeaderValues.data(), + headers.size(), + (*response).c_str(), + (*response).size(), + ctx); + else + cb(success, + timeout, + status_code, + cHeaders.data(), + cHeaderValues.data(), + headers.size(), + nullptr, + 0, + ctx); + }, + std::chrono::milliseconds{request_timeout_ms}, + request_and_path_build_timeout); + } catch (const std::exception& e) { + callback(false, false, -1, nullptr, nullptr, 0, e.what(), std::strlen(e.what()), ctx); + } +} + +} // extern "C" diff --git a/src/sodium_array.cpp b/src/sodium_array.cpp new file mode 100644 index 00000000..14ce50eb --- /dev/null +++ b/src/sodium_array.cpp @@ -0,0 +1,23 @@ +#include + +#include + +namespace session { + +void* sodium_buffer_allocate(size_t length) { + if (auto* p = sodium_malloc(length)) + return p; + throw std::bad_alloc{}; +} + +void sodium_buffer_deallocate(void* p) { + if (p) + sodium_free(p); +} + +void sodium_zero_buffer(void* ptr, size_t size) { + if (ptr) + sodium_memzero(ptr, size); +} + +} // namespace session diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 00000000..7669d0e1 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,90 @@ +#include +#include + +namespace session { + +std::vector split(std::string_view str, const std::string_view delim, bool trim) { + std::vector results; + // Special case for empty delimiter: splits on each character boundary: + if (delim.empty()) { + results.reserve(str.size()); + for (size_t i = 0; i < str.size(); i++) + results.emplace_back(str.data() + i, 1); + return results; + } + + for (size_t pos = str.find(delim); pos != std::string_view::npos; pos = str.find(delim)) { + if (!trim || !results.empty() || pos > 0) + results.push_back(str.substr(0, pos)); + str.remove_prefix(pos + delim.size()); + } + if (!trim || str.size()) + results.push_back(str); + else + while (!results.empty() && results.back().empty()) + results.pop_back(); + return results; +} + +std::tuple, std::optional> parse_url( + std::string_view url) { + std::tuple, std::optional> + result{}; + auto& [proto, host, port, path] = result; + if (auto pos = url.find("://"); pos != std::string::npos) { + auto proto_name = url.substr(0, pos); + url.remove_prefix(proto_name.size() + 3); + if (string_iequal(proto_name, "http")) + proto = "http://"; + else if (string_iequal(proto_name, "https")) + proto = "https://"; + } + if (proto.empty()) + throw std::invalid_argument{"Invalid URL: invalid/missing protocol://"}; + + bool next_allow_dot = false; + bool has_dot = false; + while (!url.empty()) { + auto c = url.front(); + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || c == '-') { + host += c; + next_allow_dot = true; + } else if (c >= 'A' && c <= 'Z') { + host += c + ('a' - 'A'); + next_allow_dot = true; + } else if (next_allow_dot && c == '.') { + host += '.'; + has_dot = true; + next_allow_dot = false; + } else { + break; + } + url.remove_prefix(1); + } + if (host.size() < 4 || !has_dot || host.back() == '.') + throw std::invalid_argument{"Invalid URL: invalid hostname"}; + + if (!url.empty() && url.front() == ':') { + url.remove_prefix(1); + uint16_t target_port; + if (auto [p, ec] = std::from_chars(url.data(), url.data() + url.size(), target_port); + ec == std::errc{}) + url.remove_prefix(p - url.data()); + else + throw std::invalid_argument{"Invalid URL: invalid port"}; + if (!(target_port == 80 && proto == "http://") && !(target_port == 443 && proto == "https:/" + "/")) + port = target_port; + } + + if (url.size() > 1 && url.front() == '/') + path = url; + else if (!url.empty() && url.front() == '/') { + url.remove_prefix(1); + path = std::nullopt; + } + + return result; +} + +} // namespace session diff --git a/src/xed25519.cpp b/src/xed25519.cpp index d5c69fcc..9d23390a 100644 --- a/src/xed25519.cpp +++ b/src/xed25519.cpp @@ -12,6 +12,7 @@ #include #include "session/export.h" +#include "session/util.hpp" namespace session::xed25519 { @@ -20,19 +21,6 @@ using bytes = std::array; namespace { - // constant time `if (b) f = g;` implementation - template - void constant_time_conditional_assign(bytes& f, const bytes& g, bool b) { - bytes x; - for (size_t i = 0; i < x.size(); i++) - x[i] = f[i] ^ g[i]; - unsigned char mask = (unsigned char)(-(signed char)b); - for (size_t i = 0; i < x.size(); i++) - x[i] &= mask; - for (size_t i = 0; i < x.size(); i++) - f[i] ^= x[i]; - } - void fe25519_montx_to_edy(fe25519 y, const fe25519 u) { fe25519 one; crypto_internal_fe25519_1(one); @@ -50,7 +38,7 @@ namespace { // This deviates from Signal's XEd25519 specified derivation of r in that we use a personalized // Black2b hash (for better performance and cryptographic properties), rather than a // custom-prefixed SHA-512 hash. - bytes<32> xed25519_compute_r(const bytes<32>& a, ustring_view msg) { + bytes<32> xed25519_compute_r(const bytes<32>& a, std::span msg) { bytes<64> random; randombytes_buf(random.data(), random.size()); @@ -74,7 +62,10 @@ namespace { // Assigns S = H(R || A || M) mod L void ed25519_hram( - unsigned char* S, const unsigned char* R, const bytes<32>& A, ustring_view msg) { + unsigned char* S, + const unsigned char* R, + const bytes<32>& A, + std::span msg) { bytes<64> hram; crypto_hash_sha512_state st; crypto_hash_sha512_init(&st); @@ -86,13 +77,10 @@ namespace { crypto_core_ed25519_scalar_reduce(S, hram.data()); } - ustring_view as_unsigned_sv(std::string_view x) { - return {reinterpret_cast(x.data()), x.size()}; - } - } // namespace -bytes<64> sign(ustring_view curve25519_privkey, ustring_view msg) { +bytes<64> sign( + std::span curve25519_privkey, std::span msg) { assert(curve25519_privkey.size() == 32); @@ -129,11 +117,14 @@ bytes<64> sign(ustring_view curve25519_privkey, ustring_view msg) { } std::string sign(std::string_view curve25519_privkey, std::string_view msg) { - auto sig = sign(as_unsigned_sv(curve25519_privkey), as_unsigned_sv(msg)); + auto sig = sign(to_span(curve25519_privkey), to_span(msg)); return std::string{reinterpret_cast(sig.data()), sig.size()}; } -bool verify(ustring_view signature, ustring_view curve25519_pubkey, ustring_view msg) { +bool verify( + std::span signature, + std::span curve25519_pubkey, + std::span msg) { assert(signature.size() == crypto_sign_ed25519_BYTES); assert(curve25519_pubkey.size() == 32); auto ed_pubkey = pubkey(curve25519_pubkey); @@ -142,11 +133,10 @@ bool verify(ustring_view signature, ustring_view curve25519_pubkey, ustring_view } bool verify(std::string_view signature, std::string_view curve25519_pubkey, std::string_view msg) { - return verify( - as_unsigned_sv(signature), as_unsigned_sv(curve25519_pubkey), as_unsigned_sv(msg)); + return verify(to_span(signature), to_span(curve25519_pubkey), to_span(msg)); } -std::array pubkey(ustring_view curve25519_pubkey) { +std::array pubkey(std::span curve25519_pubkey) { fe25519 u, y; crypto_internal_fe25519_frombytes(u, curve25519_pubkey.data()); fe25519_montx_to_edy(y, u); @@ -158,49 +148,47 @@ std::array pubkey(ustring_view curve25519_pubkey) { } std::string pubkey(std::string_view curve25519_pubkey) { - auto ed_pk = pubkey(as_unsigned_sv(curve25519_pubkey)); + auto ed_pk = pubkey(to_span(curve25519_pubkey)); return std::string{reinterpret_cast(ed_pk.data()), ed_pk.size()}; } } // namespace session::xed25519 -using session::xed25519::ustring_view; - extern "C" { -LIBSESSION_EXPORT int session_xed25519_sign( +LIBSESSION_C_API bool session_xed25519_sign( unsigned char* signature, const unsigned char* curve25519_privkey, const unsigned char* msg, - const unsigned int msg_len) { + size_t msg_len) { assert(signature != NULL); try { auto sig = session::xed25519::sign({curve25519_privkey, 32}, {msg, msg_len}); std::memcpy(signature, sig.data(), sig.size()); - return 0; + return true; } catch (...) { + return false; } - return 1; } -LIBSESSION_EXPORT int session_xed25519_verify( +LIBSESSION_C_API bool session_xed25519_verify( const unsigned char* signature, const unsigned char* pubkey, const unsigned char* msg, - const unsigned int msg_len) { - return session::xed25519::verify({signature, 64}, {pubkey, 32}, {msg, msg_len}) ? 0 : 1; + size_t msg_len) { + return session::xed25519::verify({signature, 64}, {pubkey, 32}, {msg, msg_len}); } -LIBSESSION_EXPORT int session_xed25519_pubkey( +LIBSESSION_C_API bool session_xed25519_pubkey( unsigned char* ed25519_pubkey, const unsigned char* curve25519_pubkey) { assert(ed25519_pubkey != NULL); try { auto edpk = session::xed25519::pubkey({curve25519_pubkey, 32}); std::memcpy(ed25519_pubkey, edpk.data(), edpk.size()); - return 0; + return true; } catch (...) { + return false; } - return 1; } } // extern "C" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dcc4dfa8..1dc8ef63 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,11 @@ add_subdirectory(Catch2) -add_executable(testAll +if(CMAKE_BUILD_TYPE STREQUAL "Release") + add_definitions(-DRELEASE_BUILD) +endif() + +set(LIB_SESSION_UTESTS_SOURCES + test_blinding.cpp test_bt_merge.cpp test_bugs.cpp test_compression.cpp @@ -9,12 +14,66 @@ add_executable(testAll test_configdata.cpp test_config_contacts.cpp test_config_convo_info_volatile.cpp + test_curve25519.cpp + test_ed25519.cpp test_encrypt.cpp + test_group_keys.cpp + test_group_info.cpp + test_group_members.cpp + test_hash.cpp + #test_logging.cpp # Handled separately, see below + test_multi_encrypt.cpp + test_proto.cpp + test_random.cpp + test_session_encrypt.cpp test_xed25519.cpp - ) + case_logger.cpp +) + +if (ENABLE_ONIONREQ) + list(APPEND LIB_SESSION_UTESTS_SOURCES test_session_network.cpp) + list(APPEND LIB_SESSION_UTESTS_SOURCES test_onionreq.cpp) +endif() + +add_library(test_libs INTERFACE) + +target_link_libraries(test_libs INTERFACE + libsession::config + libsodium::sodium-internal + nlohmann_json::nlohmann_json + oxen::logging) + +if (ENABLE_ONIONREQ) + target_link_libraries(test_libs INTERFACE libsession::onionreq) +else() + target_compile_definitions(test_libs INTERFACE DISABLE_ONIONREQ) +endif() + +add_executable(testAll main.cpp ${LIB_SESSION_UTESTS_SOURCES}) target_link_libraries(testAll PRIVATE - config + test_libs + Catch2::Catch2) + +# This one is compiled to a separate binary because it manipulates the logging level and sinks, and +# thus breaks logging in any tests that would run after it if bundled into the main testAll: +add_executable(testLogging test_logging.cpp) +target_link_libraries(testLogging PRIVATE + test_libs Catch2::Catch2WithMain) -add_custom_target(check COMMAND testAll) +if(NOT TARGET check) + add_custom_target(check + COMMAND testLogging + COMMAND testAll) +endif() + +add_executable(swarm-auth-test EXCLUDE_FROM_ALL swarm-auth-test.cpp) +target_link_libraries(swarm-auth-test PRIVATE config) + +if(STATIC_BUNDLE) + add_executable(static-bundle-test static_bundle.cpp) + target_include_directories(static-bundle-test PUBLIC ../include) + target_link_libraries(static-bundle-test PRIVATE "${PROJECT_BINARY_DIR}/libsession-util.a" oxenc::oxenc quic) + add_dependencies(static-bundle-test session-util) +endif() diff --git a/tests/case_logger.cpp b/tests/case_logger.cpp new file mode 100644 index 00000000..4dec6240 --- /dev/null +++ b/tests/case_logger.cpp @@ -0,0 +1,93 @@ +// This file contains a Catch2 listener than add oxen logging statements tracing the entry of cases +// and sections in the test suite. +// +// It runs in its own log level; to activate it, run alltests with `-T`/`--test-tracing`. +// +#include + +#include +#include +#include +#include +#include + +namespace fmt { +template <> +struct formatter : formatter { + template + auto format(const Catch::StringRef& val, FormatContext& ctx) const { + return formatter::format({val.data(), val.size()}, ctx); + } +}; +} // namespace fmt + +namespace session::test { +using namespace Catch; + +static auto cat = oxen::log::Cat("testcase"); + +static std::string_view sv(const Catch::StringRef& s) { + return {s.data(), s.size()}; +} + +// Bypass the usual log::trace(...) because we want to fake the source location, and want this +// even in non-debug builds. +template +static void test_trace( + const Catch::SourceLineInfo& sli, fmt::format_string fmt, T&&... args) { + + std::string_view filename{sli.file}; + if (auto pos = filename.rfind('/'); pos != std::string_view::npos) + filename.remove_prefix(pos + 1); + + spdlog::source_loc sloc{filename.data(), static_cast(sli.line), /*function name=*/""}; + + cat->log(sloc, oxen::log::Level::trace, fmt, std::forward(args)...); +} + +class CaseLogger : public Catch::EventListenerBase { + public: + using Catch::EventListenerBase::EventListenerBase; + + static std::string getDescription() { + return "Report test cases and section starting/ending events via oxen-logging"; + } + + void testCaseStarting(const TestCaseInfo& info) override { + test_trace(info.lineInfo, "Starting test case {} ({})", info.name, info.tagsAsString()); + } + void testCaseEnded(const TestCaseStats& stats) override { + auto& info = *stats.testInfo; + test_trace(info.lineInfo, "Finished test case {} ({})", info.name, info.tagsAsString()); + } + + void testCasePartialStarting(const Catch::TestCaseInfo& info, uint64_t partNumber) override { + if (partNumber > 0) + test_trace(info.lineInfo, "↪ Starting test case {} pass {}", info.name, partNumber); + } + + void testCasePartialEnded(const Catch::TestCaseStats& stats, uint64_t partNumber) override { + auto& info = *stats.testInfo; + if (partNumber > 0) + test_trace(info.lineInfo, "↩ Finished test case {} pass {}", info.name, partNumber); + } + + bool first_sect = true; + void sectionStarting(const SectionInfo& info) override { + if (first_sect) + first_sect = false; + test_trace(info.lineInfo, " ↪ Entering section {}", info.name); + } + void sectionEnded(const SectionStats& stats) override { + auto& info = stats.sectionInfo; + test_trace( + info.lineInfo, + " ↩ Finished section {} in {:.3f}ms", + info.name, + stats.durationInSeconds * 1000); + } +}; + +} // namespace session::test + +CATCH_REGISTER_LISTENER(session::test::CaseLogger) diff --git a/tests/catch2_bt_format.hpp b/tests/catch2_bt_format.hpp index fa87bae1..9cc00690 100644 --- a/tests/catch2_bt_format.hpp +++ b/tests/catch2_bt_format.hpp @@ -1,9 +1,9 @@ #pragma once #include -#include #include +#include namespace Catch { @@ -22,9 +22,9 @@ struct StringMaker { }; inline std::string StringMaker::convert(const oxenc::bt_value& value) { - return var::visit( + return std::visit( [](const auto& x) { - return StringMaker>{}.convert(x); + return StringMaker>{}.convert(x); }, static_cast(value)); } diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 00000000..a814e50e --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,46 @@ +#include +#include + +int main(int argc, char* argv[]) { + Catch::Session session; + + using namespace Catch::Clara; + std::string log_level = "critical", log_file = "stderr"; + bool test_case_tracing = false; + + auto cli = session.cli() | + Opt(log_level, + "level")["--log-level"]("oxen-logging log level to apply to the test run") | + Opt(log_file, "file")["--log-file"]( + "oxen-logging log file to output logs to, or one of or one of " + "stdout/-/stderr/syslog.") | + Opt(test_case_tracing)["-T"]["--test-tracing"]( + "enable oxen log tracing of test cases/sections"); + + session.cli(cli); + + if (int rc = session.applyCommandLine(argc, argv); rc != 0) + return rc; + + auto lvl = oxen::log::level_from_string(log_level); + + constexpr std::array print_vals = { + "stdout", "-", "", "stderr", "nocolor", "stdout-nocolor", "stderr-nocolor"}; + oxen::log::Type type; + if (std::count(print_vals.begin(), print_vals.end(), log_file)) + type = oxen::log::Type::Print; + else if (log_file == "syslog") + type = oxen::log::Type::System; + else + type = oxen::log::Type::File; + + oxen::log::add_sink( + type, log_file, "[%T.%f] [%*] [\x1b[1m%n\x1b[0m:%^%l%$|\x1b[3m%g:%#\x1b[0m] %v"); + oxen::log::reset_level(lvl); + + oxen::log::set_level( + oxen::log::Cat("testcase"), + test_case_tracing ? oxen::log::Level::trace : oxen::log::Level::off); + + return session.run(); +} diff --git a/tests/static_bundle.cpp b/tests/static_bundle.cpp new file mode 100644 index 00000000..a760a178 --- /dev/null +++ b/tests/static_bundle.cpp @@ -0,0 +1,12 @@ +// This file isn't designed to do anything useful, but just to test that we can compile and link +// against the combined static bundle (when using cmake ... -DSTATIC_BUILD=ON) + +#include +#include + +int main() { + if (std::mt19937_64{}() == 123) { + auto& k = *reinterpret_cast(12345); + k.encrypt_message(std::span{}); + } +} diff --git a/tests/swarm-auth-test.cpp b/tests/swarm-auth-test.cpp new file mode 100644 index 00000000..98c565f4 --- /dev/null +++ b/tests/swarm-auth-test.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.hpp" + +static constexpr int64_t created_ts = 1680064059; + +using namespace session::config; + +static std::array sk_from_seed(std::span seed) { + std::array ignore; + std::array sk; + crypto_sign_ed25519_seed_keypair(ignore.data(), sk.data(), seed.data()); + return sk; +} + +static std::string session_id_from_ed(std::span ed_pk) { + std::string sid; + std::array xpk; + int rc = crypto_sign_ed25519_pk_to_curve25519(xpk.data(), ed_pk.data()); + assert(rc == 0); + sid.reserve(66); + sid += "05"; + oxenc::to_hex(xpk.begin(), xpk.end(), std::back_inserter(sid)); + return sid; +} + +struct pseudo_client { + std::array secret_key; + const std::span public_key{secret_key.data() + 32, 32}; + std::string session_id{session_id_from_ed(public_key)}; + + groups::Info info; + groups::Members members; + groups::Keys keys; + + pseudo_client( + std::span seed, + bool admin, + const unsigned char* gpk, + std::optional gsk) : + secret_key{sk_from_seed(seed)}, + info{std::span{gpk, 32}, + admin ? std::make_optional>({*gsk, 64}) + : std::nullopt, + std::nullopt}, + members{std::span{gpk, 32}, + admin ? std::make_optional>({*gsk, 64}) + : std::nullopt, + std::nullopt}, + keys{to_usv(secret_key), + std::span{gpk, 32}, + admin ? std::make_optional>({*gsk, 64}) + : std::nullopt, + std::nullopt, + info, + members} {} +}; + +int main() { + + const std::vector group_seed = + "0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210"_hexbytes; + const std::vector admin_seed = + "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; + const std::vector member_seed = + "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"_hexbytes; + + std::array group_pk; + std::array group_sk; + + crypto_sign_ed25519_seed_keypair(group_pk.data(), group_sk.data(), group_seed.data()); + + pseudo_client admin{admin_seed, true, group_pk.data(), group_sk.data()}; + pseudo_client member{member_seed, false, group_pk.data(), std::nullopt}; + session::config::UserGroups member_groups{member_seed, std::nullopt}; + + auto auth_data = admin.keys.swarm_make_subaccount(member.session_id); + { + auto g = member_groups.get_or_construct_group(member.info.id); + g.auth_data = auth_data; + member_groups.set(g); + } + + session::config::UserGroups member_gr2{member_seed, std::nullopt}; + auto [seqno, push, obs] = member_groups.push(); + + std::vector>> gr_conf; + gr_conf.emplace_back("fakehash1", push); + + member_gr2.merge(gr_conf); + + auto now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + auto msg = to_usv("hello world"); + std::array store_sig; + std::vector store_to_sign; + auto store_vec = session::str_to_vec("store999{}"_format(now)); + store_to_sign.insert(store_to_sign.end(), store_vec.begin(), store_vec.end()); + + crypto_sign_ed25519_detached( + store_sig.data(), nullptr, store_to_sign.data(), store_to_sign.size(), group_sk.data()); + + nlohmann::json store{ + {"method", "store"}, + {"params", + {{"pubkey", member.info.id}, + {"namespace", 999}, + {"timestamp", now}, + {"ttl", 3600'000}, + {"data", oxenc::to_base64(msg)}, + {"signature", oxenc::to_base64(store_sig.begin(), store_sig.end())}}}}; + + std::cout << "STORE:\n\n" << store.dump() << "\n\n"; + + std::vector retrieve_to_sign; + auto retrieve_vec = session::str_to_vec("retrieve999{}"_format(now)); + retrieve_to_sign.insert(retrieve_to_sign.end(), retrieve_vec.begin(), retrieve_vec.end()); + auto subauth = member.keys.swarm_subaccount_sign(retrieve_to_sign, auth_data); + + nlohmann::json retrieve{ + {"method", "retrieve"}, + {"params", + { + {"pubkey", member.info.id}, + {"namespace", 999}, + {"timestamp", now}, + {"subaccount", subauth.subaccount}, + {"subaccount_sig", subauth.subaccount_sig}, + {"signature", subauth.signature}, + }}}; + + std::cout << "RETRIEVE:\n\n" << retrieve.dump() << "\n\n"; +} diff --git a/tests/test_blinding.cpp b/tests/test_blinding.cpp new file mode 100644 index 00000000..959a8c0b --- /dev/null +++ b/tests/test_blinding.cpp @@ -0,0 +1,358 @@ + +#include +#include +#include + +#include +#include + +#include "session/blinding.hpp" +#include "session/util.hpp" +#include "utils.hpp" + +using namespace session; + +constexpr std::array seed1{ + 0xfe, 0xcd, 0x9a, 0x60, 0x34, 0xbc, 0x9a, 0xba, 0x27, 0x39, 0x25, 0xde, 0xe7, + 0x06, 0x2b, 0x12, 0x33, 0x34, 0x58, 0x7c, 0x3c, 0x62, 0x57, 0x34, 0x1a, 0xfa, + 0xe2, 0xd7, 0xfe, 0x85, 0xe1, 0x22, 0xf4, 0xef, 0x87, 0x39, 0x08, 0xf6, 0xa5, + 0x37, 0x7b, 0xa3, 0x85, 0x3f, 0x0e, 0x2f, 0xa3, 0x26, 0xee, 0xd9, 0xe7, 0x41, + 0xed, 0xf9, 0xf7, 0xd0, 0x31, 0x1a, 0x3e, 0xcc, 0x66, 0xa5, 0x7b, 0x32}; +constexpr std::array seed2{ + 0x86, 0x59, 0xef, 0xdc, 0xbe, 0x09, 0x49, 0xe0, 0xf8, 0x11, 0x41, 0xe6, 0xd3, + 0x97, 0xe8, 0xbe, 0x75, 0xf4, 0x5d, 0x09, 0x26, 0x2f, 0x20, 0x9d, 0x59, 0x50, + 0xe9, 0x79, 0x89, 0xeb, 0x43, 0xc7, 0x35, 0x70, 0xb6, 0x9a, 0x47, 0xdc, 0x09, + 0x45, 0x44, 0xc1, 0xc5, 0x08, 0x9c, 0x40, 0x41, 0x4b, 0xbd, 0xa1, 0xff, 0xdd, + 0xe8, 0xaa, 0xb2, 0x61, 0x7f, 0xe9, 0x37, 0xee, 0x74, 0xa5, 0xee, 0x81}; + +constexpr std::array xpub1{ + 0xfe, 0x94, 0xb7, 0xad, 0x4b, 0x7f, 0x1c, 0xc1, 0xbb, 0x92, 0x67, + 0x1f, 0x1f, 0x0d, 0x24, 0x3f, 0x22, 0x6e, 0x11, 0x5b, 0x33, 0x77, + 0x04, 0x65, 0xe8, 0x2b, 0x50, 0x3f, 0xc3, 0xe9, 0x6e, 0x1f, +}; +constexpr std::array xpub2{ + 0x05, 0xc9, 0xa9, 0xbf, 0x17, 0x8f, 0xa6, 0x44, 0xd4, 0x4b, 0xeb, + 0xf6, 0x28, 0x71, 0x6d, 0xc7, 0xf2, 0xdf, 0x3d, 0x08, 0x42, 0xe9, + 0x78, 0x81, 0x96, 0x2c, 0x72, 0x36, 0x99, 0x15, 0x20, 0x73, +}; + +const std::string session_id1 = "05" + oxenc::to_hex(xpub1.begin(), xpub1.end()); +const std::string session_id2 = "05" + oxenc::to_hex(xpub2.begin(), xpub2.end()); + +TEST_CASE("Communities 25xxx-blinded pubkey derivation", "[blinding25][pubkey]") { + REQUIRE(sodium_init() >= 0); + + CHECK(blind25_id( + session_id1, + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789") == + "253b991dcbba44cfdb45d5b38880d95cff723309e3ece6fd01415ad5fa1dccc7ac"); + CHECK(blind25_id( + session_id1, + "00cdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789") == + "2598589c7885b56cbeae6ab7b4224f202815520a54995872cb1833b44db6401c8d"); + CHECK(blind25_id( + session_id2, + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789") == + "25a69cc6884530bf8498d22892e563716c4742f2845a7eb608de2aecbe7b6b5996"); + + std::vector session_id1_raw; + oxenc::from_hex(session_id1.begin(), session_id1.end(), std::back_inserter(session_id1_raw)); + CHECK(to_hex(blind25_id( + session_id1_raw, + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"_hexbytes)) == + "253b991dcbba44cfdb45d5b38880d95cff723309e3ece6fd01415ad5fa1dccc7ac"); + CHECK(to_hex(blind25_id( + {session_id1_raw.begin() + 1, session_id1_raw.end()}, + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"_hexbytes)) == + "253b991dcbba44cfdb45d5b38880d95cff723309e3ece6fd01415ad5fa1dccc7ac"); +} + +TEST_CASE("Communities 25xxx-blinded signing", "[blinding25][sign]") { + + std::array server_pks = { + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "00cdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "999def0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "888def0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "777def0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv}; + auto b25_1 = blind25_id(session_id1, server_pks[0]); + auto b25_2 = blind25_id(session_id1, server_pks[1]); + auto b25_3 = blind25_id(session_id2, server_pks[2]); + auto b25_4 = blind25_id(session_id2, server_pks[3]); + auto b25_5 = blind25_id(session_id2, server_pks[4]); + auto b25_6 = blind25_id(session_id1, server_pks[5]); + + auto sig1 = blind25_sign(to_span(seed1), server_pks[0], to_span("hello")); + CHECK(to_hex(sig1) == + "e6c57de4ac0cd278abbeef815bd88b163a037085deae789ecaaf4805884c4c3d3db25f3afa856241366cb341" + "a3a4c9bbaa2cda81d028079c956fab16a7fe6206"); + CHECK(0 == crypto_sign_verify_detached( + sig1.data(), + to_unsigned("hello"), + 5, + to_unsigned(oxenc::from_hex(b25_1).data()) + 1)); + + auto sig2 = blind25_sign(to_span(seed1), server_pks[1], to_span("world")); + CHECK(to_hex(sig2) == + "4460b606e9f55a7cba0bbe24207fe2859c3422783373788b6b070b2fa62ceba4f2a50749a6cee68e095747a3" + "69927f9f4afa86edaf055cad68110e35e8b06607"); + CHECK(0 == crypto_sign_verify_detached( + sig2.data(), + to_unsigned("world"), + 5, + to_unsigned(oxenc::from_hex(b25_2).data()) + 1)); + + auto sig3 = blind25_sign(to_span(seed2), server_pks[2], to_span("this")); + CHECK(to_hex(sig3) == + "57bb2f80c88ce2f677902ee58e02cbd83e4e1ec9e06e1c72a34b4ab76d0f5219cfd141ac5ce7016c73c8382d" + "b99df9f317f2bc0af6ca68edac2a9a7670938902"); + CHECK(0 == crypto_sign_verify_detached( + sig3.data(), + to_unsigned("this"), + 4, + to_unsigned(oxenc::from_hex(b25_3).data()) + 1)); + + auto sig4 = blind25_sign(to_span(seed2), server_pks[3], to_span("is")); + CHECK(to_hex(sig4) == + "ecce032b27b09d2d3d6df4ebab8cae86656c64fd1e3e70d6f020cd7e1a8058c57e3df7b6b01e90ccd592ac4a" + "845dde7a2fdceb1a328a6690686851583133ea0c"); + CHECK(0 == crypto_sign_verify_detached( + sig4.data(), + to_unsigned("is"), + 2, + to_unsigned(oxenc::from_hex(b25_4).data()) + 1)); + + auto sig5 = blind25_sign(to_span(seed2), server_pks[4], to_span("")); + CHECK(to_hex(sig5) == + "bf2fb9a511adbf5827e2e3bcf09f0a1cff80f85556fb76d8001aa8483b5f22e14539b170eaa0dbfa1489d1b8" + "618ce8b48d7512cb5602c7eb8a05ce330a68350b"); + CHECK(0 == + crypto_sign_verify_detached( + sig5.data(), to_unsigned(""), 0, to_unsigned(oxenc::from_hex(b25_5).data()) + 1)); + + auto sig6 = blind25_sign(to_span(seed1), server_pks[5], to_span("omg!")); + CHECK(to_hex(sig6) == + "322e280fbc3547c6b6512dbea4d60563d32acaa2df10d665c40a336c99fc3b8e4b13a7109dfdeadab2ab58b2" + "cb314eb0510b947f43e5dfb6e0ce5bf1499d240f"); + CHECK(0 == crypto_sign_verify_detached( + sig6.data(), + to_unsigned("omg!"), + 4, + to_unsigned(oxenc::from_hex(b25_6).data()) + 1)); + + // Test that it works when given just the seed instead of the whole sk: + auto sig6b = blind25_sign(to_span(seed1).subspan(0, 32), server_pks[5], to_span("omg!")); + CHECK(to_hex(sig6b) == + "322e280fbc3547c6b6512dbea4d60563d32acaa2df10d665c40a336c99fc3b8e4b13a7109dfdeadab2ab58b2" + "cb314eb0510b947f43e5dfb6e0ce5bf1499d240f"); + CHECK(0 == crypto_sign_verify_detached( + sig6b.data(), + to_unsigned("omg!"), + 4, + to_unsigned(oxenc::from_hex(b25_6).data()) + 1)); +} + +TEST_CASE("Communities 15xxx-blinded pubkey derivation", "[blinding15][pubkey]") { + REQUIRE(sodium_init() >= 0); + + std::vector session_id1_raw, session_id2_raw; + oxenc::from_hex(session_id1.begin(), session_id1.end(), std::back_inserter(session_id1_raw)); + oxenc::from_hex(session_id2.begin(), session_id2.end(), std::back_inserter(session_id2_raw)); + CHECK(to_hex(blind15_id( + session_id1_raw, + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"_hexbytes)) == + "15b74ed205f1f931e1bb1291183778a9456b835937d923b0f2e248aa3a44c07844"); + CHECK(to_hex(blind15_id( + session_id2_raw, + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"_hexbytes)) == + "1561e070286ff7a71f167e92b18c709882b148d8238c8872caf414b301ba0564fd"); + CHECK(to_hex(blind15_id( + {session_id1_raw.begin() + 1, session_id1_raw.end()}, + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"_hexbytes)) == + "15b74ed205f1f931e1bb1291183778a9456b835937d923b0f2e248aa3a44c07844"); +} + +TEST_CASE("Communities 15xxx-blinded signing", "[blinding15][sign]") { + REQUIRE(sodium_init() >= 0); + + std::array server_pks = { + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "00cdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "999def0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "888def0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "777def0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv}; + auto b15_1 = blind15_id(session_id1, server_pks[0])[0]; + auto b15_2 = blind15_id(session_id1, server_pks[1])[0]; + // session_id2 has a negative pubkey, so these next three need the negative [1] instead: + auto b15_3 = blind15_id(session_id2, server_pks[2])[1]; + auto b15_4 = blind15_id(session_id2, server_pks[3])[1]; + auto b15_5 = blind15_id(session_id2, server_pks[4])[1]; + auto b15_6 = blind15_id(session_id1, server_pks[5])[0]; + + auto sig1 = blind15_sign(to_span(seed1), server_pks[0], to_span("hello")); + CHECK(to_hex(sig1) == + "1a5ade20b43af0e16b3e591d6f86303938d7557c0ac54469dd4f5aea759f82d22cafa42587251756e133acdd" + "dd8cbec2f707a9ce09a49f2193f46a91502c5006"); + CHECK(0 == crypto_sign_verify_detached( + sig1.data(), + to_unsigned("hello"), + 5, + to_unsigned(oxenc::from_hex(b15_1).data()) + 1)); + + auto sig2 = blind15_sign(to_span(seed1), server_pks[1], to_span("world")); + CHECK(to_hex(sig2) == + "d357f74c5ec5536840aec575051f71fdb22d70f35ef31db1715f5f694842de3b39aa647c84aa8e28ec56eb76" + "2d237c9e030639c83f429826d419ac719cd4df03"); + CHECK(0 == crypto_sign_verify_detached( + sig2.data(), + to_unsigned("world"), + 5, + to_unsigned(oxenc::from_hex(b15_2).data()) + 1)); + + auto sig3 = blind15_sign(to_span(seed2), server_pks[2], to_span("this")); + CHECK(to_hex(sig3) == + "dacf91dfb411e99cd8ef4cb07b195b49289cf1a724fef122c73462818560bc29832a98d870ec4feb79dedca5" + "b59aba6a466d3ce8f3e35adf25a1813f6989fd0a"); + CHECK(0 == crypto_sign_verify_detached( + sig3.data(), + to_unsigned("this"), + 4, + to_unsigned(oxenc::from_hex(b15_3).data()) + 1)); + + auto sig4 = blind15_sign(to_span(seed2), server_pks[3], to_span("is")); + CHECK(to_hex(sig4) == + "8339ea9887d3e44131e33403df160539cdc7a0a8107772172c311e95773660a0d39ed0a6c2b2c794dde1fdc6" + "40943e403497aa02c4d1a21a7d9030742beabb05"); + CHECK(0 == crypto_sign_verify_detached( + sig4.data(), + to_unsigned("is"), + 2, + to_unsigned(oxenc::from_hex(b15_4).data()) + 1)); + + auto sig5 = blind15_sign(to_span(seed2), server_pks[4], to_span("")); + CHECK(to_hex(sig5) == + "8b0d6447decff3a21ec1809141580139c4a51e24977b0605fe7984439993f5377ebc9681e4962593108d03cc" + "8b6873c5c5ba8c30287188137d2dee9ab10afd0f"); + CHECK(0 == + crypto_sign_verify_detached( + sig5.data(), to_unsigned(""), 0, to_unsigned(oxenc::from_hex(b15_5).data()) + 1)); + + auto sig6 = blind15_sign(to_span(seed1), server_pks[5], to_span("omg!")); + CHECK(to_hex(sig6) == + "946725055399376ecebb605c79f845fbf689a47f98507c2a1f239516fd9c9104e19fe533631c27ba4e744457" + "4f0e4f0f0d422b7256ed63681a3ab2fe7e040601"); + CHECK(0 == crypto_sign_verify_detached( + sig6.data(), + to_unsigned("omg!"), + 4, + to_unsigned(oxenc::from_hex(b15_6).data()) + 1)); + + // Test that it works when given just the seed instead of the whole sk: + auto sig6b = blind15_sign(to_span(seed1).subspan(0, 32), server_pks[5], to_span("omg!")); + CHECK(to_hex(sig6b) == + "946725055399376ecebb605c79f845fbf689a47f98507c2a1f239516fd9c9104e19fe533631c27ba4e744457" + "4f0e4f0f0d422b7256ed63681a3ab2fe7e040601"); + CHECK(0 == crypto_sign_verify_detached( + sig6b.data(), + to_unsigned("omg!"), + 4, + to_unsigned(oxenc::from_hex(b15_6).data()) + 1)); +} + +TEST_CASE("Version 07xxx-blinded pubkey derivation", "[blinding07][key_pair]") { + REQUIRE(sodium_init() >= 0); + + auto [pubkey, seckey] = blind_version_key_pair(to_span(seed1)); + CHECK(oxenc::to_hex(pubkey.begin(), pubkey.end()) == + "88e8adb27e7b8ce776fcc25bc1501fb2888fcac0308e52fb10044f789ae1a8fa"); + + CHECK(oxenc::to_hex(seckey.begin() + 32, seckey.end()) == + oxenc::to_hex(pubkey.begin(), pubkey.end())); + + // Hash ourselves just to make sure we get what we expect for the seed part of the secret key: + cleared_uc32 expect_seed; + static const auto hash_key = to_span("VersionCheckKey_sig"sv); + crypto_generichash_blake2b( + expect_seed.data(), 32, seed1.data(), 32, hash_key.data(), hash_key.size()); + + CHECK(oxenc::to_hex(seckey.begin(), seckey.begin() + 32) == + oxenc::to_hex(expect_seed.begin(), expect_seed.end())); + + CHECK(oxenc::to_hex(seckey.begin(), seckey.end()) == + "91faddc2c36da4f7bcf24fd977d9ca5346ae7489cfd43c58cad9eaaa6ed60f69" + "88e8adb27e7b8ce776fcc25bc1501fb2888fcac0308e52fb10044f789ae1a8fa"); +} + +TEST_CASE("Version 07xxx-blinded signing", "[blinding07][sign]") { + REQUIRE(sodium_init() >= 0); + + auto signature = blind_version_sign(to_span(seed1), Platform::desktop, 1234567890); + CHECK(oxenc::to_hex(signature.begin(), signature.end()) == + "143c2c9828f7680ee81e6247bc7aa4777c4991add87cd724149b00452bed4e92" + "0fa57daf4627c68f43fcbddb2d465d5ea11def523f3befb2bbee39c769676305"); + + auto [pk, sk] = blind_version_key_pair(to_span(seed1)); + auto method = "GET"sv; + auto method_span = to_span(method); + auto path = "/path/to/somewhere"sv; + auto path_span = to_span(path); + auto body = to_span("some body (once told me)"); + + uint64_t timestamp = 1234567890; + std::vector full_message = to_vector("{}{}{}"_format(timestamp, method, path)); + + auto req_sig_no_body = + blind_version_sign_request(to_span(seed1), timestamp, method, path, std::nullopt); + CHECK(crypto_sign_verify_detached( + req_sig_no_body.data(), full_message.data(), full_message.size(), pk.data()) == + 0); + + full_message.insert(full_message.end(), body.begin(), body.end()); + auto req_sig = blind_version_sign_request(to_span(seed1), timestamp, method, path, body); + CHECK(crypto_sign_verify_detached( + req_sig.data(), full_message.data(), full_message.size(), pk.data()) == 0); +} + +TEST_CASE("Communities session id blinded id matching", "[blinding][matching]") { + std::array server_pks = { + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "00cdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "999def0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "888def0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv, + "777def0123456789abcdef0123456789abcdef0123456789abcdef0123456789"sv}; + auto b15_1 = blind15_id(session_id1, server_pks[0])[0]; + auto b15_2 = blind15_id(session_id1, server_pks[1])[0]; + auto b15_3 = blind15_id(session_id2, server_pks[2])[1]; + auto b15_4 = blind15_id(session_id2, server_pks[3])[1]; + auto b15_5 = blind15_id(session_id2, server_pks[4])[1]; + auto b15_6 = blind15_id(session_id1, server_pks[5])[0]; + auto b25_1 = blind25_id(session_id1, server_pks[0]); + auto b25_2 = blind25_id(session_id1, server_pks[1]); + auto b25_3 = blind25_id(session_id2, server_pks[2]); + auto b25_4 = blind25_id(session_id2, server_pks[3]); + auto b25_5 = blind25_id(session_id2, server_pks[4]); + auto b25_6 = blind25_id(session_id1, server_pks[5]); + + CHECK(session_id_matches_blinded_id(session_id1, b15_1, server_pks[0])); + CHECK(session_id_matches_blinded_id(session_id1, b15_2, server_pks[1])); + CHECK(session_id_matches_blinded_id(session_id2, b15_3, server_pks[2])); + CHECK(session_id_matches_blinded_id(session_id2, b15_4, server_pks[3])); + CHECK(session_id_matches_blinded_id(session_id2, b15_5, server_pks[4])); + CHECK(session_id_matches_blinded_id(session_id1, b15_6, server_pks[5])); + CHECK(session_id_matches_blinded_id(session_id1, b25_1, server_pks[0])); + CHECK(session_id_matches_blinded_id(session_id1, b25_2, server_pks[1])); + CHECK(session_id_matches_blinded_id(session_id2, b25_3, server_pks[2])); + CHECK(session_id_matches_blinded_id(session_id2, b25_4, server_pks[3])); + CHECK(session_id_matches_blinded_id(session_id2, b25_5, server_pks[4])); + CHECK(session_id_matches_blinded_id(session_id1, b25_6, server_pks[5])); + + auto invalid_session_id = "9"s + session_id1.substr(1); + auto invalid_blinded_id = "9"s + b15_1.substr(1); + auto invalid_server_pk = server_pks[0].substr(0, 60); + CHECK_THROWS(session_id_matches_blinded_id(invalid_session_id, b15_1, server_pks[0])); + CHECK_THROWS(session_id_matches_blinded_id(session_id1, invalid_blinded_id, server_pks[0])); + CHECK_THROWS(session_id_matches_blinded_id(session_id1, invalid_blinded_id, invalid_server_pk)); +} diff --git a/tests/test_bt_merge.cpp b/tests/test_bt_merge.cpp index 03af93df..c937e472 100644 --- a/tests/test_bt_merge.cpp +++ b/tests/test_bt_merge.cpp @@ -1,4 +1,5 @@ #include +#include #include "catch2_bt_format.hpp" #include "session/bt_merge.hpp" @@ -29,7 +30,7 @@ TEST_CASE("bt_list sorted merge", "[bt_list][merge]") { bt_list y{2, 4, 8, 16}; auto compare = [](const auto& a, const auto& b) { - return var::get(a) < var::get(b); + return std::get(a) < std::get(b); }; CHECK(session::bt::merge_sorted(x, y, compare) == bt_list{1, 2, 3, 4, 5, 8, 13, 16, 21}); diff --git a/tests/test_bugs.cpp b/tests/test_bugs.cpp index b4ccb35b..e8b4a6cc 100644 --- a/tests/test_bugs.cpp +++ b/tests/test_bugs.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -24,14 +25,14 @@ TEST_CASE("Dirty/Mutable test case", "[config][dirty]") { CHECK(oxenc::to_hex(seed.begin(), seed.end()) == oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); - session::config::Contacts c1{ustring_view{seed}, std::nullopt}; + session::config::Contacts c1{session::to_span(seed), std::nullopt}; c1.set_name("050000000000000000000000000000000000000000000000000000000000000000", "alfonso"); auto [seqno, data, obsolete] = c1.push(); CHECK(obsolete == std::vector{}); - c1.confirm_pushed(seqno, "fakehash1"); + c1.confirm_pushed(seqno, {"fakehash1"}); - session::config::Contacts c2{ustring_view{seed}, c1.dump()}; - session::config::Contacts c3{ustring_view{seed}, c1.dump()}; + session::config::Contacts c2{session::to_span(seed), c1.dump()}; + session::config::Contacts c3{session::to_span(seed), c1.dump()}; CHECK_FALSE(c2.needs_dump()); CHECK_FALSE(c2.needs_push()); @@ -44,14 +45,17 @@ TEST_CASE("Dirty/Mutable test case", "[config][dirty]") { auto [seqno2, data2, obs2] = c2.push(); auto [seqno3, data3, obs3] = c3.push(); + REQUIRE(data2.size() == 1); + REQUIRE(data3.size() == 1); REQUIRE(seqno2 == 2); - CHECK(obs2 == std::vector{"fakehash1"s}); + CHECK(as_set(obs2) == make_set("fakehash1"s)); REQUIRE(seqno3 == 2); - CHECK(obs2 == std::vector{"fakehash1"s}); + CHECK(as_set(obs3) == make_set("fakehash1"s)); - c1.merge(std::vector>{ - {{"fakehash2", data2}, {"fakehash3", data3}}}); + auto r = c1.merge(std::vector>>{ + {{"fakehash2", data2[0]}, {"fakehash3", data3[0]}}}); + CHECK(r == std::unordered_set{{"fakehash2"s, "fakehash3"s}}); CHECK(c1.needs_dump()); CHECK(c1.needs_push()); // because we have the merge conflict to push CHECK(c1.is_dirty()); @@ -68,3 +72,144 @@ TEST_CASE("Dirty/Mutable test case", "[config][dirty]") { CHECK(seqno4 == 3); // The merge *and* change should go into the same message update/seqno CHECK(as_set(obs4) == make_set("fakehash1"s, "fakehash2"s, "fakehash3"s)); } + +// There was a bug where if we merge the current config into itself then the current hash would be +// included in the old_hashes (which would result in clients deleting the current config from the +// swarm) +TEST_CASE("Merge existing config into clean state", "[config][merge_existing]") { + const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; + std::array ed_pk, curve_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair( + ed_pk.data(), ed_sk.data(), reinterpret_cast(seed.data())); + int rc = crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed_pk.data()); + REQUIRE(rc == 0); + + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"); + REQUIRE(oxenc::to_hex(curve_pk.begin(), curve_pk.end()) == + "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + CHECK(oxenc::to_hex(seed.begin(), seed.end()) == + oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); + + session::config::Contacts c1{std::span{seed}, std::nullopt}; + c1.set_name("050000000000000000000000000000000000000000000000000000000000000000", "alfonso"); + auto [seqno, data, obsolete] = c1.push(); + CHECK(obsolete == std::vector{}); + c1.confirm_pushed(seqno, {"fakehash1"s}); + c1.dump(); + CHECK(!c1.needs_dump()); + CHECK(!c1.needs_push()); + + auto r = c1.merge(std::vector>>{ + {{"fakehash1"s, session::to_span(data[0])}}}); + CHECK(as_set(r) == make_set("fakehash1"s)); + + auto old_hashes = c1.old_hashes(); + CHECK(old_hashes.empty()); +} + +// There was a bug where if the current config is in a dirty state and we merge a config which makes +// the same change we would remain in the dirty state but the merged configs has would be included +// in old_hashes (which ends up being the same hash the dirty config gets after pushing, resulting +// in the current config getting deleted from the swarm) +TEST_CASE("Merge config matching local changse", "[config][merge_matching_dirty]") { + const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; + std::array ed_pk, curve_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair( + ed_pk.data(), ed_sk.data(), reinterpret_cast(seed.data())); + int rc = crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed_pk.data()); + REQUIRE(rc == 0); + + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"); + REQUIRE(oxenc::to_hex(curve_pk.begin(), curve_pk.end()) == + "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + CHECK(oxenc::to_hex(seed.begin(), seed.end()) == + oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); + + session::config::Contacts c1{std::span{seed}, std::nullopt}; + c1.set_name("050000000000000000000000000000000000000000000000000000000000000000", "alfonso"); + auto [seqno, data, obsolete] = c1.push(); + CHECK(obsolete == std::vector{}); + c1.confirm_pushed(seqno, {"fakehash1"s}); + + session::config::Contacts c2{std::span{seed}, c1.dump()}; + + CHECK_FALSE(c2.needs_dump()); + CHECK_FALSE(c2.needs_push()); + + // If the current dirty state matches a merged config we should end up in a clean state + c1.set_name("051111111111111111111111111111111111111111111111111111111111111111", "barney"); + c2.set_name("051111111111111111111111111111111111111111111111111111111111111111", "barney"); + + auto [seqno2, data2, obs2] = c2.push(); + + REQUIRE(seqno2 == 2); + CHECK(obs2 == std::vector{"fakehash1"s}); + c2.confirm_pushed(seqno2, {"fakehash2"s}); + + CHECK(c1.is_dirty()); // already dirty before the merge + auto r = c1.merge(std::vector>>{ + {{"fakehash2"s, session::to_span(data2[0])}}}); + CHECK(r == std::unordered_set{{"fakehash2"s}}); + CHECK(c1.needs_dump()); + + CHECK_FALSE(c1.needs_push()); // the merge resulted in the config being identical + CHECK_FALSE(c1.is_dirty()); + CHECK(c1.is_clean()); + + // Ensure if there are still changes after a merge where something was merged in we remain dirty + c1.set_name("051111111111111111111111111111111111111111111111111111111111111112", "barney2"); + c1.set_name("051111111111111111111111111111111111111111111111111111111111111113", "barney3"); + c2.set_name("051111111111111111111111111111111111111111111111111111111111111112", "barney2"); + + auto [seqno3, data3, obs3] = c2.push(); + REQUIRE(seqno3 == 3); + CHECK(obs3 == std::vector{"fakehash2"s}); + c2.confirm_pushed(seqno3, {"fakehash3"s}); + + CHECK(c1.is_dirty()); // already dirty before the merge + auto r2 = c1.merge(std::vector>>{ + {{"fakehash3", session::to_span(data3[0])}}}); + CHECK(r2 == std::unordered_set{{"fakehash3"s}}); + CHECK(c1.needs_dump()); + + CHECK(c1.needs_push()); // there are still changes after the merge + CHECK(c1.is_dirty()); + CHECK_FALSE(c1.is_clean()); + + // Ensure if there are still changes after a merge where nothing was merged in we remain dirty + // (push enough changes that we have a seqNo larger than the `lag` setting we use) + for (auto i = 5; i < 20; ++i) { + c1.set_name( + fmt::format( + "0511111111111111111111111111111111111111111111111111111111111111{:02}", i), + fmt::format("barney{}", i)); + auto [seqno_i, data_i, obs_i] = c1.push(); + REQUIRE(seqno_i == i); + c1.confirm_pushed(seqno_i, {"fakehash" + std::to_string(i)}); + CHECK_FALSE(c1.needs_push()); + CHECK_FALSE(c1.is_dirty()); + CHECK(c1.is_clean()); + } + + c2.set_name("051111111111111111111111111111111111111111111111111111111111111150", "barney50"); + auto [seqno4, data4, obs4] = c2.push(); + REQUIRE(seqno4 == 4); + CHECK(obs4 == std::vector{"fakehash3"s}); + + c1.set_name("051111111111111111111111111111111111111111111111111111111111111140", "barney40"); + auto size_before_merge = c1.size(); // retrieve size before trying to merge + CHECK(c1.is_dirty()); // already dirty before the merge + auto r4 = c1.merge(std::vector>>{ + {{"fakehash21", session::to_span(data4[0])}}}); + CHECK(r4 == std::unordered_set{{"fakehash21"s}}); + CHECK(c1.needs_dump()); + + CHECK(c1.size() == size_before_merge); // barney21 didn't get merged (seqNo too old) + CHECK(c1.needs_push()); // there are still changes after the merge + CHECK(c1.is_dirty()); + CHECK_FALSE(c1.is_clean()); +} diff --git a/tests/test_compression.cpp b/tests/test_compression.cpp index 24892fbd..2667d342 100644 --- a/tests/test_compression.cpp +++ b/tests/test_compression.cpp @@ -10,12 +10,9 @@ #include "utils.hpp" namespace session::config { -void compress_message(ustring& msg, int level); +void compress_message(std::vector& msg, int level); } -using namespace std::literals; -using namespace oxenc::literals; - TEST_CASE("compression", "[config][compression]") { auto data = @@ -40,7 +37,7 @@ TEST_CASE("compression", "[config][compression]") { // This message (from the user profile test case) doesn't compress any better than plaintext // with zstd compression, so the compress_message call shouldn't change it. // clang-format off - data = + data = session::to_vector( "d" "1:#" "i1e" "1:&" "d" @@ -51,8 +48,8 @@ TEST_CASE("compression", "[config][compression]") { "1:<" "l" "l" "i0e" - "32:"_bytes + - "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965"_hexbytes + + "32:" + + session::to_string("ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965"_hexbytes) + "de" "e" "e" @@ -61,10 +58,10 @@ TEST_CASE("compression", "[config][compression]") { "1:p" "0:" "1:q" "0:" "e" - "e"_bytes; + "e"); // // If we add some more repetition in it, though, it will: - auto data2 = + auto data2 = session::to_vector( "d" "1:#" "i1e" "1:&" "d" @@ -75,8 +72,8 @@ TEST_CASE("compression", "[config][compression]") { "1:<" "l" "l" "i0e" - "32:"_bytes + - "ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965"_hexbytes + + "32:" + + session::to_string("ea173b57beca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c965"_hexbytes) + "de" "e" "e" @@ -85,7 +82,7 @@ TEST_CASE("compression", "[config][compression]") { "1:p" "0:" "1:q" "0:" "e" - "e"_bytes; + "e"); // clang-format on d = data; diff --git a/tests/test_config_contacts.cpp b/tests/test_config_contacts.cpp index db6b9510..fadcc286 100644 --- a/tests/test_config_contacts.cpp +++ b/tests/test_config_contacts.cpp @@ -4,14 +4,13 @@ #include #include +#include #include #include +#include #include "utils.hpp" -using namespace std::literals; -using namespace oxenc::literals; - static constexpr int64_t created_ts = 1680064059; TEST_CASE("Contacts", "[config][contacts]") { @@ -31,7 +30,7 @@ TEST_CASE("Contacts", "[config][contacts]") { CHECK(oxenc::to_hex(seed.begin(), seed.end()) == oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); - session::config::Contacts contacts{ustring_view{seed}, std::nullopt}; + session::config::Contacts contacts{std::span{seed}, std::nullopt}; constexpr auto definitely_real_id = "050000000000000000000000000000000000000000000000000000000000000000"sv; @@ -65,9 +64,9 @@ TEST_CASE("Contacts", "[config][contacts]") { c.set_nickname("Joey"); c.approved = true; c.approved_me = true; - c.created = created_ts; + c.created = created_ts * 1'000; c.notifications = session::config::notify_mode::all; - c.mute_until = now + 1800; + c.mute_until = (now + 1800) * 1'000'000; contacts.set(c); @@ -89,7 +88,7 @@ TEST_CASE("Contacts", "[config][contacts]") { CHECK(seqno == 1); // Pretend we uploaded it - contacts.confirm_pushed(seqno, "fakehash1"); + contacts.confirm_pushed(seqno, {"fakehash1"}); CHECK(contacts.needs_dump()); CHECK_FALSE(contacts.needs_push()); @@ -123,13 +122,14 @@ TEST_CASE("Contacts", "[config][contacts]") { CHECK(contacts2.needs_push()); std::tie(seqno, to_push, obs) = contacts2.push(); + REQUIRE(to_push.size() == 1); CHECK(seqno == 2); - std::vector> merge_configs; - merge_configs.emplace_back("fakehash2", to_push); + std::vector>> merge_configs; + merge_configs.emplace_back("fakehash2", to_push[0]); contacts.merge(merge_configs); - contacts2.confirm_pushed(seqno, "fakehash2"); + contacts2.confirm_pushed(seqno, {"fakehash2"}); CHECK_FALSE(contacts.needs_push()); CHECK(std::get(contacts.push()) == seqno); @@ -165,7 +165,7 @@ TEST_CASE("Contacts", "[config][contacts]") { session::config::profile_pic p; { // These don't stay alive, so we use set_key/set_url to make a local copy: - ustring key = "qwerty78901234567890123456789012"_bytes; + std::vector key = "qwerty78901234567890123456789012"_bytes; std::string url = "http://example.com/huge.bmp"; p.set_key(std::move(key)); p.url = std::move(url); @@ -176,22 +176,24 @@ TEST_CASE("Contacts", "[config][contacts]") { CHECK(contacts2.needs_push()); std::tie(seqno, to_push, obs) = contacts.push(); auto [seqno2, to_push2, obs2] = contacts2.push(); + REQUIRE(to_push.size() == 1); + REQUIRE(to_push2.size() == 1); CHECK(seqno == seqno2); CHECK(to_push != to_push2); CHECK(as_set(obs) == make_set("fakehash2"s)); CHECK(as_set(obs2) == make_set("fakehash2"s)); - contacts.confirm_pushed(seqno, "fakehash3a"); - contacts2.confirm_pushed(seqno2, "fakehash3b"); + contacts.confirm_pushed(seqno, {"fakehash3a"}); + contacts2.confirm_pushed(seqno2, {"fakehash3b"}); merge_configs.clear(); - merge_configs.emplace_back("fakehash3b", to_push2); + merge_configs.emplace_back("fakehash3b", to_push2[0]); contacts.merge(merge_configs); CHECK(contacts.needs_push()); merge_configs.clear(); - merge_configs.emplace_back("fakehash3a", to_push); + merge_configs.emplace_back("fakehash3a", to_push[0]); contacts2.merge(merge_configs); CHECK(contacts2.needs_push()); @@ -199,12 +201,15 @@ TEST_CASE("Contacts", "[config][contacts]") { CHECK(seqno == seqno2 + 1); std::tie(seqno2, to_push2, obs2) = contacts2.push(); CHECK(seqno == seqno2); - CHECK(to_push == to_push2); + // Disabled check for now: doesn't work with protobuf (because of the non-deterministic + // encryption in the middle of the protobuf wrapping). + // TODO: reenable once protobuf isn't always-on. + // CHECK(printable(to_push) == printable(to_push2)); CHECK(as_set(obs) == make_set("fakehash3a"s, "fakehash3b")); CHECK(as_set(obs2) == make_set("fakehash3a"s, "fakehash3b")); - contacts.confirm_pushed(seqno, "fakehash4"); - contacts2.confirm_pushed(seqno2, "fakehash4"); + contacts.confirm_pushed(seqno, {"fakehash4"}); + contacts2.confirm_pushed(seqno2, {"fakehash4"}); CHECK_FALSE(contacts.needs_push()); CHECK_FALSE(contacts2.needs_push()); @@ -220,6 +225,28 @@ TEST_CASE("Contacts", "[config][contacts]") { CHECK(session_ids[1] == third_id); CHECK(nicknames[0] == "(N/A)"); CHECK(nicknames[1] == "Nickname 3"); + + CHECK_THROWS( + c.set_nickname("12345678901234567890123456789012345678901234567890123456789012345678901" + "23456789012345678901234567890A")); + CHECK_NOTHROW( + c.set_nickname_truncated("1234567890123456789012345678901234567890123456789012345678901" + "234567890123456789012345678901234567890A")); + CHECK(c.nickname == + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567890"); + CHECK_NOTHROW( + c.set_nickname_truncated("1234567890123456789012345678901234567890123456789012345678901" + "234567890123456789012345678901234567🎂")); + CHECK(c.nickname == + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567"); + CHECK_NOTHROW( + c.set_nickname_truncated("1234567890123456789012345678901234567890123456789012345678901" + "2345678901234567890123456789012345🎂🎂")); + CHECK(c.nickname == + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" + "9012345🎂"); } TEST_CASE("Contacts (C API)", "[config][contacts][c]") { @@ -289,12 +316,17 @@ TEST_CASE("Contacts (C API)", "[config][contacts][c]") { const unsigned char* merge_data[1]; size_t merge_size[1]; merge_hash[0] = "fakehash1"; - merge_data[0] = to_push->config; - merge_size[0] = to_push->config_len; - int accepted = config_merge(conf2, merge_hash, merge_data, merge_size, 1); - REQUIRE(accepted == 1); + REQUIRE(to_push->n_configs == 1); + merge_data[0] = to_push->config[0]; + merge_size[0] = to_push->config_lens[0]; + config_string_list* accepted = config_merge(conf2, merge_hash, merge_data, merge_size, 1); + REQUIRE(accepted->len == 1); + CHECK(accepted->value[0] == "fakehash1"sv); + free(accepted); - config_confirm_pushed(conf, to_push->seqno, "fakehash1"); + const char* tmphash; // test suite cheat: &(tmphash = "asdf") to fake a length-1 array. + + config_confirm_pushed(conf, to_push->seqno, &(tmphash = "fakehash1"), 1); free(to_push); contacts_contact c3; @@ -322,12 +354,15 @@ TEST_CASE("Contacts (C API)", "[config][contacts][c]") { to_push = config_push(conf2); merge_hash[0] = "fakehash2"; - merge_data[0] = to_push->config; - merge_size[0] = to_push->config_len; + REQUIRE(to_push->n_configs == 1); + merge_data[0] = to_push->config[0]; + merge_size[0] = to_push->config_lens[0]; accepted = config_merge(conf, merge_hash, merge_data, merge_size, 1); - REQUIRE(accepted == 1); + REQUIRE(accepted->len == 1); + CHECK(accepted->value[0] == "fakehash2"sv); + free(accepted); - config_confirm_pushed(conf2, to_push->seqno, "fakehash2"); + config_confirm_pushed(conf2, to_push->seqno, &(tmphash = "fakehash2"), 1); REQUIRE(to_push->obsolete_len > 0); CHECK(to_push->obsolete_len == 1); @@ -376,6 +411,8 @@ TEST_CASE("Contacts (C API)", "[config][contacts][c]") { CHECK_FALSE(contacts_get(conf, &ci, another_id)); } +static constexpr auto EXPECT_BIG_DUMP_SIZE = 1'597'004; + TEST_CASE("huge contacts compression", "[config][compression][contacts]") { // Test that we can produce a config message whose *uncompressed* length exceeds the maximum // message length as long as its *compressed* length does not. @@ -393,9 +430,9 @@ TEST_CASE("huge contacts compression", "[config][compression][contacts]") { REQUIRE(oxenc::to_hex(curve_pk.begin(), curve_pk.end()) == "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); - session::config::Contacts contacts{ustring_view{seed}, std::nullopt}; + session::config::Contacts contacts{std::span{seed}, std::nullopt}; - for (uint16_t i = 0; i < 10000; i++) { + for (uint16_t i = 0; i < 12000; i++) { char buf[2]; oxenc::write_host_as_big(i, buf); std::string session_id = "05000000000000000000000000000000000000000000000000000000000000"; @@ -403,7 +440,7 @@ TEST_CASE("huge contacts compression", "[config][compression][contacts]") { REQUIRE(session_id.size() == 66); auto c = contacts.get_or_construct(session_id); - c.nickname = "My friend " + std::to_string(i); + c.nickname = "My friend {}"_format(i); c.approved = true; c.approved_me = true; contacts.set(c); @@ -414,8 +451,414 @@ TEST_CASE("huge contacts compression", "[config][compression][contacts]") { auto [seqno, to_push, obs] = contacts.push(); CHECK(seqno == 1); - CHECK(to_push.size() == 46'080); + CHECK(to_push.size() == 1); + CHECK(to_push[0].size() == 56'320 + 181); // 181 == protobuf overhead auto dump = contacts.dump(); - // With tons of duplicate info the push should have been nicely compressible: - CHECK(dump.size() > 1'320'000); + // With tons of duplicate info the push should have been nicely compressible, but our dump + // (which currently isn't compressed) is much larger: + CHECK(dump.size() == EXPECT_BIG_DUMP_SIZE); + + contacts.confirm_pushed(seqno, {"fakehash1"}); + dump = contacts.dump(); + CHECK(dump.size() == EXPECT_BIG_DUMP_SIZE + 11); // We will have added '9:fakehash1' +} + +TEST_CASE("huger contacts with multipart messages", "[config][multipart][contacts]") { + // Test that we can produce a config message whose *uncompressed* length exceeds the maximum + // message length as long as its *compressed* length does not. + + const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; + std::array ed_pk, curve_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair( + ed_pk.data(), ed_sk.data(), reinterpret_cast(seed.data())); + int rc = crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed_pk.data()); + REQUIRE(rc == 0); + + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"); + REQUIRE(oxenc::to_hex(curve_pk.begin(), curve_pk.end()) == + "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + + session::config::Contacts contacts{session::to_span(seed), std::nullopt}; + + std::string friend42; + + for (size_t i = 0; i < 12000; i++) { + // Unlike the above case where we have nearly identical Session IDs, here our session IDs + // are randomly generated and thus not usefully compressible, which results in a much larger + // (compressed) config. + std::mt19937_64 rng{i}; + std::array random_sessionid; + random_sessionid[0] = 0x05; + for (int i = 1; i < 33; i += 8) + oxenc::write_host_as_little(rng(), random_sessionid.data() + i); + + std::string session_id = oxenc::to_hex(random_sessionid); + + auto c = contacts.get_or_construct(session_id); + c.nickname = "My friend {}"_format(i); + c.approved = true; + c.approved_me = true; + contacts.set(c); + + if (i == 42) + friend42 = std::move(session_id); + } + + CHECK(contacts.needs_push()); + CHECK(contacts.needs_dump()); + + auto [seqno, to_push, obs] = contacts.push(); + + CHECK(seqno == 1); + REQUIRE(to_push.size() == 12); + CHECK(to_push[0].size() == 76'800); // maxed out + CHECK(to_push[1].size() == 76'800); // maxed out + CHECK(to_push[2].size() == 76'800); // maxed out + CHECK(to_push[3].size() == 76'800); // maxed out + CHECK(to_push[4].size() == 76'800); // maxed out + CHECK(to_push[5].size() == 76'800); // maxed out + CHECK(to_push[6].size() == 76'800); // maxed out + CHECK(to_push[7].size() == 76'800); // maxed out + CHECK(to_push[8].size() == 76'800); // maxed out + CHECK(to_push[9].size() == 76'800); // maxed out + CHECK(to_push[10].size() == 76'800); // maxed out + CHECK(to_push[11].size() == 1'040); // last part + + // Still compressible, but much less than the test case above + auto dump = contacts.dump(); + constexpr auto base_dump_size = EXPECT_BIG_DUMP_SIZE + /**/ + + 35 // 32:[finalhash] + + 2 // d...e + + 6 // 1:#i0e + + 18; // 1:Ti1234567890555e + + CHECK(dump.size() == base_dump_size); + + { + std::unordered_set fakehashes; + for (int i = 0; i < 12; i++) + fakehashes.insert("fakehash{:02d}"_format(i)); + contacts.confirm_pushed(seqno, fakehashes); + + CHECK(contacts.curr_hashes() == fakehashes); + CHECK(contacts.active_hashes() == fakehashes); + } + + dump = contacts.dump(); + CHECK(dump.size() == base_dump_size + 12 * 13); // 12 x "10:fakehashNN" + + auto c2 = std::make_unique(session::to_span(seed), std::nullopt); + + std::vector>> merge_configs, merge_more; + bool dump_load_in_between = false; + std::mt19937_64 rng{12345}; + + auto old_seqno = std::get(c2->push()); + REQUIRE(old_seqno == 0); + + CHECK_FALSE(c2->get(friend42)); + + // Test loading the push data back into a new config: + SECTION("all parts in expected order") { + for (int i = 0; i < 12; i++) + merge_configs.emplace_back("fakehash{:02d}"_format(i), to_push[i]); + } + SECTION("all parts, shuffled order") { + for (int i = 0; i < 12; i++) + merge_configs.emplace_back("fakehash{:02d}"_format(i), to_push[i]); + std::shuffle(merge_configs.begin(), merge_configs.end(), rng); + } + SECTION("missing parts") { + for (int i = 0; i < 12; i++) + merge_configs.emplace_back("fakehash{:02d}"_format(i), to_push[i]); + std::shuffle(merge_configs.begin(), merge_configs.end(), rng); + + // Simulate a partial fetch where we got just 8 parts in random order, then we get the last + // 2 in a follow-up fetch. + for (int i = 8; i < 12; i++) + merge_more.push_back(std::move(merge_configs[i])); + merge_configs.resize(8); + } + SECTION("missing parts with dump in between") { + for (int i = 0; i < 12; i++) + merge_configs.emplace_back("fakehash{:02d}"_format(i), to_push[i]); + std::shuffle(merge_configs.begin(), merge_configs.end(), rng); + + // Same as the above, except we are going to dump and reload from that dump in between + // fetching the first batch and the remaining ones. + dump_load_in_between = true; + for (int i = 0; i < 2; i++) { + merge_more.push_back(std::move(merge_configs.back())); + merge_configs.pop_back(); + } + } + + auto merge_hashes = [](const auto& merge_confs) { + std::unordered_set result; + for (const auto& [hash, data] : merge_confs) + result.emplace(hash); + return result; + }; + + std::unordered_set fakehashes; + for (auto& [h, d] : merge_configs) + fakehashes.insert(h); + + int merged = 0; + std::unordered_set accepted; + CHECK_FALSE(c2->needs_dump()); + accepted = c2->merge(merge_configs); + merged += merge_configs.size(); + CHECK(accepted == merge_hashes(merge_configs)); + CHECK(c2->needs_dump()); + CHECK_FALSE(c2->needs_push()); + CHECK(c2->active_hashes() == fakehashes); + + if (merged >= 12) { + CHECK(c2->curr_hashes() == fakehashes); + } else { + CHECK(c2->curr_hashes().empty()); + CHECK(c2->active_hashes() == fakehashes); + dump = c2->dump(); + size_t total_dumps = 0; + for (auto& [hash, data] : merge_configs) + total_dumps += hash.size() + data.size() - 90 /* multipart+encryption overhead */; + // Our dump should be storing all the partial bodies, with a little overhead: + CHECK(dump.size() > total_dumps); + CHECK(dump.size() < total_dumps + 500 /* ~ various other dump overhead */); + + if (dump_load_in_between) { + auto c2b = + std::make_unique(session::to_span(seed), c2->dump()); + CHECK_FALSE(c2b->needs_dump()); + c2 = std::move(c2b); + CHECK_FALSE(c2->needs_dump()); + } + + CHECK(std::get(c2->push()) == 0); + accepted = c2->merge(merge_more); + CHECK(accepted == merge_hashes(merge_more)); + CHECK(c2->needs_dump()); + for (auto& [h, d] : merge_more) + fakehashes.insert(h); + CHECK(c2->curr_hashes() == fakehashes); + CHECK(c2->active_hashes() == fakehashes); + } + + CHECK_FALSE(c2->needs_push()); + CHECK(std::get(c2->push()) == 1); + + auto myfriend = c2->get(friend42); + REQUIRE(myfriend); + CHECK(myfriend->nickname == "My friend 42"); + + dump = c2->dump(); + CHECK(dump.size() == base_dump_size + 12 * 13); // 12 x "10:fakehashNN" +} + +TEST_CASE("multipart message expiry", "[config][multipart][contacts][expiry]") { + // Tests that stored multipart message expires as expected. + + const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; + std::array ed_pk, curve_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair( + ed_pk.data(), ed_sk.data(), reinterpret_cast(seed.data())); + int rc = crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed_pk.data()); + REQUIRE(rc == 0); + + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"); + REQUIRE(oxenc::to_hex(curve_pk.begin(), curve_pk.end()) == + "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + + session::config::Contacts contacts{session::to_span(seed), std::nullopt}; + + std::string friend42; + + std::array seedi = {0}; + for (uint16_t i = 0; i < 2000; i++) { + // Unlike the above case where we have nearly identical Session IDs, here our session IDs + // are randomly generated from fixed seeds and thus not usefully compressible, which results + // in a much larger (compressed) config. + seedi[0] = i % 256; + seedi[1] = i >> 8; + std::array i_ed_pk, i_curve_pk; + std::array i_ed_sk; + crypto_sign_ed25519_seed_keypair( + i_ed_pk.data(), + i_ed_sk.data(), + reinterpret_cast(seedi.data())); + rc = crypto_sign_ed25519_pk_to_curve25519(i_curve_pk.data(), i_ed_pk.data()); + std::string session_id = "05" + oxenc::to_hex(i_curve_pk.begin(), i_curve_pk.end()); + + auto c = contacts.get_or_construct(session_id); + c.nickname = "My friend {:04d}"_format(i); + c.approved = true; + c.approved_me = true; + contacts.set(c); + + if (i == 42) + friend42 = std::move(session_id); + } + + CHECK(contacts.needs_push()); + CHECK(contacts.needs_dump()); + + auto [seqno, to_push, obs] = contacts.push(); + + CHECK(seqno == 1); + CHECK(to_push.size() == 2); + CHECK(to_push[0].size() == 76'800); // maxed out + CHECK(to_push[1].size() == 35'980); // last part + + contacts.confirm_pushed(seqno, {"fakehash0", "fakehash1"}); + + auto c2 = std::make_unique(session::to_span(seed), std::nullopt); + + c2->MULTIPART_MAX_WAIT = 200ms; + c2->MULTIPART_MAX_REMEMBER = 400ms; + + auto old_seqno = std::get(c2->push()); + REQUIRE(old_seqno == 0); + + std::vector>> merge_configs; + merge_configs.emplace_back("fakehash0", to_push[0]); + + std::unordered_set accepted; + CHECK_FALSE(c2->needs_dump()); + accepted = c2->merge(merge_configs); + CHECK(accepted == std::unordered_set{{"fakehash0"s}}); + CHECK(c2->needs_dump()); + auto dump = c2->dump(); + CHECK(dump.size() > 76'710); + CHECK(dump.size() < 77'000); + CHECK_FALSE(c2->needs_push()); + CHECK(std::get(c2->push()) == 0); + + // Wait for the stored part to expire + std::this_thread::sleep_for(220ms); + + // Dump should trigger a cleanup of cached parts: + dump = c2->dump(); + CHECK(dump.size() < 200); + + merge_configs.clear(); + merge_configs.emplace_back("fakehash1", to_push[1]); + accepted = c2->merge(merge_configs); + CHECK(accepted == std::unordered_set{{"fakehash1"s}}); + CHECK(c2->needs_dump()); + // This should *not* have completed a set, because of the earlier expiry: + CHECK(std::get(c2->push()) == 0); + dump = c2->dump(); + CHECK(dump.size() > 35'890); + CHECK(dump.size() < 36'200); + + merge_configs.clear(); + merge_configs.emplace_back("fakehash0", to_push[0]); + accepted = c2->merge(merge_configs); + CHECK(accepted == std::unordered_set{{"fakehash0"s}}); + CHECK(c2->needs_dump()); + CHECK_FALSE(c2->needs_push()); + // Now we should have completed the set + CHECK(std::get(c2->push()) == 1); + auto myfriend = c2->get(friend42); + REQUIRE(myfriend); + CHECK(myfriend->nickname == "My friend 0042"); + dump = c2->dump(); + auto full_size = dump.size(); + // We shouldn't be storing any part data, but we *are* now storing all the actual data. Every + // contact here should be a dict pair encoded as: + CHECK(full_size > 266000); + CHECK(full_size < 266300); + // Go look for the 1:* where we store multipart info, and make sure it's within the last 100 + // bytes of the dump (to make sure that we don't have cached data stored inside it): + auto pattern = session::to_span("1:*"); + auto it = std::find_end(dump.begin(), dump.end(), pattern.begin(), pattern.end()); + REQUIRE(it != dump.end()); + auto x = std::distance(dump.begin(), it); + CHECK(x > full_size - 100); + CHECK(x < dump.size()); + + //////////////////////////////////////////// + // Now we check "done" expiry + + // Initially reloading a part should do nothing, since we should have the "done" stubs still + // stored. (It's still "accepted" because that just means we found it parseable and valid, even + // though we discard it.). + accepted = c2->merge(merge_configs); + CHECK(accepted == std::unordered_set{{"fakehash0"s}}); + CHECK_FALSE(c2->needs_dump()); + dump = c2->dump(); + CHECK(dump.size() == full_size); + + // test that the remember timer is getting properly applied for a completed set rather than the + // wait timer by making sure we don't lose anything in the repeated dump: + std::this_thread::sleep_for(220ms); + dump = c2->dump(); + CHECK(dump.size() == full_size); // expect no change + + std::this_thread::sleep_for(220ms); + // Now we should hit the remember timer, and should discard the cached completed set data when + // we dump: + dump = c2->dump(); + // We should have lost the entry pair for this set, which for a done set will be: + // 32:HASH + // d1:#i0e1:Ti1234567890123ee + CHECK(dump.size() == full_size - (35 + 26)); + full_size -= 35 + 26; + + // Since we've forgotten about the completed set, this time we *should* store it: + accepted = c2->merge(merge_configs); + CHECK(accepted == std::unordered_set{{"fakehash0"s}}); + CHECK(c2->needs_dump()); + dump = c2->dump(); + CHECK(dump.size() > full_size + 76'710); + CHECK(dump.size() < full_size + 77'000); + + // Complete the set; this should *not* change the seqno, as this should be recognized as a + // duplicate seqno/config hash with the regular hash handling: + merge_configs.emplace_back("fakehash1", to_push[1]); + accepted = c2->merge(merge_configs); + CHECK(accepted == std::unordered_set{{"fakehash0"s, "fakehash1"s}}); + dump = c2->dump(); + CHECK(dump.size() == full_size + 35 + 26); + CHECK(std::get(contacts.push()) == 1); +} + +TEST_CASE("needs_dump bug", "[config][needs_dump]") { + + const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; + + session::config::Contacts contacts{std::span{seed}, std::nullopt}; + + CHECK_FALSE(contacts.needs_dump()); + + auto c = contacts.get_or_construct( + "050000000000000000000000000000000000000000000000000000000000000000"sv); + + c.approved = true; + contacts.set(c); + + CHECK(contacts.needs_dump()); + + c.approved_me = true; + contacts.set(c); + + CHECK(contacts.needs_dump()); + + (void)contacts.dump(); + + CHECK_FALSE(contacts.needs_dump()); + + c.approved = false; + contacts.set(c); + CHECK(contacts.needs_dump()); + + c.approved_me = false; + contacts.set(c); + CHECK(contacts.needs_dump()); } diff --git a/tests/test_config_convo_info_volatile.cpp b/tests/test_config_convo_info_volatile.cpp index 0edc9720..daf1ed3a 100644 --- a/tests/test_config_convo_info_volatile.cpp +++ b/tests/test_config_convo_info_volatile.cpp @@ -10,9 +10,6 @@ #include "utils.hpp" -using namespace std::literals; -using namespace oxenc::literals; - TEST_CASE("Conversations", "[config][conversations]") { const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; @@ -30,11 +27,14 @@ TEST_CASE("Conversations", "[config][conversations]") { CHECK(oxenc::to_hex(seed.begin(), seed.end()) == oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); - session::config::ConvoInfoVolatile convos{ustring_view{seed}, std::nullopt}; + session::config::ConvoInfoVolatile convos{std::span{seed}, std::nullopt}; constexpr auto definitely_real_id = "055000000000000000000000000000000000000000000000000000000000000000"sv; + constexpr auto benders_nightmare_group = + "030111101001001000101010011011010010101010111010000110100001210000"sv; + CHECK_FALSE(convos.get_1to1(definitely_real_id)); CHECK(convos.empty()); @@ -79,12 +79,23 @@ TEST_CASE("Conversations", "[config][conversations]") { // The new data doesn't get stored until we call this: convos.set(og); + CHECK_FALSE(convos.get_group(benders_nightmare_group)); + + auto g = convos.get_or_construct_group(benders_nightmare_group); + CHECK(g.id == benders_nightmare_group); + CHECK(g.last_read == 0); + CHECK_FALSE(g.unread); + + g.last_read = now_ms; + g.unread = true; + convos.set(g); + auto [seqno, to_push, obs] = convos.push(); CHECK(seqno == 1); // Pretend we uploaded it - convos.confirm_pushed(seqno, "hash1"); + convos.confirm_pushed(seqno, {"hash1"}); CHECK(convos.needs_dump()); CHECK_FALSE(convos.needs_push()); @@ -111,6 +122,11 @@ TEST_CASE("Conversations", "[config][conversations]") { CHECK(x2->pubkey_hex() == to_hex(open_group_pubkey)); CHECK(x2->unread); + auto x3 = convos2.get_group(benders_nightmare_group); + REQUIRE(x3); + CHECK(x3->last_read == now_ms); + CHECK(x3->unread); + auto another_id = "051111111111111111111111111111111111111111111111111111111111111111"sv; auto c2 = convos.get_or_construct_1to1(another_id); c2.unread = true; @@ -127,46 +143,53 @@ TEST_CASE("Conversations", "[config][conversations]") { CHECK(seqno == 2); - std::vector> merge_configs; - merge_configs.emplace_back("hash2", to_push); + REQUIRE(to_push.size() == 1); + std::vector>> merge_configs; + merge_configs.emplace_back("hash2", to_push[0]); convos.merge(merge_configs); - convos2.confirm_pushed(seqno, "hash2"); + convos2.confirm_pushed(seqno, {"hash2"}); CHECK_FALSE(convos.needs_push()); CHECK(std::get(convos.push()) == seqno); using session::config::convo::community; + using session::config::convo::group; using session::config::convo::legacy_group; using session::config::convo::one_to_one; - std::vector seen; + std::vector seen, expected; + for (const auto& e : + {"1-to-1: 051111111111111111111111111111111111111111111111111111111111111111", + "1-to-1: 055000000000000000000000000000000000000000000000000000000000000000", + "gr: 030111101001001000101010011011010010101010111010000110100001210000", + "comm: http://example.org:5678/r/sudokuroom", + "lgr: 05cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"}) + expected.emplace_back(e); + for (auto* conv : {&convos, &convos2}) { // Iterate through and make sure we got everything we expected seen.clear(); - CHECK(conv->size() == 4); + CHECK(conv->size() == 5); CHECK(conv->size_1to1() == 2); CHECK(conv->size_communities() == 1); CHECK(conv->size_legacy_groups() == 1); + CHECK(conv->size_groups() == 1); CHECK_FALSE(conv->empty()); for (const auto& convo : *conv) { if (auto* c = std::get_if(&convo)) seen.push_back("1-to-1: "s + c->session_id); + else if (auto* c = std::get_if(&convo)) + seen.push_back("gr: " + c->id); else if (auto* c = std::get_if(&convo)) seen.push_back( - "og: " + std::string{c->base_url()} + "/r/" + std::string{c->room()}); + "comm: " + std::string{c->base_url()} + "/r/" + std::string{c->room()}); else if (auto* c = std::get_if(&convo)) - seen.push_back("cl: " + c->id); + seen.push_back("lgr: " + c->id); + else + seen.push_back("unknown convo type!"); } - CHECK(seen == std::vector{ - {"1-to-1: " - "051111111111111111111111111111111111111111111111111111111111111111", - "1-to-1: " - "055000000000000000000000000000000000000000000000000000000000000000", - "og: http://example.org:5678/r/sudokuroom", - "cl: " - "05ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" - "c"}}); + CHECK(seen == expected); } CHECK_FALSE(convos.needs_push()); @@ -174,8 +197,9 @@ TEST_CASE("Conversations", "[config][conversations]") { CHECK_FALSE(convos.needs_push()); convos.erase_1to1("055000000000000000000000000000000000000000000000000000000000000000"); CHECK(convos.needs_push()); - CHECK(convos.size() == 3); + CHECK(convos.size() == 4); CHECK(convos.size_1to1() == 1); + CHECK(convos.size_groups() == 1); // Check the single-type iterators: seen.clear(); @@ -269,7 +293,7 @@ TEST_CASE("Conversations (C API)", "[config][conversations][c]") { "bad-url", "room", "0000000000000000000000000000000000000000000000000000000000000000"_hexbytes.data())); - CHECK(conf->last_error == "Invalid community URL: invalid/missing protocol://"sv); + CHECK(conf->last_error == "Invalid URL: invalid/missing protocol://"sv); CHECK_FALSE(convo_info_volatile_get_or_construct_community( conf, &og, @@ -292,11 +316,14 @@ TEST_CASE("Conversations (C API)", "[config][conversations][c]") { config_push_data* to_push = config_push(conf); auto seqno = to_push->seqno; - free(to_push); CHECK(seqno == 1); + REQUIRE(to_push->n_configs == 1); + free(to_push); + + const char* tmphash; // test suite cheat: &(tmphash = "asdf") to fake a length-1 array. // Pretend we uploaded it - config_confirm_pushed(conf, seqno, "hash1"); + config_confirm_pushed(conf, seqno, &(tmphash = "hash1"), 1); CHECK(config_needs_dump(conf)); CHECK_FALSE(config_needs_push(conf)); @@ -335,16 +362,19 @@ TEST_CASE("Conversations (C API)", "[config][conversations][c]") { to_push = config_push(conf2); CHECK(to_push->seqno == 2); + REQUIRE(to_push->n_configs == 1); const char* hash_data[1]; const unsigned char* merge_data[1]; size_t merge_size[1]; hash_data[0] = "hash123"; - merge_data[0] = to_push->config; - merge_size[0] = to_push->config_len; - int accepted = config_merge(conf, hash_data, merge_data, merge_size, 1); - REQUIRE(accepted == 1); - config_confirm_pushed(conf2, seqno, "hash123"); + merge_data[0] = to_push->config[0]; + merge_size[0] = to_push->config_lens[0]; + config_string_list* accepted = config_merge(conf, hash_data, merge_data, merge_size, 1); + REQUIRE(accepted->len == 1); + CHECK(accepted->value[0] == "hash123"sv); + free(accepted); + config_confirm_pushed(conf2, seqno, &(tmphash = "hash123"), 1); free(to_push); CHECK_FALSE(config_needs_push(conf)); @@ -366,9 +396,9 @@ TEST_CASE("Conversations (C API)", "[config][conversations][c]") { if (convo_info_volatile_it_is_1to1(it, &c1)) { seen.push_back("1-to-1: "s + c1.session_id); } else if (convo_info_volatile_it_is_community(it, &c2)) { - seen.push_back("og: "s + c2.base_url + "/r/" + c2.room); + seen.push_back("comm: "s + c2.base_url + "/r/" + c2.room); } else if (convo_info_volatile_it_is_legacy_group(it, &c3)) { - seen.push_back("cl: "s + c3.group_id); + seen.push_back("lgr: "s + c3.group_id); } } convo_info_volatile_iterator_free(it); @@ -378,8 +408,8 @@ TEST_CASE("Conversations (C API)", "[config][conversations][c]") { "051111111111111111111111111111111111111111111111111111111111111111", "1-to-1: " "055000000000000000000000000000000000000000000000000000000000000000", - "og: http://example.org:5678/r/sudokuroom", - "cl: " + "comm: http://example.org:5678/r/sudokuroom", + "lgr: " "05ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" "c"}}); } @@ -453,10 +483,11 @@ TEST_CASE("Conversation pruning", "[config][conversations][pruning]") { CHECK(oxenc::to_hex(seed.begin(), seed.end()) == oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); - session::config::ConvoInfoVolatile convos{ustring_view{seed}, std::nullopt}; + session::config::ConvoInfoVolatile convos{std::span{seed}, std::nullopt}; - auto some_pubkey = [](unsigned char x) -> ustring { - ustring s = "0000000000000000000000000000000000000000000000000000000000000000"_hexbytes; + auto some_pubkey = [](unsigned char x) -> std::vector { + std::vector s = + "0000000000000000000000000000000000000000000000000000000000000000"_hexbytes; s[31] = x; return s; }; @@ -464,9 +495,6 @@ TEST_CASE("Conversation pruning", "[config][conversations][pruning]") { auto pk = some_pubkey(x); return "05" + oxenc::to_hex(pk.begin(), pk.end()); }; - auto some_og_url = [&](unsigned char x) -> std::string { - return "https://example.com/r/room"s + std::to_string(x); - }; const auto now = std::chrono::system_clock::now() - 1ms; auto unix_timestamp = [&now](int days_ago) -> int64_t { return std::chrono::duration_cast( @@ -488,7 +516,7 @@ TEST_CASE("Conversation pruning", "[config][conversations][pruning]") { convos.set(c); } else { auto c = convos.get_or_construct_community( - "https://example.org", "room" + std::to_string(i), some_pubkey(i)); + "https://example.org", "room{}"_format(i), some_pubkey(i)); c.last_read = unix_timestamp(i); if (i % 5 == 0) c.unread = true; @@ -565,9 +593,13 @@ TEST_CASE("Conversation dump/load state bug", "[config][conversations][dump-load // Fake push: config_push_data* to_push = config_push(conf); seqno_t seqno = to_push->seqno; + REQUIRE(to_push->n_configs == 1); free(to_push); CHECK(seqno == 1); - config_confirm_pushed(conf, seqno, "somehash"); + + const char* tmphash; // test suite cheat: &(tmphash = "asdf") to fake a length-1 array. + + config_confirm_pushed(conf, seqno, &(tmphash = "somehash"), 1); CHECK(config_needs_dump(conf)); // Dump: @@ -591,7 +623,8 @@ TEST_CASE("Conversation dump/load state bug", "[config][conversations][dump-load to_push = config_push(conf); CHECK(to_push->seqno == 2); - config_confirm_pushed(conf, to_push->seqno, "hash5235"); + REQUIRE(to_push->n_configs == 1); + config_confirm_pushed(conf, to_push->seqno, &(tmphash = "hash5235"), 1); // But *before* we load the push make a dirtying change to conf2 that we *don't* push (so that // we'll be merging into a dirty-state config): @@ -607,10 +640,14 @@ TEST_CASE("Conversation dump/load state bug", "[config][conversations][dump-load const unsigned char* merge_data[1]; size_t merge_size[1]; merge_hash[0] = "hash5235"; - merge_data[0] = to_push->config; - merge_size[0] = to_push->config_len; - - config_merge(conf2, merge_hash, merge_data, merge_size, 1); + REQUIRE(to_push->n_configs == 1); + merge_data[0] = to_push->config[0]; + merge_size[0] = to_push->config_lens[0]; + + config_string_list* accepted = config_merge(conf2, merge_hash, merge_data, merge_size, 1); + REQUIRE(accepted->len == 1); + CHECK(accepted->value[0] == "hash5235"sv); + free(accepted); free(to_push); CHECK(config_needs_push(conf2)); @@ -627,7 +664,8 @@ TEST_CASE("Conversation dump/load state bug", "[config][conversations][dump-load CHECK(config_needs_push(conf2)); to_push = config_push(conf2); CHECK(to_push->seqno == 3); - config_confirm_pushed(conf2, to_push->seqno, "hashz"); + REQUIRE(to_push->n_configs == 1); + config_confirm_pushed(conf2, to_push->seqno, &(tmphash = "hashz"), 1); CHECK_FALSE(config_needs_push(conf2)); config_dump(conf2, &dump, &dumplen); diff --git a/tests/test_config_user_groups.cpp b/tests/test_config_user_groups.cpp index cccf18bf..db94916b 100644 --- a/tests/test_config_user_groups.cpp +++ b/tests/test_config_user_groups.cpp @@ -4,15 +4,14 @@ #include #include +#include #include #include #include +#include "session/config/notify.hpp" #include "utils.hpp" -using namespace std::literals; -using namespace oxenc::literals; - static constexpr int64_t created_ts = 1680064059; TEST_CASE("Open Group URLs", "[config][community_urls]") { @@ -95,7 +94,7 @@ TEST_CASE("User Groups", "[config][groups]") { CHECK(oxenc::to_hex(seed.begin(), seed.end()) == oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); - session::config::UserGroups groups{ustring_view{seed}, std::nullopt}; + session::config::UserGroups groups{std::span{seed}, std::nullopt}; constexpr auto definitely_real_id = "055000000000000000000000000000000000000000000000000000000000000000"sv; @@ -137,7 +136,7 @@ TEST_CASE("User Groups", "[config][groups]") { c.name = "Englishmen"; c.disappearing_timer = 60min; - c.joined_at = created_ts; + c.joined_at = created_ts * 1000; // milliseconds c.notifications = session::config::notify_mode::mentions_only; c.mute_until = now + 3600; CHECK(c.insert(users[0], false)); @@ -167,8 +166,8 @@ TEST_CASE("User Groups", "[config][groups]") { lg_pk.data(), lg_sk.data(), reinterpret_cast(lgroup_seed.data())); // Note: this isn't exactly what Session actually does here for legacy closed groups (rather it // uses X25519 keys) but for this test the distinction doesn't matter. - c.enc_pubkey.assign(lg_pk.data(), lg_pk.size()); - c.enc_seckey.assign(lg_sk.data(), 32); + c.enc_pubkey.assign(lg_pk.data(), lg_pk.data() + lg_pk.size()); + c.enc_seckey.assign(lg_sk.data(), lg_sk.data() + 32); c.priority = 3; CHECK(to_hex(c.enc_pubkey) == oxenc::to_hex(lg_pk.begin(), lg_pk.end())); @@ -197,6 +196,10 @@ TEST_CASE("User Groups", "[config][groups]") { // The new data doesn't get stored until we call this: groups.set(og); + auto fake_group_id = "030101010101010101010101010101010101010101010101010101010101010101"s; + auto ggg = groups.get_or_construct_group(fake_group_id); + groups.set(ggg); + auto [seqno, to_push, obs] = groups.push(); auto to_push1 = to_push; @@ -204,7 +207,7 @@ TEST_CASE("User Groups", "[config][groups]") { CHECK(obs.empty()); // Pretend we uploaded it - groups.confirm_pushed(seqno, "fakehash1"); + groups.confirm_pushed(seqno, {"fakehash1"}); CHECK(groups.needs_dump()); CHECK_FALSE(groups.needs_push()); @@ -214,7 +217,7 @@ TEST_CASE("User Groups", "[config][groups]") { std::tie(seqno, to_push, obs) = groups.push(); CHECK(seqno == 1); CHECK(obs.empty()); - CHECK(groups.current_hashes() == std::vector{{"fakehash1"s}}); + CHECK(groups.curr_hashes() == std::unordered_set{{"fakehash1"s}}); CHECK_FALSE(groups.needs_dump()); // Because we just called dump() above, to load up g2 CHECK_FALSE(g2.needs_push()); @@ -223,11 +226,12 @@ TEST_CASE("User Groups", "[config][groups]") { CHECK(seqno == 1); CHECK_FALSE(g2.needs_dump()); CHECK(obs.empty()); - CHECK(g2.current_hashes() == std::vector{{"fakehash1"s}}); + CHECK(g2.curr_hashes() == std::unordered_set{{"fakehash1"s}}); - CHECK(g2.size() == 2); + CHECK(g2.size() == 3); CHECK(g2.size_communities() == 1); CHECK(g2.size_legacy_groups() == 1); + CHECK(g2.size_groups() == 1); auto x1 = g2.get_legacy_group(definitely_real_id); REQUIRE(x1); @@ -255,16 +259,18 @@ TEST_CASE("User Groups", "[config][groups]") { if (auto* lg = std::get_if(&group)) { auto [admins, members] = lg->counts(); seen.push_back( - "legacy: " + lg->name + ", " + std::to_string(admins) + " admins, " + - std::to_string(members) + " members"); + "legacy: {}, {} admins, {} members"_format(lg->name, admins, members)); } else if (auto* og = std::get_if(&group)) { seen.push_back("community: " + og->base_url() + "/r/" + og->room()); + } else if (auto* g = std::get_if(&group)) { + seen.push_back("group: " + g->id); } else { seen.push_back("unknown"); } } CHECK(seen == std::vector{ + "group: " + fake_group_id, "community: http://example.org:5678/r/SudokuRoom", "legacy: Englishmen, 1 admins, 2 members", }); @@ -288,13 +294,13 @@ TEST_CASE("User Groups", "[config][groups]") { CHECK(g2.needs_push()); CHECK(g2.needs_dump()); - CHECK(g2.current_hashes().empty()); + CHECK(g2.curr_hashes().empty()); std::tie(seqno, to_push, obs) = g2.push(); - CHECK(g2.current_hashes().empty()); + CHECK(g2.curr_hashes().empty()); auto to_push2 = to_push; CHECK(seqno == 2); - g2.confirm_pushed(seqno, "fakehash2"); - CHECK(g2.current_hashes() == std::vector{{"fakehash2"s}}); + g2.confirm_pushed(seqno, {"fakehash2"}); + CHECK(g2.curr_hashes() == std::unordered_set{{"fakehash2"s}}); CHECK(as_set(obs) == make_set("fakehash1"s)); g2.dump(); @@ -303,16 +309,18 @@ TEST_CASE("User Groups", "[config][groups]") { CHECK(std::get(g2.push()) == 2); CHECK_FALSE(g2.needs_dump()); - std::vector> to_merge; - to_merge.emplace_back("fakehash2", to_push); + REQUIRE(to_push.size() == 1); + std::vector>> to_merge; + to_merge.emplace_back("fakehash2", to_push[0]); groups.merge(to_merge); auto x3 = groups.get_community("http://example.org:5678", "SudokuRoom"); REQUIRE(x3.has_value()); CHECK(x3->room() == "sudokuRoom"); // We picked up the capitalization change - CHECK(groups.size() == 2); + CHECK(groups.size() == 3); CHECK(groups.size_communities() == 1); CHECK(groups.size_legacy_groups() == 1); + CHECK(groups.size_groups() == 1); CHECK(c1.insert(users[4], false)); CHECK(c1.insert(users[5], true)); @@ -337,19 +345,21 @@ TEST_CASE("User Groups", "[config][groups]") { g2.erase_community("http://exAMple.ORG:5678/", "sudokuROOM"); std::tie(seqno, to_push, obs) = g2.push(); - g2.confirm_pushed(seqno, "fakehash3"); + g2.confirm_pushed(seqno, {"fakehash3"}); auto to_push3 = to_push; CHECK(seqno == 3); CHECK(as_set(obs) == make_set("fakehash2"s)); - CHECK(g2.current_hashes() == std::vector{{"fakehash3"s}}); + CHECK(g2.curr_hashes() == std::unordered_set{{"fakehash3"s}}); to_merge.clear(); - to_merge.emplace_back("fakehash3", to_push); + REQUIRE(to_push.size() == 1); + to_merge.emplace_back("fakehash3", to_push[0]); groups.merge(to_merge); - CHECK(groups.size() == 1); + CHECK(groups.size() == 2); CHECK(groups.size_communities() == 0); CHECK(groups.size_legacy_groups() == 1); + CHECK(groups.size_groups() == 1); int prio = 0; auto beanstalk_pubkey = "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"; @@ -360,25 +370,30 @@ TEST_CASE("User Groups", "[config][groups]") { groups.set(g); } - CHECK(groups.size() == 5); + CHECK(groups.size() == 6); CHECK(groups.size_communities() == 4); CHECK(groups.size_legacy_groups() == 1); + CHECK(groups.size_groups() == 1); std::tie(seqno, to_push, obs) = groups.push(); - groups.confirm_pushed(seqno, "fakehash4"); + groups.confirm_pushed(seqno, {"fakehash4"}); CHECK(seqno == 4); CHECK(as_set(obs) == make_set("fakehash1"s, "fakehash2", "fakehash3")); + REQUIRE(to_push.size() == 1); + REQUIRE(to_push1.size() == 1); + REQUIRE(to_push2.size() == 1); + REQUIRE(to_push3.size() == 1); to_merge.clear(); // Load some obsolete ones in just to check that they get immediately obsoleted - to_merge.emplace_back("fakehash10", to_push3); - to_merge.emplace_back("fakehash11", to_push1); - to_merge.emplace_back("fakehash12", to_push2); - to_merge.emplace_back("fakehash4", to_push); + to_merge.emplace_back("fakehash10", to_push3[0]); + to_merge.emplace_back("fakehash11", to_push1[0]); + to_merge.emplace_back("fakehash12", to_push2[0]); + to_merge.emplace_back("fakehash4", to_push[0]); g2.merge(to_merge); CHECK(g2.needs_dump()); CHECK_FALSE(g2.needs_push()); - CHECK(g2.current_hashes() == std::vector{{"fakehash4"s}}); + CHECK(g2.curr_hashes() == std::unordered_set{{"fakehash4"s}}); std::tie(seqno, to_push, obs) = g2.push(); CHECK(seqno == 4); CHECK(as_set(obs) == make_set("fakehash10"s, "fakehash11", "fakehash12", "fakehash3")); @@ -389,16 +404,18 @@ TEST_CASE("User Groups", "[config][groups]") { if (auto* lg = std::get_if(&group)) { auto [admins, members] = lg->counts(); seen.push_back( - "legacy: " + lg->name + ", " + std::to_string(admins) + " admins, " + - std::to_string(members) + " members"); + "legacy: {}, {} admins, {} members"_format(lg->name, admins, members)); } else if (auto* og = std::get_if(&group)) { seen.push_back("community: " + og->base_url() + "/r/" + og->room()); + } else if (auto* g = std::get_if(&group)) { + seen.push_back("group: " + g->id); } else { seen.push_back("unknown"); } } CHECK(seen == std::vector{ + "group: " + fake_group_id, "community: http://jacksbeanstalk.org/r/fee", "community: http://jacksbeanstalk.org/r/fi", "community: http://jacksbeanstalk.org/r/fo", @@ -408,6 +425,161 @@ TEST_CASE("User Groups", "[config][groups]") { } } +TEST_CASE("User Groups -- (non-legacy) groups", "[config][groups][new]") { + + const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; + std::array ed_pk, curve_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair( + ed_pk.data(), ed_sk.data(), reinterpret_cast(seed.data())); + int rc = crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed_pk.data()); + REQUIRE(rc == 0); + + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"); + REQUIRE(oxenc::to_hex(curve_pk.begin(), curve_pk.end()) == + "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + CHECK(oxenc::to_hex(seed.begin(), seed.end()) == + oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); + + session::config::UserGroups groups{std::span{seed}, std::nullopt}; + + constexpr auto definitely_real_id = + "035000000000000000000000000000000000000000000000000000000000000000"sv; + + CHECK_FALSE(groups.get_group(definitely_real_id)); + + CHECK(groups.empty()); + CHECK(groups.size() == 0); + + auto c = groups.get_or_construct_group(definitely_real_id); + + CHECK(c.secretkey.empty()); + CHECK(c.id == definitely_real_id); + CHECK(c.priority == 0); + CHECK(c.joined_at == 0); + CHECK(c.notifications == session::config::notify_mode::defaulted); + CHECK(c.mute_until == 0); + + c.secretkey = session::to_vector(ed_sk); // This *isn't* the right secret key for the group, so + // won't propagate, and so auth data will: + c.auth_data = + "01020304050000000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000"_hexbytes; + + groups.set(c); + + CHECK(groups.needs_push()); + CHECK(groups.needs_dump()); + + auto [seqno, to_push, obs] = groups.push(); + groups.confirm_pushed(seqno, {"fakehash1"}); + + auto d1 = groups.dump(); + + session::config::UserGroups g2{std::span{seed}, d1}; + + auto c2 = g2.get_group(definitely_real_id); + REQUIRE(c2.has_value()); + + CHECK(c2->id == definitely_real_id); + CHECK(c2->priority == 0); + CHECK(c2->joined_at == 0); + CHECK(c2->notifications == session::config::notify_mode::defaulted); + CHECK(c2->mute_until == 0); + CHECK_FALSE(c2->invited); + CHECK(c2->name == ""); + + c2->priority = 123; + c2->joined_at = (int64_t)1'234'567'890 * 1'000; + c2->notifications = session::config::notify_mode::mentions_only; + c2->mute_until = (int64_t)456'789'012 * 1'000'000; + c2->invited = true; + c2->name = "Magic Special Room"; + + g2.set(*c2); + + auto c2b = g2.get_or_construct_group("03" + oxenc::to_hex(ed_pk.begin(), ed_pk.end())); + c2b.secretkey = + session::to_vector(ed_sk); // This one does match the group ID, so should propagate + c2b.auth_data = // should get ignored, since we have a valid secret key set: + "01020304050000000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000"_hexbytes; + g2.set(c2b); + + std::tie(seqno, to_push, obs) = g2.push(); + g2.confirm_pushed(seqno, {"fakehash2"}); + + REQUIRE(to_push.size() == 1); + std::vector>> to_merge; + to_merge.emplace_back("fakehash2", to_push[0]); + groups.merge(to_merge); + + auto c3 = groups.get_group(definitely_real_id); + REQUIRE(c3.has_value()); + CHECK(c3->secretkey.empty()); + CHECK(to_hex(c3->auth_data) == + "01020304050000000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000"); + CHECK(c3->id == definitely_real_id); + CHECK(c3->priority == 123); + CHECK(c3->joined_at == 1234567890); + CHECK(c3->notifications == session::config::notify_mode::mentions_only); + CHECK(c3->mute_until == 456789012); + CHECK(c3->invited); + CHECK(c3->name == "Magic Special Room"); + + groups.erase(*c3); + + auto c3b = groups.get_group("03" + oxenc::to_hex(ed_pk.begin(), ed_pk.end())); + REQUIRE(c3b); + CHECK(c3b->auth_data.empty()); + CHECK(to_hex(c3b->secretkey) == to_hex(seed) + oxenc::to_hex(ed_pk.begin(), ed_pk.end())); + CHECK_FALSE(c3b->kicked()); + c3b->auth_data.resize(100); + CHECK_FALSE(c3b->kicked()); + // mark ourselves as kicked + c3b->mark_kicked(); + CHECK(c3b->kicked()); + CHECK(c3b->secretkey.empty()); + CHECK(c3b->auth_data.empty()); + // add a non empty auth_data, and reset the removed_status: we shouldn't be kicked anymore + c3b->auth_data.resize(100); + c3b->mark_invited(); + CHECK_FALSE(c3b->kicked()); + // we are not kicked, mark the group as destroyed + c3b->mark_destroyed(); + CHECK(c3b->is_destroyed()); + // the group was destroyed, so we are not `kicked` from it. + // We keep the states separate as `kicked` is not permanent but `destroyed` is. + CHECK_FALSE(c3b->kicked()); + + // reset the state to test that the transition kicked->destroyed works only in that direction + c3b->auth_data.resize(100); + c3b->removed_status = 0; + CHECK_FALSE(c3b->kicked()); + // kicked->destroyed works + c3b->mark_kicked(); + CHECK(c3b->kicked()); + c3b->mark_destroyed(); + CHECK(c3b->is_destroyed()); + // destroyed->kicked doesn't work + c3b->mark_kicked(); + CHECK_FALSE(c3b->kicked()); + CHECK(c3b->is_destroyed()); + + auto gg = groups.get_or_construct_group( + "030303030303030303030303030303030303030303030303030303030303030303"); + groups.set(gg); + CHECK(groups.erase_group("030303030303030303030303030303030303030303030303030303030303030303")); + CHECK_FALSE( + groups.erase_group("03030303030303030303030303030303030303030303030303030303030303030" + "3")); +} + TEST_CASE("User Groups members C API", "[config][groups][c]") { const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; @@ -519,7 +691,7 @@ TEST_CASE("User Groups members C API", "[config][groups][c]") { // The "normal" way to set a group when you're done with it (also properly frees `group`). user_groups_set_free_legacy_group(conf, group); - config_string_list* hashes = config_current_hashes(conf); + config_string_list* hashes = config_curr_hashes(conf); REQUIRE(hashes); CHECK(hashes->len == 0); free(hashes); @@ -527,24 +699,34 @@ TEST_CASE("User Groups members C API", "[config][groups][c]") { config_push_data* to_push = config_push(conf); CHECK(to_push->seqno == 1); - hashes = config_current_hashes(conf); + hashes = config_curr_hashes(conf); REQUIRE(hashes); CHECK(hashes->len == 0); free(hashes); - config_confirm_pushed(conf, to_push->seqno, "fakehash1"); + const char* tmphash; // test suite cheat: &(tmphash = "asdf") to fake a length-1 array. - hashes = config_current_hashes(conf); + config_confirm_pushed(conf, to_push->seqno, &(tmphash = "fakehash1"), 1); + + hashes = config_curr_hashes(conf); REQUIRE(hashes); REQUIRE(hashes->len == 1); CHECK(hashes->value[0] == "fakehash1"sv); free(hashes); - session::config::UserGroups c2{ustring_view{seed}, std::nullopt}; + size_t key_len; + unsigned char* keys = config_get_keys(conf, &key_len); + REQUIRE(keys); + REQUIRE(key_len == 1); + + session::config::UserGroups c2{std::span{seed}, std::nullopt}; - std::vector> to_merge; - to_merge.emplace_back("fakehash1", ustring_view{to_push->config, to_push->config_len}); - CHECK(c2.merge(to_merge) == 1); + REQUIRE(to_push->n_configs == 1); + std::vector>> to_merge; + to_merge.emplace_back( + "fakehash1", + std::span{to_push->config[0], to_push->config_lens[0]}); + CHECK(c2.merge(to_merge) == std::unordered_set{{"fakehash1"}}); auto grp = c2.get_legacy_group(definitely_real_id); REQUIRE(grp); @@ -568,7 +750,7 @@ TEST_CASE("User groups empty member bug", "[config][groups][bug]") { CHECK(oxenc::to_hex(seed.begin(), seed.end()) == oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); - session::config::UserGroups c{ustring_view{seed}, std::nullopt}; + session::config::UserGroups c{std::span{seed}, std::nullopt}; CHECK_FALSE(c.needs_push()); @@ -587,7 +769,7 @@ TEST_CASE("User groups empty member bug", "[config][groups][bug]") { auto [seqno, data, obs] = c.push(); CHECK(seqno == 1); auto d = c.dump(); - c.confirm_pushed(seqno, "fakehash1"); + c.confirm_pushed(seqno, {"fakehash1"}); CHECK_FALSE(c.needs_push()); { @@ -638,6 +820,98 @@ TEST_CASE("User groups empty member bug", "[config][groups][bug]") { } } +TEST_CASE("User groups mute_until & joined_at are always seconds", "[config][groups][bug]") { + // Tests a bug where setting legacy group with empty members (or empty admin) list would dirty + // the config, even when the current members (or admin) list is empty. (This isn't strictly + // specific to user groups, but that's where the bug is easily encountered). + + const auto seed = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000"_hexbytes; + std::array ed_pk, curve_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair( + ed_pk.data(), ed_sk.data(), reinterpret_cast(seed.data())); + int rc = crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed_pk.data()); + REQUIRE(rc == 0); + + CHECK(oxenc::to_hex(seed.begin(), seed.end()) == + oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); + + session::config::UserGroups c{std::span{seed}, std::nullopt}; + + CHECK_FALSE(c.needs_push()); + + { + auto lg = c.get_or_construct_legacy_group( + "051234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); + int64_t joined_at = get_timestamp_us(); + int64_t mute_until = get_timestamp_s(); + lg.joined_at = joined_at; + lg.mute_until = mute_until; + c.set(lg); + auto lg2 = c.get_or_construct_legacy_group( + "051234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); + CHECK(lg2.joined_at == joined_at / 1'000'000); // joined_at was given in microseconds + CHECK(lg2.mute_until == mute_until); // mute_until was given in seconds + c.erase_legacy_group("051234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); + } + + { + auto gr = c.get_or_construct_group( + "031234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); + int64_t joined_at = get_timestamp_ms(); + int64_t mute_until = get_timestamp_us(); + gr.joined_at = joined_at; + gr.mute_until = mute_until; + c.set(gr); + auto gr2 = c.get_or_construct_group( + "031234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); + CHECK(gr2.joined_at == joined_at / 1'000); // joined_at was given in milliseconds + CHECK(gr2.mute_until == mute_until / 1'000'000); // mute_until was given in microseconds + c.erase_group("031234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); + } + + { + const auto open_group_pubkey = + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"_hexbytes; + const auto url = "http://example.org:5678"; + const auto room = "sudoku_room"; + auto comm = c.get_or_construct_community(url, room, open_group_pubkey); + int64_t joined_at = get_timestamp_ms(); + int64_t mute_until = get_timestamp_ms(); + comm.joined_at = joined_at; + comm.mute_until = mute_until; + c.set(comm); + auto comm2 = c.get_or_construct_community(url, room, open_group_pubkey); + CHECK(comm2.joined_at == joined_at / 1'000); // joined_at was given in milliseconds + CHECK(comm2.mute_until == mute_until / 1'000); // mute_until was given in milliseconds + c.erase_community(url, room); + } + { + // this dump has: + // - an invalid joined_at (1'733'979'503'520) and + // - an invalid mute_until (1'733'979'503'520'780) values + const auto dump_with_not_seconds = + "64313a21693165313a243231303a64313a23693165313a2664313a676433333a031234567890abcdef" + "1234" + "567890abcdef1234567890abcdef1234567890abcdef64313a21693137333339373935303335323037" + "3830" + "65313a4b303a313a6a693137333339373935303335323065656565313a3c6c6c69306533323aea173b" + "57be" + "ca8af18c3519a7bbf69c3e7a05d1c049fa9558341d8ebb48b0c96564656565313a3d64313a67643333" + "3a03" + "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef64313a21303a313a4b" + "303a" + "313a6a303a65656565313a28303a313a296c6565"_hexbytes; + session::config::UserGroups c2{std::span{seed}, dump_with_not_seconds}; + + auto gr = c2.get_or_construct_group( + "031234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); + + CHECK(gr.joined_at == 1'733'979'503'520 / 1'000); + CHECK(gr.mute_until == 1'733'979'503'520'780 / 1'000'000); + } +} + namespace Catch { template <> struct StringMaker> { diff --git a/tests/test_config_userprofile.cpp b/tests/test_config_userprofile.cpp index 47c547b2..ce7a625f 100644 --- a/tests/test_config_userprofile.cpp +++ b/tests/test_config_userprofile.cpp @@ -5,12 +5,59 @@ #include #include +#include +#include #include #include "utils.hpp" using namespace std::literals; -using namespace oxenc::literals; + +TEST_CASE("UserProfile", "[config][user_profile]") { + + const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; + std::array ed_pk, curve_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair( + ed_pk.data(), ed_sk.data(), reinterpret_cast(seed.data())); + int rc = crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed_pk.data()); + REQUIRE(rc == 0); + + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"); + REQUIRE(oxenc::to_hex(curve_pk.begin(), curve_pk.end()) == + "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + CHECK(oxenc::to_hex(seed.begin(), seed.end()) == + oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); + + session::config::UserProfile profile{std::span{seed}, std::nullopt}; + + CHECK_THROWS( + profile.set_name("123456789012345678901234567890123456789012345678901234567890123456789" + "01" + "23456789012345678901234567890A")); + CHECK_NOTHROW( + profile.set_name_truncated("12345678901234567890123456789012345678901234567890123456789" + "01" + "234567890123456789012345678901234567890A")); + CHECK(profile.get_name() == + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567890"); + CHECK_NOTHROW( + profile.set_name_truncated("12345678901234567890123456789012345678901234567890123456789" + "01" + "234567890123456789012345678901234567🎂")); + CHECK(profile.get_name() == + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567"); + CHECK_NOTHROW( + profile.set_name_truncated("12345678901234567890123456789012345678901234567890123456789" + "01" + "2345678901234567890123456789012345🎂🎂")); + CHECK(profile.get_name() == + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" + "9012345🎂"); +} TEST_CASE("user profile C API", "[config][user_profile][c]") { @@ -34,8 +81,6 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { rc = user_profile_init(&conf, ed_sk.data(), NULL, 0, err); REQUIRE(rc == 0); - config_set_logger(conf, log_msg, NULL); - // We don't need to push anything, since this is an empty config CHECK_FALSE(config_needs_push(conf)); // And we haven't changed anything so don't need to dump to db @@ -50,23 +95,27 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { config_push_data* to_push = config_push(conf); REQUIRE(to_push); CHECK(to_push->seqno == 0); - CHECK(to_push->config_len == 256); + REQUIRE(to_push->n_configs == 1); + CHECK(to_push->config_lens[0] == 256 + 176); // 176 = protobuf overhead const char* enc_domain = "UserProfile"; REQUIRE(config_encryption_domain(conf) == std::string_view{enc_domain}); - size_t to_push_decr_size; - unsigned char* to_push_decrypted = config_decrypt( - to_push->config, to_push->config_len, ed_sk.data(), enc_domain, &to_push_decr_size); - REQUIRE(to_push_decrypted); - CHECK(to_push_decr_size == 216); // 256 - 40 overhead - CHECK(printable(to_push_decrypted, to_push_decr_size) == - printable( - ustring(193, '\0') + // null prefix padding - "d1:#i0e1:&de1:config[0], to_push->config[0] + to_push->config_lens[0]) == + "080112ab030a0012001aa20308062801429b0326ec9746282053eb119228e6c36012966e7d2642163169ba39" + "98af44ca65f967768dd78ee80fffab6f809f6cef49c73a36c82a89622ff0de2ceee06b8c638e2c876fa9047f" + "449dbe24b1fc89281a264fe90abdeffcdd44f797bd4572a6c5ae8d88bf372c3c717943ebd570222206fabf0e" + "e9f3c6756f5d71a32616b1df53d12887961f5c129207a79622ccc1a4bba976886d9a6ddf0fe5d570e5075d01" + "ecd627f656e95f27b4c40d5661b5664cedd3e568206effa1308b0ccd663ca61a6d39c0731891804a8cf5edcf" + "8b98eaa5580c3d436e22156e38455e403869700956c3c1dd0b4470b663e75c98c5b859b53ccef6559215d804" + "9f755be9c2d6b3f4a310f97c496fc392f65b6431dd87788ac61074fd8cd409702e1b839b3f774d38cf8b28f0" + "226c4efa5220ac6ae060793e36e7ef278d42d042f15b21291f3bb29e3158f09d154b93f83fd8a319811a26cb" + "5240d90cbb360fafec0b7eff4c676ae598540813d062dc9468365c73b4cfa2ffd02d48cdcd8f0c71324c6d0a" + "60346a7a0e50af3be64684b37f9e6c831115bf112ddd18acde08eaec376f0872a3952000"); free(to_push); - free(to_push_decrypted); // These should also be unset: auto pic = user_profile_get_pic(conf); @@ -88,10 +137,11 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { CHECK(name == "Kallie"sv); pic = user_profile_get_pic(conf); - REQUIRE(pic.url); - REQUIRE(pic.key); + REQUIRE(pic.url != ""s); + REQUIRE(pic.key != session::to_vector("").data()); CHECK(pic.url == "http://example.org/omg-pic-123.bmp"sv); - CHECK(ustring_view{pic.key, 32} == "secret78901234567890123456789012"_bytes); + CHECK(session::to_vector(std::span{pic.key, 32}) == + "secret78901234567890123456789012"_bytes); CHECK(user_profile_get_nts_priority(conf) == 9); @@ -109,7 +159,7 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { // The data to be actually pushed, expanded like this to make it somewhat human-readable: // clang-format off - auto exp_push1_decrypted = + auto exp_push1_decrypted = session::to_vector( "d" "1:#" "i1e" "1:&" "d" @@ -119,7 +169,7 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { "1:q" "32:secret78901234567890123456789012" "e" "1:<" "l" - "l" "i0e" "32:"_bytes + exp_hash0 + "de" "e" + "l" "i0e" "32:" + session::to_string(exp_hash0) + "de" "e" "e" "1:=" "d" "1:+" "0:" @@ -127,7 +177,7 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { "1:p" "0:" "1:q" "0:" "e" - "e"_bytes; + "e"); // clang-format on auto exp_push1_encrypted = "9693a69686da3055f1ecdfb239c3bf8e746951a36d888c2fb7c02e856a5c2091b24e39a7e1af828f" @@ -138,16 +188,6 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { "056009a9ebf58d45d7d696b74e0c7ff0499c4d23204976f19561dc0dba6dc53a2497d28ce03498ea" "49bf122762d7bc1d6d9c02f6d54f8384"_hexbytes; - CHECK(oxenc::to_hex(to_push->config, to_push->config + to_push->config_len) == - to_hex(exp_push1_encrypted)); - - // Raw decryption doesn't unpad (i.e. the padding is part of the encrypted data) - to_push_decrypted = config_decrypt( - to_push->config, to_push->config_len, ed_sk.data(), enc_domain, &to_push_decr_size); - CHECK(to_push_decr_size == 256 - 40); - CHECK(printable(to_push_decrypted, to_push_decr_size) == - printable(ustring(256 - 40 - exp_push1_decrypted.size(), '\0') + exp_push1_decrypted)); - // Copy this out; we need to hold onto it to do the confirmation later on seqno_t seqno = to_push->seqno; @@ -171,15 +211,19 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { CHECK(printable(dump1, dump1len) == printable( "d" "1:!" "i2e" - "1:$" + std::to_string(exp_push1_decrypted.size()) + ":" + std::string{to_sv(exp_push1_decrypted)} + "" - "1:(" "0:" + "1:${}:{}" + "1:(" "le" "1:)" "le" - "e")); + "1:*" "de" + "1:+" "de" + "e"_format(exp_push1_decrypted.size(), session::to_string(exp_push1_decrypted)))); // clang-format on free(dump1); // done with the dump; don't leak! + const char* tmphash; // test suite cheat: &(tmphash = "asdf") to fake a length-1 array. + // So now imagine we got back confirmation from the swarm that the push has been stored: - config_confirm_pushed(conf, seqno, "fakehash1"); + config_confirm_pushed(conf, seqno, &(tmphash = "fakehash1"), 1); CHECK_FALSE(config_needs_push(conf)); CHECK(config_needs_dump(conf)); // The confirmation changes state, so this makes us need a dump @@ -190,10 +234,12 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { CHECK(printable(dump1, dump1len) == printable( "d" "1:!" "i0e" - "1:$" + std::to_string(exp_push1_decrypted.size()) + ":" + std::string{to_sv(exp_push1_decrypted)} + "" - "1:(" "9:fakehash1" + "1:${}:{}" + "1:(" "l" "9:fakehash1" "e" "1:)" "le" - "e")); + "1:*" "de" + "1:+" "de" + "e"_format(exp_push1_decrypted.size(), session::to_string(exp_push1_decrypted)))); // clang-format on free(dump1); @@ -205,7 +251,6 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { // Start with an empty config, as above: config_object* conf2; REQUIRE(user_profile_init(&conf2, ed_sk.data(), NULL, 0, err) == 0); - config_set_logger(conf2, log_msg, NULL); CHECK_FALSE(config_needs_dump(conf2)); // Now imagine we just pulled down the encrypted string from the swarm; we merge it into conf2: @@ -215,8 +260,10 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { merge_hash[0] = "fakehash1"; merge_data[0] = exp_push1_encrypted.data(); merge_size[0] = exp_push1_encrypted.size(); - int accepted = config_merge(conf2, merge_hash, merge_data, merge_size, 1); - REQUIRE(accepted == 1); + config_string_list* accepted = config_merge(conf2, merge_hash, merge_data, merge_size, 1); + REQUIRE(accepted->len == 1); + CHECK(accepted->value[0] == "fakehash1"sv); + free(accepted); // Our state has changed, so we need to dump: CHECK(config_needs_dump(conf2)); @@ -257,12 +304,13 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { CHECK(config_needs_push(conf)); CHECK(config_needs_push(conf2)); to_push = config_push(conf); + CHECK(to_push->seqno == 2); // incremented, since we made a field change - config_confirm_pushed(conf2, to_push->seqno, "fakehash2"); + config_confirm_pushed(conf2, to_push->seqno, &(tmphash = "fakehash2"), 1); config_push_data* to_push2 = config_push(conf2); CHECK(to_push2->seqno == 2); // incremented, since we made a field change - config_confirm_pushed(conf2, to_push2->seqno, "fakehash3"); + config_confirm_pushed(conf2, to_push2->seqno, &(tmphash = "fakehash3"), 1); config_dump(conf, &dump1, &dump1len); config_dump(conf2, &dump2, &dump2len); @@ -272,8 +320,10 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { // Since we set different things, we're going to get back different serialized data to be // pushed: - CHECK(printable(to_push->config, to_push->config_len) != - printable(to_push2->config, to_push2->config_len)); + REQUIRE(to_push->n_configs == 1); + REQUIRE(to_push2->n_configs == 1); + CHECK(printable(to_push->config[0], to_push->config_lens[0]) != + printable(to_push2->config[0], to_push2->config_lens[0])); // Now imagine that each client pushed its `seqno=2` config to the swarm, but then each client // also fetches new messages and pulls down the other client's `seqno=2` value. @@ -281,14 +331,20 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { // Feed the new config into each other. (This array could hold multiple configs if we pulled // down more than one). merge_hash[0] = "fakehash2"; - merge_data[0] = to_push->config; - merge_size[0] = to_push->config_len; - config_merge(conf2, merge_hash, merge_data, merge_size, 1); + merge_data[0] = to_push->config[0]; + merge_size[0] = to_push->config_lens[0]; + accepted = config_merge(conf2, merge_hash, merge_data, merge_size, 1); free(to_push); + REQUIRE(accepted->len == 1); + CHECK(accepted->value[0] == "fakehash2"sv); + free(accepted); merge_hash[0] = "fakehash3"; - merge_data[0] = to_push2->config; - merge_size[0] = to_push2->config_len; - config_merge(conf, merge_hash, merge_data, merge_size, 1); + merge_data[0] = to_push2->config[0]; + merge_size[0] = to_push2->config_lens[0]; + accepted = config_merge(conf, merge_hash, merge_data, merge_size, 1); + REQUIRE(accepted->len == 1); + CHECK(accepted->value[0] == "fakehash3"sv); + free(accepted); free(to_push2); // Now after the merge we *will* want to push from both client, since both will have generated a @@ -310,16 +366,32 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { // Since only one of them set a profile pic there should be no conflict there: pic = user_profile_get_pic(conf); +#if defined(__APPLE__) || defined(__clang__) || defined(__llvm__) REQUIRE(pic.url); +#else + REQUIRE(pic.url != nullptr); +#endif CHECK(pic.url == "http://new.example.com/pic"sv); +#if defined(__APPLE__) || defined(__clang__) || defined(__llvm__) REQUIRE(pic.key); - CHECK(to_hex(ustring_view{pic.key, 32}) == +#else + REQUIRE(pic.key != nullptr); +#endif + CHECK(oxenc::to_hex(std::span{pic.key, 32}) == "7177657274007975696f31323334353637383930313233343536373839303132"); pic = user_profile_get_pic(conf2); +#if defined(__APPLE__) || defined(__clang__) || defined(__llvm__) REQUIRE(pic.url); +#else + REQUIRE(pic.url != nullptr); +#endif CHECK(pic.url == "http://new.example.com/pic"sv); +#if defined(__APPLE__) || defined(__clang__) || defined(__llvm__) REQUIRE(pic.key); - CHECK(to_hex(ustring_view{pic.key, 32}) == +#else + REQUIRE(pic.key != nullptr); +#endif + CHECK(oxenc::to_hex(std::span{pic.key, 32}) == "7177657274007975696f31323334353637383930313233343536373839303132"); CHECK(user_profile_get_nts_priority(conf) == 9); @@ -329,8 +401,8 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { CHECK(user_profile_get_blinded_msgreqs(conf) == 1); CHECK(user_profile_get_blinded_msgreqs(conf2) == 1); - config_confirm_pushed(conf, to_push->seqno, "fakehash4"); - config_confirm_pushed(conf2, to_push2->seqno, "fakehash4"); + config_confirm_pushed(conf, to_push->seqno, &(tmphash = "fakehash4"), 1); + config_confirm_pushed(conf2, to_push2->seqno, &(tmphash = "fakehash4"), 1); config_dump(conf, &dump1, &dump1len); config_dump(conf2, &dump2, &dump2len); diff --git a/tests/test_configdata.cpp b/tests/test_configdata.cpp index 82fbbe68..24948943 100644 --- a/tests/test_configdata.cpp +++ b/tests/test_configdata.cpp @@ -6,14 +6,14 @@ #include #include #include +#include #include "session/bt_merge.hpp" #include "session/version.h" #include "utils.hpp" using namespace session; -using namespace std::literals; -using namespace oxenc::literals; +using namespace fmt::literals; using config::ConfigMessage; using config::MutableConfigMessage; using oxenc::bt_dict; @@ -53,7 +53,7 @@ TEST_CASE("config data dict encoding", "[config][data][dict]") { d["D"] = config::dict{{"x", 1}, {"y", 2}}; d["d"] = config::dict{{"e", config::dict{{"f", config::dict{{"g", ""}}}}}}; - static_assert(oxenc::detail::is_bt_input_dict_container); + static_assert(oxenc::bt_input_dict_container); CHECK(oxenc::bt_serialize(d) == "d1:B1:x1:Dd1:xi1e1:yi2ee1:ai23e1:cli-3ei4e1:11:2e1:dd1:ed1:fd1:g0:eeee"); @@ -100,24 +100,15 @@ TEST_CASE("config pruning", "[config][prune]") { // shortcut to access a nested dict auto& d(config::dict_value& v) { - return var::get(v); + return std::get(v); } // or set auto& s(config::dict_value& v) { - return var::get(v); + return std::get(v); } -template -ustring_view view(const std::array& data) { - return ustring_view{data.data(), data.size()}; -} -template -std::string view_hex(const std::array& data) { - return oxenc::to_hex(data.begin(), data.end()); -} - -ustring blake2b(ustring_view data) { - ustring result; +std::vector blake2b(std::span data) { + std::vector result; result.resize(32); crypto_generichash_blake2b(result.data(), 32, data.data(), data.size(), nullptr, 0); return result; @@ -202,13 +193,13 @@ TEST_CASE("config message serialization", "[config][serialization]") { "e")); // clang-format on - const auto hash0 = "d65738bba88b0f3455cef20fe09a7b4b10f25f9db82be24a6ce1bd06da197526"_hex; - CHECK(view_hex(m.hash()) == oxenc::to_hex(hash0)); + const std::string hash0{"d65738bba88b0f3455cef20fe09a7b4b10f25f9db82be24a6ce1bd06da197526"_hex}; + CHECK(to_hex(m.hash()) == oxenc::to_hex(hash0)); auto m1 = m.increment(); m1.data().erase("foo"); - const auto hash1 = "5b30b4abf4cba71db25dbc0d977cc25df1d0a8a87cad7f561cdec2b8caf65f5e"_hex; - CHECK(view_hex(m1.hash()) == oxenc::to_hex(hash1)); + const std::string hash1{"5b30b4abf4cba71db25dbc0d977cc25df1d0a8a87cad7f561cdec2b8caf65f5e"_hex}; + CHECK(to_hex(m1.hash()) == oxenc::to_hex(hash1)); auto m2 = m1.increment(); @@ -220,8 +211,8 @@ TEST_CASE("config message serialization", "[config][serialization]") { s(d(m2.data()["bar"])[""]).erase("b"); s(d(m2.data()["bar"])[""]).insert(42); // already present - const auto hash2 = "027552203cf669070d3ecbeecfa65c65497d59aa4da490e0f68f8131ce081320"_hex; - CHECK(view_hex(m2.hash()) == oxenc::to_hex(hash2)); + const std::string hash2{"027552203cf669070d3ecbeecfa65c65497d59aa4da490e0f68f8131ce081320"_hex}; + CHECK(to_hex(m2.hash()) == oxenc::to_hex(hash2)); // clang-format off CHECK(printable(m2.serialize()) == printable( @@ -239,7 +230,7 @@ TEST_CASE("config message serialization", "[config][serialization]") { "1:<" "l" "l" "i10e" - "32:" + hash0 + + "32:{hash0}" "d" "3:bar" "d" "0:" "lli42e1:a1:belee" @@ -251,7 +242,7 @@ TEST_CASE("config message serialization", "[config][serialization]") { "e" "l" "i11e" - "32:" + hash1 + + "32:{hash1}" "d" "3:foo" "1:-" "e" @@ -265,17 +256,20 @@ TEST_CASE("config message serialization", "[config][serialization]") { "e" "3:foo" "0:" "e" - "e")); + "e", + "hash0"_a=hash0, "hash1"_a=hash1)); + // clang-format on auto m5 = m2.increment().increment().increment(); - const auto hash3 = "b83871ea06587f9254cdf2b2af8daff19bd7fb550fb90d5f8f9f546464c08bc5"_hex; - const auto hash4 = "c30e2cfa7ec93c64a1ab6420c9bccfb63da8e4c2940ed6509ffb64f3f0131860"_hex; - const auto hash5 = "3234eb7da8cf4b79b9eec2a144247279d10f6f118184f82429a42c5996bea60c"_hex; + const std::string hash3{"b83871ea06587f9254cdf2b2af8daff19bd7fb550fb90d5f8f9f546464c08bc5"_hex}, + hash4{"c30e2cfa7ec93c64a1ab6420c9bccfb63da8e4c2940ed6509ffb64f3f0131860"_hex}, + hash5{"3234eb7da8cf4b79b9eec2a144247279d10f6f118184f82429a42c5996bea60c"_hex}; - CHECK(view_hex(m2.increment().hash()) == oxenc::to_hex(hash3)); - CHECK(view_hex(m2.increment().increment().hash()) == oxenc::to_hex(hash4)); - CHECK(view_hex(m5.hash()) == oxenc::to_hex(hash5)); + CHECK(to_hex(m2.increment().hash()) == oxenc::to_hex(hash3)); + CHECK(to_hex(m2.increment().increment().hash()) == oxenc::to_hex(hash4)); + CHECK(to_hex(m5.hash()) == oxenc::to_hex(hash5)); + // clang-format off CHECK(printable(m5.serialize()) == printable( "d" "1:#" "i15e" @@ -291,14 +285,14 @@ TEST_CASE("config message serialization", "[config][serialization]") { "1:<" "l" "l" "i11e" - "32:" + hash1 + + "32:{hash1}" "d" "3:foo" "1:-" "e" "e" "l" "i12e" - "32:" + hash2 + + "32:{hash2}" "d" "3:bar" "d" "0:" "l" "li99e1:ce" "l1:be" "e" @@ -310,18 +304,19 @@ TEST_CASE("config message serialization", "[config][serialization]") { "e" "l" "i13e" - "32:" + hash3 + + "32:{hash3}" "de" "e" "l" "i14e" - "32:" + hash4 + + "32:{hash4}" "de" "e" "e" "1:=" "d" "e" - "e")); + "e", + "hash1"_a=hash1, "hash2"_a=hash2, "hash3"_a=hash3, "hash4"_a=hash4)); // clang-format on } @@ -337,14 +332,16 @@ TEST_CASE("config message signature", "[config][signing]") { "4384261cdd338f5820ca9cbbe3fc72ac8944ee60d3b795b797fbbf5597b09f17"sv; std::array secretkey; oxenc::from_hex(skey_hex.begin(), skey_hex.end(), secretkey.begin()); - auto signer = [&secretkey](ustring_view data) { - ustring result; + auto signer = [&secretkey](std::span data) { + std::vector result; result.resize(64); crypto_sign_ed25519_detached( result.data(), nullptr, data.data(), data.size(), secretkey.data()); return result; }; - auto verifier = [&secretkey](ustring_view data, ustring_view signature) { + auto verifier = [&secretkey]( + std::span data, + std::span signature) { return 0 == crypto_sign_verify_detached( signature.data(), data.data(), data.size(), secretkey.data() + 32); }; @@ -381,15 +378,17 @@ TEST_CASE("config message signature", "[config][signing]") { auto expected_sig = "77267f4de7701ae348eba0ef73175281512ba3f1051cfed22dc3e31b9c699330" "2938863e09bc8b33638161071bd8dc397d5c1d3f674120d08fbb9c64dde2e907"_hexbytes; - ustring sig(64, '\0'); + std::vector sig(64, '\0'); // Sign it ourselves, and check what we get: crypto_sign_ed25519_detached( sig.data(), nullptr, m_signing_value.data(), m_signing_value.size(), secretkey.data()); CHECK(to_hex(sig) == to_hex(expected_sig)); + auto key_bytes = "1:~64:"_bytes; + auto end_bytes = "e"_bytes; auto m_expected = m_signing_value; - m_expected += "1:~64:"_bytes; - m_expected += expected_sig; - m_expected += 'e'; + m_expected.insert(m_expected.end(), key_bytes.begin(), key_bytes.end()); + m_expected.insert(m_expected.end(), expected_sig.begin(), expected_sig.end()); + m_expected.insert(m_expected.end(), end_bytes.begin(), end_bytes.end()); CHECK(printable(m.serialize()) == printable(m_expected)); ConfigMessage msg{m_expected, verifier, signer}; @@ -397,6 +396,7 @@ TEST_CASE("config message signature", "[config][signing]") { CHECK(msg.hash() == m.hash()); CHECK(printable(msg.serialize()) == printable(m_expected)); + // Deliberately modify the signature to break it: auto m_broken = m_expected; REQUIRE(m_broken[m_broken.size() - 2] == 0x07); m_broken[m_broken.size() - 2] = 0x17; @@ -414,7 +414,7 @@ TEST_CASE("config message signature", "[config][signing]") { ConfigMessage m2{{m_broken, m_expected}, verifier, signer}; CHECK_FALSE(m2.merged()); CHECK(m2.seqno() == 10); - CHECK(view_hex(m2.hash()) == view_hex(m.hash())); + CHECK(to_hex(m2.hash()) == to_hex(m.hash())); CHECK_THROWS_MATCHES( ConfigMessage( @@ -422,30 +422,16 @@ TEST_CASE("config message signature", "[config][signing]") { verifier, nullptr, ConfigMessage::DEFAULT_DIFF_LAGS, - false, [](size_t, const auto& exc) { throw exc; }), config::config_error, Message("Config signature failed verification")); - auto m_unsigned = m_signing_value + "e"_bytes; + auto m_unsigned = m_signing_value; + m_unsigned.insert(m_unsigned.end(), end_bytes.begin(), end_bytes.end()); CHECK_THROWS_MATCHES( ConfigMessage(m_unsigned, verifier), config::missing_signature, Message("Config signature is missing")); - - ConfigMessage m_no_sig{m_unsigned, verifier, nullptr, ConfigMessage::DEFAULT_DIFF_LAGS, true}; - CHECK(m_no_sig.seqno() == 10); - CHECK(m_no_sig.data() == m.data()); - // The hash will differ because of the lack of signature - CHECK(m_no_sig.hash() != m.hash()); - - CHECK(printable(m_no_sig.serialize()) == printable(m_unsigned)); - - // If we set a signer and serialize again, we're going to get the *signed* message. (This is - // not something that should be done, really, because this message does not agree with the - // hash). - m_no_sig.signer = signer; - CHECK(printable(m_no_sig.serialize()) == printable(m_expected)); } const config::dict data118{ @@ -463,7 +449,7 @@ const auto h119 = "43094f68c1faa37eff79e1c2f3973ffd5f9d6423b00ccda306fc6e7dac5f0 const auto h120 = "e3a237f91014d31e4d30569c4a8bfcd72157804f99b8732c611c48bf126432b5"_hexbytes; const auto h121 = "1a7f602055124deaf21175ef3f32983dee7c9de570e5d9c9a0bbc2db71dcb97f"_hexbytes; const auto h122 = "46560604fe352101bb869435260d7100ccfe007be5f741c7e96303f02f394e8a"_hexbytes; -const auto m123_expected = +const auto m123_expected = to_vector( // clang-format off "d" "1:#" "i123e" @@ -491,22 +477,22 @@ const auto m123_expected = "7:string2" "7:goodbye" "e" "1:<" "l" - "l" "i119e" "32:"_bytes+h119+ "de" "e" - "l" "i120e" "32:"_bytes+h120+ "de" "e" - "l" "i121e" "32:"_bytes+h121+ "de" "e" - "l" "i122e" "32:"_bytes+h122+ "de" "e" + "l" "i119e" "32:"+to_string(h119)+ "de" "e" + "l" "i120e" "32:"+to_string(h120)+ "de" "e" + "l" "i121e" "32:"+to_string(h121)+ "de" "e" + "l" "i122e" "32:"+to_string(h122)+ "de" "e" "e" "1:=" "d" "4:int0" "1:-" "4:int1" "0:" "4:int2" "0:" "e" - "e"_bytes; + "e"); // clang-format on const auto h123 = "d9398c597b058ac7e28e3febb76ed68eb8c5b6c369610562ab5f2b596775d73c"_hexbytes; TEST_CASE("config message example 1", "[config][example]") { - /// This is the "Ordinary update" example described in docs/config-merge-logic.md + /// This is the "Ordinary update" example described in docs/api/docs/config-merge-logic.md MutableConfigMessage m118{118, 5}; CHECK(m118.seqno() == 118); CHECK(m118.lag == 5); @@ -570,24 +556,24 @@ TEST_CASE("config message example 1", "[config][example]") { CHECK(printable(m118.serialize()) == printable(m118_expected)); - CHECK(view_hex(m118.hash()) == to_hex(blake2b(m118_expected))); + CHECK(to_hex(m118.hash()) == to_hex(blake2b(m118_expected))); // Increment 5 times so that our diffs will be empty. auto m123 = m118.increment(); CHECK(m123.seqno() == 119); - CHECK(view_hex(m123.hash()) == to_hex(h119)); + CHECK(to_hex(m123.hash()) == to_hex(h119)); m123 = m123.increment(); CHECK(m123.seqno() == 120); - CHECK(view_hex(m123.hash()) == to_hex(h120)); + CHECK(to_hex(m123.hash()) == to_hex(h120)); m123 = m123.increment(); CHECK(m123.seqno() == 121); - CHECK(view_hex(m123.hash()) == to_hex(h121)); + CHECK(to_hex(m123.hash()) == to_hex(h121)); m123 = m123.increment(); CHECK(m123.seqno() == 122); - CHECK(view_hex(m123.hash()) == to_hex(h122)); + CHECK(to_hex(m123.hash()) == to_hex(h122)); m123 = m123.increment(); @@ -603,7 +589,7 @@ TEST_CASE("config message deserialization", "[config][deserialization]") { ConfigMessage m{m123_expected}; CHECK(m.seqno() == 123); - CHECK(view_hex(m.hash()) == to_hex(h123)); + CHECK(to_hex(m.hash()) == to_hex(h123)); CHECK(m.diff() == oxenc::bt_dict{ {"int0"s, "-"s}, {"int1"s, ""s}, @@ -621,7 +607,7 @@ TEST_CASE("config message deserialization", "[config][deserialization]") { // increment() MutableConfigMessage mut{m123_expected}; CHECK(mut.seqno() == 124); - CHECK(view_hex(mut.hash()) == "3ea36410cf7086ce816eb193b0c94e88632abfb75771d82f8ddb3a909124c580"); + CHECK(to_hex(mut.hash()) == "3ea36410cf7086ce816eb193b0c94e88632abfb75771d82f8ddb3a909124c580"); CHECK(mut.diff() == oxenc::bt_dict{}); CHECK_FALSE(mut.merged()); CHECK_FALSE(mut.verified_signature()); @@ -654,12 +640,12 @@ TEST_CASE("config message deserialization", "[config][deserialization]") { "7:string2" "7:goodbye" "e" "1:<" "l" - "l" "i120e" "32:"_bytes+h120+ "de" "e" - "l" "i121e" "32:"_bytes+h121+ "de" "e" - "l" "i122e" "32:"_bytes+h122+ "de" "e" + "l" "i120e" "32:"+to_string(h120)+ "de" "e" + "l" "i121e" "32:"+to_string(h121)+ "de" "e" + "l" "i122e" "32:"+to_string(h122)+ "de" "e" "l" "i123e" - "32:"_bytes+h123+ + "32:"+to_string(h123)+ "d" "4:int0" "1:-" "4:int1" "0:" @@ -668,7 +654,7 @@ TEST_CASE("config message deserialization", "[config][deserialization]") { "e" "e" "1:=" "de" - "e"_bytes)); + "e")); // clang-format on } @@ -737,7 +723,7 @@ const auto h124 = "8b73f316178765b9b3b37168e865c84bb5a78610cbb59b84d0fa4d3b4b3c1 TEST_CASE("config message example 2", "[config][example]") { /// This is the "Large, but still ordinary, update" example described in - /// docs/config-merge-logic.md + /// docs/api/docs/config-merge-logic.md MutableConfigMessage m{m123_expected}; REQUIRE(m.seqno() == 124); @@ -769,12 +755,12 @@ TEST_CASE("config message example 2", "[config][example]") { "7:string3" "3:omg" "e" "1:<" "l" - "l" "i120e" "32:"_bytes+h120+ "de" "e" - "l" "i121e" "32:"_bytes+h121+ "de" "e" - "l" "i122e" "32:"_bytes+h122+ "de" "e" + "l" "i120e" "32:"+to_string(h120)+ "de" "e" + "l" "i121e" "32:"+to_string(h121)+ "de" "e" + "l" "i122e" "32:"+to_string(h122)+ "de" "e" "l" "i123e" - "32:"_bytes+blake2b(m123_expected)+ + "32:"+to_string(blake2b(m123_expected))+ "d" "4:int0" "1:-" "4:int1" "0:" @@ -814,17 +800,17 @@ TEST_CASE("config message example 2", "[config][example]") { "7:string2" "0:" "7:string3" "0:" "e" - "e"_bytes)); + "e")); // clang-format on - CHECK(view_hex(m.hash()) == to_hex(h124)); + CHECK(to_hex(m.hash()) == to_hex(h124)); } const auto h125a = "80f229c3667de6d0fa6f96b53118e097fbda82db3ca1aea221a3db91ea9c45fb"_hexbytes; const auto h125b = "ab12f0efe9a9ed00db6b17b44ae0ff36b9f49094077fb114f415522f2a0e98de"_hexbytes; // clang-format off -const auto m126_expected = +const auto m126_expected = to_vector( "d" "1:#" "i126e" "1:&" "d" @@ -848,10 +834,10 @@ const auto m126_expected = "7:string3" "3:omg" "e" "1:<" "l" - "l" "i122e" "32:"_bytes+h122+ "de" "e" + "l" "i122e" "32:"+to_string(h122)+ "de" "e" "l" "i123e" - "32:"_bytes+h123+ + "32:"+to_string(h123)+ "d" "4:int0" "1:-" "4:int1" "0:" @@ -860,7 +846,7 @@ const auto m126_expected = "e" "l" "i124e" - "32:"_bytes+h124+ + "32:"+to_string(h124)+ "d" "5:dictA" "d" "7:goodbye" "l" "l" "i123e" "i456e" "e" "le" "e" @@ -896,7 +882,7 @@ const auto m126_expected = "e" "l" "i125e" - "32:"_bytes+h125a+ + "32:"+to_string(h125a)+ "d" "5:dictB" "d" "3:foo" "1:-" @@ -905,22 +891,23 @@ const auto m126_expected = "e" "l" "i125e" - "32:"_bytes+h125b+ + "32:"+to_string(h125b)+ "d" "4:int1" "0:" "e" "e" "e" "1:=" "de" - "e"_bytes; + "e"); // clang-format on TEST_CASE("config message example 3 - simple conflict", "[config][example][conflict]") { - /// This is the "Simple conflict resolution" example described in docs/config-merge-logic.md + /// This is the "Simple conflict resolution" example described in docs/api/ + /// docs/config-merge-logic.md MutableConfigMessage m124{m123_expected}; REQUIRE(m124.seqno() == 124); updates_124(m124); - REQUIRE(view_hex(m124.hash()) == to_hex(h124)); + REQUIRE(to_hex(m124.hash()) == to_hex(h124)); auto m125_a = m124.increment(); REQUIRE(m125_a.seqno() == 125); @@ -930,8 +917,8 @@ TEST_CASE("config message example 3 - simple conflict", "[config][example][confl REQUIRE(m125_b.seqno() == 125); m125_b.data()["int1"] = 5; - REQUIRE(view_hex(m125_a.hash()) == to_hex(h125a)); - REQUIRE(view_hex(m125_b.hash()) == to_hex(h125b)); + REQUIRE(to_hex(m125_a.hash()) == to_hex(h125a)); + REQUIRE(to_hex(m125_b.hash()) == to_hex(h125b)); REQUIRE(m125_a.hash() < m125_b.hash()); ConfigMessage m{{m125_a.serialize(), m125_b.serialize()}}; @@ -973,7 +960,7 @@ TEST_CASE("config message example 3 - simple conflict", "[config][example][confl TEST_CASE("config message example 4 - complex conflict resolution", "[config][example][conflict]") { /// This is the "Complex conflict resolution" example described in - /// docs/config-merge-logic.md + /// docs/api/docs/config-merge-logic.md ConfigMessage m123{m123_expected}; @@ -985,7 +972,7 @@ TEST_CASE("config message example 4 - complex conflict resolution", "[config][ex auto m124b = m123.increment(); updates_124(m124b); - REQUIRE(view_hex(m124b.hash()) == to_hex(h124)); + REQUIRE(to_hex(m124b.hash()) == to_hex(h124)); auto m125a = m124b.increment(); d(m125a.data()["dictB"]).erase("foo"); @@ -1061,7 +1048,7 @@ TEST_CASE("config message example 4 - complex conflict resolution", "[config][ex "1:<" "l" "l" "i123e" - "32:"_bytes+h123+ + "32:"+to_string(h123)+ "d" "4:int0" "1:-" "4:int1" "0:" @@ -1070,7 +1057,7 @@ TEST_CASE("config message example 4 - complex conflict resolution", "[config][ex "e" "l" "i124e" - "32:"_bytes+ustring{view(m124a.hash())}+ + "32:"+to_string(m124a.hash())+ "d" "5:dictB" "d" "6:answer" "0:" @@ -1080,7 +1067,7 @@ TEST_CASE("config message example 4 - complex conflict resolution", "[config][ex "e" "l" "i124e" - "32:"_bytes+h124+ + "32:"+to_string(h124)+ "d" "5:dictA" "d" "7:goodbye" "l" "l" "i123e" "i456e" "e" "le" "e" @@ -1116,7 +1103,7 @@ TEST_CASE("config message example 4 - complex conflict resolution", "[config][ex "e" "l" "i125e" - "32:"_bytes+h125a+ + "32:"+to_string(h125a)+ "d" "5:dictB" "d" "3:foo" "1:-" @@ -1125,18 +1112,18 @@ TEST_CASE("config message example 4 - complex conflict resolution", "[config][ex "e" "l" "i125e" - "32:"_bytes+h125b+ + "32:"+to_string(h125b)+ "d" "4:int1" "0:" "e" "e" - "l" "i126e" "32:"_bytes+ustring{view(m126a.hash())}+ "de" "e" - "l" "i126e" "32:"_bytes+ustring{view(m126b.hash())}+ "de" "e" + "l" "i126e" "32:"+to_string(m126a.hash())+ "de" "e" + "l" "i126e" "32:"+to_string(m126b.hash())+ "de" "e" "e" "1:=" "d" "5:dictA" "d" "7:goodbye" "l" "li789ee" "le" "e" "e" "e" - "e"_bytes)); + "e")); // clang-format on ConfigMessage m_alt1{{m127.serialize(), m125a.serialize(), m126b.serialize()}}; diff --git a/tests/test_curve25519.cpp b/tests/test_curve25519.cpp new file mode 100644 index 00000000..14323dd2 --- /dev/null +++ b/tests/test_curve25519.cpp @@ -0,0 +1,51 @@ +#include + +#include +#include + +#include "session/curve25519.h" +#include "session/curve25519.hpp" +#include "utils.hpp" + +TEST_CASE("X25519 key pair generation", "[curve25519][keypair]") { + auto kp1 = session::curve25519::curve25519_key_pair(); + auto kp2 = session::curve25519::curve25519_key_pair(); + + CHECK(kp1.first.size() == 32); + CHECK(kp1.second.size() == 32); + CHECK(kp1.first != kp2.first); + CHECK(kp1.second != kp2.second); +} + +TEST_CASE("X25519 conversion", "[curve25519][to curve25519 pubkey]") { + using namespace session; + + auto ed_pk1 = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes; + auto ed_pk2 = "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"_hexbytes; + + auto x_pk1 = curve25519::to_curve25519_pubkey(to_span(ed_pk1)); + auto x_pk2 = curve25519::to_curve25519_pubkey(to_span(ed_pk2)); + + CHECK(oxenc::to_hex(x_pk1.begin(), x_pk1.end()) == + "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + CHECK(oxenc::to_hex(x_pk2.begin(), x_pk2.end()) == + "aa654f00fc39fc69fd0db829410ca38177d7732a8d2f0934ab3872ac56d5aa74"); +} + +TEST_CASE("X25519 conversion", "[curve25519][to curve25519 seckey]") { + using namespace session; + + auto ed_sk1 = + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab78862834829a" + "87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"_hexbytes; + auto ed_sk2 = + "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876cd83ca3d13a" + "d8a954d5011aa7861abe3a29ac25b70c4ed5234aff74d34ef5786"_hexbytes; + auto x_sk1 = curve25519::to_curve25519_seckey(to_span(ed_sk1)); + auto x_sk2 = curve25519::to_curve25519_seckey(to_span(ed_sk2)); + + CHECK(oxenc::to_hex(x_sk1.begin(), x_sk1.end()) == + "207e5d97e761300f96c10adc11efdd6d5c15188a9a7682ec05b30ca017e9b447"); + CHECK(oxenc::to_hex(x_sk2.begin(), x_sk2.end()) == + "904943eff27142a8e5cd37c84e2437c9979a560b044bf9a65a8d644b325fe56a"); +} diff --git a/tests/test_ed25519.cpp b/tests/test_ed25519.cpp new file mode 100644 index 00000000..fa715b27 --- /dev/null +++ b/tests/test_ed25519.cpp @@ -0,0 +1,88 @@ +#include + +#include +#include + +#include "session/ed25519.h" +#include "session/ed25519.hpp" +#include "utils.hpp" + +TEST_CASE("Ed25519 key pair generation", "[ed25519][keypair]") { + // Generate two random key pairs and make sure they don't match + auto kp1 = session::ed25519::ed25519_key_pair(); + auto kp2 = session::ed25519::ed25519_key_pair(); + + CHECK(kp1.first.size() == 32); + CHECK(kp1.second.size() == 64); + CHECK(kp1.first != kp2.first); + CHECK(kp1.second != kp2.second); +} + +TEST_CASE("Ed25519 key pair generation seed", "[ed25519][keypair]") { + using namespace session; + + auto ed_seed1 = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes; + auto ed_seed2 = "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"_hexbytes; + auto ed_seed_invalid = "010203040506070809"_hexbytes; + + auto kp1 = session::ed25519::ed25519_key_pair(to_span(ed_seed1)); + auto kp2 = session::ed25519::ed25519_key_pair(to_span(ed_seed2)); + CHECK_THROWS(session::ed25519::ed25519_key_pair(to_span(ed_seed_invalid))); + + CHECK(kp1.first.size() == 32); + CHECK(kp1.second.size() == 64); + CHECK(kp1.first != kp2.first); + CHECK(kp1.second != kp2.second); + CHECK(oxenc::to_hex(kp1.first.begin(), kp1.first.end()) == + "8862834829a87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"); + CHECK(oxenc::to_hex(kp2.first.begin(), kp2.first.end()) == + "cd83ca3d13ad8a954d5011aa7861abe3a29ac25b70c4ed5234aff74d34ef5786"); + + auto kp_sk1 = + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab78862834829a" + "87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"; + auto kp_sk2 = + "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876cd83ca3d13a" + "d8a954d5011aa7861abe3a29ac25b70c4ed5234aff74d34ef5786"; + CHECK(oxenc::to_hex(kp1.second.begin(), kp1.second.end()) == kp_sk1); + CHECK(oxenc::to_hex(kp2.second.begin(), kp2.second.end()) == kp_sk2); +} + +TEST_CASE("Ed25519 seed for private key", "[ed25519][seed]") { + using namespace session; + + auto ed_sk1 = + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab78862834829a" + "87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"_hexbytes; + auto ed_sk2 = "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"_hexbytes; + auto ed_sk_invalid = "010203040506070809"_hexbytes; + + auto seed1 = session::ed25519::seed_for_ed_privkey(to_span(ed_sk1)); + auto seed2 = session::ed25519::seed_for_ed_privkey(to_span(ed_sk2)); + CHECK_THROWS(session::ed25519::seed_for_ed_privkey(to_span(ed_sk_invalid))); + + CHECK(oxenc::to_hex(seed1.begin(), seed1.end()) == + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"); + CHECK(oxenc::to_hex(seed2.begin(), seed2.end()) == + "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"); +} + +TEST_CASE("Ed25519", "[ed25519][signature]") { + using namespace session; + + auto ed_seed = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes; + auto ed_pk = "8862834829a87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"_hexbytes; + auto ed_invalid = "010203040506070809"_hexbytes; + + auto sig1 = session::ed25519::sign(to_span(ed_seed), to_span("hello")); + CHECK_THROWS(session::ed25519::sign(to_span(ed_invalid), to_span("hello"))); + + auto expected_sig_hex = + "e03b6e87a53d83f202f2501e9b52193dbe4a64c6503f88244948dee53271" + "85011574589aa7b59bc9757f9b9c31b7be9c9212b92ac7c81e029ee21c338ee12405"; + CHECK(oxenc::to_hex(sig1.begin(), sig1.end()) == expected_sig_hex); + + CHECK(session::ed25519::verify(sig1, ed_pk, to_span("hello"))); + CHECK_THROWS(session::ed25519::verify(ed_invalid, ed_pk, to_span("hello"))); + CHECK_THROWS(session::ed25519::verify(ed_pk, ed_invalid, to_span("hello"))); +} diff --git a/tests/test_encrypt.cpp b/tests/test_encrypt.cpp index 56cca3c3..87383753 100644 --- a/tests/test_encrypt.cpp +++ b/tests/test_encrypt.cpp @@ -12,8 +12,6 @@ #include "utils.hpp" using namespace session; -using namespace std::literals; -using namespace oxenc::literals; TEST_CASE("config message encryption", "[config][encrypt]") { auto message1 = "some message 1"_bytes; @@ -27,9 +25,9 @@ TEST_CASE("config message encryption", "[config][encrypt]") { CHECK(to_hex(enc2) != to_hex(enc1)); auto enc3 = config::encrypt(message1, key2, "test-suite1"); CHECK(to_hex(enc3) != to_hex(enc1)); - auto nonce = enc1.substr(enc1.size() - 24); - auto nonce2 = enc2.substr(enc2.size() - 24); - auto nonce3 = enc3.substr(enc3.size() - 24); + auto nonce = std::vector{enc1.begin() + (enc1.size() - 24), enc1.end()}; + auto nonce2 = std::vector{enc2.begin() + (enc2.size() - 24), enc2.end()}; + auto nonce3 = std::vector{enc3.begin() + (enc3.size() - 24), enc3.end()}; CHECK(to_hex(nonce) == "af2f4860cb4d0f8ba7e09d29e31f5e4a18f65847287a54a0"); CHECK(to_hex(nonce2) == "277e639d36ba46470dfff509a68cb73d9a96386c51739bdd"); CHECK(to_hex(nonce3) == to_hex(nonce)); diff --git a/tests/test_group_info.cpp b/tests/test_group_info.cpp new file mode 100644 index 00000000..4dfea5c1 --- /dev/null +++ b/tests/test_group_info.cpp @@ -0,0 +1,370 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "utils.hpp" + +using namespace std::literals; +using namespace oxenc::literals; +using namespace session::config; + +TEST_CASE("Group Info settings", "[config][groups][info]") { + + const auto seed = "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; + std::array ed_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair( + ed_pk.data(), ed_sk.data(), reinterpret_cast(seed.data())); + + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"); + CHECK(oxenc::to_hex(seed.begin(), seed.end()) == + oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); + + std::vector> enc_keys{ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"_hexbytes}; + + groups::Info ginfo1{session::to_span(ed_pk), session::to_span(ed_sk), std::nullopt}; + + // This is just for testing: normally you don't load keys manually but just make a groups::Keys + // object that loads the keys into the Members object for you. + for (const auto& k : enc_keys) + ginfo1.add_key(k, false); + + enc_keys.insert( + enc_keys.begin(), + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"_hexbytes); + enc_keys.push_back("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"_hexbytes); + enc_keys.push_back("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"_hexbytes); + groups::Info ginfo2{session::to_span(ed_pk), session::to_span(ed_sk), std::nullopt}; + + for (const auto& k : enc_keys) // Just for testing, as above. + ginfo2.add_key(k, false); + + ginfo1.set_name("GROUP Name"); + CHECK(ginfo1.is_dirty()); + CHECK(ginfo1.needs_push()); + CHECK(ginfo1.needs_dump()); + + auto [s1, p1, o1] = ginfo1.push(); + + CHECK(s1 == 1); + REQUIRE(p1.size() == 1); + CHECK(p1[0].size() == 256); + CHECK(o1.empty()); + + ginfo1.confirm_pushed(s1, {"fakehash1"}); + CHECK(ginfo1.needs_dump()); + CHECK_FALSE(ginfo1.needs_push()); + + std::vector>> merge_configs; + merge_configs.emplace_back("fakehash1", p1[0]); + CHECK(ginfo2.merge(merge_configs) == std::unordered_set{{"fakehash1"s}}); + CHECK_FALSE(ginfo2.needs_push()); + + CHECK(ginfo2.get_name() == "GROUP Name"); + + ginfo2.set_profile_pic( + "http://example.com/12345", + "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"_hexbytes); + ginfo2.set_expiry_timer(1h); + constexpr int64_t create_time{1682529839}; + ginfo2.set_created(create_time); + ginfo2.set_delete_before((create_time + 50 * 86400) * 1'000'000); // as microseconds + ginfo2.set_delete_attach_before((create_time + 70 * 86400) * 1'000); // as milliseconds + ginfo2.destroy_group(); + + auto [s2, p2, o2] = ginfo2.push(); + CHECK(s2 == 2); + CHECK(p2.at(0).size() == 512); + CHECK(o2 == std::vector{"fakehash1"s}); + + ginfo2.confirm_pushed(s2, {"fakehash2"}); + + ginfo1.set_name("Better name!"); + + merge_configs.clear(); + merge_configs.emplace_back("fakehash2", p2.at(0)); + + // This fails because ginfo1 doesn't yet have the new key that ginfo2 used (bbb...) + CHECK(ginfo1.merge(merge_configs) == std::unordered_set{}); + + ginfo1.add_key("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"_hexbytes); + ginfo1.add_key( + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"_hexbytes, + /*prepend=*/false); + + CHECK(ginfo1.merge(merge_configs) == std::unordered_set{{"fakehash2"s}}); + + CHECK(ginfo1.needs_push()); + auto [s3, p3, o3] = ginfo1.push(); + + CHECK(ginfo1.get_name() == "Better name!"); + CHECK(ginfo1.get_profile_pic().url == "http://example.com/12345"); + CHECK(ginfo1.get_profile_pic().key == + "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"_hexbytes); + CHECK(ginfo1.get_expiry_timer() == 1h); + CHECK(ginfo1.get_created() == create_time); + CHECK(ginfo1.get_delete_before() == create_time + 50 * 86400); + CHECK(ginfo1.get_delete_attach_before() == create_time + 70 * 86400); + CHECK(ginfo1.is_destroyed()); + + ginfo1.confirm_pushed(s3, {"fakehash3"}); + + merge_configs.clear(); + merge_configs.emplace_back("fakehash3", p3.at(0)); + CHECK(ginfo2.merge(merge_configs) == std::unordered_set{{"fakehash3"s}}); + CHECK(ginfo2.get_name() == "Better name!"); + CHECK(ginfo2.get_profile_pic().url == "http://example.com/12345"); + CHECK(ginfo2.get_profile_pic().key == + "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"_hexbytes); + CHECK(ginfo2.get_expiry_timer() == 1h); + CHECK(ginfo2.get_created() == create_time); + CHECK(ginfo2.get_delete_before() == create_time + 50 * 86400); + CHECK(ginfo2.get_delete_attach_before() == create_time + 70 * 86400); + CHECK(ginfo2.is_destroyed()); + + CHECK_THROWS( + ginfo1.set_name("1234567890123456789012345678901234567890123456789012345678901234567890" + "123456789012345678901234567890A")); + CHECK_NOTHROW( + ginfo1.set_name_truncated("123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890A")); + CHECK(ginfo1.get_name() == + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567890"); +} + +TEST_CASE("Verify-only Group Info", "[config][groups][verify-only]") { + + const auto seed = "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; + std::array ed_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair( + ed_pk.data(), ed_sk.data(), reinterpret_cast(seed.data())); + + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"); + CHECK(oxenc::to_hex(seed.begin(), seed.end()) == + oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); + + std::vector> enc_keys1; + enc_keys1.push_back( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"_hexbytes); + std::vector> enc_keys2; + enc_keys2.push_back( + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"_hexbytes); + enc_keys2.push_back( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"_hexbytes); + + // This Info object has only the public key, not the priv key, and so cannot modify things: + groups::Info ginfo{session::to_span(ed_pk), std::nullopt, std::nullopt}; + + for (const auto& k : enc_keys1) // Just for testing, as above. + ginfo.add_key(k, false); + + REQUIRE_THROWS_WITH( + ginfo.set_name("Super Group!"), "Unable to make changes to a read-only config object"); + REQUIRE_THROWS_WITH( + ginfo.set_name("Super Group!"), "Unable to make changes to a read-only config object"); + CHECK(!ginfo.is_dirty()); + + // This one is good and has the right signature: + groups::Info ginfo_rw{session::to_span(ed_pk), session::to_span(ed_sk), std::nullopt}; + + for (const auto& k : enc_keys1) // Just for testing, as above. + ginfo_rw.add_key(k, false); + + ginfo_rw.set_name("Super Group!!"); + CHECK(ginfo_rw.is_dirty()); + CHECK(ginfo_rw.needs_push()); + CHECK(ginfo_rw.needs_dump()); + + auto [seqno, to_push, obs] = ginfo_rw.push(); + + CHECK(seqno == 1); + + ginfo_rw.confirm_pushed(seqno, {"fakehash1"}); + CHECK(ginfo_rw.needs_dump()); + CHECK_FALSE(ginfo_rw.needs_push()); + + std::vector>> merge_configs; + merge_configs.emplace_back("fakehash1", to_push.at(0)); + CHECK(ginfo.merge(merge_configs) == std::unordered_set{{"fakehash1"s}}); + CHECK_FALSE(ginfo.needs_push()); + + groups::Info ginfo_rw2{session::to_span(ed_pk), session::to_span(ed_sk), std::nullopt}; + + for (const auto& k : enc_keys1) // Just for testing, as above. + ginfo_rw2.add_key(k, false); + + CHECK(ginfo_rw2.merge(merge_configs) == std::unordered_set{{"fakehash1"s}}); + CHECK_FALSE(ginfo.needs_push()); + + CHECK(ginfo.get_name() == "Super Group!!"); + + REQUIRE_THROWS_WITH( + ginfo.set_name("Super Group11"), "Unable to make changes to a read-only config object"); + // This shouldn't throw because it isn't *actually* changing a config value (i.e. re-setting the + // same value does not dirty the config). It isn't clear why you'd need to do this, but still. + ginfo.set_name("Super Group!!"); + + // Deliberately use the wrong signing key so that what we produce encrypts successfully but + // doesn't verify + const auto seed_bad1 = + "0023456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; + std::array ed_pk_bad1; + std::array ed_sk_bad1; + crypto_sign_ed25519_seed_keypair( + ed_pk_bad1.data(), + ed_sk_bad1.data(), + reinterpret_cast(seed_bad1.data())); + + groups::Info ginfo_bad1{session::to_span(ed_pk), session::to_span(ed_sk), std::nullopt}; + + for (const auto& k : enc_keys1) // Just for testing, as above. + ginfo_bad1.add_key(k, false); + + ginfo_bad1.merge(merge_configs); + ginfo_bad1.set_sig_keys(session::to_span(ed_sk_bad1)); + ginfo_bad1.set_name("Bad name, BAD!"); + auto [s_bad, p_bad, o_bad] = ginfo_bad1.push(); + + merge_configs.clear(); + merge_configs.emplace_back("badhash1", p_bad.at(0)); + + CHECK(ginfo.merge(merge_configs) == std::unordered_set{}); + CHECK_FALSE(ginfo.needs_push()); + + // Now let's get more complicated: we will have *two* valid signers who submit competing updates + ginfo_rw2.set_name("Super Group 2"); + ginfo_rw2.set_created(12345); + ginfo_rw.set_name("Super Group 3"); + ginfo_rw.set_expiry_timer(365 * 24h); + + CHECK(ginfo_rw.needs_push()); + CHECK(ginfo_rw2.needs_push()); + + auto [s2, tp2, o2] = ginfo_rw2.push(); + auto [s3, tp3, o3] = ginfo_rw.push(); + + merge_configs.clear(); + merge_configs.emplace_back("fakehash2", tp2.at(0)); + merge_configs.emplace_back("fakehash3", tp3.at(0)); + + CHECK(ginfo.merge(merge_configs) == std::unordered_set{{"fakehash2"s, "fakehash3"s}}); + CHECK(ginfo.is_clean()); + + CHECK(s2 == 2); + CHECK(s3 == 2); + CHECK_FALSE(ginfo.needs_push()); + + CHECK(ginfo_rw.merge(merge_configs) == std::unordered_set{{"fakehash2"s, "fakehash3"s}}); + CHECK(ginfo_rw2.merge(merge_configs) == std::unordered_set{{"fakehash2"s, "fakehash3"s}}); + + CHECK(ginfo_rw.needs_push()); + CHECK(ginfo_rw2.needs_push()); + + auto [s23, t23, o23] = ginfo_rw.push(); + auto [s32, t32, o32] = ginfo_rw2.push(); + + CHECK(s23 == s32); + CHECK(t23 == t32); + CHECK(o23 == o32); + + ginfo_rw.confirm_pushed(s23, {"fakehash23"}); + ginfo_rw2.confirm_pushed(s32, {"fakehash23"}); + + merge_configs.clear(); + merge_configs.emplace_back("fakehash23", t23.at(0)); + + CHECK(ginfo.merge(merge_configs) == std::unordered_set{{"fakehash23"s}}); + CHECK(ginfo_rw.merge(merge_configs) == std::unordered_set{{"fakehash23"s}}); + CHECK(ginfo_rw2.merge(merge_configs) == std::unordered_set{{"fakehash23"s}}); + + CHECK_FALSE(ginfo.needs_push()); + CHECK_FALSE(ginfo_rw.needs_push()); + CHECK_FALSE(ginfo_rw2.needs_push()); + + auto test = [](groups::Info& g) { + auto n = g.get_name(); + REQUIRE(n); + CHECK(*n == "Super Group 2"); + auto c = g.get_created(); + REQUIRE(c); + CHECK(*c == 12345); + auto et = g.get_expiry_timer(); + REQUIRE(et); + CHECK(*et == 365 * 24h); + }; + test(ginfo); + test(ginfo_rw); + test(ginfo_rw2); + + CHECK(ginfo.needs_dump()); + auto dump = ginfo.dump(); + groups::Info ginfo2{session::to_span(ed_pk), std::nullopt, dump}; + + for (const auto& k : enc_keys1) // Just for testing, as above. + ginfo2.add_key(k, false); + + CHECK(!ginfo.needs_dump()); + CHECK(!ginfo2.needs_dump()); + + auto [s4, t4, o4] = ginfo.push(); + auto [s5, t5, o5] = ginfo.push(); + CHECK(s4 == s23); + CHECK(s4 == s5); + CHECK(t4 == t23); + CHECK(t4 == t5); + CHECK(o4.empty()); + CHECK(o5.empty()); + + // This account has a different primary decryption key + groups::Info ginfo_rw3{session::to_span(ed_pk), session::to_span(ed_sk), std::nullopt}; + + for (const auto& k : enc_keys2) // Just for testing, as above. + ginfo_rw3.add_key(k, false); + + CHECK(ginfo_rw3.merge(merge_configs) == std::unordered_set{{"fakehash23"s}}); + CHECK(ginfo_rw3.get_name() == "Super Group 2"); + + auto [s6, t6, o6] = ginfo_rw3.push(); + CHECK(oxenc::to_hex(ginfo_rw3.key(0)) == + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + REQUIRE(ginfo_rw3.key_count() == 2); + CHECK(oxenc::to_hex(ginfo_rw3.key(1)) == + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + CHECK(s6 == s5); + CHECK(t6.size() == t23.size()); + CHECK(t6 != t23); + + ginfo_rw3.set_profile_pic( + "http://example.com/12345", + "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"_hexbytes); + CHECK(ginfo_rw3.needs_push()); + auto [s7, t7, o7] = ginfo_rw3.push(); + CHECK(s7 == s6 + 1); + CHECK(t7 != t6); + CHECK(o7 == std::vector{{"fakehash23"s}}); + + merge_configs.clear(); + merge_configs.emplace_back("fakehash7", t7.at(0)); + // If we don't have the new "bbb" key loaded yet, this will fail: + CHECK(ginfo.merge(merge_configs) == std::unordered_set{}); + + ginfo.add_key(enc_keys2.front()); + CHECK(ginfo.merge(merge_configs) == std::unordered_set{{"fakehash7"s}}); + + auto pic = ginfo.get_profile_pic(); + CHECK_FALSE(pic.empty()); + CHECK(pic.url == "http://example.com/12345"); + CHECK(pic.key == "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"_hexbytes); +} diff --git a/tests/test_group_keys.cpp b/tests/test_group_keys.cpp new file mode 100644 index 00000000..33d5dea4 --- /dev/null +++ b/tests/test_group_keys.cpp @@ -0,0 +1,1017 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.hpp" + +using namespace std::literals; +using namespace session::config; + +static std::array sk_from_seed(std::span seed) { + std::array ignore; + std::array sk; + crypto_sign_ed25519_seed_keypair(ignore.data(), sk.data(), seed.data()); + return sk; +} + +static std::string session_id_from_ed(std::span ed_pk) { + std::string sid; + std::array xpk; + int rc = crypto_sign_ed25519_pk_to_curve25519(xpk.data(), ed_pk.data()); + REQUIRE(rc == 0); + sid.reserve(66); + sid += "05"; + oxenc::to_hex(xpk.begin(), xpk.end(), std::back_inserter(sid)); + return sid; +} + +// Hacky little class that implements `[n]` on a std::list. This is inefficient (since it access +// has to iterate n times through the list) but we only use it on small lists in this test code so +// convenience wins over efficiency. (Why not just use a vector? Because vectors requires `T` to +// be moveable, so we'd either have to use std::unique_ptr for members, which is also annoying). +template +struct hacky_list : std::list { + T& operator[](size_t n) { return *std::next(std::begin(*this), n); } +}; + +struct pseudo_client { + std::array secret_key; + const std::span public_key{secret_key.data() + 32, 32}; + std::string session_id{session_id_from_ed(public_key)}; + + groups::Info info; + groups::Members members; + groups::Keys keys; + + pseudo_client( + std::span seed, + bool admin, + const unsigned char* gpk, + std::optional gsk, + std::optional> info_dump = std::nullopt, + std::optional> members_dump = std::nullopt, + std::optional> keys_dump = std::nullopt) : + secret_key{sk_from_seed(seed)}, + info{std::span{gpk, 32}, + admin ? std::make_optional>({*gsk, 64}) + : std::nullopt, + info_dump}, + members{std::span{gpk, 32}, + admin ? std::make_optional>({*gsk, 64}) + : std::nullopt, + members_dump}, + keys{session::to_span(secret_key), + std::span{gpk, 32}, + admin ? std::make_optional>({*gsk, 64}) + : std::nullopt, + keys_dump, + info, + members} { + if (gsk) + keys.rekey(info, members); + } +}; + +TEST_CASE("Group Keys - C++ API", "[config][groups][keys][cpp]") { + + const std::vector group_seed = + "0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210"_hexbytes; + const std::vector admin1_seed = + "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; + const std::vector admin2_seed = + "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"_hexbytes; + const std::array member_seeds = { + "000111222333444555666777888999aaabbbcccdddeeefff0123456789abcdef"_hexbytes, // member1 + "00011122435111155566677788811263446552465222efff0123456789abcdef"_hexbytes, // member2 + "00011129824754185548239498168169316979583253efff0123456789abcdef"_hexbytes, // member3 + "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"_hexbytes, // member4 + "3333333333333333333333333333333333333333333333333333333333333333"_hexbytes, // member3b + "4444444444444444444444444444444444444444444444444444444444444444"_hexbytes, // member4b + }; + + std::array group_pk; + std::array group_sk; + + crypto_sign_ed25519_seed_keypair(group_pk.data(), group_sk.data(), group_seed.data()); + REQUIRE(oxenc::to_hex(group_seed.begin(), group_seed.end()) == + oxenc::to_hex(group_sk.begin(), group_sk.begin() + 32)); + + // Using list instead of vector so that `psuedo_client` doesn't have to be moveable, which lets + // us put the Info/Member/Keys directly inside it (rather than having to use a unique_ptr, which + // would also be annoying). + hacky_list admins; + hacky_list members; + + // Initialize admin and member objects + admins.emplace_back(admin1_seed, true, group_pk.data(), group_sk.data()); + admins.emplace_back(admin2_seed, true, group_pk.data(), group_sk.data()); + + for (int i = 0; i < 4; ++i) + members.emplace_back(member_seeds[i], false, group_pk.data(), std::nullopt); + + REQUIRE(admins[0].session_id == + "05f1e8b64bbf761edf8f7b47e3a1f369985644cce0a62adb8e21604474bdd49627"); + REQUIRE(admins[1].session_id == + "05c5ba413c336f2fe1fb9a2c525f8a86a412a1db128a7841b4e0e217fa9eb7fd5e"); + REQUIRE(members[0].session_id == + "05ece06dd8e02fb2f7d9497f956a1996e199953c651f4016a2f79a3b3e38d55628"); + REQUIRE(members[1].session_id == + "053ac269b71512776b0bd4a1234aaf93e67b4e9068a2c252f3b93a20acb590ae3c"); + REQUIRE(members[2].session_id == + "05a2b03abdda4df8316f9d7aed5d2d1e483e9af269d0b39191b08321b8495bc118"); + REQUIRE(members[3].session_id == + "050a41669a06c098f22633aee2eba03764ef6813bd4f770a3a2b9033b868ca470d"); + + for (const auto& a : admins) + REQUIRE(a.members.size() == 0); + for (const auto& m : members) + REQUIRE(m.members.size() == 0); + + std::vector>> info_configs; + std::vector>> mem_configs; + + // add admin account, re-key, distribute + auto& admin1 = admins[0]; + + auto m = admin1.members.get_or_construct(admin1.session_id); + m.admin = true; + m.name = "Admin1"; + admin1.members.set(m); + + CHECK(admin1.members.needs_push()); + + auto maybe_key_config = admin1.keys.pending_config(); + REQUIRE(maybe_key_config); + auto new_keys_config1 = *maybe_key_config; + + auto [iseq1, new_info_config1, iobs1] = admin1.info.push(); + admin1.info.confirm_pushed(iseq1, {"fakehash1"}); + REQUIRE(new_info_config1.size() == 1); + info_configs.emplace_back("fakehash1", new_info_config1[0]); + + auto [mseq1, new_mem_config1, mobs1] = admin1.members.push(); + admin1.members.confirm_pushed(mseq1, {"fakehash1"}); + REQUIRE(new_mem_config1.size() == 1); + mem_configs.emplace_back("fakehash1", new_mem_config1[0]); + + /* Even though we have only added one admin, admin2 will still be able to see group info + like group size and merge all configs. This is because they have loaded the key config + message, which they can decrypt with the group secret key. + */ + for (auto& a : admins) { + a.keys.load_key_message( + "keyhash1", new_keys_config1, get_timestamp_ms(), a.info, a.members); + CHECK(a.info.merge(info_configs) == std::unordered_set{{"fakehash1"s}}); + CHECK(a.members.merge(mem_configs) == std::unordered_set{{"fakehash1"s}}); + CHECK(a.members.size() == 1); + CHECK(a.keys.active_hashes() == std::unordered_set{{"keyhash1"s}}); + } + + /* All attempts to merge non-admin members will throw, as none of the non admin members + will be able to decrypt the new info/member configs using the updated keys + */ + for (auto& m : members) { + m.keys.load_key_message( + "keyhash1", new_keys_config1, get_timestamp_ms(), m.info, m.members); + CHECK_THROWS(m.info.merge(info_configs)); + CHECK_THROWS(m.members.merge(mem_configs)); + CHECK(m.members.size() == 0); + CHECK(m.keys.active_hashes().empty()); + } + + info_configs.clear(); + mem_configs.clear(); + + // add non-admin members, re-key, distribute + for (size_t i = 0; i < members.size(); ++i) { + auto m = admin1.members.get_or_construct(members[i].session_id); + m.admin = false; + m.name = "Member{}"_format(i); + admin1.members.set(m); + } + + CHECK(admin1.members.needs_push()); + + auto new_keys_config2 = admin1.keys.rekey(admin1.info, admin1.members); + CHECK(not new_keys_config2.empty()); + + auto [iseq2, new_info_config2, iobs2] = admin1.info.push(); + admin1.info.confirm_pushed(iseq2, {"fakehash2"}); + REQUIRE(new_info_config2.size() == 1); + info_configs.emplace_back("fakehash2", new_info_config2[0]); + + auto [mseq2, new_mem_config2, mobs2] = admin1.members.push(); + admin1.members.confirm_pushed(mseq2, {"fakehash2"}); + REQUIRE(new_mem_config2.size() == 1); + mem_configs.emplace_back("fakehash2", new_mem_config2[0]); + + for (auto& a : admins) { + a.keys.load_key_message( + "keyhash2", new_keys_config2, get_timestamp_ms(), a.info, a.members); + CHECK(a.info.merge(info_configs) == std::unordered_set{{"fakehash2"s}}); + CHECK(a.members.merge(mem_configs) == std::unordered_set{{"fakehash2"s}}); + CHECK(a.members.size() == 5); + CHECK(a.keys.active_hashes() == std::unordered_set{{"keyhash1"s, "keyhash2"s}}); + } + + for (auto& m : members) { + m.keys.load_key_message( + "keyhash2", new_keys_config2, get_timestamp_ms(), m.info, m.members); + CHECK(m.info.merge(info_configs) == std::unordered_set{{"fakehash2"s}}); + CHECK(m.members.merge(mem_configs) == std::unordered_set{{"fakehash2"s}}); + CHECK(m.members.size() == 5); + CHECK(m.keys.active_hashes() == std::unordered_set{{"keyhash2"s}}); + } + + info_configs.clear(); + mem_configs.clear(); + + // change group info, re-key, distribute + admin1.info.set_name("tomatosauce"s); + admin1.info.set_description("this is where you go to play in the tomato sauce, I guess"); + + CHECK(admin1.info.needs_push()); + + auto new_keys_config3 = admin1.keys.rekey(admin1.info, admin1.members); + CHECK(not new_keys_config3.empty()); + + auto [iseq3, new_info_config3, iobs3] = admin1.info.push(); + admin1.info.confirm_pushed(iseq3, {"fakehash3"}); + REQUIRE(new_info_config3.size() == 1); + info_configs.emplace_back("fakehash3", new_info_config3[0]); + + auto [mseq3, new_mem_config3, mobs3] = admin1.members.push(); + admin1.members.confirm_pushed(mseq3, {"fakehash3"}); + REQUIRE(new_mem_config3.size() == 1); + mem_configs.emplace_back("fakehash3", new_mem_config3[0]); + + for (auto& a : admins) { + a.keys.load_key_message( + "keyhash3", new_keys_config3, get_timestamp_ms(), a.info, a.members); + CHECK(a.info.merge(info_configs) == std::unordered_set{{"fakehash3"s}}); + CHECK(a.members.merge(mem_configs) == std::unordered_set{{"fakehash3"s}}); + CHECK(a.info.get_name() == "tomatosauce"s); + CHECK(a.info.get_description() == + "this is where you go to play in the tomato sauce, I guess"s); + CHECK(a.keys.active_hashes() == + std::unordered_set{{"keyhash1"s, "keyhash2"s, "keyhash3"s}}); + } + + for (auto& m : members) { + m.keys.load_key_message( + "keyhash3", new_keys_config3, get_timestamp_ms(), m.info, m.members); + CHECK(m.info.merge(info_configs) == std::unordered_set{{"fakehash3"s}}); + CHECK(m.members.merge(mem_configs) == std::unordered_set{{"fakehash3"s}}); + CHECK(m.info.get_name() == "tomatosauce"s); + CHECK(m.info.get_description() == + "this is where you go to play in the tomato sauce, I guess"s); + CHECK(m.keys.active_hashes() == std::unordered_set{{"keyhash2"s, "keyhash3"s}}); + } + + info_configs.clear(); + mem_configs.clear(); + + // remove members, re-key, distribute + CHECK(admin1.members.size() == 5); + CHECK(admin1.members.erase(members[3].session_id)); + CHECK(admin1.members.erase(members[2].session_id)); + CHECK(admin1.members.size() == 3); + + CHECK(admin1.members.needs_push()); + + std::vector old_key = session::to_vector(admin1.keys.group_enc_key()); + auto new_keys_config4 = admin1.keys.rekey(admin1.info, admin1.members); + CHECK(not new_keys_config4.empty()); + + CHECK(old_key != session::to_vector(admin1.keys.group_enc_key())); + + auto [iseq4, new_info_config4, iobs4] = admin1.info.push(); + admin1.info.confirm_pushed(iseq4, {"fakehash4"}); + REQUIRE(new_info_config4.size() == 1); + info_configs.emplace_back("fakehash4", new_info_config4[0]); + + auto [mseq4, new_mem_config4, mobs4] = admin1.members.push(); + admin1.members.confirm_pushed(mseq4, {"fakehash4"}); + REQUIRE(new_mem_config4.size() == 1); + mem_configs.emplace_back("fakehash4", new_mem_config4[0]); + + for (auto& a : admins) { + CHECK(a.keys.load_key_message( + "keyhash4", new_keys_config4, get_timestamp_ms(), a.info, a.members)); + CHECK(a.info.merge(info_configs) == std::unordered_set{{"fakehash4"s}}); + CHECK(a.members.merge(mem_configs) == std::unordered_set{{"fakehash4"s}}); + CHECK(a.members.size() == 3); + CHECK(a.keys.active_hashes() == + std::unordered_set{{"keyhash1"s, "keyhash2"s, "keyhash3"s, "keyhash4"s}}); + } + + for (size_t i = 0; i < members.size(); i++) { + auto& m = members[i]; + bool found_key = m.keys.load_key_message( + "keyhash4", new_keys_config2, get_timestamp_ms(), m.info, m.members); + + CHECK(m.keys.active_hashes() == + std::unordered_set{{"keyhash2"s, "keyhash3"s, "keyhash4"s}}); + if (i < 2) { // We should still be in the group + CHECK(found_key); + CHECK(m.info.merge(info_configs) == std::unordered_set{{"fakehash4"s}}); + CHECK(m.members.merge(mem_configs) == std::unordered_set{{"fakehash4"s}}); + CHECK(m.members.size() == 3); + } else { + CHECK_FALSE(found_key); + CHECK(m.info.merge(info_configs) == std::unordered_set{}); + CHECK(m.members.merge(mem_configs) == std::unordered_set{}); + CHECK(m.members.size() == 5); + } + } + + members.pop_back(); + members.pop_back(); + + info_configs.clear(); + mem_configs.clear(); + + // middle-out time + auto msg = "hello to all my friends sitting in the tomato sauce"s; + + for (int i = 0; i < 5; ++i) + msg += msg; + + auto compressed = admin1.keys.encrypt_message(session::to_span(msg)); + CHECK(compressed.size() == 256); + auto uncompressed = admin1.keys.encrypt_message(session::to_span(msg), false); + CHECK(uncompressed.size() == 2048); + + CHECK(compressed.size() < msg.size()); + + // Add two new members and send them supplemental keys + for (int i = 0; i < 2; ++i) { + auto& m = members.emplace_back(member_seeds[4 + i], false, group_pk.data(), std::nullopt); + + auto memb = admin1.members.get_or_construct(m.session_id); + memb.set_invite_sent(); + memb.supplement = true; + memb.name = i == 0 ? "fred" : "JOHN"; + admin1.members.set(memb); + + CHECK_FALSE(m.keys.admin()); + } + + REQUIRE(members[2].session_id == + "054eb4fafee2bd3018a24e310de8106333c2b364eaed029a7f05d7b45ccc77683a"); + REQUIRE(members[3].session_id == + "057ce31baa9a04b5cfb83ab7ccdd7b669b911a082d29883d6aad3256294a0a5e0c"); + + // We actually send supplemental keys to members 1, as well, by mistake just to make sure it + // doesn't do or hurt anything to get a supplemental key you already have. + std::vector supp_sids; + std::transform( + std::next(members.begin()), members.end(), std::back_inserter(supp_sids), [](auto& m) { + return m.session_id; + }); + auto supp = admin1.keys.key_supplement(supp_sids); + CHECK(admin1.members.needs_push()); + CHECK_FALSE(admin1.info.needs_push()); + auto [mseq5, mpush5, mobs5] = admin1.members.push(); + REQUIRE(mpush5.size() == 1); + mem_configs.emplace_back("fakehash5", mpush5[0]); + admin1.members.confirm_pushed(mseq5, {"fakehash5"}); + info_configs.emplace_back("fakehash4", new_info_config4[0]); + + for (auto& a : admins) { + CHECK_FALSE( + a.keys.load_key_message("keyhash5", supp, get_timestamp_ms(), a.info, a.members)); + } + + for (size_t i = 0; i < members.size(); i++) { + auto& m = members[i]; + bool found_key = + m.keys.load_key_message("keyhash5", supp, get_timestamp_ms(), m.info, m.members); + + if (i < 1) { + // This supp key wasn't for us + CHECK_FALSE(found_key); + CHECK(m.keys.size() == 3); + CHECK(m.keys.group_keys().size() == 3); + } else { + CHECK(found_key); + // new_keys_config1 never went to the initial members, but did go out in the + // supplement, which is why we have the extra key here. + CHECK(m.keys.size() == 4); + CHECK(m.keys.group_keys().size() == 4); + } + + CHECK(m.info.merge(info_configs) == std::unordered_set{{"fakehash4"s}}); + CHECK(m.members.merge(mem_configs) == std::unordered_set{{"fakehash5"s}}); + REQUIRE(m.info.get_name()); + CHECK(*m.info.get_name() == "tomatosauce"sv); + CHECK(m.members.size() == 5); + + if (i < 2) + CHECK(m.keys.active_hashes() == + std::unordered_set{{"keyhash2"s, "keyhash3"s, "keyhash4"s, "keyhash5"s}}); + else + CHECK(m.keys.active_hashes() == std::unordered_set{{"keyhash5"s}}); + } + + std::pair> decrypted1, decrypted2; + REQUIRE_NOTHROW(decrypted1 = members.back().keys.decrypt_message(compressed)); + CHECK(decrypted1.first == admin1.session_id); + CHECK(session::to_string(decrypted1.second) == msg); + + REQUIRE_NOTHROW(decrypted2 = members.back().keys.decrypt_message(uncompressed)); + CHECK(decrypted2.first == admin1.session_id); + CHECK(session::to_string(decrypted2.second) == msg); + + auto bad_compressed = compressed; + bad_compressed.back() ^= 0b100; + CHECK_THROWS_WITH( + members.back().keys.decrypt_message(bad_compressed), + "unable to decrypt ciphertext with any current group keys"); + + // Duplicate members[1] from dumps + auto& m1b = members.emplace_back( + member_seeds[1], + false, + group_pk.data(), + std::nullopt, + members[1].info.dump(), + members[1].members.dump(), + members[1].keys.dump()); + CHECK(m1b.keys.size() == 4); + CHECK(m1b.keys.group_keys().size() == 4); + CHECK(m1b.keys.active_hashes() == + std::unordered_set{{"keyhash2"s, "keyhash3"s, "keyhash4"s, "keyhash5"s}}); + CHECK(m1b.members.size() == 5); + auto m1b_m2 = m1b.members.get(members[2].session_id); + REQUIRE(m1b_m2); + CHECK(m1b.members.get_status(*m1b_m2) == session::config::groups::member::Status::invite_sent); + CHECK(m1b_m2->name == "fred"); + + // Rekey after 10d, then again after 71d (10+61) and everything except those two new gens should + // get dropped as stale. + info_configs.clear(); + mem_configs.clear(); + std::vector new_keys_config6 = + session::to_vector(admin1.keys.rekey(admin1.info, admin1.members)); + auto [iseq6, ipush6, iobs6] = admin1.info.push(); + info_configs.emplace_back("ifakehash6", ipush6[0]); + REQUIRE(info_configs.size() == 1); + admin1.info.confirm_pushed(iseq6, {"ifakehash6"}); + auto [mseq6, mpush6, mobs6] = admin1.members.push(); + REQUIRE(mpush6.size() == 1); + mem_configs.emplace_back("mfakehash6", mpush6[0]); + admin1.members.confirm_pushed(mseq6, {"mfakehash6"}); + + for (auto& a : admins) { + CHECK(a.keys.load_key_message( + "keyhash6", + new_keys_config6, + get_timestamp_ms() + 10LL * 86400 * 1000, + a.info, + a.members)); + CHECK(a.info.merge(info_configs) == std::unordered_set{{"ifakehash6"s}}); + CHECK(a.members.merge(mem_configs) == std::unordered_set{{"mfakehash6"s}}); + CHECK(a.members.size() == 5); + CHECK(a.keys.active_hashes() == std::unordered_set{ + {"keyhash1"s, + "keyhash2"s, + "keyhash3"s, + "keyhash4"s, + "keyhash5"s, + "keyhash6"s}}); + } + + std::vector new_keys_config7 = + session::to_vector(admin1.keys.rekey(admin1.info, admin1.members)); + + // Make sure we can encrypt & decrypt even if the rekey is still pending: + CHECK_NOTHROW( + admin1.keys.decrypt_message(admin1.keys.encrypt_message(session::to_span("abc")))); + + auto [iseq7, ipush7, iobs7] = admin1.info.push(); + REQUIRE(ipush7.size() == 1); + info_configs.emplace_back("ifakehash7", ipush7[0]); + admin1.info.confirm_pushed(iseq7, {"ifakehash7"}); + + auto [mseq7, mpush7, mobs7] = admin1.members.push(); + REQUIRE(mpush7.size() == 1); + mem_configs.emplace_back("mfakehash7", mpush7[0]); + admin1.members.confirm_pushed(mseq7, {"mfakehash7"}); + + for (auto& a : admins) { + CHECK(a.keys.load_key_message( + "keyhash7", + new_keys_config7, + get_timestamp_ms() + 71LL * 86400 * 1000, + a.info, + a.members)); + CHECK(a.info.merge(info_configs) == std::unordered_set{{"ifakehash6"s, "ifakehash7"s}}); + CHECK(a.members.merge(mem_configs) == std::unordered_set{{"mfakehash6"s, "mfakehash7"s}}); + CHECK(a.members.size() == 5); + CHECK(a.keys.active_hashes() == std::unordered_set{{"keyhash6"s, "keyhash7"s}}); + } + + for (size_t i = 0; i < members.size(); i++) { + auto& m = members[i]; + CHECK(m.keys.load_key_message( + "keyhash6", + new_keys_config6, + get_timestamp_ms() + 10LL * 86400 * 1000, + m.info, + m.members)); + CHECK(m.keys.load_key_message( + "keyhash7", + new_keys_config7, + get_timestamp_ms() + 71LL * 86400 * 1000, + m.info, + m.members)); + CHECK(m.info.merge(info_configs) == std::unordered_set{{"ifakehash6"s, "ifakehash7"s}}); + CHECK(m.members.merge(mem_configs) == std::unordered_set{{"mfakehash6"s, "mfakehash7"s}}); + CHECK(m.members.size() == 5); + CHECK(m.keys.active_hashes() == std::unordered_set{{"keyhash6"s, "keyhash7"s}}); + } + + // Make sure keys propagate on dump restore to info/members: + pseudo_client admin1b{ + admin1_seed, + true, + group_pk.data(), + group_sk.data(), + admin1.info.dump(), + admin1.members.dump(), + admin1.keys.dump()}; + admin1b.info.set_name("Test New Name"); + admin1b.info.set_description("Test New Desc"); + CHECK_THROWS( + admin1b.info.set_name("Test New Name Really long " + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl" + "mnopqrstuvwxyz")); + CHECK_THROWS(admin1b.info.set_description(std::string(2050, 'z'))); + CHECK_NOTHROW(admin1b.info.push()); + admin1b.members.set( + admin1b.members.get_or_construct("05124076571076017981235497801235098712093870981273590" + "8746387172343")); + CHECK_NOTHROW(admin1b.members.push()); + + // Test values weren't overrided + CHECK(admin1b.info.get_name() == "Test New Name"); + CHECK(admin1b.info.get_description() == "Test New Desc"); +} + +TEST_CASE("Group Keys - C API", "[config][groups][keys][c]") { + struct pseudo_client { + std::array secret_key; + const std::span public_key{secret_key.data() + 32, 32}; + std::string session_id{session_id_from_ed(public_key)}; + + config_group_keys* keys; + config_object* info; + config_object* members; + + pseudo_client( + std::vector seed, + bool is_admin, + unsigned char* gpk, + std::optional gsk) : + secret_key{sk_from_seed(seed)} { + int rv = groups_members_init(&members, gpk, is_admin ? *gsk : NULL, NULL, 0, NULL); + REQUIRE(rv == 0); + + rv = groups_info_init(&info, gpk, is_admin ? *gsk : NULL, NULL, 0, NULL); + REQUIRE(rv == 0); + + rv = groups_keys_init( + &keys, + secret_key.data(), + gpk, + is_admin ? *gsk : NULL, + info, + members, + NULL, + 0, + NULL); + REQUIRE(rv == 0); + + if (is_admin) + REQUIRE(groups_keys_rekey(keys, info, members, nullptr, nullptr)); + } + + ~pseudo_client() { + config_free(info); + config_free(members); + } + }; + + const std::vector group_seed = + "0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210"_hexbytes; + const std::vector admin1_seed = + "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; + const std::vector admin2_seed = + "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"_hexbytes; + const std::array member_seeds = { + "000111222333444555666777888999aaabbbcccdddeeefff0123456789abcdef"_hexbytes, // member1 + "00011122435111155566677788811263446552465222efff0123456789abcdef"_hexbytes, // member2 + "00011129824754185548239498168169316979583253efff0123456789abcdef"_hexbytes, // member3 + "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"_hexbytes // member4 + }; + + std::array group_pk; + std::array group_sk; + + crypto_sign_ed25519_seed_keypair( + group_pk.data(), + group_sk.data(), + reinterpret_cast(group_seed.data())); + REQUIRE(oxenc::to_hex(group_seed.begin(), group_seed.end()) == + oxenc::to_hex(group_sk.begin(), group_sk.begin() + 32)); + + hacky_list admins; + hacky_list members; + + // Initialize admin and member objects + admins.emplace_back(admin1_seed, true, group_pk.data(), group_sk.data()); + admins.emplace_back(admin2_seed, true, group_pk.data(), group_sk.data()); + + for (int i = 0; i < 4; ++i) + members.emplace_back(member_seeds[i], false, group_pk.data(), std::nullopt); + + REQUIRE(admins[0].session_id == + "05f1e8b64bbf761edf8f7b47e3a1f369985644cce0a62adb8e21604474bdd49627"); + REQUIRE(admins[1].session_id == + "05c5ba413c336f2fe1fb9a2c525f8a86a412a1db128a7841b4e0e217fa9eb7fd5e"); + REQUIRE(members[0].session_id == + "05ece06dd8e02fb2f7d9497f956a1996e199953c651f4016a2f79a3b3e38d55628"); + REQUIRE(members[1].session_id == + "053ac269b71512776b0bd4a1234aaf93e67b4e9068a2c252f3b93a20acb590ae3c"); + REQUIRE(members[2].session_id == + "05a2b03abdda4df8316f9d7aed5d2d1e483e9af269d0b39191b08321b8495bc118"); + REQUIRE(members[3].session_id == + "050a41669a06c098f22633aee2eba03764ef6813bd4f770a3a2b9033b868ca470d"); + + for (const auto& a : admins) + REQUIRE(groups_members_size(a.members) == 0); + for (const auto& m : members) + REQUIRE(groups_members_size(m.members) == 0); + + // add admin account, re-key, distribute + auto& admin1 = admins[0]; + config_group_member new_admin1; + + REQUIRE(groups_members_get_or_construct( + admin1.members, &new_admin1, admin1.session_id.c_str())); + + new_admin1.admin = true; + groups_members_set(admin1.members, &new_admin1); + + CHECK(config_needs_push(admin1.members)); + + const unsigned char* new_keys_config_1; + size_t key_len1; + REQUIRE(groups_keys_pending_config(admin1.keys, &new_keys_config_1, &key_len1)); + + config_push_data* new_info_config1 = config_push(admin1.info); + CHECK(new_info_config1->seqno == 1); + CHECK(new_info_config1->n_configs == 1); + + config_push_data* new_mem_config1 = config_push(admin1.members); + CHECK(new_mem_config1->seqno == 1); + CHECK(new_mem_config1->n_configs == 1); + + const char* merge_hash1[1]; + const unsigned char* merge_data1[2]; + size_t merge_size1[2]; + + merge_hash1[0] = "fakehash1"; + + merge_data1[0] = new_info_config1->config[0]; + merge_size1[0] = new_info_config1->config_lens[0]; + + merge_data1[1] = new_mem_config1->config[0]; + merge_size1[1] = new_mem_config1->config_lens[0]; + + const char* tmphash; // test suite cheat: &(tmphash = "asdf") to fake a length-1 array. + + /* Even though we have only added one admin, admin2 will still be able to see group info + like group size and merge all configs. This is because they have loaded the key config + message, which they can decrypt with the group secret key. + */ + for (auto& a : admins) { + REQUIRE(groups_keys_load_message( + a.keys, + "fakekeyshash1", + new_keys_config_1, + key_len1, + get_timestamp_ms(), + a.info, + a.members)); + config_string_list* hashes; + hashes = config_merge(a.info, merge_hash1, &merge_data1[0], &merge_size1[0], 1); + REQUIRE(hashes->len); + free(hashes); + config_confirm_pushed(a.info, new_info_config1->seqno, &(tmphash = "fakehash1"), 1); + + hashes = config_merge(a.members, merge_hash1, &merge_data1[1], &merge_size1[1], 1); + REQUIRE(hashes->len); + free(hashes); + config_confirm_pushed(a.members, new_mem_config1->seqno, &(tmphash = "fakehash1"), 1); + + REQUIRE(groups_members_size(a.members) == 1); + } + + /* All attempts to merge non-admin members will throw, as none of the non admin members + will be able to decrypt the new info/member configs using the updated keys + */ + for (auto& m : members) { + // this will return true if the message was parsed successfully, NOT if the keys were + // decrypted + REQUIRE(groups_keys_load_message( + m.keys, + "fakekeyshash1", + new_keys_config_1, + key_len1, + get_timestamp_ms(), + m.info, + m.members)); + [[maybe_unused]] config_string_list* hashes; + hashes = config_merge(m.info, merge_hash1, &merge_data1[0], &merge_size1[0], 1); + REQUIRE(m.info->last_error == "Cannot merge configs without any decryption keys"sv); + m.info->last_error = nullptr; + hashes = config_merge(m.members, merge_hash1, &merge_data1[1], &merge_size1[1], 1); + REQUIRE(m.members->last_error == "Cannot merge configs without any decryption keys"sv); + m.members->last_error = nullptr; + + REQUIRE(groups_members_size(m.members) == 0); + } + + free(new_info_config1); + free(new_mem_config1); + + for (size_t i = 0; i < members.size(); ++i) { + config_group_member new_mem; + + REQUIRE(groups_members_get_or_construct( + members[i].members, &new_mem, members[i].session_id.c_str())); + new_mem.admin = false; + groups_members_set(admin1.members, &new_mem); + } + + CHECK(config_needs_push(admin1.members)); + + const unsigned char* new_keys_config_2; + size_t key_len2; + REQUIRE(groups_keys_rekey( + admin1.keys, admin1.info, admin1.members, &new_keys_config_2, &key_len2)); + + config_push_data* new_info_config2 = config_push(admin1.info); + CHECK(new_info_config2->seqno == 2); + + config_push_data* new_mem_config2 = config_push(admin1.members); + CHECK(new_mem_config2->seqno == 2); + + const char* merge_hash2[1]; + const unsigned char* merge_data2[2]; + size_t merge_size2[2]; + + merge_hash2[0] = "fakehash2"; + + REQUIRE(new_info_config2->n_configs == 1); + REQUIRE(new_mem_config2->n_configs == 1); + + merge_data2[0] = new_info_config2->config[0]; + merge_size2[0] = new_info_config2->config_lens[0]; + + merge_data2[1] = new_mem_config2->config[0]; + merge_size2[1] = new_mem_config2->config_lens[0]; + + for (auto& a : admins) { + REQUIRE(groups_keys_load_message( + a.keys, + "fakekeyshash2", + new_keys_config_2, + key_len2, + get_timestamp_ms(), + a.info, + a.members)); + config_string_list* hashes; + hashes = config_merge(a.info, merge_hash2, &merge_data2[0], &merge_size2[0], 1); + REQUIRE(hashes->len); + free(hashes); + config_confirm_pushed(a.info, new_info_config2->seqno, &(tmphash = "fakehash2"), 1); + hashes = config_merge(a.members, merge_hash2, &merge_data2[1], &merge_size2[1], 1); + REQUIRE(hashes->len); + free(hashes); + config_confirm_pushed(a.members, new_mem_config2->seqno, &(tmphash = "fakehash2"), 1); + + REQUIRE(groups_members_size(a.members) == 5); + } + + free(new_info_config2); + free(new_mem_config2); +} + +TEST_CASE("Group Keys - swarm authentication", "[config][groups][keys][swarm]") { + + const std::vector group_seed = + "0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210"_hexbytes; + const std::vector admin_seed = + "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; + const std::vector member_seed = + "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"_hexbytes; + + std::array group_pk; + std::array group_sk; + + crypto_sign_ed25519_seed_keypair(group_pk.data(), group_sk.data(), group_seed.data()); + REQUIRE(oxenc::to_hex(group_seed.begin(), group_seed.end()) == + oxenc::to_hex(group_sk.begin(), group_sk.begin() + 32)); + + CHECK(oxenc::to_hex(group_pk.begin(), group_pk.end()) == + "c50cb3ae956947a8de19135b5be2685ff348afc63fc34a837aca12bc5c1f5625"); + + pseudo_client admin{admin_seed, true, group_pk.data(), group_sk.data()}; + pseudo_client member{member_seed, false, group_pk.data(), std::nullopt}; + session::config::UserGroups member_groups{member_seed, std::nullopt}; + + CHECK(admin.session_id == "05f1e8b64bbf761edf8f7b47e3a1f369985644cce0a62adb8e21604474bdd49627"); + + CHECK(member.session_id == + "05c5ba413c336f2fe1fb9a2c525f8a86a412a1db128a7841b4e0e217fa9eb7fd5" + "e"); + CHECK(oxenc::to_hex(group_pk.begin(), group_pk.end()) == + "c50cb3ae956947a8de19135b5be2685ff348afc63fc34a837aca12bc5c1f5625"); + CHECK(member.info.id == "03c50cb3ae956947a8de19135b5be2685ff348afc63fc34a837aca12bc5c1f5625"); + + auto auth_data = admin.keys.swarm_make_subaccount(member.session_id); + { + auto g = member_groups.get_or_construct_group(member.info.id); + g.auth_data = auth_data; + member_groups.set(g); + } + + session::config::UserGroups member_gr2{member_seed, std::nullopt}; + auto [seqno, push, obs] = member_groups.push(); + + REQUIRE(push.size() == 1); + + std::vector>> gr_conf; + gr_conf.emplace_back("fakehash1", push[0]); + + member_gr2.merge(gr_conf); + + auto g = member_groups.get_group(member.info.id); + REQUIRE(g); + CHECK(g->id == member.info.id); + CHECK(g->auth_data == auth_data); + + auto to_sign = session::to_span("retrieve9991693340111000"); + auto subauth_b64 = member.keys.swarm_subaccount_sign(to_sign, auth_data); + + CHECK(subauth_b64.subaccount == "AwMAAIWvMR2nJXCFnK5+hNahNecWqMC39/TVVLjaR3imNug5"); + CHECK(subauth_b64.subaccount_sig == + "6brvv/" + "2jfciBAJeRKMGSepNJLullyrVVHijyVDE+8GC5Oc89UNxjNrq1kVV1P+pkUIRDOew24gSLFgLZfdl+BQ=="); + CHECK(subauth_b64.signature == + "c3PJ4g29v5RivKm8Tdg49vGU2/" + "6kVd0yONnpz5U5zePMYptqW3iYQ0TYf2rEzv3qqkPhS5p67M5GAccHoBHGDQ=="); + + auto subauth = member.keys.swarm_subaccount_sign(to_sign, auth_data, true); + CHECK(oxenc::to_base64(subauth.subaccount) == subauth_b64.subaccount); + CHECK(oxenc::to_base64(subauth.subaccount_sig) == subauth_b64.subaccount_sig); + CHECK(oxenc::to_base64(subauth.signature) == subauth_b64.signature); + + CHECK(0 == + crypto_sign_ed25519_verify_detached( + reinterpret_cast(subauth.signature.data()), + to_sign.data(), + to_sign.size(), + reinterpret_cast(subauth.subaccount.substr(4).data()))); + + CHECK(member.keys.swarm_verify_subaccount(auth_data)); + CHECK(session::config::groups::Keys::swarm_verify_subaccount( + member.info.id, session::to_span(member.secret_key), auth_data)); + + // Try flipping a bit in each position of the auth data and make sure it fails to validate: + for (size_t i = 0; i < auth_data.size(); i++) { + for (int b = 0; b < 8; b++) { + if (i == 35 && b == 7) // This is the sign bit of k, which can be flipped but gets + // flipped back when dealing with the missing X->Ed conversion + // sign bit, so won't actually change anything if it flips. + continue; + auto auth_data2 = auth_data; + auth_data2[i] ^= 1 << b; + CHECK_FALSE(session::config::groups::Keys::swarm_verify_subaccount( + member.info.id, session::to_span(member.secret_key), auth_data2)); + } + } +} + +TEST_CASE("Group Keys promotion", "[config][groups][keys][promotion]") { + + const std::vector group_seed = + "0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210"_hexbytes; + const std::vector admin1_seed = + "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; + const std::vector member1_seed = + "000111222333444555666777888999aaabbbcccdddeeefff0123456789abcdef"_hexbytes; + + std::array group_pk; + std::array group_sk; + + crypto_sign_ed25519_seed_keypair(group_pk.data(), group_sk.data(), group_seed.data()); + REQUIRE(oxenc::to_hex(group_seed.begin(), group_seed.end()) == + oxenc::to_hex(group_sk.begin(), group_sk.begin() + 32)); + + pseudo_client admin{admin1_seed, true, group_pk.data(), group_sk.data()}; + pseudo_client member{member1_seed, false, group_pk.data(), std::nullopt}; + + std::vector>> configs; + { + auto m = admin.members.get_or_construct(admin.session_id); + m.admin = true; + m.name = "Lrrr"; + admin.members.set(m); + } + { + auto m = admin.members.get_or_construct(member.session_id); + m.admin = false; + m.name = "Nibbler"; + admin.members.set(m); + } + admin.info.set_name("Omicron Persei 8"); + auto [mseq, mdata, mobs] = admin.members.push(); + admin.members.confirm_pushed(mseq, {"mpush1"}); + auto [iseq, idata, iobs] = admin.info.push(); + admin.info.confirm_pushed(mseq, {"ipush1"}); + + REQUIRE(admin.keys.pending_config()); + member.keys.load_key_message( + "keyhash1", + *admin.keys.pending_config(), + get_timestamp_ms(), + member.info, + member.members); + admin.keys.load_key_message( + "keyhash1", + *admin.keys.pending_config(), + get_timestamp_ms(), + member.info, + member.members); + + member.keys.load_key_message( + "keyhash2", + admin.keys.key_supplement(member.session_id), + get_timestamp_ms(), + member.info, + member.members); + + REQUIRE(mdata.size() == 1); + configs.emplace_back("mpush1", mdata[0]); + CHECK(member.members.merge(configs) == std::unordered_set{{"mpush1"s}}); + + configs.clear(); + REQUIRE(idata.size() == 1); + configs.emplace_back("ipush1", idata[0]); + CHECK(member.info.merge(configs) == std::unordered_set{{"ipush1"s}}); + + REQUIRE(admin.keys.admin()); + REQUIRE_FALSE(member.keys.admin()); + REQUIRE(member.info.is_readonly()); + REQUIRE(member.members.is_readonly()); + + member.keys.load_admin_key(session::to_span(group_sk), member.info, member.members); + + CHECK(member.keys.admin()); + CHECK_FALSE(member.members.is_readonly()); + CHECK_FALSE(member.info.is_readonly()); + + member.info.set_name("new name"s); + + CHECK(member.info.needs_push()); + auto [iseq2, idata2, iobs2] = member.info.push(); + REQUIRE(idata2.size() == 1); + + configs.clear(); + configs.emplace_back("ihash2", idata2[0]); + + CHECK(admin.info.merge(configs) == std::unordered_set{{"ihash2"s}}); + + CHECK(admin.info.get_name() == "new name"); +} diff --git a/tests/test_group_members.cpp b/tests/test_group_members.cpp new file mode 100644 index 00000000..072d105c --- /dev/null +++ b/tests/test_group_members.cpp @@ -0,0 +1,371 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "utils.hpp" + +using namespace std::literals; +using namespace session::config; + +constexpr bool is_prime100(int i) { + constexpr std::array p100 = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, + 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97}; + for (auto p : p100) + if (p >= i) + return p == i; + return false; +} + +TEST_CASE("Group Members", "[config][groups][members]") { + + const auto seed = "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; + std::array ed_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair( + ed_pk.data(), ed_sk.data(), reinterpret_cast(seed.data())); + + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"); + CHECK(oxenc::to_hex(seed.begin(), seed.end()) == + oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); + + std::vector> enc_keys{ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"_hexbytes}; + + groups::Members gmem1{session::to_span(ed_pk), session::to_span(ed_sk), std::nullopt}; + + // This is just for testing: normally you don't load keys manually but just make a groups::Keys + // object that loads the keys into the Members object for you. + for (const auto& k : enc_keys) + gmem1.add_key(k, false); + + enc_keys.insert( + enc_keys.begin(), + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"_hexbytes); + enc_keys.push_back("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"_hexbytes); + enc_keys.push_back("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"_hexbytes); + groups::Members gmem2{session::to_span(ed_pk), session::to_span(ed_sk), std::nullopt}; + + for (const auto& k : enc_keys) // Just for testing, as above. + gmem2.add_key(k, false); + + std::vector sids; + while (sids.size() < 256) { + std::array sid; + for (auto& s : sid) + s = sids.size(); + sid[0] = 0x05; + sids.push_back(oxenc::to_hex(sid.begin(), sid.end())); + } + + // 10 admins: + for (int i = 0; i < 10; i++) { + auto m = gmem1.get_or_construct(sids[i]); + m.set_promotion_accepted(); + m.name = "Admin {}"_format(i); + m.profile_picture.url = "http://example.com/{}"_format(i); + m.profile_picture.key = + "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"_hexbytes; + gmem1.set(m); + } + // 10 members: + for (int i = 10; i < 20; i++) { + auto m = gmem1.get_or_construct(sids[i]); + m.set_name("Member {}"_format(i)); + m.profile_picture.url = "http://example.com/{}"_format(i); + m.profile_picture.key = + "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"_hexbytes; + gmem1.set(m); + } + // 5 members with no attributes (not even a name): + for (int i = 20; i < 25; i++) { + auto m = gmem1.get_or_construct(sids[i]); + gmem1.set(m); + } + + REQUIRE_THROWS(gmem1.get(sids[14])->set_name(std::string(200, 'c'))); + + CHECK(gmem1.needs_push()); + auto [s1, p1, o1] = gmem1.push(); + CHECK(p1.size() == 1); + CHECK(p1.at(0).size() == 768); + + gmem1.confirm_pushed(s1, {"fakehash1"}); + CHECK(gmem1.needs_dump()); + CHECK_FALSE(gmem1.needs_push()); + + std::vector>> merge_configs; + merge_configs.emplace_back("fakehash1", p1.at(0)); + CHECK(gmem2.merge(merge_configs) == std::unordered_set{{"fakehash1"s}}); + CHECK_FALSE(gmem2.needs_push()); + + for (int i = 0; i < 25; i++) + CHECK(gmem2.get(sids[i]).has_value()); + + { + int i = 0; + for (auto& m : gmem2) { + CHECK(m.session_id == sids[i]); + CHECK_FALSE( + gmem2.get_status(m) == session::config::groups::member::Status::invite_failed); + CHECK_FALSE( + gmem2.get_status(m) == + session::config::groups::member::Status::promotion_not_sent); + CHECK_FALSE( + gmem2.get_status(m) == + session::config::groups::member::Status::promotion_failed); + CHECK_FALSE(gmem2.get_status(m) == session::config::groups::member::Status::removed); + CHECK_FALSE( + gmem2.get_status(m) == + session::config::groups::member::Status::removed_including_messages); + CHECK_FALSE(m.supplement); + if (i < 10) { + CHECK_FALSE( + gmem2.get_status(m) == + session::config::groups::member::Status::invite_not_sent); + CHECK(m.admin); + CHECK(m.name == "Admin {}"_format(i)); + CHECK_FALSE(m.profile_picture.empty()); + CHECK(gmem2.get_status(m) == + session::config::groups::member::Status::promotion_accepted); + } else { + // on gmem1, our local extra data marks m as invite_sending + CHECK(gmem1.get_status(m) == + session::config::groups::member::Status::invite_sending); + // that extra data is not pushed, so gmem2 doesn't know about it + CHECK(gmem2.get_status(m) == + session::config::groups::member::Status::invite_not_sent); + CHECK_FALSE(m.admin); + if (i < 20) { + CHECK(m.name == "Member {}"_format(i)); + CHECK_FALSE(m.profile_picture.empty()); + } else { + CHECK(m.name.empty()); + CHECK(m.profile_picture.empty()); + } + } + i++; + } + CHECK(i == 25); + } + + for (int i = 22; i < 50; i++) { + auto m = gmem2.get_or_construct(sids[i]); + m.name = "Member {}"_format(i); + gmem2.set(m); + } + for (int i = 50; i < 55; i++) { + auto m = gmem2.get_or_construct(sids[i]); + m.set_invite_sent(); + if (i % 2) + m.supplement = true; + gmem2.set(m); + } + for (int i = 55; i < 58; i++) { + auto m = gmem2.get_or_construct(sids[i]); + m.set_invite_failed(); + if (i % 2) + m.supplement = true; + gmem2.set(m); + } + for (int i = 58; i < 62; i++) { + auto m = gmem2.get_or_construct(sids[i]); + if (i >= 60) + m.set_promotion_failed(); + else + m.set_promotion_sent(); + gmem2.set(m); + } + for (int i = 62; i < 66; i++) { + auto m = gmem2.get_or_construct(sids[i]); + m.set_removed(i >= 64); + gmem2.set(m); + } + + CHECK(gmem2.get(sids[23]).value().name == "Member 23"); + + auto [s2, p2, o2] = gmem2.push(); + gmem2.confirm_pushed(s2, {"fakehash2"}); + merge_configs.emplace_back("fakehash2", p2.at(0)); // not clearing it first! + CHECK(gmem1.merge(merge_configs) == std::unordered_set{{"fakehash1"s}}); + gmem1.add_key("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"_hexbytes); + CHECK(gmem1.merge(merge_configs) == std::unordered_set{{"fakehash1"s, "fakehash2"s}}); + + CHECK(gmem1.get(sids[23]).value().name == "Member 23"); + + { + int i = 0; + for (auto& m : gmem1) { + CHECK(m.session_id == sids[i]); + CHECK(m.admin == (i < 10 || (i >= 58 && i < 62))); + CHECK(m.name == ((i == 20 || i == 21 || i >= 50) + ? "" + : "{} {}"_format(i < 10 ? "Admin" : "Member", i))); + CHECK(m.profile_picture.key == + (i < 20 ? "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"_hexbytes + : ""_hexbytes)); + CHECK(m.profile_picture.url == (i < 20 ? "http://example.com/{}"_format(i) : "")); + if (i >= 10 && i < 25) + CHECK(gmem1.get_status(m) == + session::config::groups::member::Status::invite_sending); + if (i >= 25 && i < 50) + CHECK(gmem1.get_status(m) == + session::config::groups::member::Status::invite_not_sent); + if (50 <= i && i < 55) + CHECK(gmem1.get_status(m) == session::config::groups::member::Status::invite_sent); + if (55 <= i && i < 58) + CHECK(gmem1.get_status(m) == + session::config::groups::member::Status::invite_failed); + if (i < 10) + CHECK(gmem1.get_status(m) == + session::config::groups::member::Status::promotion_accepted); + if (i >= 58 && i < 60) + CHECK(gmem1.get_status(m) == + session::config::groups::member::Status::promotion_sent); + if (i >= 60 && i < 62) + CHECK(gmem1.get_status(m) == + session::config::groups::member::Status::promotion_failed); + if (i >= 62 && i < 64) + CHECK(gmem1.get_status(m) == session::config::groups::member::Status::removed); + if (i >= 64 && i < 66) + CHECK(gmem1.get_status(m) == + session::config::groups::member::Status::removed_including_messages); + CHECK(m.supplement == (i % 2 && 50 < i && i < 58)); + i++; + } + CHECK(i == 66); + } + + for (int i = 0; i < 100; i++) { + if (is_prime100(i)) + gmem1.erase(sids[i]); + else if (i >= 50 && i <= 56) { + auto m = gmem1.get(sids[i]).value(); + if (i >= 55) + m.set_invite_sent(); + else + m.set_invite_accepted(); + gmem1.set(m); + } else if (i == 58) { + auto m = gmem1.get(sids[i]).value(); + m.set_promotion_accepted(); + gmem1.set(m); + } else if (i == 59) { + auto m = gmem1.get(sids[i]).value(); + m.set_promotion_sent(); + gmem1.set(m); + } + } + + auto [s3, p3, o3] = gmem1.push(); + gmem1.confirm_pushed(s3, {"fakehash3"}); + merge_configs.clear(); + merge_configs.emplace_back("fakehash3", p3.at(0)); + CHECK(gmem2.merge(merge_configs) == std::unordered_set{{"fakehash3"s}}); + + { + int i = 0; + for (auto& m : gmem2) { + CHECK(m.session_id == sids[i]); + CHECK(m.admin == (i < 10 || (i >= 58 && i < 62))); + CHECK(m.name == ((i == 20 || i == 21 || i >= 50) + ? "" + : "{} {}"_format(i < 10 ? "Admin" : "Member", i))); + CHECK(m.profile_picture.key == + (i < 20 ? "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"_hexbytes + : ""_hexbytes)); + CHECK(m.profile_picture.url == (i < 20 ? "http://example.com/{}"_format(i) : "")); + if (is_prime100(i) || (i >= 25 && i < 50)) + CHECK(gmem1.get_status(m) == + session::config::groups::member::Status::invite_not_sent); + if (!is_prime100(i) && i >= 10 && i < 25) + CHECK(gmem1.get_status(m) == + session::config::groups::member::Status::invite_sending); + if (i >= 50 && i < 54) + CHECK(gmem2.get_status(m) == + session::config::groups::member::Status::invite_accepted); + if (i == 53 || (i >= 55 && i < 57)) + CHECK(gmem2.get_status(m) == session::config::groups::member::Status::invite_sent); + if (i == 57) + CHECK(gmem2.get_status(m) == + session::config::groups::member::Status::invite_failed); + if (i < 10 || i == 58) + CHECK(gmem2.get_status(m) == + session::config::groups::member::Status::promotion_accepted); + if (i == 59) + CHECK(gmem2.get_status(m) == + session::config::groups::member::Status::promotion_sent); + if (i >= 60 && i < 62) + CHECK(gmem2.get_status(m) == + session::config::groups::member::Status::promotion_failed); + if (i >= 62 && i < 64) + CHECK(gmem2.get_status(m) == session::config::groups::member::Status::removed); + if (i >= 64 && i < 66) + CHECK(gmem2.get_status(m) == + session::config::groups::member::Status::removed_including_messages); + CHECK(m.supplement == (i == 55 || i == 57)); + + do + i++; + while (is_prime100(i)); + } + CHECK(i == 66); + } + + auto m = gmem1.get_or_construct(sids[0]); + CHECK_THROWS( + m.set_name("123456789012345678901234567890123456789012345678901234567890123456789012345" + "6789012345678901234567890A")); + CHECK_NOTHROW( + m.set_name_truncated("12345678901234567890123456789012345678901234567890123456789012345" + "67890123456789012345678901234567890A")); + CHECK(m.name == + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" + "901234567890"); +} + +TEST_CASE("Group Members restores extra data", "[config][groups][members]") { + + const auto seed = "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; + std::array ed_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair( + ed_pk.data(), ed_sk.data(), reinterpret_cast(seed.data())); + + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece"); + CHECK(oxenc::to_hex(seed.begin(), seed.end()) == + oxenc::to_hex(ed_sk.begin(), ed_sk.begin() + 32)); + + groups::Members gmem1{session::to_span(ed_pk), session::to_span(ed_sk), std::nullopt}; + + auto memberId1 = "050000000000000000000000000000000000000000000000000000000000000000"; + auto memberId2 = "051111111111111111111111111111111111111111111111111111111111111111"; + + auto member1 = gmem1.get_or_construct(memberId1); + auto member2 = gmem1.get_or_construct(memberId2); + + member2.set_promoted(); + gmem1.set(member1); // should be marked as "invite sending" right away + gmem1.set(member2); // should be marked as "promotion sending" right away + + CHECK(gmem1.get_status(gmem1.get_or_construct(memberId1)) == + groups::member::Status::invite_sending); + CHECK(gmem1.get_status(gmem1.get_or_construct(memberId2)) == + groups::member::Status::promotion_sending); + + auto dumped = gmem1.dump(); + + groups::Members gmem2{session::to_span(ed_pk), session::to_span(ed_sk), dumped}; + + CHECK(gmem2.get_status(gmem1.get_or_construct(memberId1)) == + groups::member::Status::invite_sending); + CHECK(gmem2.get_status(gmem1.get_or_construct(memberId2)) == + groups::member::Status::promotion_sending); +} diff --git a/tests/test_hash.cpp b/tests/test_hash.cpp new file mode 100644 index 00000000..ab1e2bc1 --- /dev/null +++ b/tests/test_hash.cpp @@ -0,0 +1,51 @@ +#include + +#include "session/hash.h" +#include "session/hash.hpp" +#include "session/util.hpp" +#include "utils.hpp" + +TEST_CASE("Hash generation", "[hash][hash]") { + auto hash1 = session::hash::hash(32, session::to_span("TestMessage"), std::nullopt); + auto hash2 = session::hash::hash(32, session::to_span("TestMessage"), std::nullopt); + auto hash3 = + session::hash::hash(32, session::to_span("TestMessage"), session::to_span("TestKey")); + auto hash4 = + session::hash::hash(32, session::to_span("TestMessage"), session::to_span("TestKey")); + auto hash5 = session::hash::hash(64, session::to_span("TestMessage"), std::nullopt); + auto hash6 = + session::hash::hash(64, session::to_span("TestMessage"), session::to_span("TestKey")); + CHECK_THROWS(session::hash::hash(10, session::to_span("TestMessage"), std::nullopt)); + CHECK_THROWS(session::hash::hash(100, session::to_span("TestMessage"), std::nullopt)); + CHECK_THROWS(session::hash::hash( + 32, + session::to_span("TestMessage"), + session::to_span("KeyThatIsTooLongKeyThatIsTooLongKeyThatIsTooLongKeyThatIsTooLongKeyTh" + "atIsTooLon" + "g"))); + + CHECK(hash1.size() == 32); + CHECK(hash2.size() == 32); + CHECK(hash3.size() == 32); + CHECK(hash4.size() == 32); + CHECK(hash5.size() == 64); + CHECK(hash6.size() == 64); + CHECK(hash1 == hash2); + CHECK(hash1 != hash3); + CHECK(hash3 == hash4); + CHECK(hash1 != hash5); + CHECK(hash3 != hash6); + CHECK(to_hex(hash1) == "2a48a12262e4548afb97fe2b04a912a02297d451169ee7ef2d01a28ea20286ab"); + CHECK(to_hex(hash2) == "2a48a12262e4548afb97fe2b04a912a02297d451169ee7ef2d01a28ea20286ab"); + CHECK(to_hex(hash3) == "3d643e479b626bb2907476e32ccf7bdbd1ac3efa0da6e2c335255c48dcc216b6"); + CHECK(to_hex(hash4) == "3d643e479b626bb2907476e32ccf7bdbd1ac3efa0da6e2c335255c48dcc216b6"); + + auto expected_hash5 = + "9d9085ac026fe3542abbeb2ea2ec05f5c37aecd7695f6cc41e9ccf39014196a39c02db69c44" + "16d5c45acc2e9469b7f274992b2858f3bb2746becb48c8b56ce4b"; + auto expected_hash6 = + "6a2faad89cf9010a4270cba07cc96cfb36688106e080b15fef66bb03c68e877874c9059edf5" + "3d03c1330b2655efdad6e4aa259118b6ea88698ea038efb9d52ce"; + CHECK(to_hex(hash5) == expected_hash5); + CHECK(to_hex(hash6) == expected_hash6); +} diff --git a/tests/test_logging.cpp b/tests/test_logging.cpp new file mode 100644 index 00000000..5fdde5ed --- /dev/null +++ b/tests/test_logging.cpp @@ -0,0 +1,114 @@ +#include + +#include +#include +#include +#include +#include + +#include "utils.hpp" + +#ifndef DISABLE_ONIONREQ +#include +#endif + +using namespace session; +using namespace oxen; +using namespace oxen::log::literals; + +std::regex timestamp_re{R"(\[\d{4}-\d\d-\d\d \d\d:\d\d:\d\d\] \[\+[\d.hms]+\])"}; +// Clears timestamps out of a log statement for testing reproducibility +std::string fixup_log(std::string_view log) { + std::string fixed; + std::regex_replace( + std::back_inserter(fixed), + log.begin(), + log.end(), + timestamp_re, + "[] []", + std::regex_constants::format_first_only); + return fixed; +} + +std::vector simple_logs; +std::vector full_logs; // "cat|level|msg" + +TEST_CASE("Logging callbacks", "[logging]") { + oxen::log::clear_sinks(); + simple_logs.clear(); + full_logs.clear(); + log_level_lowerer log_relief_a{log::Level::info, "test.a"}; + log_level_lowerer log_relief_b{log::Level::info, "test.b"}; + + SECTION("C++ lambdas") { + session::add_logger([&](std::string_view msg) { simple_logs.emplace_back(msg); }); + session::add_logger([&](auto msg, auto cat, auto level) { + full_logs.push_back("{}|{}|{}"_format(cat, level.to_string(), msg)); + }); + } + SECTION("C function pointers") { + session_add_logger_simple( + [](const char* msg, size_t msglen) { simple_logs.emplace_back(msg, msglen); }); + session_add_logger_full([](const char* msg, + size_t msglen, + const char* cat, + size_t cat_len, + LOG_LEVEL level) { + full_logs.push_back("{}|{}|{}"_format( + std::string{cat, cat_len}, + oxen::log::to_string(static_cast(level)), + std::string{msg, msglen})); + }); + } + + log::critical(log::Cat("test.a"), "abc {}", 21 * 2); +#if defined(__APPLE__) && defined(__clang__) && (__clang_major__ <= 15) +#else + int line0 = __LINE__ - 3; +#endif + log::info(log::Cat("test.b"), "hi"); +#if defined(__APPLE__) && defined(__clang__) && (__clang_major__ <= 15) +#else + int line1 = __LINE__ - 3; +#endif + + oxen::log::clear_sinks(); + + REQUIRE(simple_logs.size() == 2); + REQUIRE(full_logs.size() == 2); + CHECK(fixup_log(simple_logs[0]) == + "[] [] [test.a:critical|test_logging.cpp:{}] abc 42\n"_format(line0)); + CHECK(fixup_log(simple_logs[1]) == + "[] [] [test.b:info|test_logging.cpp:{}] hi\n"_format(line1)); + CHECK(fixup_log(full_logs[0]) == + "test.a|critical|[] [] [test.a:critical|test_logging.cpp:{}] abc 42\n"_format( + line0)); + CHECK(fixup_log(full_logs[1]) == + "test.b|info|[] [] [test.b:info|test_logging.cpp:{}] hi\n"_format( + line1)); +} + +#ifndef DISABLE_ONIONREQ +TEST_CASE("Logging callbacks with quic::Network", "[logging][network]") { + oxen::log::clear_sinks(); + simple_logs.clear(); + log_level_lowerer log_relief{log::Level::debug, "quic"}; + + session::add_logger([&](std::string_view msg) { simple_logs.emplace_back(msg); }); + + { + quic::Network net; + } + + oxen::log::clear_sinks(); + + CHECK(simple_logs.size() >= 2); + // CHECK(simple_logs == std::vector{"uncomment me to fail showing all log lines"}); +#if defined(__APPLE__) && defined(__clang__) && defined(RELEASE_BUILD) + CHECK(simple_logs.front().find("libevent loop is started") != std::string::npos); +#else + CHECK(simple_logs.front().find("Starting libevent") != std::string::npos); +#endif + CHECK(simple_logs.back().find("Loop shutdown complete") != std::string::npos); +} +#endif \ No newline at end of file diff --git a/tests/test_multi_encrypt.cpp b/tests/test_multi_encrypt.cpp new file mode 100644 index 00000000..9eed94ed --- /dev/null +++ b/tests/test_multi_encrypt.cpp @@ -0,0 +1,353 @@ +#include +#include +#include + +#include +#include +#include + +#include "utils.hpp" + +using x_pair = std::pair, std::array>; + +// Returns X25519 privkey, pubkey from an Ed25519 seed +x_pair to_x_keys(std::span ed_seed) { + std::array ed_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair(ed_pk.data(), ed_sk.data(), ed_seed.data()); + x_pair ret; + auto& [x_priv, x_pub] = ret; + [[maybe_unused]] int rc = crypto_sign_ed25519_pk_to_curve25519(x_pub.data(), ed_pk.data()); + assert(rc == 0); + crypto_sign_ed25519_sk_to_curve25519(x_priv.data(), ed_sk.data()); + return ret; +} + +TEST_CASE("Multi-recipient encryption", "[encrypt][multi]") { + + const std::array seeds = { + "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes, + "0123456789abcdef000000000000000000000000000000000000000000000000"_hexbytes, + "0123456789abcdef111111111111111100000000000000000000000000000000"_hexbytes, + "0123456789abcdef222222222222222200000000000000000000000000000000"_hexbytes, + "0123456789abcdef333333333333333300000000000000000000000000000000"_hexbytes}; + + std::array x_keys; + for (size_t i = 0; i < seeds.size(); i++) + x_keys[i] = to_x_keys(seeds[i]); + + CHECK(oxenc::to_hex(session::to_span(x_keys[0].second)) == + "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + CHECK(oxenc::to_hex(session::to_span(x_keys[1].second)) == + "d673a8fb4800d2a252d2fc4e3342a88cdfa9412853934e8993d12d593be13371"); + CHECK(oxenc::to_hex(session::to_span(x_keys[2].second)) == + "afd9716ea69ab8c7f475e1b250c86a6539e260804faecf2a803e9281a4160738"); + CHECK(oxenc::to_hex(session::to_span(x_keys[3].second)) == + "03be14feabd59122349614b88bdc90db1d1af4c230e9a73c898beec833d51f11"); + CHECK(oxenc::to_hex(session::to_span(x_keys[4].second)) == + "27b5c1ea87cef76284c752fa6ee1b9186b1a95e74e8f5b88f8b47e5191ce6f08"); + + auto nonce = "32ab4bb45d6df5cc14e1c330fb1a8b68ea3826a8c2213a49"_hexbytes; + + std::vector> recipients; + for (auto& [_, pubkey] : x_keys) + recipients.emplace_back(pubkey.data(), pubkey.size()); + + std::vector msgs{{"hello", "cruel", "world"}}; + std::vector> encrypted; + session::encrypt_for_multiple( + msgs[0], + session::to_view_vector(std::next(recipients.begin()), std::prev(recipients.end())), + nonce, + session::to_span(x_keys[0].first), + session::to_span(x_keys[0].second), + "test suite", + [&](std::span enc) { + encrypted.emplace_back(session::to_vector(enc)); + }); + + REQUIRE(encrypted.size() == 3); + CHECK(to_hex(encrypted[0]) == "e64937e5ea201b84f4e88a976dad900d91caaf6a17"); + CHECK(to_hex(encrypted[1]) == "b7a15bcd9f7b09445defcae2f1dc5085dd75cb085b"); + CHECK(to_hex(encrypted[2]) == "01c4fc2156327735f3fb5063b11ea95f6ebcc5b6cc"); + + auto m1 = session::decrypt_for_multiple( + session::to_view_vector(encrypted), + nonce, + session::to_span(x_keys[1].first), + session::to_span(x_keys[1].second), + session::to_span(x_keys[0].second), + "test suite"); + auto m2 = session::decrypt_for_multiple( + session::to_view_vector(encrypted), + nonce, + session::to_span(x_keys[2].first), + session::to_span(x_keys[2].second), + session::to_span(x_keys[0].second), + "test suite"); + auto m3 = session::decrypt_for_multiple( + session::to_view_vector(encrypted), + nonce, + session::to_span(x_keys[3].first), + session::to_span(x_keys[3].second), + session::to_span(x_keys[0].second), + "test suite"); + auto m3b = session::decrypt_for_multiple( + session::to_view_vector(encrypted), + nonce, + session::to_span(x_keys[3].first), + session::to_span(x_keys[3].second), + session::to_span(x_keys[0].second), + "not test suite"); + auto m4 = session::decrypt_for_multiple( + session::to_view_vector(encrypted), + nonce, + session::to_span(x_keys[4].first), + session::to_span(x_keys[4].second), + session::to_span(x_keys[0].second), + "test suite"); + + REQUIRE(m1); + REQUIRE(m2); + REQUIRE(m3); + CHECK_FALSE(m3b); + CHECK_FALSE(m4); + + CHECK(session::to_string(*m1) == "hello"); + CHECK(session::to_string(*m2) == "hello"); + CHECK(session::to_string(*m3) == "hello"); + + encrypted.clear(); + session::encrypt_for_multiple( + session::to_view_vector(msgs.begin(), msgs.end()), + session::to_view_vector(std::next(recipients.begin()), std::prev(recipients.end())), + nonce, + session::to_span(x_keys[0].first), + session::to_span(x_keys[0].second), + "test suite", + [&](std::span enc) { + encrypted.emplace_back(session::to_vector(enc)); + }); + + REQUIRE(encrypted.size() == 3); + CHECK(to_hex(encrypted[0]) == "e64937e5ea201b84f4e88a976dad900d91caaf6a17"); + CHECK(to_hex(encrypted[1]) == "bcb642c49c6da03f70cdaab2ed6666721318afd631"); + CHECK(to_hex(encrypted[2]) == "1ecee2215d226817edfdb097f05037eb799309103a"); + + m1 = session::decrypt_for_multiple( + session::to_view_vector(encrypted), + nonce, + session::to_span(x_keys[1].first), + session::to_span(x_keys[1].second), + session::to_span(x_keys[0].second), + "test suite"); + m2 = session::decrypt_for_multiple( + session::to_view_vector(encrypted), + nonce, + session::to_span(x_keys[2].first), + session::to_span(x_keys[2].second), + session::to_span(x_keys[0].second), + "test suite"); + m3 = session::decrypt_for_multiple( + session::to_view_vector(encrypted), + nonce, + session::to_span(x_keys[3].first), + session::to_span(x_keys[3].second), + session::to_span(x_keys[0].second), + "test suite"); + m3b = session::decrypt_for_multiple( + session::to_view_vector(encrypted), + nonce, + session::to_span(x_keys[3].first), + session::to_span(x_keys[3].second), + session::to_span(x_keys[0].second), + "not test suite"); + m4 = session::decrypt_for_multiple( + session::to_view_vector(encrypted), + nonce, + session::to_span(x_keys[4].first), + session::to_span(x_keys[4].second), + session::to_span(x_keys[0].second), + "test suite"); + + REQUIRE(m1); + REQUIRE(m2); + REQUIRE(m3); + CHECK_FALSE(m3b); + CHECK_FALSE(m4); + + CHECK(session::to_string(*m1) == "hello"); + CHECK(session::to_string(*m2) == "cruel"); + CHECK(session::to_string(*m3) == "world"); + + // Mismatch messages & recipients size throws: + CHECK_THROWS(session::encrypt_for_multiple( + session::to_view_vector(msgs.begin(), std::prev(msgs.end())), + session::to_view_vector(std::next(recipients.begin()), std::prev(recipients.end())), + nonce, + session::to_span(x_keys[0].first), + session::to_span(x_keys[0].second), + "test suite", + [&](std::span enc) { + encrypted.emplace_back(session::to_vector(enc)); + })); +} + +TEST_CASE("Multi-recipient encryption, simpler interface", "[encrypt][multi][simple]") { + + const std::array seeds = { + "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes, + "0123456789abcdef000000000000000000000000000000000000000000000000"_hexbytes, + "0123456789abcdef111111111111111100000000000000000000000000000000"_hexbytes, + "0123456789abcdef222222222222222200000000000000000000000000000000"_hexbytes, + "0123456789abcdef333333333333333300000000000000000000000000000000"_hexbytes}; + + std::array x_keys; + for (size_t i = 0; i < seeds.size(); i++) + x_keys[i] = to_x_keys(seeds[i]); + + CHECK(oxenc::to_hex(session::to_span(x_keys[0].second)) == + "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + CHECK(oxenc::to_hex(session::to_span(x_keys[1].second)) == + "d673a8fb4800d2a252d2fc4e3342a88cdfa9412853934e8993d12d593be13371"); + CHECK(oxenc::to_hex(session::to_span(x_keys[2].second)) == + "afd9716ea69ab8c7f475e1b250c86a6539e260804faecf2a803e9281a4160738"); + CHECK(oxenc::to_hex(session::to_span(x_keys[3].second)) == + "03be14feabd59122349614b88bdc90db1d1af4c230e9a73c898beec833d51f11"); + CHECK(oxenc::to_hex(session::to_span(x_keys[4].second)) == + "27b5c1ea87cef76284c752fa6ee1b9186b1a95e74e8f5b88f8b47e5191ce6f08"); + + auto nonce = "32ab4bb45d6df5cc14e1c330fb1a8b68ea3826a8c2213a49"_hexbytes; + + std::vector> recipients; + for (auto& [_, pubkey] : x_keys) + recipients.emplace_back(pubkey.data(), pubkey.size()); + + std::vector msgs{{"hello", "cruel", "world"}}; + std::vector encrypted = session::encrypt_for_multiple_simple( + msgs[0], + session::to_view_vector(std::next(recipients.begin()), std::prev(recipients.end())), + session::to_span(x_keys[0].first), + session::to_span(x_keys[0].second), + "test suite"); + + REQUIRE(encrypted.size() == + /* de */ 2 + + /* 1:# 24:...nonce... */ 3 + 27 + + /* 1:e le */ 3 + 2 + + /* XX: then data with overhead */ 3 * + (3 + 5 + crypto_aead_xchacha20poly1305_ietf_ABYTES)); + + // If we encrypt again the value should be different (because of the default randomized nonce): + CHECK(encrypted != session::encrypt_for_multiple_simple( + msgs[0], + session::to_view_vector( + std::next(recipients.begin()), std::prev(recipients.end())), + session::to_span(x_keys[0].first), + session::to_span(x_keys[0].second), + "test suite")); + + auto m1 = session::decrypt_for_multiple_simple( + encrypted, + session::to_span(x_keys[1].first), + session::to_span(x_keys[1].second), + session::to_span(x_keys[0].second), + "test suite"); + auto m2 = session::decrypt_for_multiple_simple( + encrypted, + session::to_span(x_keys[2].first), + session::to_span(x_keys[2].second), + session::to_span(x_keys[0].second), + "test suite"); + auto m3 = session::decrypt_for_multiple_simple( + encrypted, + session::to_span(x_keys[3].first), + session::to_span(x_keys[3].second), + session::to_span(x_keys[0].second), + "test suite"); + auto m3b = session::decrypt_for_multiple_simple( + encrypted, + session::to_span(x_keys[3].first), + session::to_span(x_keys[3].second), + session::to_span(x_keys[0].second), + "not test suite"); + auto m4 = session::decrypt_for_multiple_simple( + encrypted, + session::to_span(x_keys[4].first), + session::to_span(x_keys[4].second), + session::to_span(x_keys[0].second), + "test suite"); + + REQUIRE(m1); + REQUIRE(m2); + REQUIRE(m3); + CHECK_FALSE(m3b); + CHECK_FALSE(m4); + + CHECK(session::to_string(*m1) == "hello"); + CHECK(session::to_string(*m2) == "hello"); + CHECK(session::to_string(*m3) == "hello"); + + encrypted = session::encrypt_for_multiple_simple( + session::to_view_vector(msgs), + session::to_view_vector(std::next(recipients.begin()), std::prev(recipients.end())), + session::to_span(x_keys[0].first), + session::to_span(x_keys[0].second), + "test suite", + nonce); + + CHECK(printable(encrypted) == printable( + "d1:#24:{}1:el21:{}21:{}21:{}ee", + "32ab4bb45d6df5cc14e1c330fb1a8b68ea3826a8c2213a49"_hex, + "e64937e5ea201b84f4e88a976dad900d91caaf6a17"_hex, + "bcb642c49c6da03f70cdaab2ed6666721318afd631"_hex, + "1ecee2215d226817edfdb097f05037eb799309103a"_hex)); + + m1 = session::decrypt_for_multiple_simple( + encrypted, + session::to_span(x_keys[1].first), + session::to_span(x_keys[1].second), + session::to_span(x_keys[0].second), + "test suite"); + m2 = session::decrypt_for_multiple_simple( + encrypted, + session::to_span(x_keys[2].first), + session::to_span(x_keys[2].second), + session::to_span(x_keys[0].second), + "test suite"); + m3 = session::decrypt_for_multiple_simple( + encrypted, + session::to_span(x_keys[3].first), + session::to_span(x_keys[3].second), + session::to_span(x_keys[0].second), + "test suite"); + m3b = session::decrypt_for_multiple_simple( + encrypted, + session::to_span(x_keys[3].first), + session::to_span(x_keys[3].second), + session::to_span(x_keys[0].second), + "not test suite"); + m4 = session::decrypt_for_multiple_simple( + encrypted, + session::to_span(x_keys[4].first), + session::to_span(x_keys[4].second), + session::to_span(x_keys[0].second), + "test suite"); + + REQUIRE(m1); + REQUIRE(m2); + REQUIRE(m3); + CHECK_FALSE(m3b); + CHECK_FALSE(m4); + + CHECK(session::to_string(*m1) == "hello"); + CHECK(session::to_string(*m2) == "cruel"); + CHECK(session::to_string(*m3) == "world"); + + CHECK_THROWS(session::encrypt_for_multiple_simple( + session::to_view_vector(msgs.begin(), std::prev(msgs.end())), + session::to_view_vector(std::next(recipients.begin()), std::prev(recipients.end())), + session::to_span(x_keys[0].first), + session::to_span(x_keys[0].second), + "test suite")); +} diff --git a/tests/test_onionreq.cpp b/tests/test_onionreq.cpp new file mode 100644 index 00000000..c73935c5 --- /dev/null +++ b/tests/test_onionreq.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include + +#include "utils.hpp" + +using namespace session; +using namespace session::onionreq; + +TEST_CASE("Onion request encryption", "[encryption][onionreq]") { + + auto A = "bbdfc83022d0aff084a6f0c529a93d1c4d728bf7e41199afed0e01ae70d20540"_hexbytes; + auto B = "caea52c5b0c316d85ffb53ea536826618b13dee40685f166f632653114526a78"_hexbytes; + auto b = "8fcd8ad3a15c76f76f1c56dff0c529999f8c59b4acda79e05666e54d5727dca1"_hexbytes; + + auto enc_gcm = + "1eb6ae1cd72f60999486365749bd5dc15cc0b6a2a44d7d063daa5e93722f0c025fd00306403b61" + ""_hexbytes; + auto enc_gcm_broken1 = + "1eb6ae1cd72f60999486365759bd5dc15cc0b6a2a44d7d063daa5e93722f0c025fd00306403b61" + ""_hexbytes; + auto enc_gcm_broken2 = + "1eb6ae1cd72f60999486365749bd5dc15cc0b6a2a44d7d063daa5e93722f0c025fd00306403b69" + ""_hexbytes; + auto enc_xchacha20 = + "9e1a3abe60eff3ea5c23556cc7e225b6f94355315f7281f66ecf4dbb06e7899a52b863e03cde3b28" + "7d1638d765db75de02b032"_hexbytes; + auto enc_xchacha20_broken1 = + "9e1a3abe60eff3ea5c23556cc7e225b6f94355315f7281f66ecf4dbb06e7899a52b863e03cde3b28" + "7d1638d765db75de02b033"_hexbytes; + auto enc_xchacha20_broken2 = + "9e1a3abe60eff3ea5c23556ccfe225b6f94355315f7281f66ecf4dbb06e7899a52b863e03cde3b28" + "7d1638d765db75de02b032"_hexbytes; + + HopEncryption e{x25519_seckey::from_bytes(b), x25519_pubkey::from_bytes(B), true}; + + CHECK(to_string(e.decrypt_aesgcm(enc_gcm, x25519_pubkey::from_bytes(A))) == "Hello world"); + CHECK(to_string(e.decrypt_xchacha20(enc_xchacha20, x25519_pubkey::from_bytes(A))) == + "Hello world"); + CHECK_THROWS(e.decrypt_aesgcm(enc_xchacha20_broken1, x25519_pubkey::from_bytes(A))); + CHECK_THROWS(e.decrypt_aesgcm(enc_xchacha20_broken2, x25519_pubkey::from_bytes(A))); + CHECK_THROWS(e.decrypt_xchacha20(enc_xchacha20_broken1, x25519_pubkey::from_bytes(A))); + CHECK_THROWS(e.decrypt_xchacha20(enc_xchacha20_broken2, x25519_pubkey::from_bytes(A))); +} + +TEST_CASE("Onion request parser", "[onionreq][parser]") { + + auto A = "8167e97672005c669a48858c69895f395ca235219ac3f7a4210022b1f910e652"_hexbytes; + auto a = "d2ee09e1a557a077d385fcb69a11ffb6909ecdcc8348def3e0e4172c8a1431c1"_hexbytes; + auto B = "8388de69bc0d4b6196133233ad9a46ba0473474bc67718aad96a3a33c257f726"_hexbytes; + auto b = "2f4d1c0d28e137777ec0a316e9f4f763e3e66662a6c51994c6315c9ef34b6deb"_hexbytes; + + auto enc_gcm = + "270000009525d587d188c92a966eef0e7162bef99a6171a124575b998072a8ee7eb265e0b6f0930ed96504" + "7b22656e635f74797065223a20226165732d67636d222c2022657068656d6572616c5f6b6579223a202238" + "31363765393736373230303563363639613438383538633639383935663339356361323335323139616333" + "6637613432313030323262316639313065363532227d"_hexbytes; + auto enc_xchacha20 = + "33000000e440bc244ddcafd947b86fc5a964aa58de54a6d75cc0f0f3840db14b6c1176a8e2e0a04d5fbdf9" + "8f23adee1edc8362ab99b10b7b22656e635f74797065223a2022786368616368613230222c202265706865" + "6d6572616c5f6b6579223a2022383136376539373637323030356336363961343838353863363938393566" + "33393563613233353231396163336637613432313030323262316639313065363532227d"_hexbytes; + + OnionReqParser parser_gcm{B, b, enc_gcm}; + CHECK(to_string(parser_gcm.payload()) == "Hello world"); + CHECK(to_string_view(parser_gcm.remote_pubkey()) == to_string_view(A)); + auto aes_reply = parser_gcm.encrypt_reply(to_span("Goodbye world")); + CHECK(aes_reply.size() == 12 + 13 + 16); + + HopEncryption e{x25519_seckey::from_bytes(a), x25519_pubkey::from_bytes(A), false}; + CHECK(to_string(e.decrypt_aesgcm(aes_reply, x25519_pubkey::from_bytes(B))) == "Goodbye world"); + + OnionReqParser parser_xchacha20{B, b, enc_xchacha20}; + CHECK(to_string(parser_xchacha20.payload()) == "Hello world"); + CHECK(to_string_view(parser_xchacha20.remote_pubkey()) == to_string_view(A)); + auto xcha_reply = parser_xchacha20.encrypt_reply(to_span("Goodbye world")); + CHECK(xcha_reply.size() == 16 + 13 + 24); + CHECK(to_string(e.decrypt_xchacha20(xcha_reply, x25519_pubkey::from_bytes(B))) == + "Goodbye world"); +} diff --git a/tests/test_proto.cpp b/tests/test_proto.cpp new file mode 100644 index 00000000..f79706fb --- /dev/null +++ b/tests/test_proto.cpp @@ -0,0 +1,111 @@ +#include + +#include +#include +#include +#include +#include + +#include "utils.hpp" + +using namespace session::config; + +const std::vector groups{ + Namespace::UserProfile, + Namespace::Contacts, + Namespace::ConvoInfoVolatile, + Namespace::UserGroups}; + +const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; +std::array ed_pk_raw; +std::array ed_sk_raw; +std::span load_seed() { + crypto_sign_ed25519_seed_keypair(ed_pk_raw.data(), ed_sk_raw.data(), seed.data()); + return {ed_sk_raw.data(), ed_sk_raw.size()}; +} +auto ed_sk = load_seed(); + +TEST_CASE("Protobuf Handling - Wrap, Unwrap", "[config][proto][wrap]") { + auto msg = "Hello from the other side"_bytes; + + SECTION("Wrap/unwrap message types") { + for (auto& n : groups) { + auto shared_config_msg = protos::wrap_config(ed_sk, msg, 1, n); + + CHECK(not shared_config_msg.empty()); + + auto shared_config_parsed = protos::unwrap_config(ed_sk, shared_config_msg, n); + // This will be false, as ::unwrap_config will return the parsed payload if it + // successfully parses a protobuf wrapped message + CHECK_FALSE(shared_config_msg == shared_config_parsed); + // This will return true, as the parsed message will match the payload + CHECK(shared_config_parsed == msg); + } + } + + SECTION("Message type payload comparison") { + auto user_profile_msg = protos::wrap_config(ed_sk, msg, 1, Namespace::UserProfile); + auto contacts_msg = protos::wrap_config(ed_sk, msg, 1, Namespace::Contacts); + + auto user_profile_parsed = + protos::unwrap_config(ed_sk, user_profile_msg, Namespace::UserProfile); + auto contacts_parsed = protos::unwrap_config(ed_sk, contacts_msg, Namespace::Contacts); + + // All of these will return true, as the parsed messages will be identical to the + // payload, and therefore identical to one another + CHECK(user_profile_parsed == contacts_parsed); + CHECK(user_profile_parsed == msg); + CHECK(contacts_parsed == msg); + } +} + +TEST_CASE("Protobuf Handling - Error Handling", "[config][proto][error]") { + auto msg = "Hello from the other side"_bytes; + auto addendum = "jfeejj0ifdoesam"_bytes; + + const auto user_profile_msg = protos::wrap_config(ed_sk, msg, 1, Namespace::UserProfile); + const auto size = user_profile_msg.size(); + + // Testing three positions: front, inside the payload, and at the end + const std::vector positions{0, size - 4, size}; + + for (auto& p : positions) { + auto msg_copy = user_profile_msg; + msg_copy.insert(msg_copy.begin() + p, addendum.begin(), addendum.end()); + + REQUIRE_THROWS(protos::unwrap_config(ed_sk, msg_copy, Namespace::UserProfile)); + } +} + +TEST_CASE("Protobuf old config loading test", "[config][proto][old]") { + + const auto seed = "f887566576de6c16d9ec251d55e24c1400000000000000000000000000000000"_hexbytes; + std::array ed_pk_raw; + std::array ed_sk_raw; + crypto_sign_ed25519_seed_keypair(ed_pk_raw.data(), ed_sk_raw.data(), seed.data()); + std::span ed_sk{ed_sk_raw.data(), ed_sk_raw.size()}; + + auto old_conf = + "080112c2060a03505554120f2f6170692f76312f6d6573736167651a9f060806120028e1c5a0beaf313801" + "428f065228bb32b820169e0acb266f02efa007276be0668013a278fc9bfc111a40136f63de4206943c0509" + "6155fa480cd0a7f5d27d6297166f5ed5c2a323ecdf7a754308dd385cdce81e7ed3a0a305577838105a0798" + "dd92540f4b8eaa74f8c5720e0a394ce005444322354d6dfe1cb527520145f3794718e42730e15c97f7e45f" + "b53f9f7d3918ee57e5c8462f80ae0d64792c261feb4b9ce06b18a10b3d8f7af8f791b1368bd4ae9bbe0036" + "dc77f547c001e26c9c986269281bc3e8ef38c42ad2a02a9be517fc85c0c8fa4732f79138910f85bba0f898" + "f278d8c2ed3e7d00cc5b4f1eb32ffc9572ec98fac529bec7ad8560dc06fc986516c00232e9618c372c0f57" + "c19283e0424ec91864aad7277e22c085443cc0bfd39c0a83f0a1a8f856850ede7a751bd6206cb6683e462a" + "033ad282e4947adbbe4973e823676ae0a72aa5f0f607f306fe82b91da9b7fe79d4fb4e8a45cb9ad5f20c15" + "1a84073cc62d7ac794fdd2fe57bf49f1089f8644ad9f73d154d14c63d5ca7a07d1b6ab6b5846b2f4785fbf" + "738de23c250a711f54c941fd6f5aac4417125bb2d0321cd9f1b97a31f310d4ea8149732276b8df9869fbc5" + "412c9b7772961fab800a356155549ef54cefb9407d7f10b4323824aa8ea13facc79003b84dae3e5ef0db27" + "5b056f4fbdf54f5f22e62291af8427fc17c3c1b3985f6ee149729d8a5b794b7e374f408eb8f36a76a89680" + "e3c6106a9d5a82f6f04f5d8b603a97140b6469daac0ef32f84cc4ffc05f43c084591b10834b1d16d65ce14" + "15dec77cb5851c338ccbb0d5ae2d2c1e5bc8ba0f59dfbc4575fd446c8486a1ac5370d5da8eb041f2ed560a" + "bc1a6ad6ce6e00369ec5fd5eb0a35411ed24b36ecbf80f1dc6c18452c4b4bfc59131e04400df8986cac95c" + "51bbd320ba901ff6110dad0c70442286cf6220a53c6f9693636a42d5523eeb1e5fb3453169581384fb8a8f" + "3914fb6c01900a4f872f55742b117ddd7bd40c4c5911bb214e28eb9450dbdd0d831a93054c63f9a04bf50c" + "db9aac0032c484062d7ba7bbe64e07bcd633eec8378d5d914732693c5e298f015ebde2ae45769ed319e267" + "f0528f5cc6da268343b6647b20bae6e9ee8d92cca702"_hexbytes; + + CHECK_NOTHROW(protos::unwrap_config(ed_sk, old_conf, Namespace::UserProfile)); +} diff --git a/tests/test_random.cpp b/tests/test_random.cpp new file mode 100644 index 00000000..c02ad515 --- /dev/null +++ b/tests/test_random.cpp @@ -0,0 +1,16 @@ +#include + +#include "session/random.h" +#include "session/random.hpp" +#include "utils.hpp" + +TEST_CASE("Random generation", "[random][random]") { + auto rand1 = session::random::random(10); + auto rand2 = session::random::random(10); + auto rand3 = session::random::random(20); + + CHECK(rand1.size() == 10); + CHECK(rand2.size() == 10); + CHECK(rand3.size() == 20); + CHECK(rand1 != rand2); +} diff --git a/tests/test_session_encrypt.cpp b/tests/test_session_encrypt.cpp new file mode 100644 index 00000000..41efa1ff --- /dev/null +++ b/tests/test_session_encrypt.cpp @@ -0,0 +1,515 @@ +#include +#include + +#include +#include +#include +#include + +#include "utils.hpp" + +TEST_CASE("Session protocol encryption", "[session-protocol][encrypt]") { + + using namespace session; + + const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; + std::array ed_pk, curve_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair(ed_pk.data(), ed_sk.data(), seed.data()); + REQUIRE(0 == crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed_pk.data())); + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"); + REQUIRE(oxenc::to_hex(curve_pk.begin(), curve_pk.end()) == + "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + auto sid = "05" + oxenc::to_hex(curve_pk.begin(), curve_pk.end()); + std::vector sid_raw; + oxenc::from_hex(sid.begin(), sid.end(), std::back_inserter(sid_raw)); + REQUIRE(sid == "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + REQUIRE(sid_raw == + "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"_hexbytes); + + const auto seed2 = "00112233445566778899aabbccddeeff00000000000000000000000000000000"_hexbytes; + std::array ed_pk2, curve_pk2; + std::array ed_sk2; + crypto_sign_ed25519_seed_keypair(ed_pk2.data(), ed_sk2.data(), seed2.data()); + REQUIRE(0 == crypto_sign_ed25519_pk_to_curve25519(curve_pk2.data(), ed_pk2.data())); + REQUIRE(oxenc::to_hex(ed_pk2.begin(), ed_pk2.end()) == + "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"); + REQUIRE(oxenc::to_hex(curve_pk2.begin(), curve_pk2.end()) == + "aa654f00fc39fc69fd0db829410ca38177d7732a8d2f0934ab3872ac56d5aa74"); + auto sid2 = "05" + oxenc::to_hex(curve_pk2.begin(), curve_pk2.end()); + REQUIRE(sid2 == "05aa654f00fc39fc69fd0db829410ca38177d7732a8d2f0934ab3872ac56d5aa74"); + std::vector sid_raw2; + oxenc::from_hex(sid2.begin(), sid2.end(), std::back_inserter(sid_raw2)); + REQUIRE(sid_raw2 == + "05aa654f00fc39fc69fd0db829410ca38177d7732a8d2f0934ab3872ac56d5aa74"_hexbytes); + + SECTION("full secret, prefixed sid") { + auto enc = encrypt_for_recipient(to_span(ed_sk), sid_raw2, to_span("hello")); + CHECK(to_string(enc) != "hello"); + + CHECK_THROWS(decrypt_incoming(to_span(ed_sk), enc)); + + auto [msg, sender] = decrypt_incoming(to_span(ed_sk2), enc); + CHECK(to_hex(sender) == oxenc::to_hex(ed_pk.begin(), ed_pk.end())); + CHECK(to_string(msg) == "hello"); + + auto broken = enc; + broken[2] ^= 0x02; + CHECK_THROWS(decrypt_incoming(to_span(ed_sk2), broken)); + } + SECTION("only seed, unprefixed sid") { + constexpr auto lorem_ipsum = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " + "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " + "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in " + "culpa qui officia deserunt mollit anim id est laborum."sv; + auto enc = + encrypt_for_recipient({to_span(ed_sk).data(), 32}, sid_raw2, to_span(lorem_ipsum)); + CHECK(std::search( + enc.begin(), + enc.end(), + to_unsigned("dolore magna"), + to_unsigned("dolore magna") + strlen("dolore magna")) == enc.end()); + + CHECK_THROWS(decrypt_incoming(to_span(ed_sk), enc)); + + auto [msg, sender] = decrypt_incoming(to_span(ed_sk2), enc); + CHECK(to_hex(sender) == oxenc::to_hex(ed_pk.begin(), ed_pk.end())); + CHECK(to_string(msg) == lorem_ipsum); + + auto broken = enc; + broken[14] ^= 0x80; + CHECK_THROWS(decrypt_incoming(to_span(ed_sk2), broken)); + } +} + +TEST_CASE("Session protocol deterministic encryption", "[session-protocol][encrypt]") { + + using namespace session; + + const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; + std::array ed_pk, curve_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair(ed_pk.data(), ed_sk.data(), seed.data()); + REQUIRE(0 == crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed_pk.data())); + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"); + REQUIRE(oxenc::to_hex(curve_pk.begin(), curve_pk.end()) == + "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + auto sid = "05" + oxenc::to_hex(curve_pk.begin(), curve_pk.end()); + std::vector sid_raw; + oxenc::from_hex(sid.begin(), sid.end(), std::back_inserter(sid_raw)); + REQUIRE(sid == "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + REQUIRE(sid_raw == + "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"_hexbytes); + + const auto seed2 = "00112233445566778899aabbccddeeff00000000000000000000000000000000"_hexbytes; + std::array ed_pk2, curve_pk2; + std::array ed_sk2; + crypto_sign_ed25519_seed_keypair(ed_pk2.data(), ed_sk2.data(), seed2.data()); + REQUIRE(0 == crypto_sign_ed25519_pk_to_curve25519(curve_pk2.data(), ed_pk2.data())); + REQUIRE(oxenc::to_hex(ed_pk2.begin(), ed_pk2.end()) == + "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"); + REQUIRE(oxenc::to_hex(curve_pk2.begin(), curve_pk2.end()) == + "aa654f00fc39fc69fd0db829410ca38177d7732a8d2f0934ab3872ac56d5aa74"); + auto sid2 = "05" + oxenc::to_hex(curve_pk2.begin(), curve_pk2.end()); + REQUIRE(sid2 == "05aa654f00fc39fc69fd0db829410ca38177d7732a8d2f0934ab3872ac56d5aa74"); + std::vector sid_raw2; + oxenc::from_hex(sid2.begin(), sid2.end(), std::back_inserter(sid_raw2)); + REQUIRE(sid_raw2 == + "05aa654f00fc39fc69fd0db829410ca38177d7732a8d2f0934ab3872ac56d5aa74"_hexbytes); + + auto enc1 = encrypt_for_recipient(to_span(ed_sk), sid_raw2, to_span("hello")); + auto enc2 = encrypt_for_recipient(to_span(ed_sk), sid_raw2, to_span("hello")); + REQUIRE(enc1 != enc2); + + auto enc_det = encrypt_for_recipient_deterministic(to_span(ed_sk), sid_raw2, to_span("hello")); + CHECK(enc_det != enc1); + CHECK(enc_det != enc2); + CHECK(enc_det.size() == enc1.size()); + CHECK(to_hex(enc_det) == + "208f96785db92319bc7a14afecc01e17bde912d17bbb32834c03ea63b1862c2a1b730e0725ef75b2f1a276db" + "584c59a0ed9b5497bcb9f4effa893b5cb8b04dbe7a6ab457ebf972f03b006dd4572980a725399616d40184b8" + "6aa3b7b218bdc6dd7c1adccda8ef4897f0f458492240b39079c27a6c791067ab26a03067a7602b50f0434639" + "906f93e548f909d5286edde365ebddc146"); + + auto [msg, sender] = decrypt_incoming(to_span(ed_sk2), enc_det); + CHECK(to_hex(sender) == oxenc::to_hex(ed_pk.begin(), ed_pk.end())); + CHECK(to_string(msg) == "hello"); +} + +static std::array prefixed(unsigned char prefix, const session::uc32& pubkey) { + std::array result; + result[0] = prefix; + std::memcpy(result.data() + 1, pubkey.data(), 32); + return result; +} + +TEST_CASE("Session blinding protocol encryption", "[session-blinding-protocol][encrypt]") { + + using namespace session; + + const auto seed = "0123456789abcdef0123456789abcdef00000000000000000000000000000000"_hexbytes; + const auto server_pk = + "1d7e7f92b1ed3643855c98ecac02fc7274033a3467653f047d6e433540c03f17"_hexbytes; + std::array ed_pk, curve_pk; + std::array ed_sk; + crypto_sign_ed25519_seed_keypair(ed_pk.data(), ed_sk.data(), seed.data()); + REQUIRE(0 == crypto_sign_ed25519_pk_to_curve25519(curve_pk.data(), ed_pk.data())); + REQUIRE(oxenc::to_hex(ed_pk.begin(), ed_pk.end()) == + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"); + REQUIRE(oxenc::to_hex(curve_pk.begin(), curve_pk.end()) == + "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + auto sid = "05" + oxenc::to_hex(curve_pk.begin(), curve_pk.end()); + std::vector sid_raw; + oxenc::from_hex(sid.begin(), sid.end(), std::back_inserter(sid_raw)); + REQUIRE(sid == "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + REQUIRE(sid_raw == + "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"_hexbytes); + auto [blind15_pk, blind15_sk] = blind15_key_pair(to_span(ed_sk), to_span(server_pk)); + auto [blind25_pk, blind25_sk] = blind25_key_pair(to_span(ed_sk), to_span(server_pk)); + auto blind15_pk_prefixed = prefixed(0x15, blind15_pk); + auto blind25_pk_prefixed = prefixed(0x25, blind25_pk); + + const auto seed2 = "00112233445566778899aabbccddeeff00000000000000000000000000000000"_hexbytes; + std::array ed_pk2, curve_pk2; + std::array ed_sk2; + crypto_sign_ed25519_seed_keypair(ed_pk2.data(), ed_sk2.data(), seed2.data()); + REQUIRE(0 == crypto_sign_ed25519_pk_to_curve25519(curve_pk2.data(), ed_pk2.data())); + REQUIRE(oxenc::to_hex(ed_pk2.begin(), ed_pk2.end()) == + "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"); + REQUIRE(oxenc::to_hex(curve_pk2.begin(), curve_pk2.end()) == + "aa654f00fc39fc69fd0db829410ca38177d7732a8d2f0934ab3872ac56d5aa74"); + auto sid2 = "05" + oxenc::to_hex(curve_pk2.begin(), curve_pk2.end()); + REQUIRE(sid2 == "05aa654f00fc39fc69fd0db829410ca38177d7732a8d2f0934ab3872ac56d5aa74"); + std::vector sid_raw2; + oxenc::from_hex(sid2.begin(), sid2.end(), std::back_inserter(sid_raw2)); + REQUIRE(sid_raw2 == + "05aa654f00fc39fc69fd0db829410ca38177d7732a8d2f0934ab3872ac56d5aa74"_hexbytes); + auto [blind15_pk2, blind15_sk2] = blind15_key_pair(to_span(ed_sk2), to_span(server_pk)); + auto [blind25_pk2, blind25_sk2] = blind25_key_pair(to_span(ed_sk2), to_span(server_pk)); + auto blind15_pk2_prefixed = prefixed(0x15, blind15_pk2); + auto blind25_pk2_prefixed = prefixed(0x25, blind25_pk2); + + SECTION("blind15, full secret, recipient decrypt") { + auto enc = encrypt_for_blinded_recipient( + to_span(ed_sk), + to_span(server_pk), + {blind15_pk2_prefixed.data(), 33}, + to_span("hello")); + CHECK(to_string(enc) != "hello"); + + CHECK_THROWS(decrypt_from_blinded_recipient( + to_span(ed_sk2), + to_span(server_pk), + to_span(blind15_pk), + {blind15_pk2_prefixed.data(), 33}, + enc)); + CHECK_THROWS(decrypt_from_blinded_recipient( + to_span(ed_sk2), + to_span(server_pk), + {blind15_pk_prefixed.data(), 33}, + to_span(blind15_pk2), + enc)); + + auto [msg, sender] = decrypt_from_blinded_recipient( + to_span(ed_sk2), + to_span(server_pk), + {blind15_pk_prefixed.data(), 33}, + {blind15_pk2_prefixed.data(), 33}, + enc); + CHECK(sender == sid); + CHECK(to_string(msg) == "hello"); + + auto broken = enc; + broken[23] ^= 0x80; // 1 + 5 + 16 = 22 is the start of the nonce + CHECK_THROWS(decrypt_from_blinded_recipient( + to_span(ed_sk2), + to_span(server_pk), + {blind15_pk_prefixed.data(), 33}, + {blind15_pk2_prefixed.data(), 33}, + broken)); + } + SECTION("blind15, only seed, sender decrypt") { + constexpr auto lorem_ipsum = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " + "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " + "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in " + "culpa qui officia deserunt mollit anim id est laborum."sv; + auto enc = encrypt_for_blinded_recipient( + {to_span(ed_sk).data(), 32}, + to_span(server_pk), + {blind15_pk2_prefixed.data(), 33}, + to_span(lorem_ipsum)); + CHECK(std::search( + enc.begin(), + enc.end(), + to_unsigned("dolore magna"), + to_unsigned("dolore magna") + strlen("dolore magna")) == enc.end()); + + auto [msg, sender] = decrypt_from_blinded_recipient( + {to_span(ed_sk).data(), 32}, + to_span(server_pk), + {blind15_pk_prefixed.data(), 33}, + {blind15_pk2_prefixed.data(), 33}, + enc); + CHECK(sender == sid); + CHECK(to_string(msg) == lorem_ipsum); + + auto broken = enc; + broken[463] ^= 0x80; // 1 + 445 + 16 = 462 is the start of the nonce + CHECK_THROWS(decrypt_from_blinded_recipient( + {to_span(ed_sk).data(), 32}, + to_span(server_pk), + {blind15_pk_prefixed.data(), 33}, + {blind15_pk2_prefixed.data(), 33}, + broken)); + } + SECTION("blind15, only seed, recipient decrypt") { + constexpr auto lorem_ipsum = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " + "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " + "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in " + "culpa qui officia deserunt mollit anim id est laborum."sv; + auto enc = encrypt_for_blinded_recipient( + {to_span(ed_sk).data(), 32}, + to_span(server_pk), + {blind15_pk2_prefixed.data(), 33}, + to_span(lorem_ipsum)); + CHECK(std::search( + enc.begin(), + enc.end(), + to_unsigned("dolore magna"), + to_unsigned("dolore magna") + strlen("dolore magna")) == enc.end()); + + auto [msg, sender] = decrypt_from_blinded_recipient( + {to_span(ed_sk2).data(), 32}, + to_span(server_pk), + {blind15_pk_prefixed.data(), 33}, + {blind15_pk2_prefixed.data(), 33}, + enc); + CHECK(sender == sid); + CHECK(to_string(msg) == lorem_ipsum); + + auto broken = enc; + broken[463] ^= 0x80; // 1 + 445 + 16 = 462 is the start of the nonce + CHECK_THROWS(decrypt_from_blinded_recipient( + {to_span(ed_sk2).data(), 32}, + to_span(server_pk), + {blind15_pk_prefixed.data(), 33}, + {blind15_pk2_prefixed.data(), 33}, + broken)); + } + SECTION("blind25, full secret, sender decrypt") { + auto enc = encrypt_for_blinded_recipient( + to_span(ed_sk), + to_span(server_pk), + {blind25_pk2_prefixed.data(), 33}, + to_span("hello")); + CHECK(to_string(enc) != "hello"); + + CHECK_THROWS(decrypt_from_blinded_recipient( + to_span(ed_sk), + to_span(server_pk), + to_span(blind25_pk), + {blind25_pk2_prefixed.data(), 33}, + enc)); + CHECK_THROWS(decrypt_from_blinded_recipient( + to_span(ed_sk), + to_span(server_pk), + {blind25_pk_prefixed.data(), 33}, + to_span(blind25_pk2), + enc)); + + auto [msg, sender] = decrypt_from_blinded_recipient( + to_span(ed_sk), + to_span(server_pk), + {blind25_pk_prefixed.data(), 33}, + {blind25_pk2_prefixed.data(), 33}, + enc); + CHECK(sender == sid); + CHECK(to_string(msg) == "hello"); + + auto broken = enc; + broken[23] ^= 0x80; // 1 + 5 + 16 = 22 is the start of the nonce + CHECK_THROWS(decrypt_from_blinded_recipient( + to_span(ed_sk), + to_span(server_pk), + {blind25_pk_prefixed.data(), 33}, + {blind25_pk2_prefixed.data(), 33}, + broken)); + } + SECTION("blind25, full secret, recipient decrypt") { + auto enc = encrypt_for_blinded_recipient( + to_span(ed_sk), + to_span(server_pk), + {blind25_pk2_prefixed.data(), 33}, + to_span("hello")); + CHECK(to_string(enc) != "hello"); + + CHECK_THROWS(decrypt_from_blinded_recipient( + to_span(ed_sk2), + to_span(server_pk), + to_span(blind25_pk), + {blind25_pk2_prefixed.data(), 33}, + enc)); + CHECK_THROWS(decrypt_from_blinded_recipient( + to_span(ed_sk2), + to_span(server_pk), + {blind25_pk_prefixed.data(), 33}, + to_span(blind25_pk2), + enc)); + + auto [msg, sender] = decrypt_from_blinded_recipient( + to_span(ed_sk2), + to_span(server_pk), + {blind25_pk_prefixed.data(), 33}, + {blind25_pk2_prefixed.data(), 33}, + enc); + CHECK(sender == sid); + CHECK(to_string(msg) == "hello"); + + auto broken = enc; + broken[23] ^= 0x80; // 1 + 5 + 16 = 22 is the start of the nonce + CHECK_THROWS(decrypt_from_blinded_recipient( + to_span(ed_sk2), + to_span(server_pk), + {blind25_pk_prefixed.data(), 33}, + {blind25_pk2_prefixed.data(), 33}, + broken)); + } + SECTION("blind25, only seed, recipient decrypt") { + constexpr auto lorem_ipsum = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " + "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu " + "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in " + "culpa qui officia deserunt mollit anim id est laborum."sv; + auto enc = encrypt_for_blinded_recipient( + {to_span(ed_sk).data(), 32}, + to_span(server_pk), + {blind25_pk2_prefixed.data(), 33}, + to_span(lorem_ipsum)); + CHECK(std::search( + enc.begin(), + enc.end(), + to_unsigned("dolore magna"), + to_unsigned("dolore magna") + strlen("dolore magna")) == enc.end()); + + auto [msg, sender] = decrypt_from_blinded_recipient( + {to_span(ed_sk2).data(), 32}, + to_span(server_pk), + {blind25_pk_prefixed.data(), 33}, + {blind25_pk2_prefixed.data(), 33}, + enc); + CHECK(sender == sid); + CHECK(to_string(msg) == lorem_ipsum); + + auto broken = enc; + broken[463] ^= 0x80; // 1 + 445 + 16 = 462 is the start of the nonce + CHECK_THROWS(decrypt_from_blinded_recipient( + {to_span(ed_sk2).data(), 32}, + to_span(server_pk), + {blind25_pk_prefixed.data(), 33}, + {blind25_pk2_prefixed.data(), 33}, + broken)); + } +} + +TEST_CASE("Session ONS response decryption", "[session-ons][decrypt]") { + using namespace session; + + std::string_view name = "test"; + auto ciphertext = + "3575802dd9bfea72672a208840f37ca289ceade5d3ffacabe2d231f109d204329fc33e28c33" + "1580d9a8c9b8a64cacfec97"_hexbytes; + auto ciphertext_legacy = + "dbd4bc89bd2c9e5322fd9f4cadcaa66a0c38f15d0c927a86cc36e895fe1f3c532a3958d972563f52ca858e94eec22dc360"_hexbytes; + auto nonce = "00112233445566778899aabbccddeeff00ffeeddccbbaa99"_hexbytes; + + CHECK(decrypt_ons_response(name, ciphertext, nonce) == + "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + CHECK(decrypt_ons_response(name, ciphertext_legacy, std::nullopt) == + "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + CHECK_THROWS(decrypt_ons_response(name, to_span("invalid"), nonce)); + CHECK_THROWS(decrypt_ons_response(name, ciphertext, to_span("invalid"))); +} + +TEST_CASE("Session ONS response decryption C API", "[session-ons][session_decrypt_ons_response]") { + using namespace session; + + auto name = "test\0"; + auto ciphertext = + "3575802dd9bfea72672a208840f37ca289ceade5d3ffacabe2d231f109d204329fc33e28c33" + "1580d9a8c9b8a64cacfec97"_hexbytes; + auto ciphertext_legacy = + "dbd4bc89bd2c9e5322fd9f4cadcaa66a0c38f15d0c927a86cc36e895fe1f3c532a3958d972563f52ca858e94eec22dc360"_hexbytes; + auto nonce = "00112233445566778899aabbccddeeff00ffeeddccbbaa99"_hexbytes; + + char ons1[67]; + CHECK(session_decrypt_ons_response( + name, ciphertext.data(), ciphertext.size(), nonce.data(), ons1)); + CHECK(ons1 == "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"sv); + + char ons2[67]; + CHECK(session_decrypt_ons_response( + name, ciphertext_legacy.data(), ciphertext_legacy.size(), nullptr, ons2)); + CHECK(ons2 == "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"sv); +} + +TEST_CASE("Session push notification decryption", "[session-notification][decrypt]") { + using namespace session; + + auto payload = + "00112233445566778899aabbccddeeff00ffeeddccbbaa991bcba42892762dbeecbfb1a375f" + "ab4aca5f0991e99eb0344ceeafa"_hexbytes; + auto payload_padded = + "00112233445566778899aabbccddeeff00ffeeddccbbaa991bcba42892762dbeecbfb1a375f" + "ab4aca5f0991e99eb0344ceeafa"_hexbytes; + auto enc_key = "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; + + CHECK(decrypt_push_notification(payload, enc_key) == to_vector("TestMessage")); + CHECK(decrypt_push_notification(payload_padded, enc_key) == to_vector("TestMessage")); + CHECK_THROWS(decrypt_push_notification(to_span("invalid"), enc_key)); + CHECK_THROWS(decrypt_push_notification(payload, to_span("invalid"))); +} + +TEST_CASE("Session message hash", "[session][message-hash]") { + using namespace session; + + auto pubkey_hex = "0518981d2822aabc9ba8dbf83f2feac4c70eb737930bc4f254fa71e01f8464a049"sv; + auto base64_data1 = + "CAESpQMKA1BVVBIPL2FwaS92MS9tZXNzYWdlGoEDCAcSQjA1MTg5ODFkMjgyMmFhYmM5YmE4ZGJmODNmMmZlYWM0YzcwZWI3Mzc5MzBiYzRmMjU0ZmE3MWUwMWY4NDY0YTA0OSirlfaFnDI4AUKvAvYMh0I1qhBp9tDOzZhl7vMFuD7a9k/BLvPHMOkTrYsjGj2ri7T6AoJjVm/dDMsXlEP58VaGFSv+mcctCRstYox+3CchbQoVieBi2NGE1bqCeiZeLOMxQxleSZ94vzi7CoC8/NCLmTBzKvw0GBo77Tz37yPGxNLp2QO1xOuDVqM1/+4Sdj+JzMpfsZA8PDMmG3T1o8DJJ/EmwlxsmKM/eAjqtNpdF1G7wtZW5im9fiW11sQgG0/+5EsqxqEoo0xsi5TL6L9DN6zKhjXC9bu/QAfI5ZIpF5+9IHzKashPAjSswBZmlesjbFbNvNgBq4hSeXIxjtg7xDm/hfXao1WRa3TMHgfZs2bY+cNlDGqArjZT9q5XTVxsQYXq+mz/koh0qxiJktAC3C0ixs7CInORFiD18omD4oqX1/IB"sv; + auto base64_data2 = + "CAESpAMKA1BVVBIPL2FwaS92MS9tZXNzYWdlGoEDCAcSQjA1MTg5ODFkMjgyMmFhYmM5YmE4ZGJmODNmMmZlYWM0YzcwZWI3Mzc5MzBiYzRmMjU0ZmE3MWUwMWY4NDY0YTA0OSi/7peInDI4AUKvAsCTN9WMEkajMbC7EA6QOClzdXK3W6MTEElFotQ6PGNa2IKfYb+iu0MRC6ph+1hE5hzfay00v0UfB5Xen3dBgZ2drwToYhYb1zqRlIeesdwT0Yt6ct+Gn47PBL4oXOv7PJo3ys3jlq1t+xbAN/vum/8ART9xVhNIZ+3dOpS62z8pwSqusWECGw9dJDgFN6g0+2R85dco/HP9Z2SiGBaAJulKFUXKaT+jMHab3nPjoqke/lVG544iJAmNbI+KJr61YgtsbVfO02pje1RXeQtQacAtWpCYlin4fNtr6ANTs8aJDb1H1JFOG/r8PZHkPl1Fl/2cDppngZYJJo6/8IH9FpZS64le+mZy2BjP7UKfEx3ulmJIwpfqcqe9qvoTbGtljSf8wRylUkeo1E7Gg2WP8SDrgdXBwIaZp24="sv; + int16_t ns = -10; + + CHECK(compute_message_hash(pubkey_hex, ns, base64_data1) == + "xREbCx9GRzDiuU8GsEK7rR1InU6peC3vp10cBkTUDPg"); + CHECK(compute_message_hash(pubkey_hex, ns, base64_data2) == + "apKu8OMjrbU+YeVWpMSyrr1wHq51K3uKD8WM0F4E1cE"); +} + +TEST_CASE("xchacha20", "[session][xchacha20]") { + using namespace session; + + auto payload = + "da74ac6e96afda1c5a07d5bde1b8b1e1c05be73cb3c84112f31f00369d67154d00ff029090b069b48c3cf603d838d4ef623d54"_hexbytes; + auto enc_key = "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210"_hexbytes; + + CHECK(decrypt_xchacha20(payload, enc_key) == to_vector("TestMessage")); + CHECK_THROWS(decrypt_xchacha20(to_span("invalid"), enc_key)); + CHECK_THROWS(decrypt_xchacha20(payload, to_span("invalid"))); + + auto ciphertext = encrypt_xchacha20(to_span("TestMessage"), enc_key); + CHECK(decrypt_xchacha20(ciphertext, enc_key) == to_vector("TestMessage")); + CHECK_THROWS(encrypt_xchacha20(payload, to_span("invalid"))); +} diff --git a/tests/test_session_network.cpp b/tests/test_session_network.cpp new file mode 100644 index 00000000..1622277f --- /dev/null +++ b/tests/test_session_network.cpp @@ -0,0 +1,1647 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.hpp" + +using namespace session; +using namespace session::onionreq; +using namespace session::network; + +namespace { +struct TestServer { + std::shared_ptr loop; + std::shared_ptr endpoint; + service_node node; + + ~TestServer() { + loop->call_get([&]() { endpoint->close_conns(); }); + endpoint.reset(); + loop.reset(); + } +}; + +struct Result { + bool success; + bool timeout; + int16_t status_code; + std::vector> headers; + std::optional response; +}; + +service_node test_node( + const std::vector ed_pk, const uint16_t index, const bool unique_ip = true) { + return service_node{ + ed_pk, + {2, 8, 0}, + INVALID_SWARM_ID, + (unique_ip ? fmt::format("0.0.0.{}", index) : "1.1.1.1"), + index}; +} + +std::optional node_for_destination(network_destination destination) { + if (auto* dest = std::get_if(&destination)) + return *dest; + + return std::nullopt; +} + +} // namespace + +namespace session::network { +class TestNetwork : public Network { + public: + std::unordered_map call_counts; + std::mutex call_counts_mutex; + std::condition_variable call_cv; + + std::vector calls_to_ignore; + std::chrono::milliseconds retry_delay_value = 0ms; + std::optional> find_valid_path_response; + std::optional last_request_info; + bool handle_onion_requests_as_plaintext = false; + + TestNetwork( + std::optional cache_path, + bool use_testnet, + bool single_path_mode, + bool pre_build_paths) : + Network{cache_path, use_testnet, single_path_mode, pre_build_paths} { + paths_changed = [this](std::vector>) { + func_called("paths_changed"); + }; + } + + void set_suspended(bool suspended_) { suspended = suspended_; } + + bool get_suspended() { return suspended; } + + ConnectionStatus get_status() { return status; } + + void set_snode_cache(std::vector cache) { + // Need to set the `last_snode_cache_update` to `10s` ago because otherwise it'll be + // considered invalid when checking the cache validity + snode_cache = cache; + last_snode_cache_update = (std::chrono::system_clock::now() - 10s); + } + + void set_unused_connections(std::deque unused_connections_) { + unused_connections = unused_connections_; + } + + void set_in_progress_connections( + std::unordered_map in_progress_connections_) { + in_progress_connections = in_progress_connections_; + } + + void add_path(PathType path_type, std::vector nodes) { + paths[path_type].emplace_back( + onion_path{"Test", {nodes[0], nullptr, nullptr, nullptr}, nodes, 0}); + } + + void set_paths(PathType path_type, std::vector paths_) { + paths[path_type] = paths_; + } + + std::vector get_paths(PathType path_type) { return paths[path_type]; } + + void set_all_swarms(std::vector>> all_swarms_) { + all_swarms = all_swarms_; + } + + void set_swarm( + session::onionreq::x25519_pubkey swarm_pubkey, + swarm_id_t swarm_id, + std::vector swarm) { + swarm_cache[swarm_pubkey.hex()] = {swarm_id, swarm}; + } + + std::pair> get_cached_swarm( + session::onionreq::x25519_pubkey swarm_pubkey) { + return swarm_cache[swarm_pubkey.hex()]; + } + + swarm_id_t get_swarm_id(std::string swarm_pubkey_hex) { + if (swarm_pubkey_hex.size() == 66) + swarm_pubkey_hex = swarm_pubkey_hex.substr(2); + + auto pk = x25519_pubkey::from_hex(swarm_pubkey_hex); + std::promise prom; + get_swarm(pk, [&prom](swarm_id_t result, std::vector) { + prom.set_value(result); + }); + return prom.get_future().get(); + } + + void set_failure_count(service_node node, uint8_t failure_count) { + snode_failure_counts[node.to_string()] = failure_count; + } + + uint8_t get_failure_count(service_node node) { + return snode_failure_counts.try_emplace(node.to_string(), 0).first->second; + } + + uint8_t get_failure_count(PathType path_type, onion_path path) { + auto current_paths = paths[path_type]; + auto target_path = std::find_if( + current_paths.begin(), current_paths.end(), [&path](const auto& path_it) { + return path_it.nodes[0] == path.nodes[0]; + }); + + if (target_path != current_paths.end()) + return target_path->failure_count; + + return 0; + } + + void set_path_build_queue(std::deque path_build_queue_) { + path_build_queue = path_build_queue_; + } + + std::deque get_path_build_queue() { return path_build_queue; } + + void set_path_build_failures(int path_build_failures_) { + path_build_failures = path_build_failures_; + } + + int get_path_build_failures() { return path_build_failures; } + + void set_unused_nodes(std::vector unused_nodes_) { unused_nodes = unused_nodes_; } + + std::vector get_unused_nodes() { return Network::get_unused_nodes(); } + + std::vector get_unused_nodes_value() { return unused_nodes; } + + void add_pending_request(PathType path_type, request_info info) { + request_queue[path_type].emplace_back( + std::move(info), + [](bool, + bool, + int16_t, + std::vector>, + std::optional) {}); + } + + std::shared_ptr create_test_node(uint16_t port) { + oxen::quic::opt::inbound_alpns server_alpns{"oxenstorage"}; + auto server_key_pair = + session::ed25519::ed25519_key_pair(to_span(fmt::format("{:032}", port))); + auto server_x25519_pubkey = session::curve25519::to_curve25519_pubkey( + {server_key_pair.first.data(), server_key_pair.first.size()}); + auto server_x25519_seckey = session::curve25519::to_curve25519_seckey( + {server_key_pair.second.data(), server_key_pair.second.size()}); + auto creds = oxen::quic::GNUTLSCreds::make_from_ed_seckey( + to_string_view(server_key_pair.second)); + oxen::quic::Address server_local{port}; + session::onionreq::HopEncryption decryptor{ + x25519_seckey::from_bytes(to_span(server_x25519_seckey)), + x25519_pubkey::from_bytes(to_span(server_x25519_pubkey)), + true}; + + auto server_cb = [&](oxen::quic::message m) { + nlohmann::json response{{"hf", {1, 0, 0}}, {"t", 1234567890}, {"version", {2, 8, 0}}}; + m.respond(response.dump(), false); + }; + + auto onion_cb = [&](oxen::quic::message m) { + nlohmann::json response{{"hf", {2, 0, 0}}, {"t", 1234567890}, {"version", {2, 8, 0}}}; + m.respond(response.dump(), false); + }; + + oxen::quic::stream_constructor_callback server_constructor = + [&](oxen::quic::Connection& c, oxen::quic::Endpoint& e, std::optional) { + auto s = e.loop.make_shared(c, e); + s->register_handler("info", server_cb); + s->register_handler("onion_req", onion_cb); + return s; + }; + + auto loop = std::make_shared(); + auto endpoint = oxen::quic::Endpoint::endpoint(*loop, server_local, server_alpns); + endpoint->listen(creds, server_constructor); + + auto node = service_node{ + to_string_view(server_key_pair.first), + {2, 8, 0}, + INVALID_SWARM_ID, + "127.0.0.1"s, + endpoint->local().port()}; + + return std::make_shared(loop, endpoint, node); + } + + std::pair>, onion_path> create_test_path() { + std::vector> path_servers; + std::vector path_nodes; + path_nodes.reserve(3); + + for (auto i = 0; i < 3; ++i) { + path_servers.emplace_back(create_test_node(static_cast(1000 + i))); + path_nodes.emplace_back(path_servers[i]->node); + } + + std::promise>> prom; + establish_connection( + "Test", + path_nodes[0], + 3s, + [&prom](connection_info conn_info, std::optional error) { + prom.set_value({std::move(conn_info), error}); + }); + + // Wait for the result to be set + auto result = prom.get_future().get(); + REQUIRE(result.first.is_valid()); + return {path_servers, onion_path{"Test", std::move(result.first), path_nodes, uint8_t{0}}}; + } + + // Overridden Functions + + std::chrono::milliseconds retry_delay(int, std::chrono::milliseconds) override { + return retry_delay_value; + } + + void update_disk_cache_throttled(bool force_immediate_write) override { + if (check_should_ignore_and_log_call("update_disk_cache_throttled")) + return; + + Network::update_disk_cache_throttled(force_immediate_write); + } + + void establish_and_store_connection(std::string request_id) override { + if (check_should_ignore_and_log_call("establish_and_store_connection")) + return; + + Network::establish_and_store_connection(request_id); + } + + void refresh_snode_cache(std::optional existing_request_id) override { + if (check_should_ignore_and_log_call("refresh_snode_cache")) + return; + + Network::refresh_snode_cache(existing_request_id); + } + + void build_path(std::string path_id, PathType path_type) override { + if (check_should_ignore_and_log_call("build_path")) + return; + + Network::build_path(path_id, path_type); + } + + std::optional find_valid_path( + request_info info, std::vector paths) override { + if (check_should_ignore_and_log_call("find_valid_path")) + return std::nullopt; + + if (find_valid_path_response) + return *find_valid_path_response; + + return Network::find_valid_path(info, paths); + } + + void check_request_queue_timeouts(std::optional request_timeout_id) override { + if (check_should_ignore_and_log_call("check_request_queue_timeouts")) + return; + + Network::check_request_queue_timeouts(request_timeout_id); + } + + void _send_onion_request( + request_info info, network_response_callback_t handle_response) override { + last_request_info = info; + + if (check_should_ignore_and_log_call("_send_onion_request")) + return; + + Network::_send_onion_request(std::move(info), std::move(handle_response)); + } + + // Exposing Private Functions + + void establish_connection( + std::string request_id, + service_node target, + std::optional timeout, + std::function error)> callback) { + Network::establish_connection(request_id, target, timeout, std::move(callback)); + } + + void build_path_if_needed(PathType path_type, bool found_valid_path) override { + return Network::build_path_if_needed(path_type, found_valid_path); + } + + void send_request( + request_info info, connection_info conn, network_response_callback_t handle_response) { + Network::send_request(info, conn, std::move(handle_response)); + } + + void handle_errors( + request_info info, + connection_info conn_info, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response, + std::optional handle_response) override { + func_called("handle_errors"); + Network::handle_errors( + info, + conn_info, + timeout, + status_code, + headers, + response, + std::move(handle_response)); + } + + std::tuple< + int16_t, + std::vector>, + std::optional> + process_v3_onion_response(session::onionreq::Builder builder, std::string response) override { + func_called("process_v3_onion_response"); + + if (handle_onion_requests_as_plaintext) + return {200, {}, response}; + + return Network::process_v3_onion_response(builder, response); + } + + std::tuple< + int16_t, + std::vector>, + std::optional> + process_v4_onion_response(session::onionreq::Builder builder, std::string response) override { + func_called("process_v4_onion_response"); + + if (handle_onion_requests_as_plaintext) + return {200, {}, response}; + + return Network::process_v4_onion_response(builder, response); + } + + // Mocking Functions + + template + void ignore_calls_to(Strings&&... __args) { + (calls_to_ignore.emplace_back(std::forward(__args)), ...); + } + + bool check_should_ignore_and_log_call(const std::string& name) { + func_called(name); + + return std::find(calls_to_ignore.begin(), calls_to_ignore.end(), name) != + calls_to_ignore.end(); + } + + void func_called(const std::string& name) { + bool notify = false; + { + std::lock_guard lock(call_counts_mutex); + ++call_counts[name]; + notify = true; + } + + if (notify) + call_cv.notify_all(); + } + + void reset_calls() { + std::lock_guard lock_counts(call_counts_mutex); + call_counts.clear(); + } + + int get_call_count(const std::string& name) { + std::lock_guard lock(call_counts_mutex); + auto it = call_counts.find(name); + return (it != call_counts.end()) ? it->second : 0; + } + + bool called(const std::string& name, int times = 1) { return (get_call_count(name) >= times); } + + [[nodiscard]] bool called( + const std::string& name, std::chrono::milliseconds timeout, int times = 1) { + if (times <= 0) + times = 1; + + std::unique_lock lock(call_counts_mutex); + + auto predicate = [&]() { + auto it = call_counts.find(name); + return (it != call_counts.end() && it->second >= times); + }; + + return call_cv.wait_for(lock, timeout, predicate); + } + + bool did_not_call(const std::string& name) { + std::lock_guard lock(call_counts_mutex); + return !call_counts.contains(name); + } + + [[nodiscard]] bool did_not_call(const std::string& name, std::chrono::milliseconds duration) { + std::unique_lock lock(call_counts_mutex); + auto predicate = [&]() { return call_counts.contains(name); }; + + if (predicate()) + return false; // Already called + + bool was_called_during_wait = call_cv.wait_for(lock, duration, predicate); + return !was_called_during_wait; + } +}; +} // namespace session::network + +TEST_CASE("Network", "[network][parse_url]") { + auto [proto1, host1, port1, path1] = parse_url("HTTPS://example.com/test"); + auto [proto2, host2, port2, path2] = parse_url("http://example2.com:1234/test/123456"); + auto [proto3, host3, port3, path3] = parse_url("https://example3.com"); + auto [proto4, host4, port4, path4] = parse_url("https://example4.com/test?value=test"); + + CHECK(proto1 == "https://"); + CHECK(proto2 == "http://"); + CHECK(proto3 == "https://"); + CHECK(proto4 == "https://"); + CHECK(host1 == "example.com"); + CHECK(host2 == "example2.com"); + CHECK(host3 == "example3.com"); + CHECK(host4 == "example4.com"); + CHECK(port1.value_or(9999) == 9999); + CHECK(port2.value_or(9999) == 1234); + CHECK(port3.value_or(9999) == 9999); + CHECK(port4.value_or(9999) == 9999); + CHECK(path1.value_or("NULL") == "/test"); + CHECK(path2.value_or("NULL") == "/test/123456"); + CHECK(path3.value_or("NULL") == "NULL"); + CHECK(path4.value_or("NULL") == "/test?value=test"); +} + +TEST_CASE("Network", "[network][handle_errors]") { + auto ed_pk = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes; + auto ed_pk2 = "5ea34e72bb044654a6a23675690ef5ffaaf1656b02f93fb76655f9cbdbe89876"_hexbytes; + auto ed_sk = + "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab78862834829a" + "87e0afadfed763fa8785e893dbde7f2c001ff1071aa55005c347f"_hexbytes; + auto x_pk_hex = "d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"; + auto target = test_node(ed_pk, 0); + auto target2 = test_node(ed_pk2, 1); + auto target3 = test_node(ed_pk2, 2); + auto target4 = test_node(ed_pk2, 3); + auto path = + onion_path{"Test", {target, nullptr, nullptr, nullptr}, {target, target2, target3}, 0}; + auto mock_request = request_info{ + "AAAA", + target, + "test", + std::nullopt, + std::nullopt, + std::nullopt, + PathType::standard, + 0ms, + std::nullopt, + std::chrono::system_clock::now(), + std::nullopt, + true}; + Result result; + std::optional network; + + // Check the handling of the codes which make no changes + auto codes_with_no_changes = {400, 404, 406, 425}; + + for (auto code : codes_with_no_changes) { + network.emplace(std::nullopt, true, true, false); + network->set_suspended(true); // Make no requests in this test + network->ignore_calls_to("_send_onion_request", "update_disk_cache_throttled"); + network->set_paths(PathType::standard, {path}); + network->handle_errors( + mock_request, + {target, nullptr, nullptr, nullptr}, + false, + code, + {}, + std::nullopt, + [&result]( + bool success, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response) { + result = {success, timeout, status_code, headers, response}; + }); + + CHECK_FALSE(result.success); + CHECK_FALSE(result.timeout); + CHECK(result.status_code == code); + CHECK_FALSE(result.response.has_value()); + CHECK(network->get_failure_count(target) == 0); + CHECK(network->get_failure_count(target2) == 0); + CHECK(network->get_failure_count(target3) == 0); + CHECK(network->get_failure_count(PathType::standard, path) == 0); + } + + // Check general error handling (first failure) + network.emplace(std::nullopt, true, true, false); + network->set_suspended(true); // Make no requests in this test + network->ignore_calls_to("_send_onion_request", "update_disk_cache_throttled"); + network->set_paths(PathType::standard, {path}); + network->handle_errors( + mock_request, + {target, nullptr, nullptr, nullptr}, + false, + 500, + {}, + std::nullopt, + [&result]( + bool success, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response) { + result = {success, timeout, status_code, headers, response}; + }); + CHECK_FALSE(result.success); + CHECK_FALSE(result.timeout); + CHECK(result.status_code == 500); + CHECK_FALSE(result.response.has_value()); + CHECK(network->get_failure_count(target) == 0); + CHECK(network->get_failure_count(target2) == 0); + CHECK(network->get_failure_count(target3) == 0); + CHECK(network->get_failure_count(PathType::standard, path) == 1); + + // Check general error handling with no response (too many path failures) + path = onion_path{"Test", {target, nullptr, nullptr, nullptr}, {target, target2, target3}, 9}; + network.emplace(std::nullopt, true, true, false); + network->set_suspended(true); // Make no requests in this test + network->ignore_calls_to("_send_onion_request", "update_disk_cache_throttled"); + network->set_paths(PathType::standard, {path}); + network->handle_errors( + mock_request, + {target, nullptr, nullptr, nullptr}, + false, + 500, + {}, + std::nullopt, + [&result]( + bool success, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response) { + result = {success, timeout, status_code, headers, response}; + }); + + CHECK_FALSE(result.success); + CHECK_FALSE(result.timeout); + CHECK(result.status_code == 500); + CHECK_FALSE(result.response.has_value()); + CHECK(network->get_failure_count(target) == 3); // Guard node dropped + CHECK(network->get_failure_count(target2) == 1); // Other nodes incremented + CHECK(network->get_failure_count(target3) == 1); // Other nodes incremented + CHECK(network->get_failure_count(PathType::standard, path) == 0); // Path dropped and reset + + // Check general error handling with a path and specific node failure + path = onion_path{"Test", {target, nullptr, nullptr, nullptr}, {target, target2, target3}, 0}; + auto response = std::string{"Next node not found: "} + ed25519_pubkey::from_bytes(ed_pk2).hex(); + network.emplace(std::nullopt, true, true, false); + network->set_suspended(true); // Make no requests in this test + network->ignore_calls_to("_send_onion_request", "update_disk_cache_throttled"); + network->set_snode_cache({target, target2, target3, target4}); + network->set_unused_nodes({target4}); + network->set_paths(PathType::standard, {path}); + network->handle_errors( + mock_request, + {target, nullptr, nullptr, nullptr}, + false, + 500, + {}, + response, + [&result]( + bool success, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response) { + result = {success, timeout, status_code, headers, response}; + }); + + CHECK_FALSE(result.success); + CHECK_FALSE(result.timeout); + CHECK(result.status_code == 500); + CHECK(result.response == response); + CHECK(network->get_failure_count(target) == 0); + CHECK(network->get_failure_count(target2) == 3); // Node will have been dropped + CHECK(network->get_failure_count(target3) == 0); + CHECK(network->get_paths(PathType::standard).front().nodes[1] != target2); + CHECK(network->get_failure_count(PathType::standard, path) == + 1); // Incremented because conn_info is invalid + + // Check a 421 with no swarm data throws (no good way to handle this case) + network.emplace(std::nullopt, true, true, false); + network->set_suspended(true); // Make no requests in this test + network->ignore_calls_to("_send_onion_request", "update_disk_cache_throttled"); + network->set_paths(PathType::standard, {path}); + network->handle_errors( + mock_request, + {target, nullptr, nullptr, nullptr}, + false, + 421, + {}, + std::nullopt, + [&result]( + bool success, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response) { + result = {success, timeout, status_code, headers, response}; + }); + CHECK_FALSE(result.success); + CHECK_FALSE(result.timeout); + CHECK(result.status_code == 421); + CHECK(network->get_failure_count(target) == 0); + CHECK(network->get_failure_count(target2) == 0); + CHECK(network->get_failure_count(target3) == 0); + CHECK(network->get_failure_count(PathType::standard, path) == 1); + + // Check a non redirect 421 triggers a retry using a different node + auto mock_request2 = request_info{ + "BBBB", + target, + "test", + std::nullopt, + std::nullopt, + x25519_pubkey::from_hex(x_pk_hex), + PathType::standard, + 0ms, + std::nullopt, + std::chrono::system_clock::now(), + std::nullopt, + true}; + network.emplace(std::nullopt, true, true, false); + network->set_suspended(true); // Make no requests in this test + network->ignore_calls_to("_send_onion_request", "update_disk_cache_throttled"); + network->set_swarm(x25519_pubkey::from_hex(x_pk_hex), 1, {target, target2, target3}); + network->set_paths(PathType::standard, {path}); + network->reset_calls(); + network->handle_errors( + mock_request2, + {target, nullptr, nullptr, nullptr}, + false, + 421, + {}, + std::nullopt, + [](bool, + bool, + int16_t, + std::vector>, + std::optional) {}); + CHECK(network->called("_send_onion_request", 100ms)); + REQUIRE(network->last_request_info.has_value()); + CHECK(node_for_destination(network->last_request_info->destination) != + node_for_destination(mock_request2.destination)); + + // Check that when a retry request of a 421 receives it's own 421 that it tries + // to update the snode cache + auto mock_request3 = request_info{ + "BBBB", + target, + "test", + std::nullopt, + std::nullopt, + x25519_pubkey::from_hex(x_pk_hex), + PathType::standard, + 0ms, + std::nullopt, + std::chrono::system_clock::now(), + request_info::RetryReason::redirect, + true}; + network.emplace(std::nullopt, true, true, false); + network->set_suspended(true); // Make no requests in this test + network->ignore_calls_to( + "_send_onion_request", "update_disk_cache_throttled", "refresh_snode_cache"); + network->set_paths(PathType::standard, {path}); + network->handle_errors( + mock_request3, + {target, nullptr, nullptr, nullptr}, + false, + 421, + {}, + std::nullopt, + [](bool, + bool, + int16_t, + std::vector>, + std::optional) {}); + CHECK(network->called("refresh_snode_cache", 100ms)); + + // Check when the retry after refreshing the snode cache due to a 421 receives it's own 421 it + // is handled like any other error + auto mock_request4 = request_info{ + "BBBB", + target, + "test", + std::nullopt, + std::nullopt, + x25519_pubkey::from_hex(x_pk_hex), + PathType::standard, + 0ms, + std::nullopt, + std::chrono::system_clock::now(), + request_info::RetryReason::redirect_swarm_refresh, + true}; + network.emplace(std::nullopt, true, true, false); + network->set_suspended(true); // Make no requests in this test + network->ignore_calls_to("_send_onion_request", "update_disk_cache_throttled"); + network->set_paths(PathType::standard, {path}); + network->handle_errors( + mock_request4, + {target, nullptr, nullptr, nullptr}, + false, + 421, + {}, + std::nullopt, + [&result]( + bool success, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response) { + result = {success, timeout, status_code, headers, response}; + }); + CHECK_FALSE(result.success); + CHECK_FALSE(result.timeout); + CHECK(result.status_code == 421); + CHECK(network->get_failure_count(target) == 0); + CHECK(network->get_failure_count(target2) == 0); + CHECK(network->get_failure_count(target3) == 0); + CHECK(network->get_failure_count(PathType::standard, path) == 1); + + // Check a timeout with a sever destination doesn't impact the failure counts + auto server = ServerDestination{ + "https", + "open.getsession.org", + "/rooms", + x25519_pubkey::from_hex("a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da79912" + "38"), + 443, + std::nullopt, + "GET"}; + auto mock_request5 = request_info{ + "CCCC", + server, + "test", + std::nullopt, + std::nullopt, + x25519_pubkey::from_hex(x_pk_hex), + PathType::standard, + 0ms, + std::nullopt, + std::chrono::system_clock::now(), + std::nullopt, + false}; + network.emplace(std::nullopt, true, true, false); + network->set_suspended(true); // Make no requests in this test + network->ignore_calls_to("_send_onion_request", "update_disk_cache_throttled"); + network->handle_errors( + mock_request5, + {target, nullptr, nullptr, nullptr}, + true, + -1, + {}, + "Test", + [&result]( + bool success, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response) { + result = {success, timeout, status_code, headers, response}; + }); + CHECK_FALSE(result.success); + CHECK(result.timeout); + CHECK(result.status_code == -1); + CHECK(network->get_failure_count(target) == 0); + CHECK(network->get_failure_count(target2) == 0); + CHECK(network->get_failure_count(target3) == 0); + CHECK(network->get_failure_count(PathType::standard, path) == 0); + + // Check a server response starting with '500 Internal Server Error' is reported as a `500` + // error and doesn't affect the failure count + network.emplace(std::nullopt, true, true, false); + network->set_suspended(true); // Make no requests in this test + network->ignore_calls_to("_send_onion_request", "update_disk_cache_throttled"); + network->handle_errors( + mock_request4, + {target, nullptr, nullptr, nullptr}, + false, + -1, + {}, + "500 Internal Server Error", + [&result]( + bool success, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response) { + result = {success, timeout, status_code, headers, response}; + }); + CHECK_FALSE(result.success); + CHECK_FALSE(result.timeout); + CHECK(result.status_code == 500); + CHECK(network->get_failure_count(target) == 0); + CHECK(network->get_failure_count(target2) == 0); + CHECK(network->get_failure_count(target3) == 0); + CHECK(network->get_failure_count(PathType::standard, path) == 0); +} + +TEST_CASE("Network", "[network][get_unused_nodes]") { + const auto ed_pk = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes; + std::optional network; + std::vector snode_cache; + std::vector unused_nodes; + for (uint16_t i = 0; i < 12; ++i) + snode_cache.emplace_back(test_node(ed_pk, i)); + auto invalid_info = connection_info{snode_cache[0], nullptr, nullptr, nullptr}; + auto path = + onion_path{"Test", invalid_info, {snode_cache[0], snode_cache[1], snode_cache[2]}, 0}; + + auto compare_service_nodes = [](const service_node& a, const service_node& b) { + if (auto cmp = oxen::quic::Address(a) <=> oxen::quic::Address(b); cmp != 0) + return cmp < 0; + + return std::tie(a.get_remote_key(), a.swarm_id, a.storage_server_version) < + std::tie(b.get_remote_key(), b.swarm_id, b.storage_server_version); + }; + + // Should shuffle the result + network.emplace(std::nullopt, true, false, false); + network->set_snode_cache(snode_cache); + CHECK(network->get_unused_nodes() != network->get_unused_nodes()); + + // Should contain the entire snode cache initially + network.emplace(std::nullopt, true, false, false); + network->set_snode_cache(snode_cache); + unused_nodes = network->get_unused_nodes(); + std::stable_sort(unused_nodes.begin(), unused_nodes.end(), compare_service_nodes); + CHECK(unused_nodes == snode_cache); + + // Should exclude nodes used in paths + network.emplace(std::nullopt, true, false, false); + network->set_snode_cache(snode_cache); + network->set_paths(PathType::standard, {path}); + unused_nodes = network->get_unused_nodes(); + std::stable_sort(unused_nodes.begin(), unused_nodes.end(), compare_service_nodes); + CHECK(unused_nodes == std::vector{snode_cache.begin() + 3, snode_cache.end()}); + + // Should exclude nodes in unused connections + network.emplace(std::nullopt, true, false, false); + network->set_snode_cache(snode_cache); + network->set_unused_connections({invalid_info}); + unused_nodes = network->get_unused_nodes(); + std::stable_sort(unused_nodes.begin(), unused_nodes.end(), compare_service_nodes); + CHECK(unused_nodes == std::vector{snode_cache.begin() + 1, snode_cache.end()}); + + // Should exclude nodes in in-progress connections + network.emplace(std::nullopt, true, false, false); + network->set_snode_cache(snode_cache); + network->set_in_progress_connections({{"Test", snode_cache.front()}}); + unused_nodes = network->get_unused_nodes(); + std::stable_sort(unused_nodes.begin(), unused_nodes.end(), compare_service_nodes); + CHECK(unused_nodes == std::vector{snode_cache.begin() + 1, snode_cache.end()}); + + // Should exclude nodes destinations in pending requests + network.emplace(std::nullopt, true, false, false); + network->set_snode_cache(snode_cache); + network->add_pending_request( + PathType::standard, + request_info::make( + snode_cache.front(), + std::nullopt, + std::nullopt, + 1s, + std::nullopt, + PathType::standard)); + unused_nodes = network->get_unused_nodes(); + std::stable_sort(unused_nodes.begin(), unused_nodes.end(), compare_service_nodes); + CHECK(unused_nodes == std::vector{snode_cache.begin() + 1, snode_cache.end()}); + + // Should exclude nodes which have passed the failure threshold + network.emplace(std::nullopt, true, false, false); + network->set_snode_cache(snode_cache); + network->set_failure_count(snode_cache.front(), 10); + unused_nodes = network->get_unused_nodes(); + std::stable_sort(unused_nodes.begin(), unused_nodes.end(), compare_service_nodes); + CHECK(unused_nodes == std::vector{snode_cache.begin() + 1, snode_cache.end()}); + + // Should exclude nodes which have the same IP if one was excluded + std::vector same_ip_snode_cache; + auto unique_node = service_node{ed_pk, {2, 8, 0}, INVALID_SWARM_ID, "0.0.0.20", uint16_t{20}}; + for (uint16_t i = 0; i < 11; ++i) + same_ip_snode_cache.emplace_back(test_node(ed_pk, i, false)); + same_ip_snode_cache.emplace_back(unique_node); + network.emplace(std::nullopt, true, false, false); + network->set_snode_cache(same_ip_snode_cache); + network->set_failure_count(same_ip_snode_cache.front(), 10); + unused_nodes = network->get_unused_nodes(); + REQUIRE(unused_nodes.size() == 1); + CHECK(unused_nodes.front() == unique_node); +} + +TEST_CASE("Network", "[network][build_path]") { + const auto ed_pk = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes; + std::optional network; + std::vector snode_cache; + for (uint16_t i = 0; i < 12; ++i) + snode_cache.emplace_back(test_node(ed_pk, i)); + auto invalid_info = connection_info{snode_cache[0], nullptr, nullptr, nullptr}; + + // Nothing should happen if the network is suspended + network.emplace(std::nullopt, true, false, false); + network->set_suspended(true); + network->build_path("Test1", PathType::standard); + CHECK(network->did_not_call("establish_and_store_connection", 25ms)); + + // If there are no unused connections it puts the path build in the queue and calls + // establish_and_store_connection + network.emplace(std::nullopt, true, false, false); + network->ignore_calls_to("establish_and_store_connection"); + network->build_path("Test1", PathType::standard); + CHECK(network->get_path_build_queue() == std::deque{PathType::standard}); + CHECK(network->called("establish_and_store_connection", 100ms)); + + // If the unused nodes are empty it refreshes them + network.emplace(std::nullopt, true, false, false); + network->set_snode_cache(snode_cache); + network->set_unused_connections({invalid_info}); + network->set_in_progress_connections({{"TestInProgress", snode_cache.front()}}); + network->build_path("Test1", PathType::standard); + CHECK(network->get_unused_nodes_value().size() == snode_cache.size() - 3); + CHECK(network->get_path_build_queue().empty()); + + // It should exclude nodes that are already in existing paths + network.emplace(std::nullopt, true, false, false); + network->set_snode_cache(snode_cache); + network->set_unused_connections({invalid_info}); + network->set_in_progress_connections({{"TestInProgress", snode_cache.front()}}); + network->add_path(PathType::standard, {snode_cache.begin() + 1, snode_cache.begin() + 1 + 3}); + network->build_path("Test1", PathType::standard); + CHECK(network->get_unused_nodes_value().size() == (snode_cache.size() - 3 - 3)); + CHECK(network->get_path_build_queue().empty()); + + // If there aren't enough unused nodes it resets the failure count, re-queues the path build and + // triggers a snode cache refresh + network.emplace(std::nullopt, true, false, false); + network->ignore_calls_to("refresh_snode_cache"); + network->set_snode_cache(snode_cache); + network->set_unused_connections({invalid_info}); + network->set_path_build_failures(10); + network->add_path(PathType::standard, snode_cache); + network->build_path("Test1", PathType::standard); + CHECK(network->get_path_build_failures() == 0); + CHECK(network->get_path_build_queue() == std::deque{PathType::standard}); + CHECK(network->called("refresh_snode_cache", 100ms)); + + // If it can't build a path after excluding nodes with the same IP it increments the + // failure count and re-tries the path build after a small delay + network.emplace(std::nullopt, true, false, false); + network->set_snode_cache(snode_cache); + network->set_unused_connections({invalid_info}); + network->set_unused_nodes(std::vector{ + snode_cache[0], snode_cache[0], snode_cache[0], snode_cache[0]}); + network->build_path("Test1", PathType::standard); + network->ignore_calls_to("build_path"); // Ignore the 2nd loop + CHECK(network->get_path_build_failures() == 1); + CHECK(network->get_path_build_queue().empty()); + CHECK(network->called("build_path", 100ms, 2)); + + // It stores a successful non-standard path and kicks of queued requests but doesn't update the + // status or call the 'paths_changed' hook + network.emplace(std::nullopt, true, false, false); + network->find_valid_path_response = + onion_path{"Test", invalid_info, {snode_cache.begin(), snode_cache.begin() + 3}, 0}; + network->ignore_calls_to("_send_onion_request"); + network->set_snode_cache(snode_cache); + network->set_unused_connections({invalid_info}); + network->add_pending_request( + PathType::download, + request_info::make( + snode_cache.back(), + std::nullopt, + std::nullopt, + 1s, + std::nullopt, + PathType::download)); + network->build_path("Test1", PathType::download); + CHECK(network->called("_send_onion_request", 100ms)); + CHECK(network->get_paths(PathType::download).size() == 1); + + // It stores a successful 'standard' path, updates the status, calls the 'paths_changed' hook + // and kicks of queued requests + network.emplace(std::nullopt, true, false, false); + network->find_valid_path_response = + onion_path{"Test", invalid_info, {snode_cache.begin(), snode_cache.begin() + 3}, 0}; + network->ignore_calls_to("_send_onion_request"); + network->set_snode_cache(snode_cache); + network->set_unused_connections({invalid_info}); + network->add_pending_request( + PathType::standard, + request_info::make( + snode_cache.back(), + std::nullopt, + std::nullopt, + 1s, + std::nullopt, + PathType::standard)); + network->build_path("Test1", PathType::standard); + CHECK(network->called("_send_onion_request", 100ms)); + CHECK(network->get_paths(PathType::standard).size() == 1); + CHECK(network->get_status() == ConnectionStatus::connected); + CHECK(network->called("paths_changed")); +} + +TEST_CASE("Network", "[network][find_valid_path]") { + auto ed_pk = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes; + auto target = test_node(ed_pk, 1); + auto test_service_node = service_node{ + "decaf007f26d3d6f9b845ad031ffdf6d04638c25bb10b8fffbbe99135303c4b9"_hexbytes, + {2, 8, 0}, + INVALID_SWARM_ID, + "144.76.164.202", + uint16_t{35400}}; + auto network = TestNetwork(std::nullopt, true, false, false); + auto info = request_info::make(target, std::nullopt, std::nullopt, 0ms); + auto invalid_path = onion_path{ + "Test", + {test_service_node, nullptr, nullptr, nullptr}, + {test_service_node}, + uint8_t{0}}; + + // It returns nothing when given no path options + CHECK_FALSE(network.find_valid_path(info, {}).has_value()); + + // It ignores invalid paths + CHECK_FALSE(network.find_valid_path(info, {invalid_path}).has_value()); + + // Need to get a valid path for subsequent tests + std::promise>> prom; + + network.establish_connection( + "Test", + test_service_node, + 3s, + [&prom](connection_info conn_info, std::optional error) { + prom.set_value({std::move(conn_info), error}); + }); + + // Wait for the result to be set + auto result = prom.get_future().get(); + REQUIRE(result.first.is_valid()); + auto valid_path = onion_path{ + "Test", + std::move(result.first), + std::vector{test_service_node}, + uint8_t{0}}; + + // It excludes paths which include the IP of the target + auto shared_ip_info = request_info::make(test_service_node, std::nullopt, std::nullopt, 0ms); + CHECK_FALSE(network.find_valid_path(shared_ip_info, {valid_path}).has_value()); + + // It returns a path when there is a valid one + CHECK(network.find_valid_path(info, {valid_path}).has_value()); + + // In 'single_path_mode' it does allow the path to include the IP of the target (so that + // requests can still be made) + auto network_single_path = TestNetwork(std::nullopt, true, true, false); + CHECK(network_single_path.find_valid_path(shared_ip_info, {valid_path}).has_value()); +} + +TEST_CASE("Network", "[network][build_path_if_needed]") { + auto ed_pk = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes; + auto target = test_node(ed_pk, 0); + ; + std::optional network; + auto invalid_path = onion_path{ + "Test", connection_info{target, nullptr, nullptr, nullptr}, {target}, uint8_t{0}}; + + // It does not add additional path builds if there is already a path and it's in + // 'single_path_mode' + network.emplace(std::nullopt, true, true, false); + network->ignore_calls_to("establish_and_store_connection"); + network->set_paths(PathType::standard, {invalid_path}); + network->build_path_if_needed(PathType::standard, false); + CHECK(network->did_not_call("establish_and_store_connection", 25ms)); + CHECK(network->get_path_build_queue().empty()); + + // Adds a path build to the queue + network.emplace(std::nullopt, true, false, false); + network->ignore_calls_to("establish_and_store_connection"); + network->set_paths(PathType::standard, {}); + network->build_path_if_needed(PathType::standard, false); + CHECK(network->called("establish_and_store_connection", 100ms)); + CHECK(network->get_path_build_queue() == std::deque{PathType::standard}); + + // Can only add the correct number of 'standard' path builds to the queue + network.emplace(std::nullopt, true, false, false); + network->ignore_calls_to("establish_and_store_connection"); + network->build_path_if_needed(PathType::standard, false); + network->build_path_if_needed(PathType::standard, false); + CHECK(network->called("establish_and_store_connection", 100ms, 2)); + network->reset_calls(); // This triggers 'call_soon' so we need to wait until they are enqueued + network->build_path_if_needed(PathType::standard, false); + CHECK(network->did_not_call("establish_and_store_connection", 25ms)); + CHECK(network->get_path_build_queue() == + std::deque{PathType::standard, PathType::standard}); + + // Can add additional 'standard' path builds if below the minimum threshold + network.emplace(std::nullopt, true, false, false); + network->ignore_calls_to("establish_and_store_connection"); + network->set_paths(PathType::standard, {invalid_path}); + network->build_path_if_needed(PathType::standard, false); + CHECK(network->called("establish_and_store_connection", 100ms)); + CHECK(network->get_path_build_queue() == std::deque{PathType::standard}); + + // Can add more path builds if there are enough active paths of the same type, no pending paths + // and no `found_path` was provided + network.emplace(std::nullopt, true, false, false); + network->ignore_calls_to("establish_and_store_connection"); + network->set_paths(PathType::standard, {invalid_path, invalid_path}); + network->build_path_if_needed(PathType::standard, false); + CHECK(network->called("establish_and_store_connection", 100ms)); + CHECK(network->get_path_build_queue() == std::deque{PathType::standard}); + + // Cannot add more path builds if there are already enough active paths of the same type and a + // `found_path` was provided + network.emplace(std::nullopt, true, false, false); + network->ignore_calls_to("establish_and_store_connection"); + network->set_paths(PathType::standard, {invalid_path, invalid_path}); + network->build_path_if_needed(PathType::standard, true); + CHECK(network->did_not_call("establish_and_store_connection", 25ms)); + CHECK(network->get_path_build_queue().empty()); + + // Cannot add more path builds if there is already a build of the same type in the queue and the + // number of active and pending builds of the same type meet the limit + network.emplace(std::nullopt, true, false, false); + network->ignore_calls_to("establish_and_store_connection"); + network->set_paths(PathType::standard, {invalid_path}); + network->set_path_build_queue({PathType::standard}); + network->build_path_if_needed(PathType::standard, false); + CHECK(network->did_not_call("establish_and_store_connection", 25ms)); + CHECK(network->get_path_build_queue() == std::deque{PathType::standard}); + + // Can only add the correct number of 'download' path builds to the queue + network.emplace(std::nullopt, true, false, false); + network->ignore_calls_to("establish_and_store_connection"); + network->build_path_if_needed(PathType::download, false); + network->build_path_if_needed(PathType::download, false); + CHECK(network->called("establish_and_store_connection", 100ms, 2)); + network->reset_calls(); // This triggers 'call_soon' so we need to wait until they are enqueued + network->build_path_if_needed(PathType::download, false); + CHECK(network->did_not_call("establish_and_store_connection", 25ms)); + CHECK(network->get_path_build_queue() == + std::deque{PathType::download, PathType::download}); + + // Can only add the correct number of 'upload' path builds to the queue + network.emplace(std::nullopt, true, false, false); + network->ignore_calls_to("establish_and_store_connection"); + network->build_path_if_needed(PathType::upload, false); + network->build_path_if_needed(PathType::upload, false); + CHECK(network->called("establish_and_store_connection", 100ms, 2)); + network->reset_calls(); // This triggers 'call_soon' so we need to wait until they are enqueued + network->build_path_if_needed(PathType::upload, false); + CHECK(network->did_not_call("establish_and_store_connection", 25ms)); + CHECK(network->get_path_build_queue() == + std::deque{PathType::upload, PathType::upload}); +} + +TEST_CASE("Network", "[network][establish_connection]") { + auto network = TestNetwork(std::nullopt, true, true, false); + auto test_server = network.create_test_node(500); + std::promise>> prom; + + network.establish_connection( + "Test", + test_server->node, + 3s, + [&prom](connection_info info, std::optional error) { + prom.set_value({info, error}); + }); + + // Wait for the result to be set + auto result = prom.get_future().get(); + + CHECK(result.first.is_valid()); + CHECK_FALSE(result.second.has_value()); +} + +TEST_CASE("Network", "[network][check_request_queue_timeouts]") { + std::optional network; + std::optional> test_server; + std::promise prom; + + // Test that it doesn't start checking for timeouts when the request doesn't have + // a build paths timeout + network.emplace(std::nullopt, true, true, false); + test_server.emplace(network->create_test_node(501)); + network->send_onion_request( + (*test_server)->node, + to_vector("{\"method\":\"info\",\"params\":{}}"), + std::nullopt, + [](bool, + bool, + int16_t, + std::vector>, + std::optional) {}, + oxen::quic::DEFAULT_TIMEOUT, + std::nullopt); + CHECK(network->did_not_call("check_request_queue_timeouts", 300ms)); + + // Test that it does start checking for timeouts when the request has a + // paths build timeout + network.emplace(std::nullopt, true, true, false); + test_server.emplace(network->create_test_node(502)); + network->ignore_calls_to("build_path"); + network->send_onion_request( + (*test_server)->node, + to_vector("{\"method\":\"info\",\"params\":{}}"), + std::nullopt, + [](bool, + bool, + int16_t, + std::vector>, + std::optional) {}, + oxen::quic::DEFAULT_TIMEOUT, + oxen::quic::DEFAULT_TIMEOUT); + CHECK(network->called("check_request_queue_timeouts", 300ms)); + + // Test that it fails the request with a timeout if it has a build path timeout + // and the path build takes too long + network.emplace(std::nullopt, true, true, false); + test_server.emplace(network->create_test_node(503)); + network->ignore_calls_to("build_path"); + network->send_onion_request( + (*test_server)->node, + to_vector("{\"method\":\"info\",\"params\":{}}"), + std::nullopt, + [&prom](bool success, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response) { + prom.set_value({success, timeout, status_code, headers, response}); + }, + oxen::quic::DEFAULT_TIMEOUT, + 100ms); + + // Wait for the result to be set + auto result = prom.get_future().get(); + + CHECK_FALSE(result.success); + CHECK(result.timeout); +} + +TEST_CASE("Network", "[network][send_request]") { + auto network = TestNetwork(std::nullopt, true, true, false); + auto test_server = network.create_test_node(500); + std::promise prom; + + network.establish_connection( + "Test", + test_server->node, + 3s, + [&prom, &network, &test_server]( + connection_info info, std::optional error) { + if (!info.is_valid()) + return prom.set_value({false, false, -1, {}, error.value_or("Unknown Error")}); + + network.send_request( + request_info::make( + test_server->node, + to_vector("{}"), + std::nullopt, + 3s, + std::nullopt, + PathType::standard, + std::nullopt, + "info"), + std::move(info), + [&prom](bool success, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response) { + prom.set_value({success, timeout, status_code, headers, response}); + }); + }); + + // Wait for the result to be set + auto result = prom.get_future().get(); + + CHECK(result.success); + CHECK_FALSE(result.timeout); + CHECK(result.status_code == 200); + REQUIRE(result.response.has_value()); + INFO("*result.response is: " << *result.response); + REQUIRE_NOTHROW([&] { [[maybe_unused]] auto _ = nlohmann::json::parse(*result.response); }); + + auto response = nlohmann::json::parse(*result.response); + REQUIRE(response.contains("hf")); + auto hf = response["hf"].get>(); + CHECK(hf.size() == 3); + CHECK(hf[0] == 1); // Called the info callback + CHECK(response.contains("t")); + CHECK(response.contains("version")); +} + +TEST_CASE("Network", "[network][send_onion_request]") { + auto network = TestNetwork(std::nullopt, true, true, false); + auto test_server = network.create_test_node(500); + auto [test_path_servers, test_path] = network.create_test_path(); + network.handle_onion_requests_as_plaintext = true; + network.set_paths(PathType::standard, {test_path}); + std::promise result_promise; + + network.send_onion_request( + test_server->node, + to_vector("{\"method\":\"info\",\"params\":{}}"), + std::nullopt, + [&result_promise]( + bool success, + bool timeout, + int16_t status_code, + std::vector> headers, + std::optional response) { + result_promise.set_value({success, timeout, status_code, headers, response}); + }, + oxen::quic::DEFAULT_TIMEOUT, + oxen::quic::DEFAULT_TIMEOUT); + + // Wait for the result to be set + auto result = result_promise.get_future().get(); + + CHECK_FALSE(result.timeout); + CHECK(result.status_code == 200); + REQUIRE(result.success); + REQUIRE(result.response.has_value()); + INFO("*result.response is: " << *result.response); + REQUIRE_NOTHROW([&] { [[maybe_unused]] auto _ = nlohmann::json::parse(*result.response); }); + + auto response = nlohmann::json::parse(*result.response); + REQUIRE(response.contains("hf")); + auto hf = response["hf"].get>(); + CHECK(hf.size() == 3); + CHECK(hf[0] == 2); // Called the onion_req callback + CHECK(response.contains("t")); + CHECK(response.contains("version")); +} + +TEST_CASE("Network", "[network][c][network_send_onion_request]") { + auto test_network = std::make_unique(std::nullopt, true, true, false); + auto test_server_cpp = test_network->create_test_node(500); + std::optional>, onion_path>> test_path_data; + test_path_data.emplace(test_network->create_test_path()); + test_network->handle_onion_requests_as_plaintext = true; + test_network->set_paths(PathType::standard, {test_path_data->second}); + + // Convert TestNetwork to network_object to pass to C API + auto n_object = std::make_unique(); + n_object->internals = test_network.release(); + network_object* network = n_object.release(); + + // Convert test_server_cpp->node to network_service_node to pass to C API + auto ip_v4 = test_server_cpp->node.to_ipv4(); + std::array target_ip = { + static_cast(ip_v4.addr >> 24), + static_cast((ip_v4.addr >> 16) & 0xFF), + static_cast((ip_v4.addr >> 8) & 0xFF), + static_cast(ip_v4.addr & 0xFF)}; + auto test_service_node = network_service_node{}; + test_service_node.quic_port = test_server_cpp->node.port(); + std::copy(target_ip.begin(), target_ip.end(), test_service_node.ip); + auto test_pubkey_hex = oxenc::to_hex(test_server_cpp->node.view_remote_key()); + std::strcpy(test_service_node.ed25519_pubkey_hex, test_pubkey_hex.c_str()); + + // Make the request + auto body = to_vector("{\"method\":\"info\",\"params\":{}}"); + auto result_promise = std::make_shared>(); + + network_send_onion_request_to_snode_destination( + network, + test_service_node, + body.data(), + body.size(), + nullptr, + std::chrono::milliseconds{oxen::quic::DEFAULT_TIMEOUT}.count(), + std::chrono::milliseconds{oxen::quic::DEFAULT_TIMEOUT}.count(), + [](bool success, + bool timeout, + int16_t status_code, + const char* const* headers, + const char* const* header_values, + size_t headers_size, + const char* c_response, + size_t response_size, + void* ctx) { + auto result_promise = static_cast*>(ctx); + auto response_str = std::string(c_response, response_size); + std::vector> header_pairs; + header_pairs.reserve(headers_size); + + for (size_t i = 0; i < headers_size; ++i) { + if (headers[i] == nullptr) + continue; // Skip null entries + if (header_values[i] == nullptr) + continue; // Skip null entries + + header_pairs.emplace_back(headers[i], header_values[i]); + } + + result_promise->set_value( + {success, timeout, status_code, header_pairs, response_str}); + }, + static_cast(result_promise.get())); + + // Wait for the result to be set + auto result = result_promise->get_future().get(); + + CHECK(result.success); + CHECK_FALSE(result.timeout); + CHECK(result.status_code == 200); + REQUIRE(result.response.has_value()); + INFO("*result.response is: " << *result.response); + REQUIRE_NOTHROW([&] { [[maybe_unused]] auto _ = nlohmann::json::parse(*result.response); }); + + auto response = nlohmann::json::parse(*result.response); + REQUIRE(response.contains("hf")); + auto hf = response["hf"].get>(); + CHECK(hf.size() == 3); + CHECK(hf[0] == 2); // Called the onion_req callback + CHECK(response.contains("t")); + CHECK(response.contains("version")); + test_path_data.reset(); + network_free(network); +} + +TEST_CASE("Network", "[network][detail][pubkey_to_swarm_space]") { + x25519_pubkey pk; + + pk = x25519_pubkey::from_hex( + "3506f4a71324b7dd114eddbf4e311f39dde243e1f2cb97c40db1961f70ebaae8"); + CHECK(session::network::detail::pubkey_to_swarm_space(pk) == 17589930838143112648ULL); + pk = x25519_pubkey::from_hex( + "cf27da303a50ac8c4b2d43d27259505c9bcd73fc21cf2a57902c3d050730b604"); + CHECK(session::network::detail::pubkey_to_swarm_space(pk) == 10370619079776428163ULL); + pk = x25519_pubkey::from_hex( + "d3511706b8b34f6e8411bf07bd22ba6b2435ca56846fbccf6eb1e166a6cd15cc"); + CHECK(session::network::detail::pubkey_to_swarm_space(pk) == 2144983569669512198ULL); + pk = x25519_pubkey::from_hex( + "0f06693428fca9102a451e3f28d9cc743d8ea60a89ab6aa69eb119470c11cbd3"); + CHECK(session::network::detail::pubkey_to_swarm_space(pk) == 9690840703409570833ULL); + pk = x25519_pubkey::from_hex( + "ffba630924aa1224bb930dde21c0d11bf004608f2812217f8ac812d6c7e3ad48"); + CHECK(session::network::detail::pubkey_to_swarm_space(pk) == 4532060000165252872ULL); + pk = x25519_pubkey::from_hex( + "eeeeeeeeeeeeeeee777777777777777711111111111111118888888888888888"); + CHECK(session::network::detail::pubkey_to_swarm_space(pk) == 0); + pk = x25519_pubkey::from_hex( + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); + CHECK(session::network::detail::pubkey_to_swarm_space(pk) == 0); + pk = x25519_pubkey::from_hex( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"); + CHECK(session::network::detail::pubkey_to_swarm_space(pk) == 1); + pk = x25519_pubkey::from_hex( + "ffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffff"); + CHECK(session::network::detail::pubkey_to_swarm_space(pk) == 1ULL << 63); + pk = x25519_pubkey::from_hex( + "000000000000000000000000000000000000000000000000ffffffffffffffff"); + CHECK(session::network::detail::pubkey_to_swarm_space(pk) == (uint64_t)-1); + pk = x25519_pubkey::from_hex( + "0000000000000000000000000000000000000000000000000123456789abcdef"); + CHECK(session::network::detail::pubkey_to_swarm_space(pk) == 0x0123456789abcdefULL); +} + +TEST_CASE("Network", "[network][get_swarm]") { + auto ed_pk = "4cb76fdc6d32278e3f83dbf608360ecc6b65727934b85d2fb86862ff98c46ab7"_hexbytes; + std::vector>> swarms = { + {100, {}}, {200, {}}, {300, {}}, {399, {}}, {498, {}}, {596, {}}, {694, {}}}; + auto network = TestNetwork(std::nullopt, true, true, false); + network.set_snode_cache({test_node(ed_pk, 0)}); + network.set_all_swarms(swarms); + + // Exact matches: + // 0x64 = 100, 0xc8 = 200, 0x1f2 = 498 + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000006" + "4") == 100); + CHECK(network.get_swarm_id("0500000000000000000000000000000000000000000000000000000000000000c" + "8") == 200); + CHECK(network.get_swarm_id("0500000000000000000000000000000000000000000000000000000000000001f" + "2") == 498); + + // Nearest + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000000" + "0") == 100); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000000" + "1") == 100); + + // Nearest, with wraparound + // 0x8000... is closest to the top value + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000800000000000000" + "0") == 694); + + // 0xa000... is closest (via wraparound) to the smallest + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000a00000000000000" + "0") == 100); + + // This is the invalid swarm id for swarms, but should still work for a client + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000fffffffffffffff" + "f") == 100); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000fffffffffffffff" + "e") == 100); + + // Midpoint tests; we prefer the lower value when exactly in the middle between two swarms. + // 0x96 = 150 + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000009" + "5") == 100); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000009" + "6") == 100); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000009" + "7") == 200); + + // 0xfa = 250 + CHECK(network.get_swarm_id("0500000000000000000000000000000000000000000000000000000000000000f" + "9") == 200); + CHECK(network.get_swarm_id("0500000000000000000000000000000000000000000000000000000000000000f" + "a") == 200); + CHECK(network.get_swarm_id("0500000000000000000000000000000000000000000000000000000000000000f" + "b") == 300); + + // 0x15d = 349 + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000015" + "d") == 300); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000015" + "e") == 399); + + // 0x1c0 = 448 + CHECK(network.get_swarm_id("0500000000000000000000000000000000000000000000000000000000000001c" + "0") == 399); + CHECK(network.get_swarm_id("0500000000000000000000000000000000000000000000000000000000000001c" + "1") == 498); + + // 0x223 = 547 + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000022" + "2") == 498); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000022" + "3") == 498); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000022" + "4") == 596); + + // 0x285 = 645 + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000028" + "5") == 596); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000028" + "6") == 694); + + // 0x800....d is the midpoint between 694 and 100 (the long way). We always round "down" (which + // in this case, means wrapping to the largest swarm). + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000800000000000018" + "c") == 694); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000800000000000018" + "d") == 694); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000800000000000018" + "e") == 100); + + // With a swarm at -20 the midpoint is now 40 (=0x28). When our value is the *low* value we + // prefer the *last* swarm in the case of a tie (while consistent with the general case of + // preferring the left edge, it means we're inconsistent with the other wraparound case, above. + // *sigh*). + swarms.push_back({(uint64_t)-20, {}}); + network.set_all_swarms(swarms); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000002" + "7") == swarms.back().first); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000002" + "8") == swarms.back().first); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000000000000000002" + "9") == swarms.front().first); + + // The code used to have a broken edge case if we have a swarm at zero and a client at max-u64 + // because of an overflow in how the distance is calculated (the first swarm will be calculated + // as max-u64 away, rather than 1 away), and so the id always maps to the highest swarm (even + // though 0xfff...fe maps to the lowest swarm; the first check here, then, would fail. + swarms.insert(swarms.begin(), {0, {}}); + network.set_all_swarms(swarms); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000fffffffffffffff" + "f") == 0); + CHECK(network.get_swarm_id("05000000000000000000000000000000000000000000000000fffffffffffffff" + "e") == 0); +} diff --git a/tests/test_xed25519.cpp b/tests/test_xed25519.cpp index 67089302..143ba82b 100644 --- a/tests/test_xed25519.cpp +++ b/tests/test_xed25519.cpp @@ -4,11 +4,10 @@ #include +#include "session/util.hpp" #include "session/xed25519.h" #include "session/xed25519.hpp" -using session::xed25519::ustring_view; - constexpr std::array seed1{ 0xfe, 0xcd, 0x9a, 0x60, 0x34, 0xbc, 0x9a, 0xba, 0x27, 0x39, 0x25, 0xde, 0xe7, 0x06, 0x2b, 0x12, 0x33, 0x34, 0x58, 0x7c, 0x3c, 0x62, 0x57, 0x34, 0x1a, 0xfa, @@ -22,8 +21,8 @@ constexpr std::array seed2{ 0x45, 0x44, 0xc1, 0xc5, 0x08, 0x9c, 0x40, 0x41, 0x4b, 0xbd, 0xa1, 0xff, 0xdd, 0xe8, 0xaa, 0xb2, 0x61, 0x7f, 0xe9, 0x37, 0xee, 0x74, 0xa5, 0xee, 0x81}; -constexpr ustring_view pub1{seed1.data() + 32, 32}; -constexpr ustring_view pub2{seed2.data() + 32, 32}; +constexpr std::span pub1{seed1.data() + 32, 32}; +constexpr std::span pub2{seed2.data() + 32, 32}; constexpr std::array xpub1{ 0xfe, 0x94, 0xb7, 0xad, 0x4b, 0x7f, 0x1c, 0xc1, 0xbb, 0x92, 0x67, @@ -42,16 +41,9 @@ constexpr std::array pub2_abs{ 0xb2, 0x61, 0x7f, 0xe9, 0x37, 0xee, 0x74, 0xa5, 0xee, 0x01, }; -template -static ustring_view view(const std::array& x) { - return {x.data(), x.size()}; -} -static ustring_view view(const std::string_view x) { - return ustring_view{reinterpret_cast(x.data()), x.size()}; -} template static std::string view_hex(const std::array& x) { - return oxenc::to_hex(view(x)); + return oxenc::to_hex(session::to_span(x)); } TEST_CASE("XEd25519 pubkey conversion", "[xed25519][pubkey]") { @@ -65,11 +57,11 @@ TEST_CASE("XEd25519 pubkey conversion", "[xed25519][pubkey]") { REQUIRE(rc == 0); REQUIRE(view_hex(xpk2) == view_hex(xpub2)); - auto xed1 = session::xed25519::pubkey(view(xpub1)); + auto xed1 = session::xed25519::pubkey(session::to_span(xpub1)); REQUIRE(view_hex(xed1) == oxenc::to_hex(pub1)); // This one fails because the original Ed pubkey is negative - auto xed2 = session::xed25519::pubkey(view(xpub2)); + auto xed2 = session::xed25519::pubkey(session::to_span(xpub2)); REQUIRE(view_hex(xed2) != oxenc::to_hex(pub2)); // After making the xed negative we should be okay: xed2[31] |= 0x80; @@ -89,14 +81,14 @@ TEST_CASE("XEd25519 signing", "[xed25519][sign]") { std::array xpk2; rc = crypto_sign_ed25519_pk_to_curve25519(xpk2.data(), pub2.data()); - const auto msg = view("hello world"); + const auto msg = session::to_span("hello world"); - auto xed_sig1 = session::xed25519::sign(view(xsk1), msg); + auto xed_sig1 = session::xed25519::sign(session::to_span(xsk1), msg); rc = crypto_sign_ed25519_verify_detached(xed_sig1.data(), msg.data(), msg.size(), pub1.data()); REQUIRE(rc == 0); - auto xed_sig2 = session::xed25519::sign(view(xsk2), msg); + auto xed_sig2 = session::xed25519::sign(session::to_span(xsk2), msg); // This one will fail, because Xed signing always uses the positive but our actual pub2 is the // negative: @@ -118,26 +110,26 @@ TEST_CASE("XEd25519 verification", "[xed25519][verify]") { rc = crypto_sign_ed25519_sk_to_curve25519(xsk2.data(), seed2.data()); REQUIRE(rc == 0); - const auto msg = view("hello world"); + const auto msg = session::to_span("hello world"); - auto xed_sig1 = session::xed25519::sign(view(xsk1), msg); - auto xed_sig2 = session::xed25519::sign(view(xsk2), msg); + auto xed_sig1 = session::xed25519::sign(session::to_span(xsk1), msg); + auto xed_sig2 = session::xed25519::sign(session::to_span(xsk2), msg); - REQUIRE(session::xed25519::verify(view(xed_sig1), view(xpub1), msg)); - REQUIRE(session::xed25519::verify(view(xed_sig2), view(xpub2), msg)); + REQUIRE(session::xed25519::verify(session::to_span(xed_sig1), session::to_span(xpub1), msg)); + REQUIRE(session::xed25519::verify(session::to_span(xed_sig2), session::to_span(xpub2), msg)); // Unlike regular Ed25519, XEd25519 uses randomness in the signature, so signing the same value // a second should give us a different signature: - auto xed_sig1b = session::xed25519::sign(view(xsk1), msg); + auto xed_sig1b = session::xed25519::sign(session::to_span(xsk1), msg); REQUIRE(view_hex(xed_sig1b) != view_hex(xed_sig1)); } TEST_CASE("XEd25519 pubkey conversion (C wrapper)", "[xed25519][pubkey][c]") { - auto xed1 = session::xed25519::pubkey(view(xpub1)); + auto xed1 = session::xed25519::pubkey(session::to_span(xpub1)); REQUIRE(view_hex(xed1) == oxenc::to_hex(pub1)); // This one fails because the original Ed pubkey is negative - auto xed2 = session::xed25519::pubkey(view(xpub2)); + auto xed2 = session::xed25519::pubkey(session::to_span(xpub2)); REQUIRE(view_hex(xed2) != oxenc::to_hex(pub2)); // After making the xed negative we should be okay: xed2[31] |= 0x80; @@ -156,13 +148,11 @@ TEST_CASE("XEd25519 signing (C wrapper)", "[xed25519][sign][c]") { std::array xpk2; rc = crypto_sign_ed25519_pk_to_curve25519(xpk2.data(), pub2.data()); - const auto msg = view("hello world"); + const auto msg = session::to_span("hello world"); std::array xed_sig1, xed_sig2; - rc = session_xed25519_sign(xed_sig1.data(), xsk1.data(), msg.data(), msg.size()); - REQUIRE(rc == 0); - rc = session_xed25519_sign(xed_sig2.data(), xsk2.data(), msg.data(), msg.size()); - REQUIRE(rc == 0); + REQUIRE(session_xed25519_sign(xed_sig1.data(), xsk1.data(), msg.data(), msg.size())); + REQUIRE(session_xed25519_sign(xed_sig2.data(), xsk2.data(), msg.data(), msg.size())); rc = crypto_sign_ed25519_verify_detached(xed_sig1.data(), msg.data(), msg.size(), pub1.data()); REQUIRE(rc == 0); @@ -183,16 +173,12 @@ TEST_CASE("XEd25519 verification (C wrapper)", "[xed25519][verify][c]") { rc = crypto_sign_ed25519_sk_to_curve25519(xsk2.data(), seed2.data()); REQUIRE(rc == 0); - const auto msg = view("hello world"); + const auto msg = session::to_span("hello world"); std::array xed_sig1, xed_sig2; - rc = session_xed25519_sign(xed_sig1.data(), xsk1.data(), msg.data(), msg.size()); - REQUIRE(rc == 0); - rc = session_xed25519_sign(xed_sig2.data(), xsk2.data(), msg.data(), msg.size()); - REQUIRE(rc == 0); + REQUIRE(session_xed25519_sign(xed_sig1.data(), xsk1.data(), msg.data(), msg.size())); + REQUIRE(session_xed25519_sign(xed_sig2.data(), xsk2.data(), msg.data(), msg.size())); - rc = session_xed25519_verify(xed_sig1.data(), xpub1.data(), msg.data(), msg.size()); - REQUIRE(rc == 0); - rc = session_xed25519_verify(xed_sig2.data(), xpub2.data(), msg.data(), msg.size()); - REQUIRE(rc == 0); + REQUIRE(session_xed25519_verify(xed_sig1.data(), xpub1.data(), msg.data(), msg.size())); + REQUIRE(session_xed25519_verify(xed_sig2.data(), xpub2.data(), msg.data(), msg.size())); } diff --git a/tests/utils.hpp b/tests/utils.hpp index aff40513..cf4d70cd 100644 --- a/tests/utils.hpp +++ b/tests/utils.hpp @@ -1,28 +1,67 @@ #pragma once +#include #include -#include +#include +#include #include +#include +#include #include #include #include +#include +#include #include "session/config/base.h" +#include "session/types.hpp" +#include "session/util.hpp" -using ustring = std::basic_string; -using ustring_view = std::basic_string_view; +using namespace std::literals; +using namespace oxenc::literals; +using namespace oxen::log::literals; -inline ustring operator""_bytes(const char* x, size_t n) { - return {reinterpret_cast(x), n}; +namespace session { + +/// RAII class that resets the log level for the given category while the object is alive, then +/// resets it to what it was at construction when the object is destroyed. +struct log_level_override { + oxen::log::Level previous; + std::string category; + + log_level_override(oxen::log::Level l, std::string category) : + previous{oxen::log::get_level(category)}, category{category} { + oxen::log::set_level(category, l); + } + ~log_level_override() { oxen::log::set_level(category, previous); } +}; + +/// Same as above, but only raises the log level to a more serious cutoff (leaving it alone if +/// already higher). +struct log_level_raiser : log_level_override { + log_level_raiser(oxen::log::Level l, std::string category) : + log_level_override{std::max(l, oxen::log::get_level(category)), category} {} +}; +/// Same as above, but only lowers the log level to a more frivolous cutoff (leaving it alone if +/// already lower). +struct log_level_lowerer : log_level_override { + log_level_lowerer(oxen::log::Level l, std::string category) : + log_level_override{std::min(l, oxen::log::get_level(category)), category} {} +}; +} // namespace session + +inline std::vector operator""_bytes(const char* x, size_t n) { + auto begin = reinterpret_cast(x); + return {begin, begin + n}; } -inline ustring operator""_hexbytes(const char* x, size_t n) { - ustring bytes; +inline std::vector operator""_hexbytes(const char* x, size_t n) { + std::vector bytes; oxenc::from_hex(x, x + n, std::back_inserter(bytes)); return bytes; } -inline std::string to_hex(ustring_view bytes) { +inline std::string to_hex(std::vector bytes) { std::string hex; oxenc::to_hex(bytes.begin(), bytes.end(), std::back_inserter(hex)); return hex; @@ -32,14 +71,29 @@ inline constexpr auto operator""_kiB(unsigned long long kiB) { return kiB * 1024; } -inline std::string_view to_sv(ustring_view x) { - return {reinterpret_cast(x.data()), x.size()}; +// Returns the current timestamp in milliseconds +inline int64_t get_timestamp_ms() { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); +} + +// Returns the current timestamp in seconds +inline int64_t get_timestamp_s() { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + ; } -inline ustring_view to_usv(std::string_view x) { - return {reinterpret_cast(x.data()), x.size()}; + +// Returns the current timestamp in microseconds +inline int64_t get_timestamp_us() { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); } -inline std::string printable(ustring_view x) { +inline std::string printable(std::span x) { std::string p; for (auto c : x) { if (c >= 0x20 && c <= 0x7e) @@ -50,21 +104,18 @@ inline std::string printable(ustring_view x) { return p; } inline std::string printable(std::string_view x) { - return printable(to_usv(x)); + return printable(session::to_span(x)); +} +template + requires(sizeof...(T) > 0) +inline std::string printable(fmt::format_string format, T&&... args) { + return printable(session::to_span(fmt::format(format, std::forward(args)...))); } std::string printable(const unsigned char* x) = delete; inline std::string printable(const unsigned char* x, size_t n) { return printable({x, n}); } -inline void log_msg(config_log_level lvl, const char* msg, void*) { - INFO((lvl == LOG_LEVEL_ERROR ? "ERROR" - : lvl == LOG_LEVEL_WARNING ? "Warning" - : lvl == LOG_LEVEL_INFO ? "Info" - : "debug") - << ": " << msg); -} - template std::set as_set(const Container& c) { return {c.begin(), c.end()}; diff --git a/utils/android.sh b/utils/android.sh index 444c4e04..a39a7b64 100755 --- a/utils/android.sh +++ b/utils/android.sh @@ -39,7 +39,8 @@ for abi in "${abis[@]}"; do -DANDROID_ABI=$abi \ -DANDROID_ARM_MODE=arm \ -DANDROID_PLATFORM=android-23 \ - -DANDROID_STL=c++_static + -DANDROID_STL=c++_static \ + -DLOCAL_MIRROR=https://oxen.rocks/deps done cd build-android @@ -48,8 +49,6 @@ pkg="${archive%%.tar.xz}" mkdir -p "$pkg"/include cp -rv ../include/session "$pkg"/include/ -mkdir -p "$pkg"/include/oxenc -cp -v ../external/oxen-encoding/oxenc/*.h x86_64/external/oxen-encoding/oxenc/version.h "$pkg"/include/oxenc/ for abi in "${abis[@]}"; do mkdir -p "$pkg"/lib/$abi diff --git a/utils/ci/drone-docs-upload.sh b/utils/ci/drone-docs-upload.sh index cb144a73..3edebe18 100755 --- a/utils/ci/drone-docs-upload.sh +++ b/utils/ci/drone-docs-upload.sh @@ -18,8 +18,8 @@ chmod 600 ~/ssh_key sftp -i ~/ssh_key -b - -o StrictHostKeyChecking=off apidocs@chianina.oxen.io </dev/null) if [ $? -ne 0 ]; then @@ -20,7 +20,7 @@ if [ $? -ne 0 ]; then fi cd "$(dirname $0)/../" -readarray -t sources < <(find include src tests | grep -E '\.([hc](pp)?)$' | grep -v '\#' | grep -v Catch2) +readarray -t sources < <(find include proto src tests | grep -E '\.([hc](pp)?)$' | grep -v '\#' | grep -v Catch2 | grep -v -E '\.pb\.(h|cc)$') if [ "$1" = "verify" ] ; then if [ $($binary --output-replacements-xml "${sources[@]}" | grep '' | wc -l) -ne 0 ] ; then exit 2 diff --git a/utils/ios.sh b/utils/ios.sh index b3f63085..1259abeb 100755 --- a/utils/ios.sh +++ b/utils/ios.sh @@ -22,16 +22,20 @@ VALID_DEVICE_ARCH_PLATFORMS=(OS64) OUTPUT_DIR="${TARGET_BUILD_DIR:-build-ios}" IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET:-13} ENABLE_BITCODE=${ENABLE_BITCODE:-OFF} -SHOULD_ACHIVE=${2:-true} # Parameter 2 is a flag indicating whether we want to archive the result +CONFIGURATION=${CONFIGURATION:-App_Store_Release} +BUILD_STATIC_LIBS=${2:-true} # Parameter 2 is a flag indicating whether we want to build the static libraries +MERGE_STATIC_LIBS=${3:-true} # Parameter 3 is a flag indicating whether we want to merge any static libraries +CREATE_FRAMEWORK=${4:-true} # Parameter 4 is a flag indicating whether we want to generate the framework +SHOULD_ACHIVE=${5:-true} # Parameter 5 is a flag indicating whether we want to archive the result # We want to customise the env variable so can't just default the value if [ -z "${TARGET_TEMP_DIR}" ]; then - BUILD_DIR="build-ios" + BUILD_DIR="./build-ios" elif [ "${#ARCHS[@]}" = 1 ]; then BUILD_DIR="${TARGET_TEMP_DIR}/../libSession-util" fi -# Can't dafault an array in the same way as above +# Can't default an array in the same way as above if [ -z "${ARCHS}" ]; then ARCHS=(arm64 x86_64) elif [ "${#ARCHS[@]}" = 1 ]; then @@ -97,87 +101,148 @@ if [ -z $PLATFORM_NAME ] || [ $PLATFORM_NAME = "iphoneos" ]; then fi # Build the individual architectures -for i in "${!TARGET_ARCHS[@]}"; do - build="${BUILD_DIR}/${TARGET_ARCHS[$i]}" - platform="${TARGET_PLATFORMS[$i]}" - echo "Building ${TARGET_ARCHS[$i]} for $platform in $build" - - ./utils/static-bundle.sh "$build" "" \ - -DCMAKE_TOOLCHAIN_FILE="${projdir}/external/ios-cmake/ios.toolchain.cmake" \ - -DPLATFORM=$platform \ - -DDEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET \ - -DENABLE_BITCODE=$ENABLE_BITCODE -done - -# If needed combine simulator builds into a multi-arch lib -if [ "${#TARGET_SIM_ARCHS[@]}" -eq "1" ]; then - # Single device build - mkdir -p "${BUILD_DIR}/sim" - rm -rf "${BUILD_DIR}/sim/libsession-util.a" - cp "${BUILD_DIR}/${TARGET_SIM_ARCHS[0]}/libsession-util.a" "${BUILD_DIR}/sim/libsession-util.a" -elif [ "${#TARGET_SIM_ARCHS[@]}" -gt "1" ]; then - # Combine multiple device builds into a multi-arch lib - mkdir -p "${BUILD_DIR}/sim" - lipo -create "${BUILD_DIR}"/sim-*/libsession-util.a -output "${BUILD_DIR}/sim/libsession-util.a" +submodule_check=ON +build_type="Release" + +# Make the logs look nicer +SHOULD_BUILD_STATIC_LIBS="" +SHOULD_MERGE_STATIC_LIBS="" +SHOULD_CREATE_FRAMEWORK="" +FINAL_SHOULD_ACHIVE="" + +if [ $BUILD_STATIC_LIBS = true ]; then + SHOULD_BUILD_STATIC_LIBS="SHOULD_BUILD_STATIC_LIBS" +fi +if [ $MERGE_STATIC_LIBS = true ]; then + SHOULD_MERGE_STATIC_LIBS="SHOULD_MERGE_STATIC_LIBS" +fi +if [ $CREATE_FRAMEWORK = true ]; then + SHOULD_CREATE_FRAMEWORK="SHOULD_CREATE_FRAMEWORK" +fi +if [ $SHOULD_ACHIVE = true ]; then + FINAL_SHOULD_ACHIVE="SHOULD_ACHIVE" fi -# If needed combine device builds into a multi-arch lib -if [ "${#TARGET_DEVICE_ARCHS[@]}" -eq "1" ]; then - # Single device build - mkdir -p "${BUILD_DIR}/ios" - rm -rf "${BUILD_DIR}/ios/libsession-util.a" - cp "${BUILD_DIR}/${TARGET_DEVICE_ARCHS[0]}/libsession-util.a" "${BUILD_DIR}/ios/libsession-util.a" -elif [ "${#TARGET_DEVICE_ARCHS[@]}" -gt "1" ]; then - # Combine multiple device builds into a multi-arch lib - mkdir -p "${BUILD_DIR}/ios" - lipo -create "${BUILD_DIR}"/ios-*/libsession-util.a -output "${BUILD_DIR}/ios/libsession-util.a" +if [ "$CONFIGURATION" == "Debug" ] || [ "$CONFIGURATION" == "Debug_Compile_LibSession" ]; then + submodule_check=OFF + build_type="Debug" fi +if [ "${SHOULD_BUILD_STATIC_LIBS}" == "SHOULD_BUILD_STATIC_LIBS" ]; then + for i in "${!TARGET_ARCHS[@]}"; do + build="${BUILD_DIR}/${TARGET_ARCHS[$i]}" + platform="${TARGET_PLATFORMS[$i]}" + echo "Building ${TARGET_ARCHS[$i]} for $platform in $build" + + env -i PATH="$PATH" SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" \ + ./utils/static-bundle.sh "$build" "" \ + -DCMAKE_TOOLCHAIN_FILE="${projdir}/external/ios-cmake/ios.toolchain.cmake" \ + -DPLATFORM=$platform \ + -DDEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET \ + -DENABLE_BITCODE=$ENABLE_BITCODE \ + -DBUILD_TESTS=OFF \ + -DBUILD_STATIC_DEPS=ON \ + -DENABLE_VISIBILITY=ON \ + -DSUBMODULE_CHECK=$submodule_check \ + -DCMAKE_BUILD_TYPE=$build_type \ + -DLOCAL_MIRROR=https://oxen.rocks/deps + done +fi -# Create a '.xcframework' so XCode can deal with the different architectures -rm -rf "${OUTPUT_DIR}/libsession-util.xcframework" +if [ "${SHOULD_MERGE_STATIC_LIBS}" == "SHOULD_MERGE_STATIC_LIBS" ]; then + # If needed combine simulator builds into a multi-arch lib + sim_files=( "${BUILD_DIR}/sim-"* ) + if [ "${#TARGET_SIM_ARCHS[@]}" -eq "1" ] && [ -e "${sim_files[0]}" ]; then + # Single device build + mkdir -p "${BUILD_DIR}/sim" + rm -rf "${BUILD_DIR}/sim/libsession-util.a" + cp "${BUILD_DIR}/${TARGET_SIM_ARCHS[0]}/libsession-util.a" "${BUILD_DIR}/sim/libsession-util.a" + elif [ "${#TARGET_SIM_ARCHS[@]}" -gt "1" ] && [ -e "${sim_files[0]}" ]; then + # Combine multiple device builds into a multi-arch lib + mkdir -p "${BUILD_DIR}/sim" + lipo -create "${BUILD_DIR}"/sim-*/libsession-util.a -output "${BUILD_DIR}/sim/libsession-util.a" + else + echo "No sim build static libs found" + fi -if [ "${#TARGET_SIM_ARCHS}" -gt "0" ] && [ "${#TARGET_DEVICE_ARCHS}" -gt "0" ]; then - xcodebuild -create-xcframework \ - -library "${BUILD_DIR}/ios/libsession-util.a" \ - -library "${BUILD_DIR}/sim/libsession-util.a" \ - -output "${OUTPUT_DIR}/libsession-util.xcframework" -elif [ "${#TARGET_DEVICE_ARCHS}" -gt "0" ]; then - xcodebuild -create-xcframework \ - -library "${BUILD_DIR}/ios/libsession-util.a" \ - -output "${OUTPUT_DIR}/libsession-util.xcframework" -else - xcodebuild -create-xcframework \ - -library "${BUILD_DIR}/sim/libsession-util.a" \ - -output "${OUTPUT_DIR}/libsession-util.xcframework" + # If needed combine device builds into a multi-arch lib + ios_files=( "${BUILD_DIR}/ios-"* ) + if [ "${#TARGET_DEVICE_ARCHS[@]}" -eq "1" ] && [ -e "${ios_files[0]}" ]; then + # Single device build + mkdir -p "${BUILD_DIR}/ios" + rm -rf "${BUILD_DIR}/ios/libsession-util.a" + cp "${BUILD_DIR}/${TARGET_DEVICE_ARCHS[0]}/libsession-util.a" "${BUILD_DIR}/ios/libsession-util.a" + elif [ "${#TARGET_DEVICE_ARCHS[@]}" -gt "1" ] && [ -e "${ios_files[0]}" ]; then + # Combine multiple device builds into a multi-arch lib + mkdir -p "${BUILD_DIR}/ios" + lipo -create "${BUILD_DIR}"/ios-*/libsession-util.a -output "${BUILD_DIR}/ios/libsession-util.a" + else + echo "No ios build static libs found" + fi fi -# Copy the headers over -cp -rv include/session "${OUTPUT_DIR}/libsession-util.xcframework" - -# The 'module.modulemap' is needed for XCode to be able to find the headers -modmap="${OUTPUT_DIR}/libsession-util.xcframework/module.modulemap" -echo "module SessionUtil {" >"$modmap" -echo " module capi {" >>"$modmap" -for x in $(cd include && find session -name '*.h'); do - echo " header \"$x\"" >>"$modmap" -done -echo -e " export *\n }" >>"$modmap" -if false; then - # If we include the cpp headers like this then Xcode will try to load them as C headers (which - # of course breaks) and doesn't provide any way to only load the ones you need (because this is - # Apple land, why would anything useful be available?). So we include the headers in the - # archive but can't let xcode discover them because it will do it wrong. - echo -e "\n module cppapi {" >>"$modmap" - for x in $(cd include && find session -name '*.hpp'); do +if [ "${SHOULD_CREATE_FRAMEWORK}" == "SHOULD_CREATE_FRAMEWORK" ]; then + # Create a '.xcframework' so XCode can deal with the different architectures + rm -rf "${OUTPUT_DIR}/libsession-util.xcframework" + sim_files=( "${BUILD_DIR}/sim-"* ) + ios_files=( "${BUILD_DIR}/ios-"* ) + + if [ "${#TARGET_SIM_ARCHS}" -gt "0" ] && [ -e "${sim_files[0]}" ] && [ "${#TARGET_DEVICE_ARCHS}" -gt "0" ] && [ -e "${ios_files[0]}" ]; then + xcodebuild -create-xcframework \ + -library "${BUILD_DIR}/ios/libsession-util.a" \ + -headers "include" \ + -library "${BUILD_DIR}/sim/libsession-util.a" \ + -headers "include" \ + -output "${OUTPUT_DIR}/libsession-util.xcframework" + elif [ "${#TARGET_DEVICE_ARCHS}" -gt "0" ] && [ -e "${ios_files[0]}" ]; then + xcodebuild -create-xcframework \ + -library "${BUILD_DIR}/ios/libsession-util.a" \ + -headers "include" \ + -output "${OUTPUT_DIR}/libsession-util.xcframework" + elif [ -e "${sim_files[0]}" ]; then + xcodebuild -create-xcframework \ + -library "${BUILD_DIR}/sim/libsession-util.a" \ + -headers "include" \ + -output "${OUTPUT_DIR}/libsession-util.xcframework" + else + echo "No static libraries to turn into framework" + exit 1 + fi + + # The 'module.modulemap' is needed for XCode to be able to find the headers + modmap="${OUTPUT_DIR}/module.modulemap" + echo "module SessionUtil {" >"$modmap" + echo " module capi {" >>"$modmap" + for x in $(cd include && find session -name '*.h'); do echo " header \"$x\"" >>"$modmap" done echo -e " export *\n }" >>"$modmap" -fi -echo "}" >>"$modmap" + echo "}" >>"$modmap" -if [ $SHOULD_ACHIVE = true ]; then - (cd "${OUTPUT_DIR}/.." && tar cvJf "${UNIQUE_NAME}.tar.xz" "${UNIQUE_NAME}") + # Need to add the module.modulemap into each architecture directory in the xcframework + for dir in "${OUTPUT_DIR}/libsession-util.xcframework"/*/; do + cp "${modmap}" "${dir}/Headers/module.modulemap" + done + + rm -rf "${modmap}" + + if [ $FINAL_SHOULD_ACHIVE = "SHOULD_ACHIVE" ]; then + (cd "${OUTPUT_DIR}/.." && tar cvJf "${UNIQUE_NAME}.tar.xz" "${UNIQUE_NAME}") + fi + + echo "Packaged everything up at ${OUTPUT_DIR}/libsession-util.xcframework" +else + # Copy the static libraries to the output + rm -rf "${OUTPUT_DIR}/libsession-util-sim.a" + rm -rf "${OUTPUT_DIR}/libsession-util-dev.a" + + if [ -f "${BUILD_DIR}/sim/libsession-util.a" ]; then + cp "${BUILD_DIR}/sim/libsession-util.a" "${OUTPUT_DIR}/libsession-util-sim.a" + fi + + if [ -f "${BUILD_DIR}/ios/libsession-util.a" ]; then + cp "${BUILD_DIR}/ios/libsession-util.a" "${OUTPUT_DIR}/libsession-util-dev.a" + fi + + echo "Packaged everything up at ${OUTPUT_DIR}/libsession-util-{sim|dev}.a" fi - -echo "Packaged everything up at ${OUTPUT_DIR}/libsession-util.xcframework" diff --git a/utils/macos.sh b/utils/macos.sh index 71071199..4519eb83 100755 --- a/utils/macos.sh +++ b/utils/macos.sh @@ -48,7 +48,8 @@ for i in arm64 x86_64; do ./utils/static-bundle.sh "$build" "" \ -DCMAKE_SYSTEM_NAME=Darwin \ -DARCH_TRIPLET="$i-apple-darwin16" \ - -DCMAKE_OSX_ARCHITECTURES=$i + -DCMAKE_OSX_ARCHITECTURES=$i \ + -DLOCAL_MIRROR=https://oxen.rocks/deps fi done diff --git a/utils/static-bundle.sh b/utils/static-bundle.sh index 0eeade5d..1b6ad0a9 100755 --- a/utils/static-bundle.sh +++ b/utils/static-bundle.sh @@ -63,7 +63,7 @@ cmake -G 'Unix Makefiles' \ "$@" \ "$projdir" -make -j${JOBS:-$(nproc)} VERBOSE=1 session-util +make -j${JOBS:-$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1)} VERBOSE=1 session-util if [ -z "$archive" ]; then exit 0 @@ -72,8 +72,6 @@ fi mkdir -p "$pkg"/{lib,include} cp -v libsession-util.a "$pkg"/lib cp -rv "$projdir"/include/session "$pkg"/include -mkdir -p "$pkg"/include/oxenc -cp -v "$projdir"/external/oxen-encoding/oxenc/*.h external/oxen-encoding/oxenc/version.h "$pkg"/include/oxenc/ if [ -z "$zip" ]; then tar cvJf "$archive" "$pkg"