├── .onedev-buildspec.yml ├── examples ├── ExampleClient │ ├── config.toml │ ├── CMakeLists.txt │ └── main.cpp ├── ExampleServer │ ├── CMakeLists.txt │ ├── Endpoints.hpp │ ├── config.toml │ ├── UserManager.hpp │ ├── Node.hpp │ ├── main.cpp │ ├── UserManager.cpp │ ├── User.hpp │ └── User.cpp ├── MumbleInit.hpp ├── MumbleInit.cpp └── CMakeLists.txt ├── tests ├── TestCrypt │ ├── CMakeLists.txt │ └── main.cpp ├── TestOpus │ ├── CMakeLists.txt │ └── main.cpp ├── TestHash │ ├── CMakeLists.txt │ └── main.cpp ├── TestBase64 │ ├── CMakeLists.txt │ └── main.cpp ├── TestPacketDataStream │ ├── CMakeLists.txt │ ├── main.cpp │ ├── TestPacketDataStream.hpp │ └── TestPacketDataStream.cpp ├── ThreadManager.hpp ├── ThreadManager.cpp └── CMakeLists.txt ├── vcpkg.json ├── cmake ├── wepoll_cmakelists.txt ├── FindOpus.cmake ├── compiler_utilities.cmake └── setup_dependencies.cmake ├── .gitignore ├── scripts └── runClangFormat.sh ├── src ├── Base64.hpp ├── UDP.hpp ├── proto │ ├── CMakeLists.txt │ └── MumbleUDP.proto ├── Hash.hpp ├── Key.hpp ├── TCP.hpp ├── Cert.hpp ├── Crypt.hpp ├── TLS.hpp ├── Lib.cpp ├── Opus.hpp ├── Connection.hpp ├── Peer.hpp ├── CryptOCB2.hpp ├── TCP.cpp ├── UDP.cpp ├── Monitor.hpp ├── CMakeLists.txt ├── Base64.cpp ├── Hash.cpp ├── IP.cpp ├── Socket.hpp ├── Key.cpp ├── TLS.cpp ├── Socket.cpp ├── Monitor.cpp ├── Cert.cpp ├── Connection.cpp ├── Crypt.cpp ├── Peer.cpp └── CryptOCB2.cpp ├── include └── mumble │ ├── NonCopyable.hpp │ ├── Lib.hpp │ ├── Base64.hpp │ ├── Hash.hpp │ ├── Key.hpp │ ├── CryptOCB2.hpp │ ├── IP.hpp │ ├── Crypt.hpp │ ├── Cert.hpp │ ├── Connection.hpp │ ├── Endian.hpp │ ├── Peer.hpp │ ├── Pack.hpp │ ├── Macros.hpp │ ├── Opus.hpp │ ├── Types.hpp │ └── PacketDataStream.hpp ├── .github └── workflows │ ├── pr-checks.yml │ └── build.yml ├── CMakePresets.json ├── LICENSE ├── CMakeLists.txt └── .clang-format /.onedev-buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 41 2 | imports: 3 | - projectPath: Mumble 4 | revision: mirroring 5 | -------------------------------------------------------------------------------- /examples/ExampleClient/config.toml: -------------------------------------------------------------------------------- 1 | [local] 2 | tcpIP = "::" 3 | tcpPort = 0 4 | [peer] 5 | tcpIP = "::" 6 | tcpPort = 64738 7 | -------------------------------------------------------------------------------- /tests/TestCrypt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | add_executable(TestCrypt 7 | "main.cpp" 8 | ) 9 | -------------------------------------------------------------------------------- /tests/TestOpus/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | add_executable(TestOpus 7 | "main.cpp" 8 | ) 9 | -------------------------------------------------------------------------------- /examples/ExampleClient/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | add_executable(ExampleClient 7 | "main.cpp" 8 | ) 9 | -------------------------------------------------------------------------------- /tests/TestHash/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | add_executable(TestHash 7 | "main.cpp" 8 | 9 | "Data.hpp" 10 | ) 11 | -------------------------------------------------------------------------------- /tests/TestBase64/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | add_executable(TestBase64 7 | "main.cpp" 8 | 9 | "Data.hpp" 10 | ) 11 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", 3 | "name": "libmumble", 4 | "version-string": "0.1.0", 5 | "builtin-baseline": "215a2535590f1f63788ac9bd2ed58ad15e6afdff", 6 | "dependencies": [ 7 | "boost-thread", 8 | "ms-gsl", 9 | "openssl", 10 | "opus", 11 | "protobuf" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tests/TestPacketDataStream/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Mumble Developers. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | add_executable(TestPacketDataStream 7 | "main.cpp" 8 | 9 | "TestPacketDataStream.cpp" 10 | "TestPacketDataStream.hpp" 11 | ) 12 | -------------------------------------------------------------------------------- /examples/ExampleServer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | add_executable(ExampleServer 7 | "main.cpp" 8 | 9 | "Endpoints.hpp" 10 | "Node.cpp" 11 | "Node.hpp" 12 | "User.cpp" 13 | "User.hpp" 14 | "UserManager.cpp" 15 | "UserManager.hpp" 16 | ) 17 | -------------------------------------------------------------------------------- /cmake/wepoll_cmakelists.txt: -------------------------------------------------------------------------------- 1 | # This is a drop-in CMakeLists.txt file intended for the wepoll 2 | # source tree (https://github.com/piscisaureus/wepoll), which 3 | # doesn't provide cmake support out of the box 4 | 5 | project(wepoll LANGUAGES "C") 6 | 7 | add_library(wepoll_lib STATIC 8 | "wepoll.c" 9 | ) 10 | 11 | target_include_directories(wepoll_lib PUBLIC "${PROJECT_SOURCE_DIR}") 12 | target_link_libraries(wepoll_lib PRIVATE WS2_32) 13 | 14 | add_library(wepoll::wepoll ALIAS wepoll_lib) 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | _dependencies/ 3 | 4 | compile_commands.json 5 | 6 | # Prerequisites 7 | *.d 8 | 9 | # Compiled Object files 10 | *.slo 11 | *.lo 12 | *.o 13 | *.obj 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Compiled Dynamic libraries 20 | *.so 21 | *.dylib 22 | *.dll 23 | 24 | # Fortran module files 25 | *.mod 26 | *.smod 27 | 28 | # Compiled Static libraries 29 | *.lai 30 | *.la 31 | *.a 32 | *.lib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | 39 | -------------------------------------------------------------------------------- /scripts/runClangFormat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2020-2022 The Mumble Developers. All rights reserved. 4 | # Use of this source code is governed by a BSD-style license 5 | # that can be found in the LICENSE file at the root of the 6 | # Mumble source tree or at . 7 | 8 | git ls-files --cached --modified --others -z -- \ 9 | ':^3rdparty/' ':^build*' '**/*'.cpp '**/*'.c '**/*'.hpp '**/*'.h '**/*'.cxx '**/*'.cc | 10 | xargs -0 -r -- clang-format --style=file -i 11 | -------------------------------------------------------------------------------- /examples/MumbleInit.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_EXAMPLE_MUMBLEINIT_HPP 7 | #define MUMBLE_EXAMPLE_MUMBLEINIT_HPP 8 | 9 | class MumbleInit { 10 | public: 11 | MumbleInit(); 12 | ~MumbleInit(); 13 | 14 | explicit operator bool() const; 15 | 16 | private: 17 | bool m_ok; 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /tests/TestPacketDataStream/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Mumble Developers. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "TestPacketDataStream.hpp" 7 | 8 | int main() { 9 | TestPacketDataStream::integer(); 10 | TestPacketDataStream::floating(); 11 | TestPacketDataStream::string(); 12 | TestPacketDataStream::space(); 13 | TestPacketDataStream::undersize(); 14 | 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /src/Base64.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_BASE64_HPP 7 | #define MUMBLE_SRC_BASE64_HPP 8 | 9 | #include "mumble/Base64.hpp" 10 | 11 | #include 12 | 13 | namespace mumble { 14 | class Base64::P { 15 | friend Base64; 16 | 17 | public: 18 | P(); 19 | ~P(); 20 | 21 | private: 22 | EVP_ENCODE_CTX *m_ctx; 23 | }; 24 | } // namespace mumble 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /include/mumble/NonCopyable.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_NONCOPYABLE_HPP 7 | #define MUMBLE_NONCOPYABLE_HPP 8 | 9 | namespace mumble { 10 | class NonCopyable { 11 | public: 12 | NonCopyable() = default; 13 | ~NonCopyable() = default; 14 | 15 | private: 16 | NonCopyable(const NonCopyable &) = delete; 17 | NonCopyable &operator=(const NonCopyable &) = delete; 18 | }; 19 | } // namespace mumble 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/UDP.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_UDP_HPP 7 | #define MUMBLE_SRC_UDP_HPP 8 | 9 | #include "mumble/Types.hpp" 10 | 11 | #include "Socket.hpp" 12 | 13 | namespace mumble { 14 | class SocketUDP : public Socket { 15 | public: 16 | SocketUDP(); 17 | 18 | Code read(Endpoint &endpoint, BufView &buf); 19 | Code write(const Endpoint &endpoint, const BufViewConst buf); 20 | }; 21 | } // namespace mumble 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /tests/TestPacketDataStream/TestPacketDataStream.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Mumble Developers. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_TESTPACKETDATASTREAM_TESTPACKETDATASTREAM_HPP 7 | #define MUMBLE_TESTPACKETDATASTREAM_TESTPACKETDATASTREAM_HPP 8 | 9 | class TestPacketDataStream { 10 | public: 11 | static void integer(); 12 | static void floating(); 13 | static void string(); 14 | static void space(); 15 | static void undersize(); 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /include/mumble/Lib.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_LIB_HPP 7 | #define MUMBLE_LIB_HPP 8 | 9 | #include "Macros.hpp" 10 | #include "Types.hpp" 11 | 12 | #include 13 | 14 | namespace mumble { 15 | namespace lib { 16 | MUMBLE_EXPORT Version version(); 17 | 18 | MUMBLE_EXPORT Code init(); 19 | MUMBLE_EXPORT Code deinit(); 20 | MUMBLE_EXPORT size_t initCount(); 21 | } // namespace lib 22 | } // namespace mumble 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/proto/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | find_package(Protobuf REQUIRED) 7 | 8 | add_library(Proto OBJECT) 9 | 10 | target_disable_warnings(Proto) 11 | 12 | target_include_directories(Proto 13 | INTERFACE 14 | ${CMAKE_CURRENT_BINARY_DIR} 15 | ) 16 | 17 | set_target_properties(Proto PROPERTIES UNITY_BUILD OFF) 18 | 19 | target_link_libraries(Proto 20 | PRIVATE 21 | protobuf::libprotobuf 22 | ) 23 | 24 | protobuf_generate( 25 | TARGET Proto 26 | PROTOS 27 | "MumbleTCP.proto" 28 | "MumbleUDP.proto" 29 | ) 30 | -------------------------------------------------------------------------------- /src/Hash.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_HASH_HPP 7 | #define MUMBLE_SRC_HASH_HPP 8 | 9 | #include "mumble/Hash.hpp" 10 | 11 | #include 12 | 13 | #include 14 | 15 | namespace mumble { 16 | class Hash::P { 17 | friend Hash; 18 | 19 | public: 20 | P(); 21 | ~P(); 22 | 23 | explicit operator bool(); 24 | 25 | std::string_view type(); 26 | bool setType(const std::string_view name = {}); 27 | 28 | private: 29 | EVP_MD_CTX *m_ctx; 30 | }; 31 | } // namespace mumble 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/Key.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_KEY_HPP 7 | #define MUMBLE_SRC_KEY_HPP 8 | 9 | #include "mumble/Key.hpp" 10 | 11 | #include 12 | 13 | #include 14 | 15 | namespace mumble { 16 | class Key::P { 17 | friend Key; 18 | 19 | public: 20 | P(EVP_PKEY *pkey); 21 | P(const std::string_view pem, const bool isPrivate, std::string_view password = {}); 22 | ~P(); 23 | 24 | private: 25 | static int passwordCallback(char *buf, const int size, int, void *userdata); 26 | 27 | EVP_PKEY *m_pkey; 28 | }; 29 | } // namespace mumble 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/TCP.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_TCP_HPP 7 | #define MUMBLE_SRC_TCP_HPP 8 | 9 | #include "Socket.hpp" 10 | 11 | #include "mumble/Types.hpp" 12 | 13 | #include 14 | #include 15 | 16 | namespace mumble { 17 | class SocketTCP : public Socket { 18 | public: 19 | SocketTCP(); 20 | SocketTCP(const int32_t handle); 21 | 22 | int listen(); 23 | 24 | std::pair< int, int32_t > accept(Endpoint &endpoint); 25 | int connect(const Endpoint &endpoint); 26 | 27 | int getPeerEndpoint(Endpoint &endpoint) const; 28 | }; 29 | } // namespace mumble 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /tests/ThreadManager.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_TEST_THREADMANAGER_HPP 7 | #define MUMBLE_TEST_THREADMANAGER_HPP 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace boost { 14 | class thread; 15 | } 16 | 17 | class ThreadManager { 18 | public: 19 | using ThreadFunc = std::function< void() >; 20 | 21 | ThreadManager(); 22 | ~ThreadManager(); 23 | 24 | void add(const ThreadFunc &func); 25 | 26 | void requestStop(); 27 | void wait(); 28 | 29 | static uint32_t physicalNum(); 30 | 31 | private: 32 | std::vector< boost::thread > m_threads; 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /include/mumble/Base64.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_BASE64_HPP 7 | #define MUMBLE_BASE64_HPP 8 | 9 | #include "Macros.hpp" 10 | #include "NonCopyable.hpp" 11 | #include "Types.hpp" 12 | 13 | #include 14 | 15 | namespace mumble { 16 | class MUMBLE_EXPORT Base64 : NonCopyable { 17 | public: 18 | class P; 19 | 20 | Base64(); 21 | virtual ~Base64(); 22 | 23 | virtual explicit operator bool(); 24 | 25 | virtual size_t decode(const BufView out, const BufViewConst in); 26 | static size_t encode(const BufView out, const BufViewConst in); 27 | 28 | private: 29 | std::unique_ptr< P > m_p; 30 | }; 31 | } // namespace mumble 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /examples/ExampleServer/Endpoints.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_EXAMPLESERVER_ENDPOINTS_HPP 7 | #define MUMBLE_EXAMPLESERVER_ENDPOINTS_HPP 8 | 9 | #include "mumble/Types.hpp" 10 | 11 | #include 12 | 13 | using Endpoint = mumble::Endpoint; 14 | using Endpoints = std::unordered_set< Endpoint >; 15 | 16 | namespace std { 17 | template<> struct hash< Endpoint > { 18 | size_t operator()(const Endpoint &endpoint) const { 19 | size_t hash = endpoint.port; 20 | 21 | for (const auto byte : endpoint.ip.v6()) { 22 | hash += byte; 23 | } 24 | 25 | return std::hash< size_t >()(hash); 26 | } 27 | }; 28 | } // namespace std 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /cmake/FindOpus.cmake: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | # We use pkg-config to search for Opus 7 | find_package(PkgConfig REQUIRED) 8 | 9 | pkg_check_modules(Opus opus QUIET) 10 | 11 | if (Opus_FOUND) 12 | # Define an actual CMake target that can be used conveniently 13 | add_library(opus_interface INTERFACE) 14 | target_include_directories(opus_interface INTERFACE ${Opus_INCLUDE_DIRS}) 15 | target_link_libraries(opus_interface INTERFACE ${Opus_LINK_LIBRARIES}) 16 | 17 | add_library(Opus::opus ALIAS opus_interface) 18 | endif() 19 | 20 | include(FindPackageHandleStandardArgs) 21 | find_package_handle_standard_args(Opus 22 | REQUIRED_VARS Opus_LINK_LIBRARIES Opus_INCLUDE_DIRS 23 | VERSION_VAR Opus_VERSION 24 | ) 25 | -------------------------------------------------------------------------------- /.github/workflows/pr-checks.yml: -------------------------------------------------------------------------------- 1 | name: PR-Checks 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | pr-checks: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Install Dependencies 11 | run: sudo apt install libboost-thread-dev libopus-dev libprotobuf-dev 12 | shell: bash 13 | 14 | - uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 1 17 | 18 | - name: Check line endings 19 | uses: erclu/check-crlf@v1 20 | 21 | - name: Generate compile-command DB 22 | run: mkdir build; cd build; cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..; cd ..; ln -s build/compile_commands.json . 23 | shell: bash 24 | 25 | - name: Check code formatting 26 | uses: jidicula/clang-format-action@v4.10.2 27 | with: 28 | clang-format-version: '10' 29 | check-path: '.' 30 | exclude-regex: '(build/.*|_dependencies/.*)' 31 | include-regex: '.*\.(cpp|hpp|c|h)$' 32 | -------------------------------------------------------------------------------- /examples/MumbleInit.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "MumbleInit.hpp" 7 | 8 | #include "mumble/IP.hpp" 9 | #include "mumble/Lib.hpp" 10 | #include "mumble/Types.hpp" 11 | 12 | #include 13 | #include 14 | 15 | using namespace mumble; 16 | 17 | MumbleInit::MumbleInit() { 18 | const auto code = lib::init(); 19 | m_ok = code == Code::Success; 20 | if (!m_ok) { 21 | printf("MumbleInit() failed with error \"%s\"!\n", text(code).data()); 22 | } 23 | } 24 | 25 | MumbleInit::~MumbleInit() { 26 | const auto code = lib::deinit(); 27 | if (code != Code::Success) { 28 | printf("~MumbleInit() failed with error \"%s\"!\n", text(code).data()); 29 | } 30 | } 31 | 32 | MumbleInit::operator bool() const { 33 | return m_ok; 34 | }; 35 | -------------------------------------------------------------------------------- /tests/ThreadManager.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "ThreadManager.hpp" 7 | 8 | #include 9 | 10 | ThreadManager::ThreadManager() = default; 11 | 12 | ThreadManager::~ThreadManager() { 13 | requestStop(); 14 | } 15 | 16 | void ThreadManager::add(const ThreadFunc &func) { 17 | m_threads.emplace_back(func); 18 | } 19 | 20 | void ThreadManager::requestStop() { 21 | for (auto &thread : m_threads) { 22 | thread.interrupt(); 23 | } 24 | } 25 | 26 | void ThreadManager::wait() { 27 | for (auto &thread : m_threads) { 28 | if (thread.joinable()) { 29 | thread.join(); 30 | } 31 | } 32 | } 33 | 34 | uint32_t ThreadManager::physicalNum() { 35 | const auto num = boost::thread::hardware_concurrency(); 36 | return num ? num : 4; 37 | } 38 | -------------------------------------------------------------------------------- /examples/ExampleServer/config.toml: -------------------------------------------------------------------------------- 1 | maxUsers = 4000000 2 | 3 | [nodes] 4 | [nodes.1] 5 | bandwidth = 1000000 6 | [nodes.1.identity] 7 | cert = "cert.pem" 8 | key = "key.pem" 9 | [nodes.1.tcp] 10 | ip = "::" 11 | ipv6Only = false 12 | port = 64738 13 | [nodes.1.udp] 14 | ip = "::" 15 | ipv6Only = false 16 | port = 64738 17 | [nodes.2] 18 | bandwidth = 750000 19 | [nodes.2.identity] 20 | cert = "cert.pem" 21 | key = "key.pem" 22 | [nodes.2.tcp] 23 | ip = "::" 24 | ipv6Only = true 25 | port = 64739 26 | [nodes.2.udp] 27 | ip = "::" 28 | ipv6Only = true 29 | port = 64739 30 | [nodes.3] 31 | bandwidth = 500000 32 | [nodes.3.identity] 33 | cert = "cert.pem" 34 | key = "key.pem" 35 | [nodes.3.tcp] 36 | ip = "0.0.0.0" 37 | ipv6Only = false 38 | port = 64740 39 | [nodes.3.udp] 40 | ip = "0.0.0.0" 41 | ipv6Only = false 42 | port = 64740 43 | -------------------------------------------------------------------------------- /src/Cert.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_CERT_HPP 7 | #define MUMBLE_SRC_CERT_HPP 8 | 9 | #include "mumble/Cert.hpp" 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | namespace mumble { 17 | class Cert::P { 18 | friend Cert; 19 | 20 | public: 21 | P(X509 *x509); 22 | P(const DerViewConst der); 23 | P(const std::string_view pem, std::string_view password = {}); 24 | ~P(); 25 | 26 | private: 27 | static std::string parseASN1String(const ASN1_STRING *string); 28 | static TimePoint parseASN1Time(const ASN1_TIME *time); 29 | static Attributes parseX509Name(const X509_NAME *name); 30 | 31 | static int passwordCallback(char *buf, const int size, int, void *userdata); 32 | 33 | X509 *m_x509; 34 | }; 35 | } // namespace mumble 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/Crypt.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_CRYPT_HPP 7 | #define MUMBLE_SRC_CRYPT_HPP 8 | 9 | #include "mumble/Crypt.hpp" 10 | 11 | #include "mumble/Types.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | namespace mumble { 20 | class Crypt::P { 21 | friend Crypt; 22 | 23 | public: 24 | P(); 25 | ~P(); 26 | 27 | explicit operator bool(); 28 | 29 | uint32_t blockSize() const; 30 | 31 | std::string_view cipher(); 32 | bool setCipher(const std::string_view name = {}); 33 | 34 | size_t process(const bool encrypt, const BufView out, const BufViewConst in, const BufView tag, 35 | const BufViewConst aad); 36 | 37 | private: 38 | Buf m_key; 39 | Buf m_nonce; 40 | bool m_padding; 41 | EVP_CIPHER_CTX *m_ctx; 42 | }; 43 | } // namespace mumble 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /include/mumble/Hash.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_HASH_HPP 7 | #define MUMBLE_HASH_HPP 8 | 9 | #include "Macros.hpp" 10 | #include "NonCopyable.hpp" 11 | #include "Types.hpp" 12 | 13 | #include 14 | 15 | namespace mumble { 16 | class MUMBLE_EXPORT Hash : NonCopyable { 17 | public: 18 | class P; 19 | 20 | Hash(Hash &&crypt); 21 | Hash(); 22 | virtual ~Hash(); 23 | 24 | virtual explicit operator bool() const; 25 | 26 | virtual Hash &operator=(Hash &&crypt); 27 | 28 | virtual size_t operator()(const BufView out, const BufViewConst in); 29 | 30 | virtual void *handle() const; 31 | 32 | virtual std::string_view type() const; 33 | virtual bool setType(const std::string_view name); 34 | 35 | virtual uint32_t blockSize() const; 36 | 37 | virtual bool reset(); 38 | 39 | private: 40 | std::unique_ptr< P > m_p; 41 | }; 42 | } // namespace mumble 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /include/mumble/Key.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_KEY_HPP 7 | #define MUMBLE_KEY_HPP 8 | 9 | #include "Macros.hpp" 10 | 11 | #include 12 | #include 13 | 14 | namespace mumble { 15 | class MUMBLE_EXPORT Key { 16 | public: 17 | class P; 18 | 19 | Key(); 20 | Key(const Key &key); 21 | Key(Key &&key); 22 | Key(void *handle); 23 | Key(const std::string_view pem, const bool isPrivate, std::string_view password = {}); 24 | virtual ~Key(); 25 | 26 | virtual explicit operator bool() const; 27 | 28 | virtual Key &operator=(const Key &key); 29 | virtual Key &operator=(Key &&key); 30 | 31 | virtual bool operator==(const Key &key) const; 32 | 33 | virtual void *handle() const; 34 | 35 | virtual bool isPrivate() const; 36 | 37 | virtual std::string pem() const; 38 | 39 | private: 40 | std::unique_ptr< P > m_p; 41 | }; 42 | } // namespace mumble 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /include/mumble/CryptOCB2.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_CRYPTOCB2_HPP 7 | #define MUMBLE_CRYPTOCB2_HPP 8 | 9 | #include "Macros.hpp" 10 | #include "NonCopyable.hpp" 11 | #include "Types.hpp" 12 | 13 | #include 14 | 15 | namespace mumble { 16 | class MUMBLE_EXPORT CryptOCB2 : NonCopyable { 17 | public: 18 | class P; 19 | 20 | CryptOCB2(); 21 | virtual ~CryptOCB2(); 22 | 23 | virtual explicit operator bool() const; 24 | 25 | virtual uint32_t blockSize() const; 26 | virtual uint32_t keySize() const; 27 | virtual uint32_t nonceSize() const; 28 | 29 | virtual BufViewConst key() const; 30 | virtual Buf genKey() const; 31 | virtual bool setKey(const BufViewConst key); 32 | 33 | virtual BufViewConst nonce() const; 34 | virtual Buf genNonce() const; 35 | virtual bool setNonce(const BufViewConst nonce); 36 | 37 | virtual size_t decrypt(BufView out, BufViewConst in, const BufViewConst tag = {}); 38 | virtual size_t encrypt(BufView out, BufViewConst in, const BufView tag = {}); 39 | 40 | private: 41 | std::unique_ptr< P > m_p; 42 | }; 43 | } // namespace mumble 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /cmake/compiler_utilities.cmake: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | include(CompilerFlags) 7 | 8 | function(target_disable_warnings TARGET) 9 | get_compiler_flags( 10 | DISABLE_ALL_WARNINGS 11 | DISABLE_DEFAULT_FLAGS 12 | OUTPUT_VARIABLE REQUESTED_FLAGS 13 | ) 14 | 15 | target_compile_options(${TARGET} 16 | PRIVATE 17 | ${REQUESTED_FLAGS} 18 | ) 19 | endfunction() 20 | 21 | function(target_setup_default_flags TARGET) 22 | set(DESIRED ENABLE_MOST_WARNINGS) 23 | 24 | if (LIBMUMBLE_WARNINGS_AS_ERRORS) 25 | list(APPEND DESIRED ENABLE_WARNINGS_AS_ERRORS) 26 | endif() 27 | if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") 28 | list(APPEND DESIRED OPTIMIZE_FOR_SPEED) 29 | else() 30 | list(APPEND DESIRED OPTIMIZE_FOR_DEBUG) 31 | endif() 32 | 33 | get_compiler_flags( 34 | ${DESIRED} 35 | OUTPUT_VARIABLE REQUESTED_FLAGS 36 | ) 37 | 38 | target_compile_options(${TARGET} 39 | PRIVATE 40 | ${REQUESTED_FLAGS} 41 | ) 42 | 43 | if (MSVC) 44 | target_compile_options(${TARGET} 45 | PRIVATE 46 | # "PImpl fix" for MSVC 47 | /wd4251 48 | ) 49 | endif() 50 | endfunction() 51 | 52 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 18, 6 | "patch": 4 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "ninja", 11 | "displayName": "Ninja", 12 | "binaryDir": "${sourceDir}/builds/${presetName}", 13 | "generator": "Ninja" 14 | }, 15 | { 16 | "name": "ninja-vcpkg", 17 | "displayName": "Ninja with vcpkg", 18 | "binaryDir": "${sourceDir}/builds/${presetName}", 19 | "generator": "Ninja", 20 | "cacheVariables": { 21 | "CMAKE_TOOLCHAIN_FILE": { 22 | "type": "FILEPATH", 23 | "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" 24 | }, 25 | "LIBMUMBLE_BUNDLED_GSL": { 26 | "type": "BOOL", 27 | "value": "OFF" 28 | } 29 | } 30 | } 31 | ], 32 | "buildPresets": [ 33 | { 34 | "name": "ninja", 35 | "configurePreset": "ninja", 36 | "displayName": "Ninja" 37 | }, 38 | { 39 | "name": "ninja-vcpkg", 40 | "configurePreset": "ninja-vcpkg", 41 | "displayName": "Ninja with vcpkg" 42 | } 43 | ], 44 | "testPresets": [ 45 | { 46 | "name": "ninja", 47 | "configurePreset": "ninja" 48 | }, 49 | { 50 | "name": "ninja-vcpkg", 51 | "configurePreset": "ninja-vcpkg" 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | list(APPEND TESTS 7 | "TestBase64" 8 | "TestCrypt" 9 | "TestHash" 10 | "TestOpus" 11 | "TestPacketDataStream" 12 | ) 13 | 14 | add_library(libmumble_test_base OBJECT 15 | "ThreadManager.cpp" 16 | ) 17 | 18 | target_include_directories(libmumble_test_base 19 | PUBLIC 20 | ${CMAKE_CURRENT_SOURCE_DIR} 21 | ) 22 | 23 | find_package(Boost REQUIRED 24 | COMPONENTS 25 | thread 26 | ) 27 | 28 | target_link_libraries(libmumble_test_base 29 | PUBLIC 30 | Mumble::libmumble 31 | Boost::thread 32 | ) 33 | 34 | # All tests follow the convention of naming their subdirectory the same as their target. 35 | # Therefore we can use the TARGET variable to reference the directory as well as the target. 36 | foreach(TARGET IN LISTS TESTS) 37 | add_subdirectory(${TARGET}) 38 | add_test( 39 | NAME ${TARGET} 40 | COMMAND ${TARGET} 41 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 42 | ) 43 | 44 | target_setup_default_flags(${TARGET}) 45 | 46 | set_target_properties(${TARGET} 47 | PROPERTIES 48 | RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/test" 49 | ) 50 | 51 | target_link_libraries(${TARGET} 52 | PRIVATE 53 | libmumble_test_base 54 | ) 55 | endforeach() 56 | -------------------------------------------------------------------------------- /examples/ExampleServer/UserManager.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_EXAMPLESERVER_USERMANAGER_HPP 7 | #define MUMBLE_EXAMPLESERVER_USERMANAGER_HPP 8 | 9 | #include "Endpoints.hpp" 10 | 11 | #include "mumble/Types.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | class User; 20 | 21 | class UserManager { 22 | public: 23 | UserManager(const uint32_t max); 24 | ~UserManager(); 25 | 26 | using BufView = mumble::BufView; 27 | using BufViewConst = mumble::BufViewConst; 28 | using UserPtr = std::shared_ptr< User >; 29 | 30 | UserPtr operator[](const uint32_t id); 31 | UserPtr operator[](const Endpoint &endpoint); 32 | 33 | bool full(); 34 | 35 | uint32_t max(); 36 | uint32_t num(); 37 | 38 | std::optional< uint32_t > reserveID(); 39 | 40 | void add(const UserPtr &user); 41 | void del(const uint32_t id); 42 | 43 | UserPtr tryDecrypt(const BufView out, const BufViewConst in, const Endpoint &endpoint); 44 | 45 | private: 46 | void thread(); 47 | 48 | uint32_t m_minID, m_maxID; 49 | std::shared_mutex m_mutex; 50 | std::unordered_map< uint32_t, UserPtr > m_users; 51 | std::unordered_map< Endpoint, UserPtr > m_endpoints; 52 | }; 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | include(FetchContent) 7 | 8 | FetchContent_Declare( 9 | toml11 10 | GIT_REPOSITORY https://github.com/ToruNiina/toml11 11 | GIT_TAG v3.7.1 12 | GIT_SHALLOW ON 13 | ) 14 | FetchContent_MakeAvailable(toml11) 15 | 16 | 17 | list(APPEND EXAMPLES 18 | "ExampleClient" 19 | "ExampleServer" 20 | ) 21 | 22 | add_library(libmumble_example_base OBJECT) 23 | 24 | target_include_directories(libmumble_example_base 25 | PUBLIC 26 | ${CMAKE_CURRENT_SOURCE_DIR} 27 | ) 28 | 29 | target_sources(libmumble_example_base 30 | PRIVATE 31 | "MumbleInit.cpp" 32 | ) 33 | 34 | target_link_libraries(libmumble_example_base 35 | PUBLIC 36 | Mumble::libmumble 37 | toml11::toml11 38 | ) 39 | 40 | # All examples follow the convention of naming their subdirectory the same as their target. 41 | # Therefore we can use the TARGET variable to reference the directory as well as the target. 42 | foreach(TARGET IN LISTS EXAMPLES) 43 | add_subdirectory(${TARGET}) 44 | 45 | target_setup_default_flags(${TARGET}) 46 | 47 | set_target_properties(${TARGET} 48 | PROPERTIES 49 | RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/example" 50 | ) 51 | 52 | target_link_libraries(${TARGET} 53 | PRIVATE 54 | libmumble_example_base 55 | ) 56 | endforeach() 57 | -------------------------------------------------------------------------------- /src/TLS.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_TLS_HPP 7 | #define MUMBLE_SRC_TLS_HPP 8 | 9 | #include "TCP.hpp" 10 | 11 | #include "mumble/Cert.hpp" 12 | #include "mumble/Key.hpp" 13 | #include "mumble/Types.hpp" 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | namespace mumble { 21 | class SocketTLS : public SocketTCP { 22 | public: 23 | enum Code : int8_t { Memory = -3, Failure, Unknown, Success, Retry, Shutdown, WaitIn, WaitOut }; 24 | 25 | SocketTLS(SocketTLS &&socket); 26 | SocketTLS(const int32_t handle, const bool server); 27 | ~SocketTLS(); 28 | 29 | explicit operator bool() const; 30 | 31 | bool isServer() const; 32 | 33 | bool setCert(const Cert::Chain &cert, const Key &key); 34 | 35 | Cert::Chain peerCert() const; 36 | 37 | uint32_t pending() const; 38 | 39 | Code accept(); 40 | Code connect(); 41 | Code disconnect(); 42 | 43 | Code read(BufView &buf); 44 | Code write(BufViewConst &buf); 45 | 46 | private: 47 | Code interpretLibCode(const int code, const bool processed = true, const bool remaining = false); 48 | static int verifyCallback(int, X509_STORE_CTX *); 49 | 50 | SSL *m_ssl; 51 | SSL_CTX *m_sslCtx; 52 | std::atomic_bool m_closed; 53 | }; 54 | } // namespace mumble 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /examples/ExampleServer/Node.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_EXAMPLESERVER_NODE_HPP 7 | #define MUMBLE_EXAMPLESERVER_NODE_HPP 8 | 9 | #include "mumble/Cert.hpp" 10 | #include "mumble/Key.hpp" 11 | #include "mumble/Message.hpp" 12 | #include "mumble/Peer.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace mumble { 20 | class Session; 21 | } 22 | 23 | class UserManager; 24 | 25 | class Node { 26 | public: 27 | Node(const std::shared_ptr< UserManager > &userManager, const std::string_view tcpIP, const uint32_t tcpPort, 28 | const std::string_view udpIP, const uint32_t udpPort, const uint32_t bandwidth); 29 | ~Node(); 30 | 31 | explicit operator bool() const; 32 | 33 | bool start(); 34 | 35 | bool setCert(const std::string_view certPath, const std::string_view keyPath); 36 | 37 | private: 38 | using SessionPtr = std::unique_ptr< mumble::Session >; 39 | 40 | bool startTCP(); 41 | bool startUDP(); 42 | 43 | bool fillPing(mumble::udp::Message::Ping &ping); 44 | 45 | bool m_ok; 46 | uint32_t m_bandwidth; 47 | 48 | std::shared_ptr< UserManager > m_userManager; 49 | 50 | std::vector< mumble::Cert > m_certChain; 51 | mumble::Key m_certKey; 52 | 53 | mumble::Peer m_server; 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/Lib.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "mumble/Lib.hpp" 7 | 8 | #include "mumble/Types.hpp" 9 | 10 | #include 11 | 12 | #ifdef OS_WINDOWS 13 | # include 14 | #endif 15 | 16 | using namespace mumble; 17 | 18 | static std::atomic_size_t g_initCount; 19 | 20 | Version lib::version() { 21 | return { 1, 5, 0 }; 22 | } 23 | 24 | Code lib::init() { 25 | #ifdef OS_WINDOWS 26 | if (g_initCount == 0) { 27 | WSADATA data; 28 | switch (WSAStartup(MAKEWORD(2, 2), &data)) { 29 | case 0: 30 | break; 31 | case WSAVERNOTSUPPORTED: 32 | return Code::Unsupport; 33 | case WSAEINPROGRESS: 34 | case WSAEPROCLIM: 35 | case WSASYSNOTREADY: 36 | return Code::Busy; 37 | default: 38 | return Code::Failure; 39 | } 40 | 41 | if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2) { 42 | WSACleanup(); 43 | return Code::Unsupport; 44 | } 45 | } 46 | #endif 47 | ++g_initCount; 48 | 49 | return Code::Success; 50 | } 51 | 52 | Code lib::deinit() { 53 | if (g_initCount == 0) { 54 | return Code::Init; 55 | } 56 | #ifdef OS_WINDOWS 57 | if (--g_initCount == 0) { 58 | WSACleanup(); 59 | } 60 | #else 61 | --g_initCount; 62 | #endif 63 | return Code::Success; 64 | } 65 | 66 | size_t lib::initCount() { 67 | return g_initCount; 68 | } 69 | -------------------------------------------------------------------------------- /src/Opus.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_OPUS_HPP 7 | #define MUMBLE_SRC_OPUS_HPP 8 | 9 | #include "mumble/Opus.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | struct OpusDecoder; 16 | struct OpusEncoder; 17 | 18 | namespace mumble { 19 | template< typename T > class OpusBase { 20 | public: 21 | OpusBase(const uint8_t channels); 22 | 23 | explicit operator bool(); 24 | 25 | template< typename R > bool get(const int32_t request, R *value); 26 | template< typename R > bool set(const int32_t request, const R value); 27 | 28 | protected: 29 | struct Destructor { 30 | void operator()(T *ctx) { 31 | auto bytes = reinterpret_cast< std::byte * >(ctx); 32 | delete[] bytes; 33 | } 34 | }; 35 | 36 | bool m_inited; 37 | uint8_t m_channels; 38 | std::unique_ptr< T, Destructor > m_ctx; 39 | }; 40 | 41 | class Opus::Decoder::P : public OpusBase<::OpusDecoder > { 42 | friend Opus::Decoder; 43 | 44 | public: 45 | P(const uint8_t channels); 46 | ~P() = default; 47 | }; 48 | 49 | class Opus::Encoder::P : public OpusBase<::OpusEncoder > { 50 | friend Opus::Encoder; 51 | 52 | public: 53 | P(const uint8_t channels); 54 | ~P() = default; 55 | 56 | static int32_t toApplication(const Preset preset); 57 | static Preset toPreset(const int32_t application); 58 | }; 59 | } // namespace mumble 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | skip_test: 7 | runs-on: ubuntu-latest 8 | outputs: 9 | should_skip: ${{ steps.skip_check.outputs.should_skip }} 10 | steps: 11 | - id: skip_check 12 | uses: fkirc/skip-duplicate-actions@v5 13 | with: 14 | concurrent_skipping: same_content_newer 15 | skip_after_successful_duplicate: 'true' 16 | 17 | build: 18 | needs: skip_test 19 | 20 | if: needs.skip_test.outputs.should_skip != 'true' 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | include: 26 | - os: ubuntu-latest 27 | triplet: x64-linux 28 | - os: windows-latest 29 | triplet: x64-windows-static-md 30 | - os: windows-latest 31 | triplet: x86-windows-static-md 32 | - os: macos-latest 33 | triplet: x64-osx 34 | 35 | name: ${{matrix.triplet}} 36 | runs-on: ${{matrix.os}} 37 | 38 | env: 39 | VCPKG_DEFAULT_TRIPLET: ${{matrix.triplet}} 40 | 41 | steps: 42 | - uses: actions/checkout@v3 43 | with: 44 | fetch-depth: 1 45 | 46 | - uses: lukka/get-cmake@latest 47 | 48 | - uses: lukka/run-vcpkg@v11 49 | 50 | - uses: lukka/run-cmake@v10.3 51 | with: 52 | configurePreset: 'ninja-vcpkg' 53 | buildPreset: 'ninja-vcpkg' 54 | testPreset: 'ninja-vcpkg' 55 | configurePresetAdditionalArgs: "[ '-DLIBMUMBLE_WARNINGS_AS_ERRORS=ON', '-DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }}' ]" 56 | -------------------------------------------------------------------------------- /include/mumble/IP.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_IP_HPP 7 | #define MUMBLE_IP_HPP 8 | 9 | #include "Macros.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | struct sockaddr_in6; 19 | 20 | namespace mumble { 21 | class MUMBLE_EXPORT IP { 22 | public: 23 | class P; 24 | 25 | static constexpr uint8_t v6Size = 16; 26 | static constexpr uint8_t v4Size = 4; 27 | 28 | using V6 = std::array< uint8_t, v6Size >; 29 | using V4 = std::array< uint8_t, v4Size >; 30 | using View = gsl::span< uint8_t >; 31 | using ViewConst = gsl::span< const uint8_t >; 32 | 33 | IP(); 34 | IP(const IP &ip); 35 | IP(const ViewConst view); 36 | IP(const std::string_view string); 37 | IP(const sockaddr_in6 &sockaddr); 38 | virtual ~IP(); 39 | 40 | virtual IP &operator=(const IP &ip); 41 | virtual bool operator==(const IP &ip) const; 42 | 43 | virtual ViewConst v6() const; 44 | virtual ViewConst v4() const; 45 | 46 | virtual View v6(); 47 | virtual View v4(); 48 | 49 | virtual bool isV6() const; 50 | virtual bool isV4() const; 51 | 52 | virtual bool isWildcard() const; 53 | 54 | virtual std::string text() const; 55 | 56 | virtual void toSockAddr(sockaddr_in6 &sockaddr) const; 57 | 58 | private: 59 | std::array< uint8_t, v6Size > m_bytes; 60 | }; 61 | } // namespace mumble 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /src/Connection.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_CONNECTION_HPP 7 | #define MUMBLE_SRC_CONNECTION_HPP 8 | 9 | #include "mumble/Connection.hpp" 10 | 11 | #include "mumble/Cert.hpp" 12 | #include "mumble/Types.hpp" 13 | 14 | #include "Monitor.hpp" 15 | #include "TLS.hpp" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace mumble { 23 | class Connection::P : public SocketTLS { 24 | friend Connection; 25 | 26 | public: 27 | using State = Monitor::Event::State; 28 | 29 | P(SocketTLS &&socket); 30 | ~P(); 31 | 32 | explicit operator bool() const; 33 | 34 | mumble::Code handleState(const State state); 35 | 36 | private: 37 | [[nodiscard]] std::lock_guard< std::recursive_mutex > lock() { 38 | return std::lock_guard< std::recursive_mutex >(m_mutex); 39 | } 40 | 41 | mumble::Code read(BufView buf, const bool wait, const std::function< bool() > halt); 42 | mumble::Code write(BufViewConst buf, const bool wait, const std::function< bool() > halt); 43 | 44 | mumble::Code handleCode(const Code code, const bool wait); 45 | mumble::Code handleWait(Monitor &monitor); 46 | 47 | static constexpr mumble::Code interpretTLSCode(const Code code); 48 | 49 | Feedback m_feedback; 50 | 51 | Monitor m_monitorIn; 52 | Monitor m_monitorOut; 53 | 54 | Cert::Chain m_cert; 55 | uint32_t m_timeouts; 56 | std::atomic_flag m_closed; 57 | std::recursive_mutex m_mutex; 58 | }; 59 | } // namespace mumble 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (C) 2022 The Mumble Developers 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | 3. Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | For questions and inquiries about libmumble's license, 33 | please contact . 34 | -------------------------------------------------------------------------------- /include/mumble/Crypt.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_CRYPT_HPP 7 | #define MUMBLE_CRYPT_HPP 8 | 9 | #include "Macros.hpp" 10 | #include "NonCopyable.hpp" 11 | #include "Types.hpp" 12 | 13 | #include 14 | 15 | namespace mumble { 16 | class MUMBLE_EXPORT Crypt : NonCopyable { 17 | public: 18 | class P; 19 | 20 | Crypt(Crypt &&crypt); 21 | Crypt(); 22 | virtual ~Crypt(); 23 | 24 | virtual explicit operator bool() const; 25 | 26 | virtual Crypt &operator=(Crypt &&crypt); 27 | 28 | virtual void *handle() const; 29 | 30 | virtual std::string_view cipher() const; 31 | virtual bool setCipher(const std::string_view name); 32 | 33 | virtual uint32_t blockSize() const; 34 | virtual uint32_t keySize() const; 35 | virtual uint32_t nonceSize() const; 36 | 37 | virtual BufViewConst key() const; 38 | virtual Buf genKey() const; 39 | virtual bool setKey(const BufViewConst key); 40 | 41 | virtual BufViewConst nonce() const; 42 | virtual Buf genNonce() const; 43 | virtual bool setNonce(const BufViewConst nonce); 44 | 45 | virtual bool usesPadding() const; 46 | virtual bool togglePadding(const bool enable); 47 | 48 | virtual bool reset(); 49 | 50 | virtual size_t decrypt(const BufView out, const BufViewConst in, const BufViewConst tag = {}, 51 | const BufViewConst aad = {}); 52 | virtual size_t encrypt(const BufView out, const BufViewConst in, const BufView tag = {}, 53 | const BufViewConst aad = {}); 54 | 55 | private: 56 | std::unique_ptr< P > m_p; 57 | }; 58 | } // namespace mumble 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | cmake_minimum_required(VERSION 3.16) 7 | 8 | set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 9 | 10 | project(libmumble 11 | VERSION "0.1.0" 12 | DESCRIPTION "The official Mumble library." 13 | HOMEPAGE_URL "https://www.mumble.info" 14 | LANGUAGES "C" "CXX" 15 | ) 16 | 17 | option(LIBMUMBLE_BUILD_EXAMPLES "Build example client and server" OFF) 18 | option(LIBMUMBLE_BUILD_TESTS "Build tests" ON) 19 | option(LIBMUMBLE_BUNDLED_GSL "Use the bundled GSL version instead of looking for one on the system" ON) 20 | option(LIBMUMBLE_STATIC "Build the library as static instead of shared" OFF) 21 | option(LIBMUMBLE_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF) 22 | 23 | 24 | include(setup_dependencies) 25 | include(compiler_utilities) 26 | 27 | 28 | set(CMAKE_CXX_EXTENSIONS OFF) 29 | set(CMAKE_CXX_STANDARD 17) 30 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 31 | 32 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 33 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 34 | set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) 35 | 36 | 37 | if(WIN32) 38 | add_definitions( 39 | "-DOS_WINDOWS" 40 | "-DNOMINMAX" 41 | "-DWIN32_LEAN_AND_MEAN" 42 | "-D_CRT_SECURE_NO_WARNINGS" 43 | ) 44 | else() 45 | add_definitions( 46 | "-DOS_UNIX" 47 | ) 48 | endif() 49 | 50 | if(MSVC) 51 | add_compile_options( 52 | # https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus 53 | "/Zc:__cplusplus" 54 | ) 55 | endif() 56 | 57 | 58 | add_subdirectory(src) 59 | 60 | 61 | if (LIBMUMBLE_BUILD_EXAMPLES) 62 | add_subdirectory(examples) 63 | endif() 64 | 65 | if (LIBMUMBLE_BUILD_TESTS) 66 | include(CTest) 67 | add_subdirectory(tests) 68 | endif() 69 | -------------------------------------------------------------------------------- /include/mumble/Cert.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_CERT_HPP 7 | #define MUMBLE_CERT_HPP 8 | 9 | #include "Macros.hpp" 10 | #include "Types.hpp" 11 | 12 | #include "mumble/Key.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace mumble { 19 | class MUMBLE_EXPORT Cert { 20 | public: 21 | class P; 22 | 23 | using Attributes = std::map< std::string_view, std::string >; 24 | using Chain = std::vector< Cert >; 25 | using Der = std::vector< std::byte >; 26 | using DerView = gsl::span< std::byte >; 27 | using DerViewConst = gsl::span< const std::byte >; 28 | using TimePoint = std::chrono::system_clock::time_point; 29 | 30 | Cert(); 31 | Cert(const Cert &cert); 32 | Cert(Cert &&cert); 33 | Cert(void *handle); 34 | Cert(const DerViewConst der); 35 | Cert(const std::string_view pem, std::string_view password = {}); 36 | virtual ~Cert(); 37 | 38 | virtual explicit operator bool() const; 39 | 40 | virtual Cert &operator=(const Cert &cert); 41 | virtual Cert &operator=(Cert &&cert); 42 | 43 | virtual bool operator==(const Cert &cert) const; 44 | 45 | virtual void *handle() const; 46 | 47 | virtual Der der() const; 48 | virtual std::string pem() const; 49 | 50 | virtual Key publicKey() const; 51 | 52 | virtual TimePoint since() const; 53 | virtual TimePoint until() const; 54 | 55 | virtual bool isAuthority() const; 56 | virtual bool isIssuer(const Cert &cert) const; 57 | virtual bool isSelfIssued() const; 58 | 59 | virtual Attributes subjectAttributes() const; 60 | virtual Attributes issuerAttributes() const; 61 | 62 | private: 63 | std::unique_ptr< P > m_p; 64 | }; 65 | } // namespace mumble 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/Peer.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_PEER_HPP 7 | #define MUMBLE_SRC_PEER_HPP 8 | 9 | #include "mumble/Peer.hpp" 10 | 11 | #include "mumble/Types.hpp" 12 | 13 | #include "Monitor.hpp" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace boost { 22 | class thread; 23 | } 24 | 25 | namespace mumble { 26 | class SocketTCP; 27 | class SocketUDP; 28 | 29 | class Peer::P { 30 | friend Peer; 31 | 32 | public: 33 | P() = default; 34 | ~P() = default; 35 | 36 | private: 37 | template< typename Feedback, typename Socket > struct Proto { 38 | Proto() : m_halt(false) {} 39 | 40 | Code stop(); 41 | 42 | Code bind(Endpoint &endpoint, const bool ipv6Only); 43 | Code unbind(); 44 | 45 | Feedback m_feedback; 46 | 47 | std::atomic_bool m_halt; 48 | Monitor m_monitor; 49 | std::unique_ptr< Socket > m_socket; 50 | std::unique_ptr< boost::thread > m_thread; 51 | }; 52 | 53 | struct TCP : Proto< FeedbackTCP, SocketTCP > { 54 | Code start(const FeedbackTCP &feedback, const uint32_t threads); 55 | 56 | Code bind(Endpoint &endpoint, const bool ipv6Only); 57 | 58 | void threadFunc(const uint32_t threads); 59 | 60 | std::shared_mutex m_mutex; 61 | std::unordered_map< int32_t, SharedConnection > m_connections; 62 | }; 63 | 64 | struct UDP : Proto< FeedbackUDP, SocketUDP > { 65 | Code start(const FeedbackUDP &feedback, const uint32_t bufferSize); 66 | 67 | void threadFunc(const uint32_t bufferSize); 68 | }; 69 | 70 | P(const P &) = delete; 71 | P &operator=(const P &) = delete; 72 | 73 | TCP m_tcp; 74 | UDP m_udp; 75 | }; 76 | } // namespace mumble 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /src/CryptOCB2.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_CRYPTOCB2_HPP 7 | #define MUMBLE_SRC_CRYPTOCB2_HPP 8 | 9 | #include "mumble/CryptOCB2.hpp" 10 | 11 | #include "mumble/Types.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | 21 | namespace mumble { 22 | class CryptOCB2::P { 23 | friend CryptOCB2; 24 | 25 | public: 26 | #if defined(__LP64__) 27 | using SubBlock = uint64_t; 28 | 29 | static constexpr uint8_t subBlocks = 2; 30 | static constexpr uint8_t shiftBits = 63; 31 | #else 32 | using SubBlock = uint32_t; 33 | 34 | static constexpr uint8_t subBlocks = 4; 35 | static constexpr uint8_t shiftBits = 31; 36 | #endif 37 | using KeyBlock = std::array< SubBlock, subBlocks >; 38 | using KeyBlockView = gsl::span< SubBlock, subBlocks >; 39 | using KeyBlockViewConst = gsl::span< const SubBlock, subBlocks >; 40 | 41 | static constexpr uint8_t blockSize = 128 / 8; 42 | static constexpr uint8_t keySize = 128 / 8; 43 | static constexpr uint8_t nonceSize = 128 / 8; 44 | 45 | P(); 46 | ~P(); 47 | 48 | private: 49 | size_t process(const bool encrypt, const BufView out, const BufViewConst in); 50 | 51 | static void xorBlock(const KeyBlockView dst, const KeyBlockViewConst a, const KeyBlockViewConst b); 52 | 53 | static void s2(const KeyBlockView block); 54 | static void s3(const KeyBlockView block); 55 | 56 | static KeyBlockView toBlockView(const BufView buf); 57 | static KeyBlockViewConst toBlockView(const BufViewConst buf); 58 | 59 | bool m_ok; 60 | FixedBuf< keySize > m_key; 61 | FixedBuf< nonceSize > m_nonce; 62 | EVP_CIPHER_CTX *m_ctx; 63 | }; 64 | } // namespace mumble 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /include/mumble/Connection.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_CONNECTION_HPP 7 | #define MUMBLE_CONNECTION_HPP 8 | 9 | #include "Cert.hpp" 10 | #include "Key.hpp" 11 | #include "Macros.hpp" 12 | #include "NonCopyable.hpp" 13 | 14 | #include 15 | 16 | namespace mumble { 17 | namespace tcp { 18 | class Pack; 19 | } 20 | 21 | class MUMBLE_EXPORT Connection : NonCopyable { 22 | public: 23 | class P; 24 | using UniqueP = std::unique_ptr< P >; 25 | 26 | struct Feedback { 27 | std::function< void() > opened; 28 | std::function< void() > closed; 29 | 30 | std::function< void(Code code) > failed; 31 | 32 | std::function< uint32_t() > timeout; 33 | std::function< uint32_t() > timeouts; 34 | 35 | std::function< void(tcp::Pack &pack) > pack; 36 | }; 37 | 38 | Connection(Connection &&connection); 39 | Connection(const int32_t socketHandle, const bool server); 40 | virtual ~Connection(); 41 | 42 | virtual explicit operator bool() const; 43 | 44 | virtual Code operator()( 45 | const Feedback &feedback, const std::function< bool() > halt = []() { return false; }); 46 | 47 | virtual const UniqueP &p() const; 48 | virtual int32_t socketHandle() const; 49 | 50 | virtual Endpoint endpoint() const; 51 | virtual Endpoint peerEndpoint() const; 52 | 53 | virtual const Cert::Chain &cert() const; 54 | virtual Cert::Chain peerCert() const; 55 | 56 | virtual bool setCert(const Cert::Chain &cert, const Key &key); 57 | 58 | virtual Code process( 59 | const bool wait = true, const std::function< bool() > halt = []() { return false; }); 60 | virtual Code write( 61 | const BufViewConst data, const bool wait = true, const std::function< bool() > halt = []() { return false; }); 62 | 63 | private: 64 | UniqueP m_p; 65 | }; 66 | } // namespace mumble 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /cmake/setup_dependencies.cmake: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | include(FetchContent) 7 | 8 | set(LIBMUMBLE_DEPENDENCY_DIR "${PROJECT_SOURCE_DIR}/_dependencies" CACHE STRING "Directory into which dependencies shall be downloaded into") 9 | 10 | set(FETCHCONTENT_BASE_DIR "${LIBMUMBLE_DEPENDENCY_DIR}") 11 | 12 | FetchContent_Declare( 13 | GSL 14 | GIT_REPOSITORY https://github.com/microsoft/GSL 15 | GIT_TAG v4.0.0 16 | GIT_SHALLOW ON 17 | ) 18 | FetchContent_Declare( 19 | quickpool 20 | GIT_REPOSITORY https://github.com/tnagler/quickpool.git 21 | # The latest release currently still has issues that make it unusable for us 22 | GIT_TAG ddc415bec1fc624e1c6b21c1b47063ca2eef84de 23 | GIT_SHALLOW ON 24 | ) 25 | FetchContent_Declare( 26 | wepoll 27 | GIT_REPOSITORY https://github.com/piscisaureus/wepoll.git 28 | GIT_TAG v1.5.8 29 | GIT_SHALLOW ON 30 | PATCH_COMMAND "${CMAKE_COMMAND}" -E copy "${PROJECT_SOURCE_DIR}/cmake/wepoll_cmakelists.txt" "./CMakeLists.txt" 31 | ) 32 | FetchContent_Declare( 33 | cmake_compiler_flags 34 | GIT_REPOSITORY https://github.com/Krzmbrzl/cmake-compiler-flags.git 35 | GIT_TAG v2.0.0 36 | GIT_SHALLOW ON 37 | ) 38 | 39 | # Set some options for the dependencies 40 | set(QUICKPOOL_TEST ${LIBMUMBLE_BUILD_TESTS} CACHE INTERNAL "") 41 | 42 | 43 | message(STATUS ">>> Configuring dependencies (potentially includes downloading)") 44 | 45 | FetchContent_MakeAvailable(quickpool cmake_compiler_flags) 46 | 47 | if (LIBMUMBLE_BUNDLED_GSL) 48 | FetchContent_MakeAvailable(GSL) 49 | endif() 50 | 51 | if (WIN32) 52 | FetchContent_MakeAvailable(wepoll) 53 | endif() 54 | 55 | # Append the compiler flags CMake module to the module path 56 | FetchContent_GetProperties(cmake_compiler_flags SOURCE_DIR COMPILER_FLAGS_SRC_DIR) 57 | list(APPEND CMAKE_MODULE_PATH "${COMPILER_FLAGS_SRC_DIR}") 58 | 59 | message(STATUS "<<< Dependency configuration finished") 60 | -------------------------------------------------------------------------------- /src/TCP.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "TCP.hpp" 7 | 8 | #include "mumble/Endian.hpp" 9 | #include "mumble/IP.hpp" 10 | 11 | #include 12 | 13 | #ifdef OS_WINDOWS 14 | # include 15 | #else 16 | # include 17 | # include 18 | #endif 19 | 20 | using namespace mumble; 21 | 22 | SocketTCP::SocketTCP() : Socket(Type::TCP) { 23 | } 24 | 25 | SocketTCP::SocketTCP(const int32_t handle) : Socket(handle) { 26 | } 27 | 28 | int SocketTCP::listen() { 29 | if (::listen(m_handle, SOMAXCONN) != 0) { 30 | return osError(); 31 | } 32 | 33 | return 0; 34 | } 35 | 36 | std::pair< int, int32_t > SocketTCP::accept(Endpoint &endpoint) { 37 | sockaddr_in6 addr; 38 | #ifdef OS_WINDOWS 39 | int size = sizeof(addr); 40 | #else 41 | socklen_t size = sizeof(addr); 42 | #endif 43 | // Explicit return data type because socket handles are unsigned on Windows. 44 | const auto handle = static_cast< int32_t >(::accept(m_handle, reinterpret_cast< sockaddr * >(&addr), &size)); 45 | if (handle == invalidHandle) { 46 | return { osError(), invalidHandle }; 47 | } 48 | 49 | endpoint.ip = IP(addr); 50 | endpoint.port = Endian::toHost(addr.sin6_port); 51 | 52 | return { 0, handle }; 53 | } 54 | 55 | int SocketTCP::connect(const Endpoint &endpoint) { 56 | sockaddr_in6 addr = {}; 57 | endpoint.ip.toSockAddr(addr); 58 | addr.sin6_port = Endian::toNetwork(endpoint.port); 59 | 60 | if (::connect(m_handle, reinterpret_cast< sockaddr * >(&addr), sizeof(addr)) != 0) { 61 | return osError(); 62 | } 63 | 64 | return 0; 65 | } 66 | 67 | int SocketTCP::getPeerEndpoint(Endpoint &endpoint) const { 68 | sockaddr_in6 addr; 69 | #ifdef OS_WINDOWS 70 | int size = sizeof(addr); 71 | #else 72 | socklen_t size = sizeof(addr); 73 | #endif 74 | if (getpeername(m_handle, reinterpret_cast< sockaddr * >(&addr), &size) != 0) { 75 | return osError(); 76 | } 77 | 78 | endpoint.ip = IP(addr); 79 | endpoint.port = Endian::toHost(addr.sin6_port); 80 | 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /src/UDP.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "UDP.hpp" 7 | 8 | #include "mumble/Endian.hpp" 9 | #include "mumble/IP.hpp" 10 | 11 | #include 12 | #include 13 | 14 | #ifdef OS_WINDOWS 15 | # include 16 | 17 | # define CAST_BUF(var) reinterpret_cast< char * >(var) 18 | # define CAST_BUF_CONST(var) reinterpret_cast< const char * >(var) 19 | # define CAST_SIZE(var) static_cast< int >(var) 20 | #else 21 | # include 22 | # include 23 | #endif 24 | 25 | #define CAST_SOCKADDR(var) reinterpret_cast< sockaddr * >(var) 26 | #define CAST_SOCKADDR_CONST(var) reinterpret_cast< const sockaddr * >(var) 27 | 28 | using namespace mumble; 29 | 30 | SocketUDP::SocketUDP() : Socket(Type::UDP) { 31 | } 32 | 33 | Code SocketUDP::read(Endpoint &endpoint, BufView &buf) { 34 | sockaddr_in6 addr; 35 | #ifdef OS_WINDOWS 36 | auto addrsize = static_cast< int >(sizeof(addr)); 37 | const auto ret = 38 | recvfrom(m_handle, CAST_BUF(buf.data()), CAST_SIZE(buf.size()), 0, CAST_SOCKADDR(&addr), &addrsize); 39 | #else 40 | socklen_t addrsize = sizeof(addr); 41 | const auto ret = recvfrom(m_handle, buf.data(), buf.size(), 0, CAST_SOCKADDR(&addr), &addrsize); 42 | #endif 43 | if (ret <= 0) { 44 | return osErrorToCode(osError()); 45 | } 46 | 47 | endpoint.ip = IP(addr); 48 | endpoint.port = Endian::toHost(addr.sin6_port); 49 | 50 | assert(ret >= 0); 51 | buf = buf.first(static_cast< std::size_t >(ret)); 52 | 53 | return Code::Success; 54 | } 55 | 56 | Code SocketUDP::write(const Endpoint &endpoint, const BufViewConst buf) { 57 | sockaddr_in6 addr = {}; 58 | endpoint.ip.toSockAddr(addr); 59 | addr.sin6_port = Endian::toNetwork(endpoint.port); 60 | #ifdef OS_WINDOWS 61 | const auto ret = sendto(m_handle, CAST_BUF_CONST(buf.data()), CAST_SIZE(buf.size()), 0, CAST_SOCKADDR_CONST(&addr), 62 | sizeof(addr)); 63 | #else 64 | const auto ret = sendto(m_handle, buf.data(), buf.size(), 0, CAST_SOCKADDR_CONST(&addr), sizeof(addr)); 65 | #endif 66 | if (ret <= 0) { 67 | return osErrorToCode(osError()); 68 | } 69 | 70 | return Code::Success; 71 | } 72 | -------------------------------------------------------------------------------- /include/mumble/Endian.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_ENDIAN_HPP 7 | #define MUMBLE_ENDIAN_HPP 8 | 9 | #include "Macros.hpp" 10 | 11 | #include 12 | 13 | #ifdef MUMBLE_COMPILER_MSVC 14 | # include 15 | #endif 16 | 17 | namespace mumble { 18 | class Endian { 19 | public: 20 | static constexpr bool isBig() { return SingleSplit64(0x0102030405060708).split[0] == 1; } 21 | static constexpr bool isLittle() { return SingleSplit64(0x0102030405060708).split[0] == 8; } 22 | #ifdef MUMBLE_COMPILER_MSVC 23 | static uint16_t swap(const uint16_t value) { 24 | return _byteswap_ushort(value); 25 | #else 26 | static constexpr uint16_t swap(const uint16_t value) { 27 | return __builtin_bswap16(value); 28 | #endif 29 | } 30 | #ifdef MUMBLE_COMPILER_MSVC 31 | static uint32_t swap(const uint32_t value) { 32 | return _byteswap_ulong(value); 33 | #else 34 | static constexpr uint32_t swap(const uint32_t value) { 35 | return __builtin_bswap32(value); 36 | #endif 37 | } 38 | #ifdef MUMBLE_COMPILER_MSVC 39 | static uint64_t swap(const uint64_t value) { 40 | return _byteswap_uint64(value); 41 | #else 42 | static constexpr uint64_t swap(const uint64_t value) { 43 | return __builtin_bswap64(value); 44 | #endif 45 | } 46 | 47 | static constexpr uint16_t toNetwork(const uint16_t value) { return isBig() ? value : swap(value); } 48 | static constexpr uint32_t toNetwork(const uint32_t value) { return isBig() ? value : swap(value); } 49 | static constexpr uint64_t toNetwork(const uint64_t value) { return isBig() ? value : swap(value); } 50 | 51 | static constexpr uint16_t toHost(const uint16_t value) { return isBig() ? value : swap(value); } 52 | static constexpr uint32_t toHost(const uint32_t value) { return isBig() ? value : swap(value); } 53 | static constexpr uint64_t toHost(const uint64_t value) { return isBig() ? value : swap(value); } 54 | 55 | private: 56 | union SingleSplit64 { 57 | uint64_t single; 58 | uint8_t split[sizeof(single)]; 59 | 60 | constexpr SingleSplit64(const uint64_t value) : single(value) {} 61 | }; 62 | }; 63 | } // namespace mumble 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/Monitor.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_MONITOR_HPP 7 | #define MUMBLE_SRC_MONITOR_HPP 8 | 9 | #include "mumble/Macros.hpp" 10 | 11 | #include "Socket.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #if defined(HAVE_EPOLL) || defined(HAVE_WEPOLL) 21 | struct epoll_event; 22 | #else 23 | struct pollfd; 24 | #endif 25 | 26 | namespace mumble { 27 | class Monitor { 28 | public: 29 | struct Event { 30 | enum State : uint8_t { 31 | None = 0b00000000, 32 | Triggered = 0b00000001, 33 | InReady = 0b00000010, 34 | OutReady = 0b00000100, 35 | Disconnected = 0b00001000, 36 | Error = 0b00010000 37 | }; 38 | 39 | int32_t fd; 40 | State state; 41 | 42 | Event() : fd(Socket::invalidHandle), state(None) {} 43 | Event(const int32_t fd) : fd(fd), state(None) {} 44 | }; 45 | 46 | static constexpr auto timeoutMax = std::numeric_limits< uint32_t >::max(); 47 | 48 | using EventsView = gsl::span< Event >; 49 | 50 | Monitor(); 51 | ~Monitor(); 52 | 53 | explicit operator bool() const; 54 | 55 | uint32_t num() const; 56 | 57 | bool add(const int32_t fd, const bool in, const bool out); 58 | bool del(const int32_t fd); 59 | 60 | bool trigger(); 61 | bool untrigger(); 62 | 63 | uint32_t wait(const EventsView events, const uint32_t timeout); 64 | 65 | private: 66 | Monitor(const Monitor &) = delete; 67 | Monitor &operator=(const Monitor &) = delete; 68 | #if defined(HAVE_EPOLL) || defined(HAVE_WEPOLL) 69 | using Target = epoll_event; 70 | uint32_t waitEpoll(const EventsView events, const uint32_t timeout); 71 | # ifdef HAVE_EPOLL 72 | int m_handle; 73 | # else 74 | HANDLE m_handle; 75 | # endif 76 | #else 77 | using Target = pollfd; 78 | uint32_t waitPoll(const EventsView events, const uint32_t timeout); 79 | #endif 80 | Socket::Pair m_trigger; 81 | std::vector< Target > m_targets; 82 | std::unordered_set< int32_t > m_fds; 83 | }; 84 | } // namespace mumble 85 | 86 | MUMBLE_ENUM_OPERATORS(mumble::Monitor::Event::State) 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /include/mumble/Peer.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_PEER_HPP 7 | #define MUMBLE_PEER_HPP 8 | 9 | #include "Macros.hpp" 10 | #include "Message.hpp" 11 | #include "NonCopyable.hpp" 12 | #include "Types.hpp" 13 | 14 | #include 15 | #include 16 | 17 | namespace mumble { 18 | class Connection; 19 | 20 | class MUMBLE_EXPORT Peer : NonCopyable { 21 | public: 22 | class P; 23 | 24 | using SharedConnection = std::shared_ptr< Connection >; 25 | 26 | struct Feedback { 27 | std::function< void() > started; 28 | std::function< void() > stopped; 29 | 30 | std::function< void(Code code) > failed; 31 | 32 | std::function< uint32_t() > timeout; 33 | }; 34 | 35 | struct FeedbackTCP : Feedback { 36 | std::function< bool(Endpoint &endpoint, int32_t socketHandle) > connection; 37 | }; 38 | 39 | struct FeedbackUDP : Feedback { 40 | std::function< void(Endpoint &endpoint, BufView buf) > encrypted; 41 | std::function< void(Endpoint &endpoint, udp::Message::Ping &ping) > ping; 42 | std::function< void(Endpoint &endpoint, legacy::udp::Ping &ping) > legacyPing; 43 | }; 44 | 45 | Peer(); 46 | Peer(Peer &&peer); 47 | virtual ~Peer(); 48 | 49 | virtual Peer &operator=(Peer &&peer); 50 | 51 | virtual explicit operator bool() const; 52 | 53 | static std::pair< Code, int32_t > connect(const Endpoint &peerEndpoint, const Endpoint &endpoint = {}); 54 | 55 | virtual Code startTCP(const FeedbackTCP &feedback, const uint32_t threads = 0); 56 | virtual Code stopTCP(); 57 | 58 | virtual Code startUDP(const FeedbackUDP &feedback, const uint32_t bufferSize = 0); 59 | virtual Code stopUDP(); 60 | 61 | virtual Code bindTCP(Endpoint &endpoint, const bool ipv6Only = false); 62 | virtual Code unbindTCP(); 63 | 64 | virtual Code bindUDP(Endpoint &endpoint, const bool ipv6Only = false); 65 | virtual Code unbindUDP(); 66 | 67 | virtual Code addTCP(const SharedConnection &connection); 68 | virtual Code delTCP(const SharedConnection &connection); 69 | 70 | virtual Code sendUDP(const Endpoint &endpoint, const BufViewConst data); 71 | 72 | private: 73 | std::unique_ptr< P > m_p; 74 | }; 75 | } // namespace mumble 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /examples/ExampleServer/main.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "MumbleInit.hpp" 7 | #include "Node.hpp" 8 | #include "UserManager.hpp" 9 | 10 | #include "mumble/Types.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | using namespace mumble; 27 | 28 | int32_t main(const int argc, const char **argv) { 29 | if (argc > 2) { 30 | printf("Usage: `example_server `\n"); 31 | return 1; 32 | } 33 | 34 | std::string confPath = "config.toml"; 35 | if (argc > 1) { 36 | confPath = argv[1]; 37 | } 38 | 39 | const auto conf = toml::parse(confPath); 40 | 41 | MumbleInit mumbleInit; 42 | if (!mumbleInit) { 43 | return 2; 44 | } 45 | 46 | auto userManager = std::make_shared< UserManager >(toml::find< uint32_t >(conf, "maxUsers")); 47 | 48 | std::vector< std::unique_ptr< Node > > nodes; 49 | 50 | for (const auto &nodeConf : toml::find(conf, "nodes").as_table()) { 51 | const auto bandwidth = toml::find< uint32_t >(nodeConf.second, "bandwidth"); 52 | 53 | const auto &identity = toml::find(nodeConf.second, "identity"); 54 | const auto cert = toml::find< std::string_view >(identity, "cert"); 55 | const auto key = toml::find< std::string_view >(identity, "key"); 56 | 57 | const auto &tcp = toml::find(nodeConf.second, "tcp"); 58 | const auto tcpIP = toml::find< std::string_view >(tcp, "ip"); 59 | const auto tcpPort = toml::find< uint32_t >(tcp, "port"); 60 | 61 | const auto &udp = toml::find(nodeConf.second, "udp"); 62 | const auto udpIP = toml::find< std::string_view >(udp, "ip"); 63 | const auto udpPort = toml::find< uint32_t >(udp, "port"); 64 | 65 | auto node = std::make_unique< Node >(userManager, tcpIP, tcpPort, udpIP, udpPort, bandwidth); 66 | if (!*node) { 67 | return 3; 68 | } 69 | 70 | if (!node->setCert(cert, key)) { 71 | return 4; 72 | } 73 | 74 | if (!node->start()) { 75 | return 5; 76 | } 77 | 78 | nodes.push_back(std::move(node)); 79 | } 80 | 81 | getchar(); 82 | 83 | printf("Shutting down...\n"); 84 | 85 | return 0; 86 | } 87 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of libmumble. 2 | # Use of this source code is governed by a BSD-style license 3 | # that can be found in the LICENSE file at the root of the 4 | # Mumble source tree or at . 5 | 6 | find_package(Boost REQUIRED 7 | COMPONENTS 8 | thread 9 | ) 10 | 11 | if (NOT TARGET Microsoft.GSL::GSL) 12 | find_package(Microsoft.GSL REQUIRED) 13 | endif() 14 | 15 | find_package(OpenSSL REQUIRED) 16 | find_package(Opus REQUIRED) 17 | 18 | add_subdirectory(proto) 19 | 20 | if(NOT LIBMUMBLE_STATIC) 21 | add_library(mumble_library SHARED) 22 | else() 23 | add_library(mumble_library STATIC) 24 | endif() 25 | 26 | add_library(Mumble::libmumble ALIAS mumble_library) 27 | 28 | target_setup_default_flags(mumble_library) 29 | 30 | set_target_properties(mumble_library 31 | PROPERTIES 32 | ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR} 33 | LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR} 34 | RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR} 35 | ) 36 | 37 | if(WIN32) 38 | # We use the bundled wepoll library 39 | set(HAVE_EPOLL TRUE) 40 | else() 41 | include(CheckSymbolExists) 42 | 43 | check_symbol_exists("epoll_create" "sys/epoll.h" HAVE_EPOLL) 44 | endif() 45 | 46 | target_compile_definitions(mumble_library 47 | PRIVATE 48 | "MUMBLE_SRC" 49 | 50 | $<$:$,HAVE_WEPOLL,HAVE_EPOLL>> 51 | ) 52 | 53 | target_include_directories(mumble_library 54 | PUBLIC 55 | "${PROJECT_SOURCE_DIR}/include" 56 | ) 57 | 58 | target_sources(mumble_library 59 | PRIVATE 60 | "Base64.cpp" 61 | "Base64.hpp" 62 | "Cert.cpp" 63 | "Cert.hpp" 64 | "Connection.cpp" 65 | "Connection.hpp" 66 | "Crypt.cpp" 67 | "Crypt.hpp" 68 | "CryptOCB2.cpp" 69 | "CryptOCB2.hpp" 70 | "Hash.cpp" 71 | "Hash.hpp" 72 | "IP.cpp" 73 | "Key.cpp" 74 | "Key.hpp" 75 | "Lib.cpp" 76 | "Monitor.cpp" 77 | "Monitor.hpp" 78 | "Opus.cpp" 79 | "Opus.hpp" 80 | "Pack.cpp" 81 | "Peer.cpp" 82 | "Peer.hpp" 83 | "Socket.cpp" 84 | "Socket.hpp" 85 | "TCP.cpp" 86 | "TCP.hpp" 87 | "TLS.cpp" 88 | "TLS.hpp" 89 | "UDP.cpp" 90 | "UDP.hpp" 91 | ) 92 | 93 | target_link_libraries(mumble_library 94 | PRIVATE 95 | Proto 96 | 97 | Boost::thread 98 | OpenSSL::Crypto 99 | OpenSSL::SSL 100 | quickpool 101 | 102 | Opus::opus 103 | 104 | $<$:wepoll::wepoll> 105 | PUBLIC 106 | Microsoft.GSL::GSL 107 | ) 108 | 109 | -------------------------------------------------------------------------------- /src/Base64.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "Base64.hpp" 7 | 8 | #include "mumble/Types.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #define CHECK \ 18 | if (!*this) { \ 19 | return {}; \ 20 | } 21 | 22 | #define CAST_BUF(var) (reinterpret_cast< unsigned char * >(var)) 23 | #define CAST_BUF_CONST(var) (reinterpret_cast< const unsigned char * >(var)) 24 | #define CAST_SIZE(var) (static_cast< int >(var)) 25 | 26 | using namespace mumble; 27 | 28 | using P = Base64::P; 29 | 30 | Base64::Base64() : m_p(new P) { 31 | } 32 | 33 | Base64::~Base64() = default; 34 | 35 | Base64::operator bool() { 36 | return m_p && m_p->m_ctx; 37 | } 38 | 39 | size_t Base64::decode(const BufView out, const BufViewConst in) { 40 | CHECK 41 | 42 | if (!out.size()) { 43 | // 4 input bytes = max. 3 output bytes. 44 | // 45 | // EVP_DecodeUpdate() ignores: 46 | // - Leading/trailing whitespace. 47 | // - Trailing newlines, carriage returns or EOF characters. 48 | // 49 | // EVP_DecodeFinal() fails if the input is not divisible by 4. 50 | return in.size() / 4 * 3; 51 | } 52 | 53 | // We don't use EVP_DecodeBlock() because it adds padding if the output is not divisible by 3. 54 | EVP_DecodeInit(m_p->m_ctx); 55 | 56 | int written_1; 57 | if (EVP_DecodeUpdate(m_p->m_ctx, CAST_BUF(out.data()), &written_1, CAST_BUF_CONST(in.data()), CAST_SIZE(in.size())) 58 | < 0) { 59 | return {}; 60 | } 61 | 62 | int written_2; 63 | if (EVP_DecodeFinal(m_p->m_ctx, CAST_BUF(out.data()), &written_2) <= 0) { 64 | return {}; 65 | } 66 | 67 | assert(written_1 >= 0); 68 | assert(written_2 >= 0); 69 | return static_cast< std::size_t >(written_1 + written_2); 70 | } 71 | 72 | size_t Base64::encode(const BufView out, const BufViewConst in) { 73 | if (!out.size()) { 74 | // 3 input bytes = 4 output bytes. 75 | // +1 for the NUL terminator. 76 | // 77 | // EVP_EncodeBlock() adds padding when the input is not divisible by 3. 78 | // Note: n + 2 / 3 == ceil(n / 3.0f) 79 | return static_cast< size_t >(4 * (in.size() + 2) / 3 + 1); 80 | } 81 | 82 | // EVP_EncodeBlock() returns the length of the string without the NUL terminator. 83 | const auto written = EVP_EncodeBlock(CAST_BUF(out.data()), CAST_BUF_CONST(in.data()), CAST_SIZE(in.size())); 84 | if (written < 0) { 85 | return {}; 86 | } 87 | 88 | return static_cast< std::size_t >(written + 1); 89 | } 90 | 91 | P::P() : m_ctx(EVP_ENCODE_CTX_new()) { 92 | } 93 | 94 | P::~P() { 95 | if (m_ctx) { 96 | EVP_ENCODE_CTX_free(m_ctx); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: true 6 | # Setting AlignConsecutiveDeclarations to true would be nice but it doesn't work right with PointerAlignment=Right 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: InlineOnly 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: false 19 | AlwaysBreakTemplateDeclarations: false 20 | BinPackArguments: true 21 | BinPackParameters: true 22 | BreakBeforeBinaryOperators: NonAssignment 23 | BreakBeforeBraces: Attach 24 | BreakBeforeTernaryOperators: true 25 | BreakConstructorInitializers: BeforeColon 26 | BreakStringLiterals: true 27 | ColumnLimit: 120 28 | CompactNamespaces: false 29 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 30 | ConstructorInitializerIndentWidth: 4 31 | ContinuationIndentWidth: 4 32 | Cpp11BracedListStyle: false 33 | DerivePointerAlignment: false 34 | DisableFormat: false 35 | FixNamespaceComments: true 36 | ForEachMacros: 37 | - foreach 38 | - Q_FOREACH 39 | - BOOST_FOREACH 40 | IncludeBlocks: Preserve 41 | IncludeCategories: 42 | # Global.h must always be included last 43 | - Regex: 'Global.h' 44 | Priority: 10000 45 | # Since a lot of windows-headers rely on windows.h, it has to be included first 46 | - Regex: 'windows.h' 47 | Priority: 1 48 | # Assign a priority < INT_MAX to all other includes in order to be able to force headers 49 | # to come after them 50 | - Regex: '.*' 51 | Priority: 10 52 | IndentCaseLabels: true 53 | IndentPPDirectives: AfterHash 54 | IndentWidth: 4 55 | IndentWrappedFunctionNames: true 56 | KeepEmptyLinesAtTheStartOfBlocks: false 57 | MacroBlockBegin: '' 58 | MacroBlockEnd: '' 59 | MaxEmptyLinesToKeep: 3 60 | NamespaceIndentation: Inner 61 | PenaltyBreakAssignment: 2 62 | PenaltyBreakBeforeFirstCallParameter: 19 63 | PenaltyBreakComment: 300 64 | PenaltyBreakFirstLessLess: 120 65 | PenaltyBreakString: 1000 66 | PenaltyExcessCharacter: 1000000 67 | PenaltyReturnTypeOnItsOwnLine: 60 68 | PointerAlignment: Right 69 | ReflowComments: true 70 | SortIncludes: true 71 | SortUsingDeclarations: true 72 | SpaceAfterCStyleCast: true 73 | SpaceAfterTemplateKeyword: false 74 | SpaceBeforeAssignmentOperators: true 75 | SpaceBeforeParens: ControlStatements 76 | SpaceInEmptyParentheses: false 77 | SpacesBeforeTrailingComments: 1 78 | SpacesInAngles: true 79 | SpacesInContainerLiterals: true 80 | SpacesInParentheses: false 81 | SpacesInSquareBrackets: false 82 | Standard: Cpp11 83 | TabWidth: 4 84 | UseTab: ForContinuationAndIndentation 85 | ... 86 | 87 | -------------------------------------------------------------------------------- /tests/TestOpus/main.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "ThreadManager.hpp" 7 | 8 | #include "mumble/Opus.hpp" 9 | #include "mumble/Types.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | static constexpr size_t iterations = 1000; 20 | 21 | static constexpr uint32_t sampleRate = 48000; 22 | 23 | static constexpr std::array< uint16_t, 6 > bufferSamples = { 120, 240, 480, 960, 1920, 2880 }; 24 | 25 | using namespace mumble; 26 | 27 | template< typename T > static bool initOpus(T &opus) { 28 | const auto code = opus.init(sampleRate); 29 | if (code != Code::Success) { 30 | printf("Failed to init Opus with error \"%s\"!\n", text(code).data()); 31 | return false; 32 | } 33 | 34 | return true; 35 | } 36 | 37 | template< typename T > static constexpr T toView(const BufView buf, const uint32_t samples) { 38 | return { reinterpret_cast< typename T::value_type * >(buf.data()), samples }; 39 | } 40 | 41 | static uint8_t thread(const uint8_t channels) { 42 | using FView = Opus::FloatView; 43 | using IView = Opus::IntegerView; 44 | 45 | Opus::Decoder decoder(channels); 46 | if (!initOpus(decoder)) { 47 | return 1; 48 | } 49 | 50 | Opus::Encoder encoder(channels); 51 | if (!initOpus(encoder)) { 52 | return 2; 53 | } 54 | 55 | for (size_t i = 0; i < iterations; ++i) { 56 | if (boost::this_thread::interruption_requested()) { 57 | return 0; 58 | } 59 | 60 | Buf in(sizeof(float) * bufferSamples.back() * channels); 61 | Buf out(in.size()); 62 | 63 | for (const auto frames : bufferSamples) { 64 | const uint32_t samples = frames * channels; 65 | 66 | auto encoded = encoder(out, toView< FView >(in, samples)); 67 | if (!encoded.size()) { 68 | return 3; 69 | } 70 | 71 | if (!decoder(toView< FView >(out, samples), encoded).size()) { 72 | return 4; 73 | } 74 | 75 | encoded = encoder(out, toView< IView >(in, samples)); 76 | if (!encoded.size()) { 77 | return 5; 78 | } 79 | 80 | if (!decoder(toView< IView >(out, samples), encoded).size()) { 81 | return 6; 82 | } 83 | } 84 | } 85 | 86 | return 0; 87 | } 88 | 89 | int32_t main() { 90 | int32_t ret = 0; 91 | 92 | ThreadManager manager; 93 | 94 | for (uint8_t i = 0; i < 2; ++i) { 95 | const ThreadManager::ThreadFunc func = [&ret, &manager, i]() { 96 | const auto threadRet = thread(i + 1); 97 | if (threadRet != 0) { 98 | ret = threadRet; 99 | manager.requestStop(); 100 | } 101 | }; 102 | 103 | manager.add(func); 104 | } 105 | 106 | manager.wait(); 107 | 108 | return ret; 109 | } 110 | -------------------------------------------------------------------------------- /examples/ExampleServer/UserManager.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "UserManager.hpp" 7 | 8 | #include "Endpoints.hpp" 9 | #include "User.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace mumble; 16 | 17 | using UserPtr = UserManager::UserPtr; 18 | 19 | UserManager::UserManager(const uint32_t max) : m_minID(1), m_maxID(max) { 20 | } 21 | 22 | UserManager::~UserManager() = default; 23 | 24 | UserPtr UserManager::operator[](const uint32_t id) { 25 | std::shared_lock lock(m_mutex); 26 | 27 | const auto iter = m_users.find(id); 28 | if (iter != m_users.cend()) { 29 | return iter->second; 30 | } 31 | 32 | return {}; 33 | } 34 | 35 | UserPtr UserManager::operator[](const Endpoint &endpoint) { 36 | std::shared_lock lock(m_mutex); 37 | 38 | const auto iter = m_endpoints.find(endpoint); 39 | if (iter != m_endpoints.cend()) { 40 | return iter->second; 41 | } 42 | 43 | return {}; 44 | } 45 | 46 | bool UserManager::full() { 47 | std::shared_lock lock(m_mutex); 48 | 49 | return m_users.size() >= max(); 50 | } 51 | 52 | uint32_t UserManager::max() { 53 | return m_maxID - m_minID + 1; 54 | } 55 | 56 | uint32_t UserManager::num() { 57 | std::shared_lock lock(m_mutex); 58 | 59 | return m_users.size(); 60 | } 61 | 62 | std::optional< uint32_t > UserManager::reserveID() { 63 | std::unique_lock lock(m_mutex); 64 | 65 | if (m_users.size() >= max()) { 66 | return {}; 67 | } 68 | 69 | for (uint32_t id = m_minID; id < m_maxID; ++id) { 70 | if (m_users.find(id) == m_users.cend()) { 71 | m_users.emplace(id, nullptr); 72 | 73 | return id; 74 | } 75 | } 76 | 77 | return {}; 78 | } 79 | 80 | void UserManager::add(const UserPtr &user) { 81 | std::unique_lock lock(m_mutex); 82 | 83 | m_users[user->id()] = user; 84 | } 85 | 86 | void UserManager::del(const uint32_t id) { 87 | std::unique_lock lock(m_mutex); 88 | 89 | const auto user = m_users.extract(id).mapped(); 90 | if (!user) { 91 | return; 92 | } 93 | 94 | for (const auto &endpoint : user->endpoints()) { 95 | m_endpoints.erase(endpoint); 96 | } 97 | } 98 | 99 | UserPtr UserManager::tryDecrypt(const BufView out, const BufViewConst in, const Endpoint &endpoint) { 100 | UserPtr user; 101 | 102 | { 103 | std::shared_lock lock(m_mutex); 104 | 105 | for (auto &iter : m_users) { 106 | if (iter.second && iter.second->decrypt(out, in)) { 107 | user = iter.second; 108 | } 109 | } 110 | } 111 | 112 | if (!user) { 113 | return {}; 114 | } 115 | 116 | { 117 | std::unique_lock lock(m_mutex); 118 | 119 | user->addEndpoint(endpoint); 120 | 121 | m_endpoints.insert_or_assign(endpoint, user); 122 | } 123 | 124 | return user; 125 | } 126 | -------------------------------------------------------------------------------- /tests/TestHash/main.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "Data.hpp" 7 | #include "ThreadManager.hpp" 8 | 9 | #include "mumble/Hash.hpp" 10 | #include "mumble/Types.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #include 24 | 25 | static constexpr size_t iterations = 100000; 26 | 27 | using namespace mumble; 28 | 29 | static std::string toHex(const BufViewConst bytes) { 30 | std::ostringstream stream; 31 | 32 | stream << std::hex << std::setfill('0'); 33 | 34 | for (const auto byte : bytes) { 35 | stream << std::setw(2) << static_cast< int >(byte); 36 | } 37 | 38 | return stream.str(); 39 | } 40 | 41 | static uint8_t test(Hash &hash, const Data::List &list, const size_t index) { 42 | const auto &input = Data::input[index]; 43 | const BufViewConst in(reinterpret_cast< const std::byte * >(input.data()), input.size()); 44 | 45 | Buf digest(hash({}, in)); 46 | 47 | if (!hash(digest, in)) { 48 | return 10; 49 | } 50 | 51 | if (toHex(digest) != list[index]) { 52 | return 11; 53 | } 54 | 55 | return 0; 56 | } 57 | 58 | static uint8_t thread() { 59 | Hash sha2; 60 | if (!sha2.setType("SHA512")) { 61 | return 1; 62 | } 63 | 64 | Hash sha3; 65 | if (!sha3.setType("SHA3-512")) { 66 | return 2; 67 | } 68 | 69 | Hash blake2b; 70 | if (!blake2b.setType("BLAKE2b512")) { 71 | return 3; 72 | } 73 | 74 | std::random_device device; 75 | std::mt19937 algorithm(device()); 76 | std::uniform_int_distribution< size_t > gen(0, std::tuple_size< Data::List >() - 1); 77 | 78 | for (size_t i = 0; i < iterations; ++i) { 79 | if (boost::this_thread::interruption_requested()) { 80 | return 0; 81 | } 82 | 83 | const auto index = gen(algorithm); 84 | 85 | auto ret = test(sha2, Data::sha2, index); 86 | if (ret != 0) { 87 | return ret; 88 | } 89 | 90 | ret = test(sha3, Data::sha3, index); 91 | if (ret != 0) { 92 | return ret; 93 | } 94 | 95 | ret = test(blake2b, Data::blake2b, index); 96 | if (ret != 0) { 97 | return ret; 98 | } 99 | } 100 | 101 | return 0; 102 | } 103 | 104 | int32_t main() { 105 | int32_t ret = 0; 106 | 107 | ThreadManager manager; 108 | 109 | for (uint32_t i = 0; i < manager.physicalNum(); ++i) { 110 | const ThreadManager::ThreadFunc func = [&ret, &manager]() { 111 | const auto threadRet = thread(); 112 | if (threadRet != 0) { 113 | ret = threadRet; 114 | manager.requestStop(); 115 | } 116 | }; 117 | 118 | manager.add(func); 119 | } 120 | 121 | manager.wait(); 122 | 123 | return ret; 124 | } 125 | -------------------------------------------------------------------------------- /include/mumble/Pack.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_PACK_HPP 7 | #define MUMBLE_PACK_HPP 8 | 9 | #include "Endian.hpp" 10 | #include "Macros.hpp" 11 | #include "Types.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace google { 20 | namespace protobuf { 21 | class Message; 22 | } 23 | } // namespace google 24 | 25 | namespace mumble { 26 | template< typename NetHeader > class Pack { 27 | public: 28 | Pack(const Pack &pack) = default; 29 | Pack(Pack &&pack) = default; 30 | Pack(const size_t dataSize = 0) : m_buf(sizeof(NetHeader) + dataSize) {} 31 | virtual ~Pack() = default; 32 | 33 | virtual Pack &operator=(const Pack &pack) = default; 34 | virtual Pack &operator=(Pack &&pack) = default; 35 | 36 | virtual bool operator==(const Pack &pack) const { return pack.m_buf == m_buf; } 37 | 38 | virtual BufViewConst buf() const { return m_buf; } 39 | 40 | virtual BufView buf() { return m_buf; } 41 | 42 | virtual BufViewConst data() const { return { m_buf.data() + sizeof(NetHeader), m_buf.size() - sizeof(NetHeader) }; } 43 | 44 | virtual BufView data() { return { m_buf.data() + sizeof(NetHeader), m_buf.size() - sizeof(NetHeader) }; } 45 | 46 | virtual const NetHeader &header() const { return *reinterpret_cast< const NetHeader * >(m_buf.data()); } 47 | 48 | virtual NetHeader &header() { return *reinterpret_cast< NetHeader * >(m_buf.data()); } 49 | 50 | protected: 51 | Buf m_buf; 52 | }; 53 | 54 | namespace tcp { 55 | struct Message; 56 | 57 | MUMBLE_PACK(struct NetHeader { 58 | uint16_t type = Endian::toNetwork(std::numeric_limits< decltype(type) >::max()); 59 | uint32_t size = 0; 60 | }); 61 | 62 | class MUMBLE_EXPORT Pack : public mumble::Pack< NetHeader > { 63 | public: 64 | Pack(const Message &message, const uint32_t extraDataSize = 0); 65 | Pack(const NetHeader &header = {}, const uint32_t extraDataSize = 0); 66 | Pack(const google::protobuf::Message &proto, const uint32_t extraDataSize = 0); 67 | virtual ~Pack(); 68 | 69 | virtual bool operator()(Message &message, uint32_t dataSize = std::numeric_limits< uint32_t >::max()) const; 70 | }; 71 | } // namespace tcp 72 | 73 | namespace udp { 74 | struct Message; 75 | 76 | MUMBLE_PACK(struct NetHeader { uint8_t type = std::numeric_limits< decltype(type) >::max(); }); 77 | 78 | class MUMBLE_EXPORT Pack : public mumble::Pack< NetHeader > { 79 | public: 80 | Pack(const Message &message, const uint32_t extraDataSize = 0); 81 | Pack(const NetHeader &header = {}, const uint32_t dataSize = 0); 82 | Pack(const google::protobuf::Message &proto, const uint32_t extraDataSize = 0); 83 | virtual ~Pack(); 84 | 85 | virtual bool operator()(Message &message, uint32_t dataSize = std::numeric_limits< uint32_t >::max()) const; 86 | }; 87 | } // namespace udp 88 | } // namespace mumble 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /tests/TestBase64/main.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "Data.hpp" 7 | #include "ThreadManager.hpp" 8 | 9 | #include "mumble/Base64.hpp" 10 | #include "mumble/Types.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | static constexpr size_t iterations = 1000000; 23 | 24 | using namespace mumble; 25 | 26 | static std::string decode(Base64 &base64, const Data::Entry &entry) { 27 | const BufViewConst in(reinterpret_cast< const std::byte * >(entry.second.data()), entry.second.size()); 28 | 29 | std::string ret; 30 | ret.resize(base64.decode({}, in)); 31 | 32 | const BufView out(reinterpret_cast< std::byte * >(ret.data()), ret.size()); 33 | 34 | const auto written = base64.decode(out, in); 35 | if (!written) { 36 | return {}; 37 | } 38 | 39 | ret.resize(written); 40 | 41 | return ret; 42 | } 43 | 44 | static std::string encode(const Data::Entry &entry) { 45 | const BufViewConst in(reinterpret_cast< const std::byte * >(entry.first.data()), entry.first.size()); 46 | 47 | std::string ret; 48 | ret.resize(Base64::encode({}, in) - 1); 49 | 50 | const BufView out(reinterpret_cast< std::byte * >(ret.data()), ret.size()); 51 | 52 | const auto written = Base64::encode(out, in); 53 | if (!written) { 54 | return {}; 55 | } 56 | 57 | ret.resize(written - 1); 58 | 59 | return ret; 60 | } 61 | 62 | static uint8_t thread() { 63 | Base64 base64; 64 | 65 | std::random_device device; 66 | std::mt19937 algorithm(device()); 67 | std::uniform_int_distribution< size_t > gen(0, std::tuple_size< Data::Table >() - 1); 68 | 69 | for (size_t i = 0; i < iterations; ++i) { 70 | if (boost::this_thread::interruption_requested()) { 71 | return 0; 72 | } 73 | 74 | auto entry = Data::ascii[gen(algorithm)]; 75 | 76 | auto decoded = decode(base64, entry); 77 | if (decoded != entry.first) { 78 | return 1; 79 | } 80 | 81 | auto encoded = encode(entry); 82 | if (encoded != entry.second) { 83 | return 2; 84 | } 85 | 86 | entry = Data::unicode[gen(algorithm)]; 87 | 88 | decoded = decode(base64, entry); 89 | if (decoded != entry.first) { 90 | return 3; 91 | } 92 | 93 | encoded = encode(entry); 94 | if (encoded != entry.second) { 95 | return 4; 96 | } 97 | } 98 | 99 | return 0; 100 | } 101 | 102 | int32_t main() { 103 | int32_t ret = 0; 104 | 105 | ThreadManager manager; 106 | 107 | for (uint32_t i = 0; i < manager.physicalNum(); ++i) { 108 | const ThreadManager::ThreadFunc func = [&ret, &manager]() { 109 | const auto threadRet = thread(); 110 | if (threadRet != 0) { 111 | ret = threadRet; 112 | manager.requestStop(); 113 | } 114 | }; 115 | 116 | manager.add(func); 117 | } 118 | 119 | manager.wait(); 120 | 121 | return ret; 122 | } 123 | -------------------------------------------------------------------------------- /examples/ExampleServer/User.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_EXAMPLESERVER_USER_HPP 7 | #define MUMBLE_EXAMPLESERVER_USER_HPP 8 | 9 | #include "Endpoints.hpp" 10 | 11 | #include "mumble/Cert.hpp" 12 | #include "mumble/Connection.hpp" 13 | #include "mumble/CryptOCB2.hpp" 14 | #include "mumble/Types.hpp" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace mumble { 24 | class Key; 25 | 26 | namespace tcp { 27 | struct Message; 28 | class Pack; 29 | } // namespace tcp 30 | } // namespace mumble 31 | 32 | class User { 33 | public: 34 | using BufView = mumble::BufView; 35 | using BufViewConst = mumble::BufViewConst; 36 | using Cert = mumble::Cert; 37 | using Code = mumble::Code; 38 | using Connection = mumble::Connection; 39 | using CryptOCB2 = mumble::CryptOCB2; 40 | using Key = mumble::Key; 41 | using Message = mumble::tcp::Message; 42 | using Pack = mumble::tcp::Pack; 43 | 44 | struct Packet { 45 | mumble::Endpoint endpoint; 46 | mumble::Buf buf; 47 | 48 | Packet() noexcept = default; 49 | 50 | Packet(const Packet &packet) noexcept : endpoint(packet.endpoint), buf(packet.buf) {} 51 | 52 | Packet(const mumble::Endpoint &endpoint, const BufViewConst buf) noexcept 53 | : endpoint(endpoint), buf(buf.begin(), buf.end()) {} 54 | 55 | Packet &operator=(const Packet &&packet) noexcept { 56 | endpoint = packet.endpoint; 57 | buf = std::move(packet.buf); 58 | 59 | return *this; 60 | } 61 | }; 62 | 63 | User(const int32_t socketHandle, const uint32_t id); 64 | virtual ~User(); 65 | 66 | uint32_t id() const; 67 | 68 | const std::shared_ptr< Connection > &connection() const; 69 | 70 | bool cryptOK() const; 71 | 72 | uint32_t good() const; 73 | uint32_t late() const; 74 | uint32_t lost() const; 75 | 76 | BufViewConst key() const; 77 | 78 | BufViewConst decryptNonce() const; 79 | BufViewConst encryptNonce() const; 80 | 81 | size_t decrypt(const BufView out, const BufViewConst in); 82 | size_t encrypt(const BufView out, const BufViewConst in); 83 | 84 | const Endpoints &endpoints() const; 85 | void addEndpoint(const Endpoint &endpoint); 86 | void delEndpoint(const Endpoint &endpoint); 87 | 88 | Code connect(const Connection::Feedback &feedback, const Cert::Chain &cert, const Key &key); 89 | 90 | void send(const Message &message); 91 | void send(const Pack &pack); 92 | 93 | private: 94 | uint32_t m_id; 95 | bool m_cryptOK; 96 | uint32_t m_good; 97 | uint32_t m_late; 98 | uint32_t m_lost; 99 | Endpoints m_endpoints; 100 | CryptOCB2 m_decrypt; 101 | CryptOCB2 m_encrypt; 102 | std::vector< std::byte > m_decryptNonce; 103 | std::vector< std::byte > m_encryptNonce; 104 | std::array< uint8_t, UINT8_MAX + 1 > m_decryptHistory; 105 | std::shared_ptr< Connection > m_connection; 106 | }; 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /tests/TestCrypt/main.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "ThreadManager.hpp" 7 | 8 | #include "mumble/Crypt.hpp" 9 | #include "mumble/CryptOCB2.hpp" 10 | #include "mumble/Types.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include 21 | 22 | static constexpr size_t iterations = 100000; 23 | 24 | static constexpr size_t minBufSize = 1; 25 | static constexpr size_t maxBufSize = 1024; 26 | 27 | using namespace mumble; 28 | 29 | static size_t bufSize(std::mt19937 &algorithm) { 30 | std::uniform_int_distribution< size_t > gen(minBufSize, maxBufSize); 31 | return gen(algorithm); 32 | } 33 | 34 | template< typename T > static uint8_t test(T &crypt, const BufViewConst in) { 35 | if (!crypt.setKey(crypt.genKey())) { 36 | return 10; 37 | } 38 | 39 | if (!crypt.setNonce(crypt.genNonce())) { 40 | return 11; 41 | } 42 | 43 | Buf out(crypt.encrypt({}, in)); 44 | Buf tag(crypt.blockSize()); 45 | 46 | auto written = crypt.encrypt(out, in, tag); 47 | if (!written) { 48 | return 12; 49 | } 50 | 51 | written = crypt.decrypt(out, out, tag); 52 | if (!written) { 53 | return 13; 54 | } 55 | 56 | if (!std::equal(out.cbegin(), out.cend(), in.begin())) { 57 | return 14; 58 | } 59 | 60 | return 0; 61 | } 62 | 63 | static uint8_t thread() { 64 | Crypt cryptChaCha20; 65 | if (!cryptChaCha20.setCipher("ChaCha20-Poly1305")) { 66 | return 1; 67 | } 68 | 69 | Crypt cryptGCM; 70 | if (!cryptGCM.setCipher("AES-256-GCM")) { 71 | return 2; 72 | } 73 | 74 | CryptOCB2 cryptOCB2; 75 | 76 | std::random_device device; 77 | std::mt19937 algorithm(device()); 78 | 79 | for (size_t i = 0; i < iterations; ++i) { 80 | if (boost::this_thread::interruption_requested()) { 81 | return 0; 82 | } 83 | 84 | std::vector< std::byte > in(bufSize(algorithm)); 85 | 86 | std::generate(in.begin(), in.end(), [&algorithm]() { 87 | // We use < uint16_t > because the minimum size allowed is 2 bytes. 88 | std::uniform_int_distribution< uint16_t > gen(0, UINT8_MAX); 89 | return static_cast< std::byte >(gen(algorithm)); 90 | }); 91 | 92 | auto ret = test(cryptChaCha20, in); 93 | if (ret != 0) { 94 | return ret; 95 | } 96 | 97 | ret = test(cryptGCM, in); 98 | if (ret != 0) { 99 | return ret; 100 | } 101 | 102 | ret = test(cryptOCB2, in); 103 | if (ret != 0) { 104 | return ret; 105 | } 106 | } 107 | 108 | return 0; 109 | } 110 | 111 | int32_t main() { 112 | int32_t ret = 0; 113 | 114 | ThreadManager manager; 115 | 116 | for (uint32_t i = 0; i < manager.physicalNum(); ++i) { 117 | const ThreadManager::ThreadFunc func = [&ret, &manager]() { 118 | const auto threadRet = thread(); 119 | if (threadRet != 0) { 120 | ret = threadRet; 121 | manager.requestStop(); 122 | } 123 | }; 124 | 125 | manager.add(func); 126 | } 127 | 128 | manager.wait(); 129 | 130 | return ret; 131 | } 132 | -------------------------------------------------------------------------------- /src/Hash.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "Hash.hpp" 7 | 8 | #include "mumble/Types.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #define CHECK \ 20 | if (!*this) { \ 21 | return {}; \ 22 | } 23 | 24 | #define CAST_BUF(var) (reinterpret_cast< unsigned char * >(var)) 25 | 26 | using namespace mumble; 27 | 28 | using P = Hash::P; 29 | 30 | Hash::Hash(Hash &&crypt) : m_p(std::exchange(crypt.m_p, nullptr)) { 31 | } 32 | 33 | Hash::Hash() : m_p(new P) { 34 | } 35 | 36 | Hash::~Hash() = default; 37 | 38 | Hash::operator bool() const { 39 | return m_p && *m_p; 40 | } 41 | 42 | Hash &Hash::operator=(Hash &&crypt) { 43 | m_p = std::exchange(crypt.m_p, nullptr); 44 | return *this; 45 | } 46 | 47 | size_t Hash::operator()(const BufView out, const BufViewConst in) { 48 | CHECK 49 | 50 | if (!out.size()) { 51 | int size = EVP_MD_CTX_size(m_p->m_ctx); 52 | assert(size >= 0); 53 | return static_cast< std::size_t >(size); 54 | } 55 | 56 | if (EVP_DigestInit_ex(m_p->m_ctx, nullptr, nullptr) <= 0) { 57 | return {}; 58 | } 59 | 60 | if (EVP_DigestUpdate(m_p->m_ctx, in.data(), in.size()) <= 0) { 61 | return {}; 62 | } 63 | 64 | uint32_t written; 65 | if (EVP_DigestFinal_ex(m_p->m_ctx, CAST_BUF(out.data()), &written) <= 0) { 66 | return {}; 67 | } 68 | 69 | return written; 70 | } 71 | 72 | void *Hash::handle() const { 73 | CHECK 74 | 75 | return m_p->m_ctx; 76 | } 77 | 78 | std::string_view Hash::type() const { 79 | CHECK 80 | 81 | return m_p->type(); 82 | } 83 | 84 | bool Hash::setType(const std::string_view name) { 85 | CHECK 86 | 87 | return m_p->setType(name); 88 | } 89 | 90 | uint32_t Hash::blockSize() const { 91 | CHECK 92 | 93 | int size = EVP_MD_CTX_block_size(m_p->m_ctx); 94 | assert(size >= 0); 95 | return static_cast< std::uint32_t >(size); 96 | } 97 | 98 | bool Hash::reset() { 99 | CHECK 100 | 101 | return EVP_MD_CTX_reset(m_p->m_ctx) > 0; 102 | } 103 | 104 | P::P() : m_ctx(EVP_MD_CTX_new()) { 105 | if (m_ctx) { 106 | setType(); 107 | } 108 | } 109 | 110 | P::~P() { 111 | if (m_ctx) { 112 | EVP_MD_CTX_free(m_ctx); 113 | } 114 | } 115 | 116 | P::operator bool() { 117 | #if OPENSSL_VERSION_MAJOR >= 3 118 | return m_ctx && EVP_MD_CTX_get0_md(m_ctx); 119 | #else 120 | return m_ctx && EVP_MD_CTX_md(m_ctx); 121 | #endif 122 | } 123 | 124 | std::string_view P::type() { 125 | #if OPENSSL_VERSION_MAJOR >= 3 126 | const auto type = EVP_MD_CTX_get0_md(m_ctx); 127 | #else 128 | const auto type = EVP_MD_CTX_md(m_ctx); 129 | #endif 130 | if (type == EVP_md_null()) { 131 | return {}; 132 | } 133 | 134 | return EVP_MD_name(type); 135 | } 136 | 137 | bool P::setType(const std::string_view name) { 138 | const auto type = name.empty() ? EVP_md_null() : EVP_get_digestbyname(name.data()); 139 | if (!type) { 140 | return false; 141 | } 142 | 143 | return EVP_DigestInit_ex(m_ctx, type, nullptr) > 0; 144 | } 145 | -------------------------------------------------------------------------------- /include/mumble/Macros.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_MACROS_HPP 7 | #define MUMBLE_MACROS_HPP 8 | 9 | #ifdef _MSC_VER 10 | # define MUMBLE_COMPILER_MSVC 11 | #endif 12 | 13 | #define MUMBLE_ENUM_OPERATORS(T) \ 14 | static inline T operator~(const T lhs) { \ 15 | return static_cast< T >(~static_cast< std::underlying_type< T >::type >(lhs)); \ 16 | } \ 17 | static inline T operator|(const T lhs, const T rhs) { \ 18 | return static_cast< T >(static_cast< std::underlying_type< T >::type >(lhs) \ 19 | | static_cast< std::underlying_type< T >::type >(rhs)); \ 20 | } \ 21 | static inline T operator&(const T lhs, const T rhs) { \ 22 | return static_cast< T >(static_cast< std::underlying_type< T >::type >(lhs) \ 23 | & static_cast< std::underlying_type< T >::type >(rhs)); \ 24 | } \ 25 | static inline T operator^(const T lhs, const T rhs) { \ 26 | return static_cast< T >(static_cast< std::underlying_type< T >::type >(lhs) \ 27 | ^ static_cast< std::underlying_type< T >::type >(rhs)); \ 28 | } \ 29 | static inline T &operator|=(T &lhs, const T rhs) { \ 30 | return reinterpret_cast< T & >(reinterpret_cast< std::underlying_type< T >::type & >(lhs) |= \ 31 | static_cast< std::underlying_type< T >::type >(rhs)); \ 32 | } \ 33 | static inline T &operator&=(T &lhs, const T rhs) { \ 34 | return reinterpret_cast< T & >(reinterpret_cast< std::underlying_type< T >::type & >(lhs) &= \ 35 | static_cast< std::underlying_type< T >::type >(rhs)); \ 36 | } \ 37 | static inline T &operator^=(T &lhs, const T rhs) { \ 38 | return reinterpret_cast< T & >(reinterpret_cast< std::underlying_type< T >::type & >(lhs) ^= \ 39 | static_cast< std::underlying_type< T >::type >(rhs)); \ 40 | } 41 | 42 | #ifndef MUMBLE_SRC 43 | # define MUMBLE_EXPORT 44 | #else 45 | # ifdef MUMBLE_COMPILER_MSVC 46 | # define MUMBLE_EXPORT __declspec(dllexport) 47 | # else 48 | # define MUMBLE_EXPORT __attribute__((visibility("default"))) 49 | # endif 50 | #endif 51 | 52 | #ifdef MUMBLE_COMPILER_MSVC 53 | # define MUMBLE_PACK(decl) __pragma(pack(push, 1)) decl __pragma(pack(pop)) 54 | #else 55 | # define MUMBLE_PACK(decl) decl __attribute__((__packed__)) 56 | #endif 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /src/IP.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "mumble/IP.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #ifdef OS_WINDOWS 15 | # include 16 | #else 17 | # include 18 | # include 19 | # include 20 | #endif 21 | 22 | static constexpr uint8_t v6StrSize = 46; 23 | static constexpr uint8_t v4StrSize = 16; 24 | 25 | using namespace mumble; 26 | 27 | using P = IP::P; 28 | using View = IP::View; 29 | using ViewConst = IP::ViewConst; 30 | 31 | IP::IP() : m_bytes({}) { 32 | } 33 | 34 | IP::IP(const IP &ip) : m_bytes(ip.m_bytes) { 35 | } 36 | 37 | IP::IP(const ViewConst view) { 38 | switch (view.size()) { 39 | case v6Size: 40 | std::copy(view.begin(), view.end(), m_bytes.begin()); 41 | break; 42 | case v4Size: 43 | std::fill_n(&m_bytes[0], 10, 0x00); 44 | std::fill_n(&m_bytes[10], 2, 0xff); 45 | std::copy(view.begin(), view.end(), &m_bytes[12]); 46 | } 47 | } 48 | 49 | IP::IP(const std::string_view string) { 50 | if (inet_pton(AF_INET6, string.data(), m_bytes.data()) == 1) { 51 | return; 52 | } 53 | 54 | std::fill_n(&m_bytes[0], 10, 0x00); 55 | std::fill_n(&m_bytes[10], 2, 0xff); 56 | 57 | std::sscanf(string.data(), "%hhu.%hhu.%hhu.%hhu", &m_bytes[12], &m_bytes[13], &m_bytes[14], &m_bytes[15]); 58 | } 59 | 60 | IP::IP(const sockaddr_in6 &sockaddr) { 61 | std::memcpy(m_bytes.data(), &sockaddr.sin6_addr, m_bytes.size()); 62 | } 63 | 64 | IP::~IP() = default; 65 | 66 | IP &IP::operator=(const IP &ip) { 67 | m_bytes = ip.m_bytes; 68 | return *this; 69 | } 70 | 71 | bool IP::operator==(const IP &ip) const { 72 | return m_bytes == ip.m_bytes; 73 | } 74 | 75 | ViewConst IP::v6() const { 76 | return m_bytes; 77 | } 78 | 79 | ViewConst IP::v4() const { 80 | return { m_bytes.data() + 12, m_bytes.size() - 12 }; 81 | } 82 | 83 | View IP::v6() { 84 | return m_bytes; 85 | } 86 | 87 | View IP::v4() { 88 | return { m_bytes.data() + 12, m_bytes.size() - 12 }; 89 | } 90 | 91 | bool IP::isV6() const { 92 | return !isV4(); 93 | } 94 | 95 | bool IP::isV4() const { 96 | if (!std::all_of(&m_bytes[0], &m_bytes[9], [](const uint8_t byte) { return byte == 0x00; })) { 97 | return false; 98 | } 99 | 100 | if (!std::all_of(&m_bytes[10], &m_bytes[11], [](const uint8_t byte) { return byte == 0xff; })) { 101 | return false; 102 | } 103 | 104 | return true; 105 | } 106 | 107 | bool IP::isWildcard() const { 108 | const auto view = isV6() ? v6() : v4(); 109 | return std::all_of(view.begin(), view.end(), [](const uint8_t byte) { return byte == 0x00; }); 110 | } 111 | 112 | std::string IP::text() const { 113 | std::string ret; 114 | 115 | if (isV6()) { 116 | ret.resize(v6StrSize); 117 | 118 | if (!inet_ntop(AF_INET6, m_bytes.data(), ret.data(), v6StrSize)) { 119 | return {}; 120 | } 121 | } else { 122 | ret.resize(v4StrSize); 123 | 124 | std::snprintf(ret.data(), v4StrSize, "%hhu.%hhu.%hhu.%hhu", m_bytes[12], m_bytes[13], m_bytes[14], m_bytes[15]); 125 | } 126 | 127 | return ret; 128 | } 129 | 130 | void IP::toSockAddr(sockaddr_in6 &sockaddr) const { 131 | sockaddr.sin6_family = AF_INET6; 132 | std::memcpy(&sockaddr.sin6_addr, m_bytes.data(), sizeof(sockaddr.sin6_addr)); 133 | } 134 | -------------------------------------------------------------------------------- /src/Socket.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_SRC_SOCKET_HPP 7 | #define MUMBLE_SRC_SOCKET_HPP 8 | 9 | #include "mumble/Types.hpp" 10 | 11 | #include 12 | #include 13 | 14 | #ifdef OS_WINDOWS 15 | # include 16 | #else 17 | # include 18 | #endif 19 | 20 | namespace mumble { 21 | class Socket { 22 | public: 23 | using Pair = std::pair< Socket, Socket >; 24 | 25 | enum class Type : uint8_t { Unknown, Local, TCP, UDP }; 26 | 27 | static constexpr int8_t invalidHandle = -1; 28 | 29 | Socket(); 30 | Socket(Socket &&socket); 31 | Socket(const int32_t handle); 32 | Socket(const Type type); 33 | ~Socket(); 34 | 35 | explicit operator bool() const; 36 | 37 | int32_t handle() const; 38 | int32_t stealHandle(); 39 | 40 | int getEndpoint(Endpoint &endpoint) const; 41 | int setEndpoint(const Endpoint &endpoint, const bool ipv6Only = false); 42 | 43 | int setBlocking(const bool enable); 44 | 45 | static Pair localPair(); 46 | 47 | static void close(const int32_t handle); 48 | 49 | static int osError(); 50 | static constexpr Code osErrorToCode(const int error) { 51 | switch (error) { 52 | case 0: 53 | return Code::Success; 54 | #ifdef OS_WINDOWS 55 | case WSAEACCES: 56 | case WSAECONNREFUSED: 57 | return Code::Refuse; 58 | case WSAEHOSTUNREACH: 59 | case WSAENETUNREACH: 60 | return Code::Reach; 61 | case WSAEADDRNOTAVAIL: 62 | case WSAEAFNOSUPPORT: 63 | case WSAEDESTADDRREQ: 64 | case WSAEFAULT: 65 | case WSAEINVAL: 66 | case WSAEISCONN: 67 | case WSAENETDOWN: 68 | case WSAENOTCONN: 69 | case WSAENOTSOCK: 70 | case WSAEOPNOTSUPP: 71 | return Code::Invalid; 72 | case WSAEMSGSIZE: 73 | case WSAENOBUFS: 74 | return Code::Memory; 75 | case WSANOTINITIALISED: 76 | return Code::Init; 77 | case WSAETIMEDOUT: 78 | return Code::Timeout; 79 | case WSAECONNABORTED: 80 | return Code::Cancel; 81 | case WSAEINTR: 82 | case WSAEWOULDBLOCK: 83 | return Code::Retry; 84 | case WSAEADDRINUSE: 85 | case WSAEALREADY: 86 | case WSAEINPROGRESS: 87 | return Code::Busy; 88 | case WSAECONNRESET: 89 | case WSAENETRESET: 90 | case WSAESHUTDOWN: 91 | return Code::Disconnect; 92 | #else 93 | case EACCES: 94 | case ECONNREFUSED: 95 | return Code::Refuse; 96 | case EHOSTUNREACH: 97 | case ENETUNREACH: 98 | return Code::Reach; 99 | case EADDRNOTAVAIL: 100 | case EAFNOSUPPORT: 101 | case EBADF: 102 | case EDESTADDRREQ: 103 | case EFAULT: 104 | case EINVAL: 105 | case EISCONN: 106 | case ENOTCONN: 107 | case ENOTSOCK: 108 | case EOPNOTSUPP: 109 | case EPROTOTYPE: 110 | return Code::Invalid; 111 | case EMSGSIZE: 112 | case ENOBUFS: 113 | case ENOMEM: 114 | return Code::Memory; 115 | case ETIMEDOUT: 116 | return Code::Timeout; 117 | case ECONNABORTED: 118 | return Code::Cancel; 119 | # if EAGAIN != EWOULDBLOCK 120 | case EAGAIN: 121 | # endif 122 | case EWOULDBLOCK: 123 | case EINTR: 124 | return Code::Retry; 125 | case EADDRINUSE: 126 | case EALREADY: 127 | case EINPROGRESS: 128 | return Code::Busy; 129 | case ECONNRESET: 130 | case EPIPE: 131 | return Code::Disconnect; 132 | #endif 133 | } 134 | 135 | return Code::Unknown; 136 | } 137 | 138 | protected: 139 | int32_t m_handle; 140 | }; 141 | } // namespace mumble 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /tests/TestPacketDataStream/TestPacketDataStream.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Mumble Developers. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "TestPacketDataStream.hpp" 7 | 8 | #include "mumble/PacketDataStream.hpp" 9 | 10 | #include 11 | #include 12 | 13 | using namespace mumble; 14 | 15 | static constexpr auto bufferSize = 256; 16 | 17 | void TestPacketDataStream::integer() { 18 | std::array< std::byte, bufferSize > buffer{}; 19 | 20 | for (uint8_t i = 0; i < 64; ++i) { 21 | for (const uint64_t valueOut : { 1ULL << i, ~(1ULL << i) }) { 22 | uint64_t valueIn; 23 | 24 | PacketDataStream out(buffer); 25 | out << valueOut; 26 | PacketDataStream in({ buffer.data(), out.data().size() }); 27 | in >> valueIn; 28 | 29 | assert(valueIn == valueOut); 30 | assert(in); 31 | assert(in.seek().empty()); 32 | } 33 | } 34 | } 35 | 36 | void TestPacketDataStream::floating() { 37 | std::array< std::byte, bufferSize > buffer{}; 38 | 39 | for (uint16_t i = 1; i < 256; ++i) { 40 | for (const double valueOut : { 1.0 / (1.0 * i), 1.0 / (-1.0 * i) }) { 41 | double valueIn; 42 | 43 | PacketDataStream out(buffer); 44 | out << valueOut; 45 | PacketDataStream in({ buffer.data(), out.data().size() }); 46 | in >> valueIn; 47 | 48 | assert(valueIn == valueOut); 49 | assert(in); 50 | assert(in.seek().empty()); 51 | } 52 | } 53 | } 54 | 55 | void TestPacketDataStream::string() { 56 | std::array< std::byte, bufferSize > buffer{}; 57 | 58 | for (const auto valueOut : 59 | { std::string_view(), std::string_view(""), std::string_view("String"), std::string_view("Bærtur") }) { 60 | std::string valueIn; 61 | 62 | PacketDataStream out(buffer); 63 | out << valueOut; 64 | PacketDataStream in({ buffer.data(), out.data().size() }); 65 | in >> valueIn; 66 | 67 | assert(valueIn == valueOut); 68 | assert(in); 69 | assert(in.seek().empty()); 70 | } 71 | } 72 | 73 | void TestPacketDataStream::space() { 74 | std::array< std::byte, bufferSize > buffer; 75 | buffer.fill(std::byte(0x55)); 76 | 77 | PacketDataStream out({ buffer.data(), 1 }); 78 | 79 | const int8_t valueOut = -2; 80 | 81 | out << valueOut; 82 | assert(out); 83 | assert(out.data().size() == 1); 84 | assert(out.seek().empty()); 85 | assert(buffer[1] == std::byte(0x55)); 86 | 87 | out << valueOut; 88 | assert(!out); 89 | assert(out.data().size() == 1); 90 | assert(out.seek().empty()); 91 | assert(buffer[1] == std::byte(0x55)); 92 | 93 | PacketDataStream in({ buffer.data(), 1 }); 94 | 95 | int8_t valueIn; 96 | 97 | in >> valueIn; 98 | assert(valueIn == valueOut); 99 | assert(in); 100 | assert(in.data().size() == 1); 101 | assert(in.seek().empty()); 102 | 103 | in >> valueIn; 104 | assert(!in); 105 | assert(in.data().size() == 1); 106 | assert(in.seek().empty()); 107 | } 108 | 109 | void TestPacketDataStream::undersize() { 110 | std::array< std::byte, bufferSize > buffer{}; 111 | 112 | std::array< std::byte, 32 > data; 113 | data.fill(std::byte('Z')); 114 | 115 | for (uint8_t i = 0; i < data.size(); ++i) { 116 | PacketDataStream out({ buffer.data(), i }); 117 | out << data; 118 | 119 | assert(data.size() + 1 - i == out.undersize()); 120 | assert(!out); 121 | assert(out.seek().empty()); 122 | } 123 | 124 | PacketDataStream out({ buffer.data(), data.size() + 1 }); 125 | out << data; 126 | 127 | assert(out.undersize() == 0); 128 | assert(out); 129 | assert(out.seek().empty()); 130 | } 131 | -------------------------------------------------------------------------------- /include/mumble/Opus.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_OPUS_HPP 7 | #define MUMBLE_OPUS_HPP 8 | 9 | #include "Macros.hpp" 10 | #include "NonCopyable.hpp" 11 | #include "Types.hpp" 12 | 13 | #include 14 | 15 | namespace mumble { 16 | class MUMBLE_EXPORT Opus : NonCopyable { 17 | public: 18 | class Decoder; 19 | class Encoder; 20 | 21 | using FloatView = gsl::span< float >; 22 | using FloatViewConst = gsl::span< const float >; 23 | using IntegerView = gsl::span< int16_t >; 24 | using IntegerViewConst = gsl::span< const int16_t >; 25 | 26 | virtual explicit operator bool() const = 0; 27 | 28 | virtual uint8_t channels() const = 0; 29 | virtual uint32_t sampleRate() const = 0; 30 | 31 | virtual bool inDTX() const = 0; 32 | 33 | virtual bool usesPhaseInversion() const = 0; 34 | virtual bool togglePhaseInversion(const bool enable) = 0; 35 | 36 | static uint8_t packetChannels(const BufViewConst packet); 37 | static uint32_t packetFrames(const BufViewConst packet); 38 | static uint32_t packetSamples(const BufViewConst packet, const uint32_t sampleRate); 39 | static uint32_t packetSamplesPerFrame(const BufViewConst packet, const uint32_t sampleRate); 40 | }; 41 | 42 | class MUMBLE_EXPORT Opus::Decoder : public Opus { 43 | public: 44 | class P; 45 | 46 | Decoder(Decoder &&decoder); 47 | Decoder(const uint8_t channels); 48 | virtual ~Decoder(); 49 | 50 | virtual explicit operator bool() const override; 51 | 52 | virtual FloatView operator()(const FloatView out, const BufViewConst in, const bool decodeFEC = false); 53 | virtual IntegerView operator()(const IntegerView out, const BufViewConst in, const bool decodeFEC = false); 54 | 55 | virtual Code init(const uint32_t sampleRate = 48000); 56 | virtual Code reset(); 57 | 58 | virtual uint8_t channels() const override; 59 | virtual uint32_t sampleRate() const override; 60 | 61 | virtual bool inDTX() const override; 62 | 63 | virtual bool usesPhaseInversion() const override; 64 | virtual bool togglePhaseInversion(const bool enable) override; 65 | 66 | virtual uint32_t packetSamples(const BufViewConst packet); 67 | 68 | private: 69 | std::unique_ptr< P > m_p; 70 | }; 71 | 72 | class MUMBLE_EXPORT Opus::Encoder : public Opus { 73 | public: 74 | enum class Preset : uint8_t { Unknown, VoIP, Audio, LowDelay }; 75 | 76 | class P; 77 | 78 | Encoder(Encoder &&encoder); 79 | Encoder(const uint8_t channels); 80 | virtual ~Encoder(); 81 | 82 | virtual explicit operator bool() const override; 83 | 84 | virtual BufView operator()(const BufView out, const FloatViewConst in); 85 | virtual BufView operator()(const BufView out, const IntegerViewConst in); 86 | 87 | virtual Code init(const uint32_t sampleRate = 48000, const Preset preset = Preset::VoIP); 88 | virtual Code reset(); 89 | 90 | virtual uint8_t channels() const override; 91 | virtual uint32_t sampleRate() const override; 92 | 93 | virtual Preset preset() const; 94 | virtual bool setPreset(const Preset preset); 95 | 96 | virtual uint32_t bitrate() const; 97 | virtual bool setBitrate(const uint32_t bitrate); 98 | 99 | virtual bool inDTX() const override; 100 | 101 | virtual bool usesPhaseInversion() const override; 102 | virtual bool togglePhaseInversion(const bool enable) override; 103 | 104 | virtual bool usesVBR() const; 105 | virtual bool toggleVBR(const bool enable); 106 | 107 | private: 108 | std::unique_ptr< P > m_p; 109 | }; 110 | } // namespace mumble 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /src/Key.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "Key.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #define CHECK \ 21 | if (!*this) { \ 22 | return {}; \ 23 | } 24 | 25 | #define CAST_SIZE(var) (static_cast< int >(var)) 26 | 27 | using namespace mumble; 28 | 29 | using P = Key::P; 30 | 31 | Key::Key() : m_p(new P(EVP_PKEY_new())) { 32 | } 33 | 34 | Key::Key(const Key &key) : m_p(new P(key.pem(), key.isPrivate())) { 35 | } 36 | 37 | Key::Key(Key &&key) : m_p(std::exchange(key.m_p, nullptr)) { 38 | } 39 | 40 | Key::Key(void *handle) : m_p(new P(static_cast< EVP_PKEY * >(handle))) { 41 | } 42 | 43 | Key::Key(const std::string_view pem, const bool isPrivate, std::string_view password) 44 | : m_p(new P(pem, isPrivate, password)) { 45 | } 46 | 47 | Key::~Key() = default; 48 | 49 | Key::operator bool() const { 50 | return m_p && m_p->m_pkey; 51 | } 52 | 53 | Key &Key::operator=(const Key &key) { 54 | #if OPENSSL_VERSION_MAJOR >= 3 55 | m_p = std::make_unique< P >(EVP_PKEY_dup(key.m_p->m_pkey)); 56 | #else 57 | m_p = std::make_unique< P >(key.pem(), key.isPrivate()); 58 | #endif 59 | return *this; 60 | } 61 | 62 | Key &Key::operator=(Key &&key) { 63 | m_p = std::exchange(key.m_p, nullptr); 64 | return *this; 65 | } 66 | 67 | bool Key::operator==(const Key &key) const { 68 | if (!*this || !key) { 69 | return !*this && !key; 70 | } 71 | #if OPENSSL_VERSION_MAJOR >= 3 72 | return EVP_PKEY_eq(m_p->m_pkey, key.m_p->m_pkey) == 1; 73 | #else 74 | return EVP_PKEY_cmp(m_p->m_pkey, key.m_p->m_pkey) == 1; 75 | #endif 76 | } 77 | 78 | void *Key::handle() const { 79 | return m_p->m_pkey; 80 | } 81 | 82 | bool Key::isPrivate() const { 83 | CHECK 84 | 85 | return i2d_PrivateKey(m_p->m_pkey, nullptr) > 0; 86 | } 87 | 88 | std::string Key::pem() const { 89 | CHECK 90 | 91 | auto bio = BIO_new(BIO_s_secmem()); 92 | if (!bio) { 93 | return {}; 94 | } 95 | 96 | int ret; 97 | 98 | if (isPrivate()) { 99 | ret = PEM_write_bio_PrivateKey(bio, m_p->m_pkey, nullptr, nullptr, 0, nullptr, nullptr); 100 | } else { 101 | ret = PEM_write_bio_PUBKEY(bio, m_p->m_pkey); 102 | } 103 | 104 | std::string pem; 105 | 106 | if (ret > 0) { 107 | BUF_MEM *mem; 108 | if (BIO_get_mem_ptr(bio, &mem) > 0) { 109 | pem.append(mem->data, mem->length); 110 | } 111 | } 112 | 113 | BIO_free_all(bio); 114 | 115 | return pem; 116 | } 117 | 118 | P::P(EVP_PKEY *pkey) : m_pkey(pkey) { 119 | } 120 | 121 | P::P(const std::string_view pem, const bool isPrivate, std::string_view password) : m_pkey(nullptr) { 122 | auto bio = BIO_new_mem_buf(pem.data(), CAST_SIZE(pem.size())); 123 | if (!bio) { 124 | return; 125 | } 126 | 127 | if (isPrivate) { 128 | PEM_read_bio_PrivateKey(bio, &m_pkey, &P::passwordCallback, &password); 129 | } else { 130 | PEM_read_bio_PUBKEY(bio, &m_pkey, &P::passwordCallback, &password); 131 | } 132 | 133 | BIO_free_all(bio); 134 | } 135 | 136 | P::~P() { 137 | if (m_pkey) { 138 | EVP_PKEY_free(m_pkey); 139 | } 140 | } 141 | 142 | int P::passwordCallback(char *buf, const int size, int, void *userdata) { 143 | auto password = static_cast< const std::string_view * >(userdata); 144 | 145 | int length = CAST_SIZE(password->size()); 146 | if (length > size) { 147 | length = size; 148 | } 149 | 150 | assert(length >= 0); 151 | memcpy(buf, password->data(), static_cast< std::size_t >(length)); 152 | 153 | return length; 154 | } 155 | -------------------------------------------------------------------------------- /examples/ExampleClient/main.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "MumbleInit.hpp" 7 | 8 | #include "mumble/Connection.hpp" 9 | #include "mumble/Lib.hpp" 10 | #include "mumble/Message.hpp" 11 | #include "mumble/Pack.hpp" 12 | #include "mumble/Peer.hpp" 13 | #include "mumble/Types.hpp" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | using namespace mumble; 31 | 32 | static Connection::Feedback connectionFeedback(Connection &connection, std::condition_variable &cv) { 33 | using Message = tcp::Message; 34 | using Pack = tcp::Pack; 35 | using Type = Message::Type; 36 | 37 | Connection::Feedback feedback; 38 | 39 | feedback.opened = [&connection]() { 40 | printf("Connection opened!\n"); 41 | 42 | Message::Version ver; 43 | ver.version = lib::version(); 44 | ver.release = "Custom client"; 45 | connection.write(Pack(ver).buf()); 46 | }; 47 | 48 | feedback.closed = [&cv]() { 49 | printf("Connection closed!\n"); 50 | 51 | cv.notify_all(); 52 | }; 53 | 54 | feedback.failed = [&cv](const Code code) { 55 | printf("Connection failed with error \"%s\"!\n", text(code).data()); 56 | 57 | cv.notify_all(); 58 | }; 59 | 60 | feedback.pack = [](Pack &pack) { 61 | const auto type = Message::type(pack); 62 | if (type != Type::UDPTunnel) { 63 | printf("%s received!\n", Message::text(type).data()); 64 | } 65 | }; 66 | 67 | return feedback; 68 | } 69 | 70 | static Peer::FeedbackTCP peerFeedback() { 71 | Peer::FeedbackTCP feedback; 72 | 73 | feedback.started = []() { printf("TCP started!\n"); }; 74 | feedback.stopped = []() { printf("TCP stopped!\n"); }; 75 | 76 | feedback.failed = [](const Code code) { printf("TCP failed with error \"%s\"!\n", text(code).data()); }; 77 | 78 | feedback.timeout = []() { return 10000; }; 79 | 80 | return feedback; 81 | } 82 | 83 | int32_t main(const int argc, const char **argv) { 84 | if (argc > 2) { 85 | printf("Usage: `example_client `\n"); 86 | return 1; 87 | } 88 | 89 | std::string confPath = "config.toml"; 90 | if (argc > 1) { 91 | confPath = argv[1]; 92 | } 93 | 94 | const auto conf = toml::parse(confPath); 95 | 96 | MumbleInit mumbleInit; 97 | if (!mumbleInit) { 98 | return 2; 99 | } 100 | 101 | const auto local = toml::find(conf, "local"); 102 | const auto peer = toml::find(conf, "peer"); 103 | 104 | const auto localTcpIP = toml::find< std::string_view >(local, "tcpIP"); 105 | const auto localTcpPort = toml::find< uint16_t >(local, "tcpPort"); 106 | 107 | const auto peerTcpIP = toml::find< std::string_view >(peer, "tcpIP"); 108 | const auto peerTcpPort = toml::find< uint16_t >(peer, "tcpPort"); 109 | 110 | const auto ret = Peer::connect({ peerTcpIP, peerTcpPort }, { localTcpIP, localTcpPort }); 111 | if (ret.first != Code::Success) { 112 | printf("Peer::connect() failed with error \"%s\"!\n", text(ret.first).data()); 113 | return 3; 114 | } 115 | 116 | std::condition_variable cv; 117 | 118 | auto connection = std::make_shared< Connection >(ret.second, false); 119 | auto code = (*connection)(connectionFeedback(*connection, cv)); 120 | if (code != Code::Success) { 121 | printf("Connection() failed with error \"%s\"!\n", text(code).data()); 122 | return 4; 123 | } 124 | 125 | Peer client; 126 | client.addTCP(connection); 127 | code = client.startTCP(peerFeedback()); 128 | if (code != Code::Success) { 129 | printf("Peer::startTCP() failed with error \"%s\"!\n", text(code).data()); 130 | return 5; 131 | } 132 | 133 | std::mutex mutex; 134 | std::unique_lock< std::mutex > lock(mutex); 135 | cv.wait(lock); 136 | 137 | return 0; 138 | } 139 | -------------------------------------------------------------------------------- /src/proto/MumbleUDP.proto: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | syntax = "proto3"; 7 | 8 | package MumbleUDP; 9 | 10 | option optimize_for = SPEED; 11 | 12 | message Audio { 13 | oneof Header { 14 | // When this audio is sent by the client to the server, this is set to the target of the audio data. This target 15 | // is a number in the range [0, 2^{32} - 1], where 0 means "normal talking", 2^{5} - 1 means "server loopback" 16 | // and all other targets are understood as shout/whisper targets that have previously been registered via a 17 | // VoiceTarget message (via TCP). 18 | uint32 target = 1; 19 | // When this audio is sent by the server to the client, this indicates the context in which the audio has been sent. 20 | // 0: Normal speech 21 | // 1: Shout to channel 22 | // 2: Whisper to user 23 | // 3: Received via channel listener 24 | uint32 context = 2; 25 | }; 26 | 27 | // The session of the client (sender) this audio was originally sent from. This field is not required when sending 28 | // audio to the server, but will always be set when receiving audio from the server. 29 | uint32 sender_session = 3; 30 | 31 | // The number of the first contained audio frame (indicating the position of that frame in the overall audio stream) 32 | uint64 frame_number = 4; 33 | 34 | // The actual voice data payload in the Opus format. 35 | bytes opus_data = 5; 36 | 37 | // Optional positional data indicating the speaker's position in a virtual world (in meters). This "list" is really 38 | // expected to be an array of size 3 containing the X, Y and Z coordinates of the position (in that order). 39 | repeated float positional_data = 6; 40 | 41 | // A volume adjustment determined by the server for this audio packet. It is up to the client to apply this adjustment to 42 | // the resulting audio (or not). Note: A value of 0 means that this field is unset. 43 | float volume_adjustment = 7; 44 | 45 | // Note that we skip the field indices up to (including) 15 in order to have them available for future extensions of the 46 | // protocol with fields that are encountered very often. The reason is that all field indices <= 15 require only a single 47 | // byte of encoding overhead, whereas the once > 15 require (at least) two bytes. The reason lies in the Protobuf encoding 48 | // scheme that uses 1 bit for a varint continuation flag, 3 bit to encode a field's type and the remaining 4 bit of the 49 | // first byte are thus available for the field index. Therefore the first 2^4 = 16 field indices (aka values 0 to 15) can 50 | // be encoded using only a single byte. For details see https://developers.google.com/protocol-buffers/docs/encoding 51 | 52 | // A flag indicating whether this audio packet represents the end of transmission for the current audio stream 53 | bool is_terminator = 16; 54 | } 55 | 56 | /** 57 | * Ping message for checking UDP connectivity (and roundtrip ping) and potentially obtaining further server 58 | * details (e.g. version). 59 | */ 60 | message Ping { 61 | // Timestamp as encoded by the client. A server is not supposed to attempt to decode or modify this field. Therefore, 62 | // clients may choose an arbitrary format for this timestamp (as long as it fits into a uint64 field). 63 | uint64 timestamp = 1; 64 | 65 | // A flag set by the sending client, if it wants to obtain additional information about the server. 66 | bool request_extended_information = 2; 67 | 68 | 69 | // Below are the fields for the "additional information" that are filled out by the server on request. 70 | 71 | // The version of the server in the new version format. 72 | // The new protobuf Ping packet introduced with 1.5 drops support for the legacy version format 73 | // since both server and client have to support this new format. 74 | // (See https://github.com/mumble-voip/mumble/issues/5827) 75 | uint64 server_version_v2 = 3; 76 | 77 | // The amount of users currently connected to the server 78 | uint32 user_count = 4; 79 | 80 | // The maximum amount of users permitted on this server 81 | uint32 max_user_count = 5; 82 | 83 | // The maximum bandwidth each user is allowed to use for sending audio to the server 84 | uint32 max_bandwidth_per_user = 6; 85 | } 86 | -------------------------------------------------------------------------------- /src/TLS.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "TLS.hpp" 7 | 8 | #include "mumble/Cert.hpp" 9 | #include "mumble/Key.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | using namespace mumble; 24 | 25 | using Code = SocketTLS::Code; 26 | 27 | SocketTLS::SocketTLS(SocketTLS &&socket) 28 | : SocketTCP(std::move(socket)), m_ssl(std::exchange(socket.m_ssl, nullptr)), 29 | m_sslCtx(std::exchange(socket.m_sslCtx, nullptr)), m_closed(socket.m_closed.load()) { 30 | } 31 | 32 | SocketTLS::SocketTLS(const int32_t handle, const bool server) 33 | : SocketTCP(handle), m_ssl(nullptr), m_sslCtx(nullptr), m_closed(true) { 34 | if (!*static_cast< SocketTCP * >(this)) { 35 | return; 36 | } 37 | 38 | m_sslCtx = SSL_CTX_new(server ? TLS_server_method() : TLS_client_method()); 39 | if (!m_sslCtx) { 40 | return; 41 | } 42 | 43 | m_ssl = SSL_new(m_sslCtx); 44 | if (!m_ssl) { 45 | return; 46 | } 47 | 48 | SSL_set_fd(m_ssl, m_handle); 49 | SSL_set_read_ahead(m_ssl, 1); 50 | SSL_set_options(m_ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); 51 | SSL_set_verify(m_ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, verifyCallback); 52 | } 53 | 54 | SocketTLS::~SocketTLS() { 55 | if (m_ssl) { 56 | SSL_free(m_ssl); 57 | } 58 | 59 | if (m_sslCtx) { 60 | SSL_CTX_free(m_sslCtx); 61 | } 62 | } 63 | 64 | SocketTLS::operator bool() const { 65 | return m_ssl; 66 | } 67 | 68 | bool SocketTLS::isServer() const { 69 | return SSL_is_server(m_ssl); 70 | } 71 | 72 | bool SocketTLS::setCert(const Cert::Chain &cert, const Key &key) { 73 | if (!cert.size()) { 74 | return false; 75 | } 76 | 77 | if (SSL_use_certificate(m_ssl, static_cast< X509 * >(cert[0].handle())) <= 0) { 78 | return false; 79 | } 80 | 81 | if (cert.size() >= 2) { 82 | std::for_each(cert.cbegin() + 1, cert.cend(), 83 | [this](const Cert &cert) { SSL_add1_chain_cert(m_ssl, cert.handle()); }); 84 | } 85 | 86 | if (SSL_use_PrivateKey(m_ssl, static_cast< EVP_PKEY * >(key.handle())) <= 0) { 87 | return false; 88 | } 89 | 90 | if (SSL_check_private_key(m_ssl) <= 0) { 91 | return false; 92 | } 93 | 94 | return true; 95 | } 96 | 97 | Cert::Chain SocketTLS::peerCert() const { 98 | Cert::Chain cert; 99 | 100 | const auto stack = SSL_get0_verified_chain(m_ssl); 101 | for (int i = 0; i < sk_X509_num(stack); ++i) { 102 | const auto x509 = sk_X509_value(stack, i); 103 | if (X509_up_ref(x509)) { 104 | cert.push_back(x509); 105 | } 106 | } 107 | 108 | return cert; 109 | } 110 | 111 | uint32_t SocketTLS::pending() const { 112 | int pending = SSL_pending(m_ssl); 113 | assert(pending >= 0); 114 | return static_cast< uint32_t >(pending); 115 | } 116 | 117 | ::Code SocketTLS::accept() { 118 | ERR_clear_error(); 119 | 120 | const auto code = interpretLibCode(SSL_accept(m_ssl)); 121 | if (code == Code::Success) { 122 | m_closed = false; 123 | } 124 | 125 | return code; 126 | } 127 | 128 | ::Code SocketTLS::connect() { 129 | ERR_clear_error(); 130 | 131 | const auto code = interpretLibCode(SSL_connect(m_ssl)); 132 | if (code == Code::Success) { 133 | m_closed = false; 134 | } 135 | 136 | return code; 137 | } 138 | 139 | ::Code SocketTLS::disconnect() { 140 | if (m_closed) { 141 | return Code::Success; 142 | } 143 | 144 | m_closed = true; 145 | 146 | ERR_clear_error(); 147 | 148 | return interpretLibCode(SSL_shutdown(m_ssl)); 149 | } 150 | 151 | ::Code SocketTLS::read(BufView &buf) { 152 | ERR_clear_error(); 153 | 154 | size_t read = 0; 155 | const int ret = SSL_read_ex(m_ssl, buf.data(), buf.size(), &read); 156 | 157 | buf = buf.subspan(read); 158 | 159 | return interpretLibCode(ret, read, buf.size()); 160 | } 161 | 162 | ::Code SocketTLS::write(BufViewConst &buf) { 163 | ERR_clear_error(); 164 | 165 | size_t written = 0; 166 | const int ret = SSL_write_ex(m_ssl, buf.data(), buf.size(), &written); 167 | 168 | buf = buf.subspan(written); 169 | 170 | return interpretLibCode(ret, written, buf.size()); 171 | } 172 | 173 | ::Code SocketTLS::interpretLibCode(const int code, const bool processed, const bool remaining) { 174 | switch (SSL_get_error(m_ssl, code)) { 175 | case SSL_ERROR_NONE: 176 | if (processed) { 177 | return remaining ? Retry : Success; 178 | } 179 | 180 | return Shutdown; 181 | case SSL_ERROR_ZERO_RETURN: 182 | return Shutdown; 183 | case SSL_ERROR_WANT_READ: 184 | return WaitIn; 185 | case SSL_ERROR_WANT_WRITE: 186 | return WaitOut; 187 | case SSL_ERROR_SYSCALL: 188 | if (ERR_get_error() == 0 && osErrorToCode(osError()) == mumble::Code::Retry) { 189 | return Retry; 190 | } 191 | m_closed = true; 192 | 193 | if (!processed) { 194 | return Shutdown; 195 | } 196 | [[fallthrough]]; 197 | case SSL_ERROR_SSL: 198 | return Failure; 199 | } 200 | 201 | return Unknown; 202 | } 203 | 204 | int SocketTLS::verifyCallback(int, X509_STORE_CTX *) { 205 | return 1; 206 | } 207 | -------------------------------------------------------------------------------- /include/mumble/Types.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_TYPES_HPP 7 | #define MUMBLE_TYPES_HPP 8 | 9 | // For "include-what-you-use": 10 | // https://github.com/include-what-you-use/include-what-you-use/issues/828 11 | namespace mumble {} 12 | 13 | #include "IP.hpp" 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | namespace mumble { 22 | enum class Code : int8_t { 23 | Ssl = -12, 24 | Refuse, 25 | Reach, 26 | Listen, 27 | Open, 28 | Unsupport, 29 | Invalid, 30 | Init, 31 | Timeout, 32 | Memory, 33 | Failure, 34 | Unknown, 35 | Success, 36 | Cancel, 37 | Retry, 38 | Busy, 39 | Disconnect 40 | }; 41 | 42 | struct Endpoint { 43 | IP ip; 44 | uint16_t port; 45 | 46 | Endpoint() : port(0) {} 47 | Endpoint(const Endpoint &endpoint) = default; 48 | Endpoint(const IP &ip) : ip(ip), port(0) {} 49 | Endpoint(const uint16_t port) : port(port) {} 50 | Endpoint(const IP &ip, const uint16_t port) : ip(ip), port(port) {} 51 | virtual ~Endpoint() = default; 52 | 53 | virtual Endpoint &operator=(const Endpoint &endpoint) = default; 54 | virtual Endpoint &operator=(Endpoint &&endpoint) = default; 55 | 56 | virtual bool operator==(const Endpoint &endpoint) const { return endpoint.ip == ip && endpoint.port == port; } 57 | }; 58 | 59 | struct Version { 60 | uint16_t major; 61 | uint16_t minor; 62 | uint16_t patch; 63 | uint16_t extra; 64 | 65 | Version(const uint16_t major = 0, const uint16_t minor = 0, const uint16_t patch = 0, const uint16_t extra = 0) 66 | : major(major), minor(minor), patch(patch), extra(extra) {} 67 | 68 | Version(const uint64_t blob) 69 | : Version(static_cast< uint16_t >((blob & maskMajor64) >> offsetMajor64), 70 | static_cast< uint16_t >((blob & maskMinor64) >> offsetMinor64), 71 | static_cast< uint16_t >((blob & maskPatch64) >> offsetPatch64), 72 | static_cast< uint16_t >((blob & maskExtra64) >> offsetExtra64)) {} 73 | 74 | Version(const uint32_t blob) 75 | : Version(static_cast< uint16_t >((blob & maskMajor32) >> offsetMajor32), 76 | static_cast< uint16_t >((blob & maskMinor32) >> offsetMinor32), 77 | static_cast< uint16_t >((blob & maskPatch32) >> offsetPatch32)) {} 78 | 79 | uint64_t blob64() const { 80 | return (static_cast< uint64_t >(major) << offsetMajor64) | (static_cast< uint64_t >(minor) << offsetMinor64) 81 | | (static_cast< uint64_t >(patch) << offsetPatch64) | (static_cast< uint64_t >(extra) << offsetExtra64); 82 | } 83 | 84 | uint32_t blob32() const { 85 | return (std::min< uint32_t >(major, std::numeric_limits< uint16_t >::max()) << offsetMajor32) 86 | | (std::min< uint32_t >(minor, std::numeric_limits< uint8_t >::max()) << offsetMinor32) 87 | | (std::min< uint32_t >(patch, std::numeric_limits< uint8_t >::max()) << offsetPatch32); 88 | } 89 | 90 | constexpr bool isValid() const { return (major | minor | patch | extra) != 0; } 91 | 92 | static constexpr auto maskMajor64 = 0xFFFF000000000000; 93 | static constexpr auto maskMinor64 = 0xFFFF00000000; 94 | static constexpr auto maskPatch64 = 0xFFFF0000; 95 | static constexpr auto maskExtra64 = 0xFFFF; 96 | static constexpr auto maskMajor32 = 0xFFFF0000; 97 | static constexpr auto maskMinor32 = 0xFF00; 98 | static constexpr auto maskPatch32 = 0xFF; 99 | 100 | static constexpr auto offsetMajor64 = 48; 101 | static constexpr auto offsetMinor64 = 32; 102 | static constexpr auto offsetPatch64 = 16; 103 | static constexpr auto offsetExtra64 = 0; 104 | static constexpr auto offsetMajor32 = 16; 105 | static constexpr auto offsetMinor32 = 8; 106 | static constexpr auto offsetPatch32 = 0; 107 | }; 108 | 109 | using Buf = std::vector< std::byte >; 110 | using BufView = gsl::span< std::byte >; 111 | using BufViewConst = gsl::span< const std::byte >; 112 | 113 | template< size_t size > using FixedBuf = std::array< std::byte, size >; 114 | 115 | static constexpr std::string_view text(const Code code) { 116 | switch (code) { 117 | case Code::Ssl: 118 | return "Ssl"; 119 | case Code::Refuse: 120 | return "Refuse"; 121 | case Code::Reach: 122 | return "Reach"; 123 | case Code::Listen: 124 | return "Listen"; 125 | case Code::Open: 126 | return "Open"; 127 | case Code::Unsupport: 128 | return "Unsupport"; 129 | case Code::Invalid: 130 | return "Invalid"; 131 | case Code::Init: 132 | return "Init"; 133 | case Code::Timeout: 134 | return "Timeout"; 135 | case Code::Memory: 136 | return "Memory"; 137 | case Code::Failure: 138 | return "Failure"; 139 | case Code::Unknown: 140 | return "Unknown"; 141 | case Code::Success: 142 | return "Success"; 143 | case Code::Cancel: 144 | return "Cancel"; 145 | case Code::Retry: 146 | return "Retry"; 147 | case Code::Busy: 148 | return "Busy"; 149 | case Code::Disconnect: 150 | return "Disconnect"; 151 | } 152 | 153 | return {}; 154 | } 155 | 156 | static constexpr std::byte toByte(const char byte) { 157 | return static_cast< std::byte >(byte); 158 | } 159 | 160 | static inline void toBuf(Buf &buf, const std::string_view str) { 161 | buf.resize(str.size()); 162 | std::transform(str.cbegin(), str.cend(), buf.begin(), toByte); 163 | } 164 | 165 | } // namespace mumble 166 | 167 | #endif 168 | -------------------------------------------------------------------------------- /src/Socket.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "Socket.hpp" 7 | 8 | #include "mumble/Endian.hpp" 9 | #include "mumble/IP.hpp" 10 | 11 | #include 12 | 13 | #ifdef OS_WINDOWS 14 | # include 15 | # include 16 | #else 17 | # include 18 | # include 19 | # include 20 | 21 | # include 22 | # include 23 | #endif 24 | 25 | using namespace mumble; 26 | 27 | using Pair = Socket::Pair; 28 | 29 | Socket::Socket() : m_handle(invalidHandle) { 30 | } 31 | 32 | Socket::Socket(Socket &&socket) : m_handle(socket.stealHandle()) { 33 | } 34 | 35 | Socket::Socket(const int32_t handle) : m_handle(handle) { 36 | } 37 | 38 | Socket::Socket(const Type type) { 39 | switch (type) { 40 | case Type::Local: 41 | #ifdef OS_WINDOWS 42 | // https://github.com/microsoft/WSL/issues/5272 43 | m_handle = static_cast< int32_t >(socket(PF_UNIX, SOCK_STREAM, 0)); 44 | #else 45 | m_handle = socket(PF_LOCAL, SOCK_DGRAM, 0); 46 | #endif 47 | break; 48 | case Type::TCP: 49 | m_handle = static_cast< int32_t >(socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP)); 50 | break; 51 | case Type::UDP: 52 | m_handle = static_cast< int32_t >(socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)); 53 | break; 54 | default: 55 | m_handle = invalidHandle; 56 | } 57 | } 58 | 59 | Socket::~Socket() { 60 | close(m_handle); 61 | } 62 | 63 | Socket::operator bool() const { 64 | return m_handle != invalidHandle; 65 | } 66 | 67 | int32_t Socket::handle() const { 68 | return m_handle; 69 | } 70 | 71 | int32_t Socket::stealHandle() { 72 | return std::exchange(m_handle, invalidHandle); 73 | } 74 | 75 | int Socket::getEndpoint(Endpoint &endpoint) const { 76 | sockaddr_in6 addr; 77 | #ifdef OS_WINDOWS 78 | int size = sizeof(addr); 79 | #else 80 | socklen_t size = sizeof(addr); 81 | #endif 82 | if (getsockname(m_handle, reinterpret_cast< sockaddr * >(&addr), &size) != 0) { 83 | return osError(); 84 | } 85 | 86 | endpoint.ip = IP(addr); 87 | endpoint.port = Endian::toHost(addr.sin6_port); 88 | 89 | return 0; 90 | } 91 | 92 | int Socket::setEndpoint(const Endpoint &endpoint, const bool ipv6Only) { 93 | #ifdef OS_WINDOWS 94 | // IPV6_V6ONLY demands a 4-byte integer... 95 | DWORD value = ipv6Only; 96 | #else 97 | int value = ipv6Only; 98 | #endif 99 | if (setsockopt(m_handle, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast< char * >(&value), sizeof(value)) != 0) { 100 | return osError(); 101 | } 102 | 103 | value = 1; 104 | #ifdef SO_EXCLUSIVEADDRUSE 105 | if (setsockopt(m_handle, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, reinterpret_cast< char * >(&value), sizeof(value)) != 0) { 106 | return osError(); 107 | } 108 | #endif 109 | sockaddr_in6 addr = {}; 110 | endpoint.ip.toSockAddr(addr); 111 | addr.sin6_port = Endian::toNetwork(endpoint.port); 112 | 113 | if (bind(m_handle, reinterpret_cast< sockaddr * >(&addr), sizeof(addr)) != 0) { 114 | return osError(); 115 | } 116 | 117 | return 0; 118 | } 119 | 120 | int Socket::setBlocking(const bool enable) { 121 | #ifdef OS_WINDOWS 122 | u_long value = !enable; 123 | if (ioctlsocket(m_handle, FIONBIO, &value) != 0) { 124 | return osError(); 125 | } 126 | #else 127 | const int flags = fcntl(m_handle, F_GETFL, 0); 128 | if (flags == -1) { 129 | return osError(); 130 | } 131 | 132 | const int new_flags = enable ? flags & ~O_NONBLOCK : flags | O_NONBLOCK; 133 | if (flags == new_flags) { 134 | return 0; 135 | } 136 | 137 | if (fcntl(m_handle, F_SETFL, new_flags) != 0) { 138 | return osError(); 139 | } 140 | #endif 141 | return 0; 142 | } 143 | 144 | Pair Socket::localPair() { 145 | #ifdef OS_WINDOWS 146 | // https://github.com/microsoft/WSL/issues/4240 147 | Socket listener(Type::Local); 148 | if (!listener) { 149 | return {}; 150 | } 151 | 152 | sockaddr_un addr = {}; 153 | addr.sun_family = AF_UNIX; 154 | const auto length = GetTempPath(sizeof(addr.sun_path), addr.sun_path); 155 | if (!length) { 156 | return {}; 157 | } 158 | 159 | LARGE_INTEGER ticks; 160 | QueryPerformanceCounter(&ticks); 161 | snprintf(addr.sun_path + length, sizeof(addr.sun_path) - length, "MumbleSocket%llu", ticks.QuadPart); 162 | 163 | if (bind(listener.handle(), reinterpret_cast< sockaddr * >(&addr), sizeof(addr)) != 0) { 164 | return {}; 165 | } 166 | 167 | if (listen(listener.handle(), 1) != 0) { 168 | return {}; 169 | } 170 | 171 | auto first = Socket(Type::Local); 172 | if (!first) { 173 | return {}; 174 | } 175 | 176 | if (connect(first.handle(), reinterpret_cast< sockaddr * >(&addr), sizeof(addr)) != 0) { 177 | return {}; 178 | } 179 | 180 | DeleteFile(addr.sun_path); 181 | 182 | auto second = static_cast< int32_t >(accept(listener.handle(), nullptr, nullptr)); 183 | if (!second) { 184 | return {}; 185 | } 186 | 187 | return { first.stealHandle(), second }; 188 | #else 189 | int handles[2]; 190 | if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, handles) != 0) { 191 | return {}; 192 | } 193 | 194 | return { handles[0], handles[1] }; 195 | #endif 196 | } 197 | 198 | void Socket::close(const int32_t handle) { 199 | if (handle == invalidHandle) { 200 | return; 201 | } 202 | #ifdef OS_WINDOWS 203 | shutdown(handle, SD_BOTH); 204 | closesocket(handle); 205 | #else 206 | shutdown(handle, SHUT_RDWR); 207 | ::close(handle); 208 | #endif 209 | } 210 | 211 | int Socket::osError() { 212 | #ifdef OS_WINDOWS 213 | return WSAGetLastError(); 214 | #else 215 | return errno; 216 | #endif 217 | } 218 | -------------------------------------------------------------------------------- /src/Monitor.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "Monitor.hpp" 7 | 8 | #include "mumble/Types.hpp" 9 | 10 | #ifdef OS_WINDOWS 11 | # include 12 | #else 13 | # include 14 | #endif 15 | 16 | #if defined(HAVE_EPOLL) 17 | # include 18 | # include 19 | #elif defined(HAVE_WEPOLL) 20 | # include 21 | # define close epoll_close 22 | #else 23 | # include 24 | 25 | # ifdef OS_WINDOWS 26 | # define poll WSAPoll 27 | # else 28 | # include 29 | # endif 30 | #endif 31 | 32 | using namespace mumble; 33 | 34 | Monitor::Monitor() : m_trigger(Socket::localPair()) { 35 | #if defined(HAVE_EPOLL) || defined(HAVE_WEPOLL) 36 | m_handle = epoll_create(1); 37 | #endif 38 | if (*this) { 39 | add(m_trigger.first.handle(), true, false); 40 | } 41 | } 42 | 43 | Monitor::~Monitor() { 44 | #if defined(HAVE_EPOLL) || defined(HAVE_WEPOLL) 45 | if (*this) { 46 | close(m_handle); 47 | } 48 | #endif 49 | } 50 | 51 | Monitor::operator bool() const { 52 | #if defined(HAVE_EPOLL) 53 | return m_handle != -1; 54 | #elif defined(HAVE_WEPOLL) 55 | return m_handle; 56 | #else 57 | return true; 58 | #endif 59 | } 60 | 61 | uint32_t Monitor::num() const { 62 | return static_cast< uint32_t >(m_fds.size()); 63 | } 64 | 65 | bool Monitor::add(const int32_t fd, const bool in, const bool out) { 66 | if (m_fds.find(fd) != m_fds.cend()) { 67 | return false; 68 | } 69 | 70 | Target target = {}; 71 | #if defined(HAVE_EPOLL) || defined(HAVE_WEPOLL) 72 | target.data.fd = fd; 73 | target.events = EPOLLRDHUP; 74 | 75 | if (in) { 76 | target.events |= EPOLLIN; 77 | } 78 | 79 | if (out) { 80 | target.events |= EPOLLOUT; 81 | } 82 | 83 | if (epoll_ctl(m_handle, EPOLL_CTL_ADD, fd, &target) != 0) { 84 | return false; 85 | } 86 | #else 87 | target.fd = fd; 88 | 89 | if (in) { 90 | target.events |= POLLIN; 91 | } 92 | 93 | if (out) { 94 | target.events |= POLLOUT; 95 | } 96 | #endif 97 | m_targets.push_back(target); 98 | 99 | m_fds.insert(fd); 100 | 101 | return true; 102 | } 103 | 104 | bool Monitor::del(const int32_t fd) { 105 | if (!m_fds.erase(fd)) { 106 | return false; 107 | } 108 | #if defined(HAVE_EPOLL) || defined(HAVE_WEPOLL) 109 | epoll_ctl(m_handle, EPOLL_CTL_DEL, fd, nullptr); 110 | 111 | m_targets.pop_back(); 112 | #else 113 | const auto iter = 114 | std::find_if(m_targets.cbegin(), m_targets.cend(), [fd](const Target &target) { return target.fd == fd; }); 115 | m_targets.erase(iter); 116 | #endif 117 | return true; 118 | } 119 | 120 | bool Monitor::trigger() { 121 | if (!m_trigger.second) { 122 | return false; 123 | } 124 | #ifdef OS_WINDOWS 125 | constexpr char byte = 0; 126 | #else 127 | constexpr uint8_t byte = 0; 128 | #endif 129 | static_assert(sizeof(byte) == 1); 130 | 131 | return send(m_trigger.second.handle(), &byte, sizeof(byte), 0) >= 1; 132 | } 133 | 134 | bool Monitor::untrigger() { 135 | if (!m_trigger.first) { 136 | return false; 137 | } 138 | #ifdef OS_WINDOWS 139 | char byte; 140 | #else 141 | uint8_t byte; 142 | #endif 143 | static_assert(sizeof(byte) == 1); 144 | 145 | return recv(m_trigger.first.handle(), &byte, sizeof(byte), 0) >= 1; 146 | } 147 | 148 | uint32_t Monitor::wait(const EventsView events, const uint32_t timeout) { 149 | if (!events.size()) { 150 | return {}; 151 | } 152 | #if defined(HAVE_EPOLL) || defined(HAVE_WEPOLL) 153 | return waitEpoll(events, timeout); 154 | #else 155 | return waitPoll(events, timeout); 156 | #endif 157 | } 158 | 159 | #if defined(HAVE_EPOLL) || defined(HAVE_WEPOLL) 160 | # include 161 | 162 | uint32_t Monitor::waitEpoll(const EventsView events, const uint32_t timeout) { 163 | const int32_t ret = epoll_wait(m_handle, m_targets.data(), static_cast< int >(m_targets.size()), 164 | timeout == timeoutMax ? -1 : static_cast< int >(timeout)); 165 | if (ret < 1) { 166 | return {}; 167 | } 168 | 169 | uint32_t num = 0; 170 | 171 | for (const auto &target : gsl::span< Target >(m_targets.data(), static_cast< std::size_t >(ret))) { 172 | if (num >= events.size()) { 173 | break; 174 | } 175 | 176 | auto &event = events[num++]; 177 | 178 | event.fd = target.data.fd; 179 | event.state = Event::None; 180 | 181 | if (target.events & EPOLLIN) { 182 | if (target.data.fd != m_trigger.first.handle()) { 183 | event.state |= Event::InReady; 184 | } else { 185 | event.state |= Event::Triggered; 186 | untrigger(); 187 | continue; 188 | } 189 | } 190 | 191 | if (target.events & EPOLLOUT) { 192 | event.state |= Event::OutReady; 193 | } 194 | 195 | if (target.events & EPOLLHUP || target.events & EPOLLRDHUP) { 196 | event.state |= Event::Disconnected; 197 | } 198 | 199 | if (target.events & EPOLLERR) { 200 | event.state |= Event::Error; 201 | } 202 | } 203 | 204 | return num; 205 | } 206 | #else 207 | uint32_t Monitor::waitPoll(const EventsView events, const uint32_t timeout) { 208 | const int32_t ret = poll(m_targets.data(), static_cast< nfds_t >(m_targets.size()), 209 | timeout == timeoutMax ? -1 : static_cast< int >(timeout)); 210 | if (ret < 1) { 211 | return {}; 212 | } 213 | 214 | uint32_t num = 0; 215 | 216 | for (const auto &target : m_targets) { 217 | if (num >= events.size() || num >= static_cast< uint32_t >(ret)) { 218 | break; 219 | } 220 | 221 | if (!target.revents) { 222 | continue; 223 | } 224 | 225 | auto &event = events[num++]; 226 | 227 | event.fd = target.fd; 228 | event.state = Event::None; 229 | 230 | if (target.revents & POLLIN) { 231 | if (target.fd != m_trigger.first.handle()) { 232 | event.state |= Event::InReady; 233 | } else { 234 | event.state |= Event::Triggered; 235 | untrigger(); 236 | continue; 237 | } 238 | } 239 | 240 | if (target.revents & POLLOUT) { 241 | event.state |= Event::OutReady; 242 | } 243 | 244 | if (target.revents & POLLHUP) { 245 | event.state |= Event::Disconnected; 246 | } 247 | 248 | if (target.revents & POLLERR) { 249 | event.state |= Event::Error; 250 | } 251 | } 252 | 253 | return num; 254 | } 255 | #endif 256 | -------------------------------------------------------------------------------- /examples/ExampleServer/User.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "User.hpp" 7 | 8 | #include "Endpoints.hpp" 9 | 10 | #include "mumble/Connection.hpp" 11 | #include "mumble/Pack.hpp" 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | using namespace mumble; 20 | 21 | User::User(const int32_t socketHandle, const uint32_t id) 22 | : m_id(id), m_cryptOK(false), m_good(0), m_late(0), m_lost(0), m_decryptHistory({}), 23 | m_connection(std::make_shared< Connection >(socketHandle, true)) { 24 | const auto key = m_decrypt.genKey(); 25 | if (!m_decrypt.setKey(key) || !m_encrypt.setKey(key)) { 26 | return; 27 | } 28 | 29 | m_decryptNonce = m_decrypt.genNonce(); 30 | m_encryptNonce = m_encrypt.genNonce(); 31 | if (!m_decrypt.setNonce(m_decryptNonce) || !m_encrypt.setNonce(m_encryptNonce)) { 32 | return; 33 | } 34 | 35 | m_cryptOK = true; 36 | } 37 | 38 | User::~User() = default; 39 | 40 | uint32_t User::id() const { 41 | return m_id; 42 | } 43 | 44 | const std::shared_ptr< Connection > &User::connection() const { 45 | return m_connection; 46 | } 47 | 48 | bool User::cryptOK() const { 49 | return m_cryptOK; 50 | } 51 | 52 | uint32_t User::good() const { 53 | return m_good; 54 | } 55 | 56 | uint32_t User::late() const { 57 | return m_late; 58 | } 59 | 60 | uint32_t User::lost() const { 61 | return m_lost; 62 | } 63 | 64 | BufViewConst User::key() const { 65 | return m_decrypt.key(); 66 | } 67 | 68 | BufViewConst User::decryptNonce() const { 69 | return m_decrypt.nonce(); 70 | } 71 | 72 | BufViewConst User::encryptNonce() const { 73 | return m_encrypt.nonce(); 74 | } 75 | 76 | size_t User::decrypt(const BufView out, const BufViewConst in) { 77 | // The checksum header occupies 4 bytes. 78 | if (in.size() < 4) { 79 | return {}; 80 | } 81 | 82 | const auto nonceByte = static_cast< uint8_t >(in[0]); 83 | const auto tag = in.subspan(1, 3); 84 | const auto encrypted = in.subspan(4); 85 | const auto prevNonce = m_decryptNonce; 86 | 87 | gsl::span< uint8_t > nonce(reinterpret_cast< uint8_t * >(m_decryptNonce.data()), m_decryptNonce.size()); 88 | 89 | bool restore = false; 90 | 91 | int32_t lost = 0; 92 | int32_t late = 0; 93 | 94 | if (((nonce[0] + 1) & 0xFF) == nonceByte) { 95 | // In order as expected. 96 | if (nonceByte > nonce[0]) { 97 | nonce[0] = nonceByte; 98 | } else if (nonceByte < nonce[0]) { 99 | nonce[0] = nonceByte; 100 | for (uint32_t i = 1; i < nonce.size(); ++i) 101 | if (++nonce[i]) 102 | break; 103 | } else { 104 | return {}; 105 | } 106 | } else { 107 | // This is either out of order or a repeat. 108 | 109 | int32_t diff = nonceByte - nonce[0]; 110 | if (diff > 128) 111 | diff = diff - 256; 112 | else if (diff < -128) 113 | diff = diff + 256; 114 | 115 | if ((nonceByte < nonce[0]) && (diff > -30) && (diff < 0)) { 116 | // Late packet, but no wraparound. 117 | late = 1; 118 | lost = -1; 119 | nonce[0] = nonceByte; 120 | restore = true; 121 | } else if ((nonceByte > nonce[0]) && (diff > -30) && (diff < 0)) { 122 | // Last was 0x02, here comes 0xff from last round 123 | late = 1; 124 | lost = -1; 125 | nonce[0] = nonceByte; 126 | for (uint32_t i = 1; i < nonce.size(); ++i) { 127 | if (nonce[i]--) 128 | break; 129 | } 130 | 131 | restore = true; 132 | } else if ((nonceByte > nonce[0]) && (diff > 0)) { 133 | // Lost a few packets, but beyond that we're good. 134 | lost = nonceByte - nonce[0] - 1; 135 | nonce[0] = nonceByte; 136 | } else if ((nonceByte < nonce[0]) && (diff > 0)) { 137 | // Lost a few packets, and wrapped around 138 | lost = 256 - nonce[0] + nonceByte - 1; 139 | nonce[0] = nonceByte; 140 | for (uint32_t i = 1; i < nonce.size(); ++i) 141 | if (++nonce[i]) 142 | break; 143 | } else { 144 | return {}; 145 | } 146 | 147 | if (m_decryptHistory[nonce[0]] == nonce[1]) { 148 | m_decryptNonce = prevNonce; 149 | return {}; 150 | } 151 | } 152 | 153 | if (!m_decrypt.setNonce(m_decryptNonce)) { 154 | return {}; 155 | } 156 | 157 | const auto written = m_decrypt.decrypt(out, encrypted, tag); 158 | if (!written) { 159 | m_decryptNonce = prevNonce; 160 | return {}; 161 | } 162 | 163 | m_decryptHistory[nonce[0]] = nonce[1]; 164 | 165 | if (restore) { 166 | m_decryptNonce = prevNonce; 167 | } 168 | 169 | ++m_good; 170 | m_late += late; 171 | m_lost += lost; 172 | 173 | return written; 174 | } 175 | 176 | size_t User::encrypt(const BufView out, const BufViewConst in) { 177 | // The maximum packet size allowed in the Mumble protocol is 1024 bytes. 178 | // 4 bytes are used for the checksum header, leaving 1020 bytes for the data. 179 | if (out.size() < 4 || in.size() > 1020) { 180 | return {}; 181 | } 182 | 183 | for (auto &byte : m_encryptNonce) { 184 | if (++*reinterpret_cast< uint8_t * >(&byte)) { 185 | break; 186 | } 187 | } 188 | 189 | if (!m_encrypt.setNonce(m_encryptNonce)) { 190 | return {}; 191 | } 192 | 193 | const auto encrypted = out.subspan(4); 194 | if (encrypted.empty()) { 195 | return {}; 196 | } 197 | 198 | Buf tag(m_encrypt.blockSize()); 199 | 200 | const auto written = m_encrypt.encrypt(encrypted, in, tag); 201 | if (!written) { 202 | return {}; 203 | } 204 | 205 | out[0] = m_encryptNonce[0]; 206 | std::copy_n(tag.cbegin(), 3, out.begin() + 1); 207 | 208 | return written + 4; 209 | } 210 | 211 | const Endpoints &User::endpoints() const { 212 | return m_endpoints; 213 | } 214 | 215 | void User::addEndpoint(const Endpoint &endpoint) { 216 | m_endpoints.emplace(endpoint); 217 | } 218 | 219 | void User::delEndpoint(const Endpoint &endpoint) { 220 | m_endpoints.extract(endpoint); 221 | } 222 | 223 | Code User::connect(const Connection::Feedback &feedback, const Cert::Chain &cert, const Key &key) { 224 | if (!m_connection->setCert(cert, key)) { 225 | return Code::Failure; 226 | } 227 | 228 | return (*m_connection)(feedback); 229 | } 230 | 231 | void User::send(const Message &message) { 232 | send(Pack(message)); 233 | } 234 | 235 | void User::send(const Pack &pack) { 236 | m_connection->write(pack.buf()); 237 | } 238 | -------------------------------------------------------------------------------- /src/Cert.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "Cert.hpp" 7 | 8 | #include "mumble/Key.hpp" 9 | #include "mumble/Types.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #define CHECK \ 29 | if (!*this) { \ 30 | return {}; \ 31 | } 32 | 33 | #define CAST_SIZE(var) (static_cast< int >(var)) 34 | 35 | using namespace mumble; 36 | 37 | using Attributes = Cert::Attributes; 38 | using Der = Cert::Der; 39 | using P = Cert::P; 40 | using TimePoint = Cert::TimePoint; 41 | 42 | Cert::Cert() : m_p(new P(X509_new())) { 43 | } 44 | 45 | Cert::Cert(const Cert &cert) : m_p(new P(X509_dup(cert.m_p->m_x509))) { 46 | } 47 | 48 | Cert::Cert(Cert &&cert) : m_p(std::exchange(cert.m_p, nullptr)) { 49 | } 50 | 51 | Cert::Cert(void *handle) : m_p(new P(static_cast< X509 * >(handle))) { 52 | } 53 | 54 | Cert::Cert(const DerViewConst der) : m_p(new P(der)) { 55 | } 56 | 57 | Cert::Cert(const std::string_view pem, std::string_view password) : m_p(new P(pem, password)) { 58 | } 59 | 60 | Cert::~Cert() = default; 61 | 62 | Cert::operator bool() const { 63 | return m_p && m_p->m_x509; 64 | } 65 | 66 | Cert &Cert::operator=(const Cert &cert) { 67 | m_p = std::make_unique< P >(cert ? X509_dup(cert.m_p->m_x509) : nullptr); 68 | return *this; 69 | } 70 | 71 | Cert &Cert::operator=(Cert &&cert) { 72 | m_p = std::exchange(cert.m_p, nullptr); 73 | return *this; 74 | } 75 | 76 | bool Cert::operator==(const Cert &cert) const { 77 | if (!*this || !cert) { 78 | return !*this && !cert; 79 | } 80 | 81 | return X509_cmp(m_p->m_x509, cert.m_p->m_x509) == 0; 82 | } 83 | 84 | void *Cert::handle() const { 85 | CHECK 86 | 87 | return m_p->m_x509; 88 | } 89 | 90 | Der Cert::der() const { 91 | CHECK 92 | 93 | auto bio = BIO_new(BIO_s_secmem()); 94 | if (!bio) { 95 | return {}; 96 | } 97 | 98 | Der der; 99 | 100 | if (i2d_X509_bio(bio, m_p->m_x509) > 0) { 101 | BUF_MEM *mem; 102 | if (BIO_get_mem_ptr(bio, &mem) > 0) { 103 | der.resize(mem->length); 104 | memcpy(der.data(), mem->data, der.size()); 105 | } 106 | } 107 | 108 | BIO_free_all(bio); 109 | 110 | return der; 111 | } 112 | 113 | std::string Cert::pem() const { 114 | CHECK 115 | 116 | auto bio = BIO_new(BIO_s_secmem()); 117 | if (!bio) { 118 | return {}; 119 | } 120 | 121 | std::string pem; 122 | 123 | if (PEM_write_bio_X509(bio, m_p->m_x509) > 0) { 124 | BUF_MEM *mem; 125 | if (BIO_get_mem_ptr(bio, &mem) > 0) { 126 | pem.append(mem->data, mem->length); 127 | } 128 | } 129 | 130 | BIO_free_all(bio); 131 | 132 | return pem; 133 | } 134 | 135 | Key Cert::publicKey() const { 136 | CHECK 137 | 138 | return X509_get_pubkey(m_p->m_x509); 139 | } 140 | 141 | TimePoint Cert::since() const { 142 | CHECK 143 | 144 | return P::parseASN1Time(X509_get0_notBefore(m_p->m_x509)); 145 | } 146 | 147 | TimePoint Cert::until() const { 148 | CHECK 149 | 150 | return P::parseASN1Time(X509_get0_notAfter(m_p->m_x509)); 151 | } 152 | 153 | bool Cert::isAuthority() const { 154 | CHECK 155 | 156 | return X509_check_ca(m_p->m_x509); 157 | } 158 | 159 | bool Cert::isIssuer(const Cert &cert) const { 160 | CHECK 161 | 162 | return cert ? X509_check_issued(m_p->m_x509, cert.m_p->m_x509) == X509_V_OK : false; 163 | } 164 | 165 | bool Cert::isSelfIssued() const { 166 | return isIssuer(*this); 167 | } 168 | 169 | Attributes Cert::subjectAttributes() const { 170 | CHECK 171 | 172 | return P::parseX509Name(X509_get_subject_name(m_p->m_x509)); 173 | } 174 | 175 | Attributes Cert::issuerAttributes() const { 176 | CHECK 177 | 178 | return P::parseX509Name(X509_get_issuer_name(m_p->m_x509)); 179 | } 180 | 181 | P::P(X509 *x509) : m_x509(x509) { 182 | } 183 | 184 | P::P(const DerViewConst der) { 185 | auto bytes = gsl::as_bytes(der); 186 | d2i_X509(&m_x509, reinterpret_cast< const unsigned char ** >(&bytes), static_cast< long >(bytes.size())); 187 | } 188 | 189 | P::P(const std::string_view pem, std::string_view password) : m_x509(nullptr) { 190 | auto bio = BIO_new_mem_buf(pem.data(), CAST_SIZE(pem.size())); 191 | if (!bio) { 192 | return; 193 | } 194 | 195 | PEM_read_bio_X509(bio, &m_x509, &P::passwordCallback, &password); 196 | 197 | BIO_free_all(bio); 198 | } 199 | 200 | P::~P() { 201 | if (m_x509) { 202 | X509_free(m_x509); 203 | } 204 | } 205 | 206 | std::string P::parseASN1String(const ASN1_STRING *string) { 207 | if (!string) { 208 | return {}; 209 | } 210 | 211 | unsigned char *buf; 212 | const int size = ASN1_STRING_to_UTF8(&buf, string); 213 | if (size < 0) { 214 | return {}; 215 | } 216 | 217 | std::string ret(reinterpret_cast< char * >(buf), static_cast< std::size_t >(size)); 218 | 219 | OPENSSL_free(buf); 220 | 221 | return ret; 222 | } 223 | 224 | TimePoint P::parseASN1Time(const ASN1_TIME *time) { 225 | if (!time) { 226 | return {}; 227 | } 228 | 229 | tm tm; 230 | if (ASN1_TIME_to_tm(time, &tm) <= 0) { 231 | return {}; 232 | } 233 | 234 | return std::chrono::system_clock::from_time_t(mktime(&tm)); 235 | } 236 | 237 | Attributes P::parseX509Name(const X509_NAME *name) { 238 | if (!name) { 239 | return {}; 240 | } 241 | 242 | Attributes attributes; 243 | 244 | for (auto i = 0; i < X509_NAME_entry_count(name); ++i) { 245 | const auto entry = X509_NAME_get_entry(name, i); 246 | if (!entry) { 247 | continue; 248 | } 249 | 250 | const auto object = X509_NAME_ENTRY_get_object(entry); 251 | if (!object) { 252 | continue; 253 | } 254 | 255 | const auto nid = OBJ_obj2nid(object); 256 | if (nid == NID_undef) { 257 | continue; 258 | } 259 | 260 | const auto string = X509_NAME_ENTRY_get_data(entry); 261 | if (!string) { 262 | continue; 263 | } 264 | 265 | attributes.insert({ OBJ_nid2sn(nid), parseASN1String(string) }); 266 | } 267 | 268 | return attributes; 269 | } 270 | 271 | int P::passwordCallback(char *buf, const int size, int, void *userdata) { 272 | auto password = static_cast< const std::string_view * >(userdata); 273 | 274 | int length = CAST_SIZE(password->size()); 275 | if (length > size) { 276 | length = size; 277 | } 278 | 279 | memcpy(buf, password->data(), static_cast< std::size_t >(length)); 280 | 281 | return length; 282 | } 283 | -------------------------------------------------------------------------------- /src/Connection.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "Connection.hpp" 7 | 8 | #include "mumble/Endian.hpp" 9 | #include "mumble/Key.hpp" 10 | #include "mumble/Message.hpp" 11 | #include "mumble/Pack.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace mumble; 19 | 20 | using Feedback = Connection::Feedback; 21 | using P = Connection::P; 22 | using UniqueP = Connection::UniqueP; 23 | 24 | Connection::Connection(Connection &&connection) : m_p(std::exchange(connection.m_p, nullptr)) { 25 | } 26 | 27 | Connection::Connection(const int32_t socketHandle, const bool server) 28 | : m_p(std::make_unique< P >(SocketTLS(socketHandle, server))) { 29 | } 30 | 31 | Connection::~Connection() = default; 32 | 33 | Connection::operator bool() const { 34 | return m_p && *m_p; 35 | } 36 | 37 | Code Connection::operator()(const Feedback &feedback, const std::function< bool() > halt) { 38 | const auto guard = m_p->lock(); 39 | 40 | if (!m_p->m_monitorIn.add(m_p->m_handle, true, false) || !m_p->m_monitorOut.add(m_p->m_handle, false, true)) { 41 | return Code::Failure; 42 | } 43 | 44 | m_p->m_feedback = feedback; 45 | 46 | while (!halt()) { 47 | const auto code = m_p->handleCode(m_p->isServer() ? m_p->accept() : m_p->connect(), true); 48 | switch (code) { 49 | case Code::Success: 50 | m_p->m_closed.clear(); 51 | m_p->m_feedback.opened(); 52 | [[fallthrough]]; 53 | default: 54 | return code; 55 | case Code::Retry: 56 | continue; 57 | } 58 | } 59 | 60 | return Code::Cancel; 61 | } 62 | 63 | const UniqueP &Connection::p() const { 64 | return m_p; 65 | } 66 | 67 | int32_t Connection::socketHandle() const { 68 | return m_p->handle(); 69 | } 70 | 71 | Endpoint Connection::endpoint() const { 72 | Endpoint endpoint; 73 | m_p->getEndpoint(endpoint); 74 | return endpoint; 75 | } 76 | 77 | Endpoint Connection::peerEndpoint() const { 78 | Endpoint endpoint; 79 | m_p->getPeerEndpoint(endpoint); 80 | return endpoint; 81 | } 82 | 83 | const Cert::Chain &Connection::cert() const { 84 | return m_p->m_cert; 85 | } 86 | 87 | Cert::Chain Connection::peerCert() const { 88 | return m_p->peerCert(); 89 | } 90 | 91 | bool Connection::setCert(const Cert::Chain &cert, const Key &key) { 92 | const auto guard = m_p->lock(); 93 | 94 | return m_p->setCert(cert, key); 95 | } 96 | 97 | Code Connection::process(const bool wait, const std::function< bool() > halt) { 98 | using NetHeader = tcp::NetHeader; 99 | using Pack = tcp::Pack; 100 | 101 | const auto guard = m_p->lock(); 102 | do { 103 | NetHeader header; 104 | auto code = m_p->read({ reinterpret_cast< std::byte * >(&header), sizeof(header) }, wait, halt); 105 | if (code != Code::Success) { 106 | return code; 107 | } 108 | 109 | if (Endian::toHost(header.size) > std::numeric_limits< uint16_t >::max()) { 110 | if (!m_p->m_closed.test_and_set()) { 111 | m_p->m_feedback.failed(Code::Invalid); 112 | } 113 | 114 | return Code::Invalid; 115 | } 116 | 117 | Pack pack(header); 118 | 119 | code = m_p->read(pack.data(), wait, halt); 120 | if (code != Code::Success) { 121 | return code; 122 | } 123 | 124 | m_p->m_feedback.pack(pack); 125 | } while (m_p->pending() >= sizeof(NetHeader)); 126 | 127 | return Code::Success; 128 | } 129 | 130 | Code Connection::write(const BufViewConst data, const bool wait, const std::function< bool() > halt) { 131 | const auto guard = m_p->lock(); 132 | 133 | return m_p->write(data, wait, halt); 134 | } 135 | 136 | P::P(SocketTLS &&socket) : SocketTLS(std::move(socket)) { 137 | m_closed.test_and_set(); 138 | 139 | setBlocking(false); 140 | } 141 | 142 | P::~P() { 143 | disconnect(); 144 | } 145 | 146 | P::operator bool() const { 147 | return SocketTLS::operator bool() && m_monitorIn && m_monitorOut; 148 | } 149 | 150 | Code P::read(BufView buf, const bool wait, const std::function< bool() > halt) { 151 | using Code = mumble::Code; 152 | 153 | while (!halt()) { 154 | const auto code = handleCode(SocketTLS::read(buf), wait); 155 | if (code == Code::Retry) { 156 | continue; 157 | } else { 158 | return code; 159 | } 160 | } 161 | 162 | return Code::Cancel; 163 | } 164 | 165 | Code P::write(BufViewConst buf, const bool wait, const std::function< bool() > halt) { 166 | using Code = mumble::Code; 167 | 168 | while (!halt()) { 169 | const auto code = handleCode(SocketTLS::write(buf), wait); 170 | if (code == Code::Retry) { 171 | continue; 172 | } else { 173 | return code; 174 | } 175 | } 176 | 177 | return Code::Cancel; 178 | } 179 | 180 | Code P::handleCode(const Code code, const bool wait) { 181 | using Code = mumble::Code; 182 | 183 | auto ret = P::interpretTLSCode(code); 184 | if (ret == Code::Busy && wait) { 185 | ret = handleWait(code == WaitIn ? m_monitorIn : m_monitorOut); 186 | 187 | if (ret == Code::Timeout && ++m_timeouts < m_feedback.timeouts()) { 188 | ret = Code::Retry; 189 | } 190 | } 191 | 192 | switch (ret) { 193 | case Code::Disconnect: 194 | if (!m_closed.test_and_set()) { 195 | m_feedback.closed(); 196 | } 197 | [[fallthrough]]; 198 | case Code::Success: 199 | m_timeouts = 0; 200 | case Code::Retry: 201 | case Code::Busy: 202 | break; 203 | default: 204 | if (!m_closed.test_and_set()) { 205 | m_feedback.failed(ret); 206 | } 207 | } 208 | 209 | return ret; 210 | } 211 | 212 | Code P::handleWait(Monitor &monitor) { 213 | using Code = mumble::Code; 214 | 215 | Monitor::Event event; 216 | if (!monitor.wait({ &event, 1 }, m_feedback.timeout ? m_feedback.timeout() : monitor.timeoutMax)) { 217 | return Code::Timeout; 218 | } 219 | 220 | return handleState(event.state); 221 | } 222 | 223 | mumble::Code P::handleState(const State state) { 224 | using Code = mumble::Code; 225 | 226 | if (state & State::Disconnected) { 227 | if (!m_closed.test_and_set()) { 228 | m_feedback.closed(); 229 | } 230 | 231 | return Code::Disconnect; 232 | } 233 | 234 | if (state & State::Error) { 235 | if (!m_closed.test_and_set()) { 236 | m_feedback.failed(Code::Failure); 237 | } 238 | 239 | return Code::Failure; 240 | } 241 | 242 | if (state & State::Triggered || state & State::InReady || state & State::OutReady) { 243 | return Code::Retry; 244 | } 245 | 246 | return Code::Unknown; 247 | } 248 | 249 | constexpr mumble::Code P::interpretTLSCode(const Code code) { 250 | using Code = mumble::Code; 251 | 252 | switch (code) { 253 | case Memory: 254 | return Code::Memory; 255 | case Failure: 256 | return Code::Failure; 257 | case Unknown: 258 | break; 259 | case Success: 260 | return Code::Success; 261 | case Retry: 262 | return Code::Retry; 263 | case Shutdown: 264 | return Code::Disconnect; 265 | case WaitIn: 266 | case WaitOut: 267 | return Code::Busy; 268 | } 269 | 270 | return Code::Unknown; 271 | } 272 | -------------------------------------------------------------------------------- /src/Crypt.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "Crypt.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #define CHECK \ 17 | if (!*this) { \ 18 | return {}; \ 19 | } 20 | 21 | #define CAST_BUF(var) (reinterpret_cast< unsigned char * >(var)) 22 | #define CAST_BUF_CONST(var) (reinterpret_cast< const unsigned char * >(var)) 23 | #define CAST_SIZE(var) (static_cast< int >(var)) 24 | 25 | using namespace mumble; 26 | 27 | using P = Crypt::P; 28 | 29 | Crypt::Crypt(Crypt &&crypt) : m_p(std::exchange(crypt.m_p, nullptr)) { 30 | } 31 | 32 | Crypt::Crypt() : m_p(new P) { 33 | } 34 | 35 | Crypt::~Crypt() = default; 36 | 37 | Crypt::operator bool() const { 38 | return m_p && *m_p; 39 | } 40 | 41 | Crypt &Crypt::operator=(Crypt &&crypt) { 42 | m_p = std::exchange(crypt.m_p, nullptr); 43 | return *this; 44 | } 45 | 46 | void *Crypt::handle() const { 47 | CHECK 48 | 49 | return m_p->m_ctx; 50 | } 51 | 52 | std::string_view Crypt::cipher() const { 53 | CHECK 54 | 55 | return m_p->cipher(); 56 | } 57 | 58 | bool Crypt::setCipher(const std::string_view name) { 59 | CHECK 60 | 61 | return m_p->setCipher(name); 62 | } 63 | 64 | uint32_t Crypt::blockSize() const { 65 | CHECK 66 | 67 | return m_p->blockSize(); 68 | } 69 | 70 | uint32_t Crypt::keySize() const { 71 | CHECK 72 | 73 | return static_cast< uint32_t >(m_p->m_key.size()); 74 | } 75 | 76 | uint32_t Crypt::nonceSize() const { 77 | CHECK 78 | 79 | return static_cast< uint32_t >(m_p->m_nonce.size()); 80 | } 81 | 82 | BufViewConst Crypt::key() const { 83 | CHECK 84 | 85 | return m_p->m_key; 86 | } 87 | 88 | Buf Crypt::genKey() const { 89 | const auto size = keySize(); 90 | if (!size) { 91 | return {}; 92 | } 93 | 94 | Buf key(size); 95 | if (EVP_CIPHER_CTX_rand_key(m_p->m_ctx, CAST_BUF(key.data())) <= 0) { 96 | return {}; 97 | } 98 | 99 | return key; 100 | } 101 | 102 | bool Crypt::setKey(const BufViewConst key) { 103 | CHECK 104 | 105 | if (m_p->m_key.size() != key.size()) { 106 | if (EVP_CIPHER_CTX_set_key_length(m_p->m_ctx, CAST_SIZE(key.size())) <= 0) { 107 | return false; 108 | } 109 | 110 | m_p->m_key.resize(key.size()); 111 | } 112 | 113 | m_p->m_key.assign(key.begin(), key.end()); 114 | 115 | return EVP_CipherInit_ex(m_p->m_ctx, nullptr, nullptr, CAST_BUF_CONST(key.data()), nullptr, -1) > 0; 116 | } 117 | 118 | BufViewConst Crypt::nonce() const { 119 | CHECK 120 | 121 | return m_p->m_nonce; 122 | } 123 | 124 | Buf Crypt::genNonce() const { 125 | const auto size = nonceSize(); 126 | if (!size) { 127 | return {}; 128 | } 129 | 130 | Buf nonce(size); 131 | if (RAND_priv_bytes(CAST_BUF(nonce.data()), CAST_SIZE(nonce.size())) <= 0) { 132 | return {}; 133 | } 134 | 135 | return nonce; 136 | } 137 | 138 | bool Crypt::setNonce(const BufViewConst nonce) { 139 | CHECK 140 | 141 | if (m_p->m_nonce.size() != nonce.size()) { 142 | if (EVP_CIPHER_CTX_ctrl(m_p->m_ctx, EVP_CTRL_AEAD_SET_IVLEN, CAST_SIZE(nonce.size()), nullptr) <= 0) { 143 | return false; 144 | } 145 | 146 | m_p->m_nonce.resize(nonce.size()); 147 | } 148 | 149 | m_p->m_nonce.assign(nonce.begin(), nonce.end()); 150 | 151 | return EVP_CipherInit_ex(m_p->m_ctx, nullptr, nullptr, nullptr, CAST_BUF_CONST(nonce.data()), -1) > 0; 152 | } 153 | 154 | bool Crypt::usesPadding() const { 155 | return m_p->m_padding; 156 | } 157 | 158 | bool Crypt::togglePadding(const bool enable) { 159 | CHECK 160 | 161 | m_p->m_padding = enable; 162 | 163 | return true; 164 | } 165 | 166 | bool Crypt::reset() { 167 | CHECK 168 | 169 | return EVP_CIPHER_CTX_reset(m_p->m_ctx) > 0; 170 | } 171 | 172 | size_t Crypt::decrypt(const BufView out, const BufViewConst in, const BufViewConst tag, const BufViewConst aad) { 173 | CHECK 174 | 175 | return m_p->process(false, out, in, { const_cast< std::byte * >(tag.data()), tag.size() }, aad); 176 | } 177 | 178 | size_t Crypt::encrypt(const BufView out, const BufViewConst in, const BufView tag, const BufViewConst aad) { 179 | CHECK 180 | 181 | return m_p->process(true, out, in, tag, aad); 182 | } 183 | 184 | P::P() : m_padding(true), m_ctx(EVP_CIPHER_CTX_new()) { 185 | if (m_ctx) { 186 | setCipher(); 187 | } 188 | } 189 | 190 | P::~P() { 191 | if (m_ctx) { 192 | EVP_CIPHER_CTX_free(m_ctx); 193 | } 194 | } 195 | 196 | P::operator bool() { 197 | return m_ctx && EVP_CIPHER_CTX_cipher(m_ctx); 198 | } 199 | 200 | uint32_t P::blockSize() const { 201 | const int size = EVP_CIPHER_CTX_block_size(m_ctx); 202 | return size >= 0 ? static_cast< uint32_t >(size) : 0; 203 | } 204 | 205 | std::string_view P::cipher() { 206 | const auto cipher = EVP_CIPHER_CTX_cipher(m_ctx); 207 | if (cipher == EVP_enc_null()) { 208 | return {}; 209 | } 210 | 211 | return EVP_CIPHER_name(cipher); 212 | } 213 | 214 | bool P::setCipher(const std::string_view name) { 215 | const auto cipher = name.empty() ? EVP_enc_null() : EVP_get_cipherbyname(name.data()); 216 | if (!cipher) { 217 | return false; 218 | } 219 | 220 | if (EVP_CipherInit_ex(m_ctx, cipher, nullptr, nullptr, nullptr, -1) <= 0) { 221 | return false; 222 | } 223 | 224 | m_key.resize(static_cast< std::size_t >(EVP_CIPHER_CTX_key_length(m_ctx))); 225 | m_nonce.resize(static_cast< std::size_t >(EVP_CIPHER_CTX_iv_length(m_ctx))); 226 | 227 | m_key = {}; 228 | m_nonce = {}; 229 | 230 | return true; 231 | } 232 | 233 | size_t P::process(const bool encrypt, const BufView out, const BufViewConst in, const BufView tag, 234 | const BufViewConst aad) { 235 | if (!out.size()) { 236 | if (m_padding) { 237 | const auto size = blockSize(); 238 | return size > 1 ? in.size() + size : in.size(); 239 | } else { 240 | return in.size(); 241 | } 242 | } 243 | 244 | if (static_cast< bool >(EVP_CIPHER_CTX_encrypting(m_ctx)) == encrypt) { 245 | if (EVP_CipherInit_ex(m_ctx, nullptr, nullptr, nullptr, nullptr, encrypt) <= 0) { 246 | return {}; 247 | } 248 | } else { 249 | // Certain cipher implementations require resetting the key, nonce or both when switching operation mode. 250 | // Not doing so doesn't cause any apparent failure, until you realize the output is messed up. 251 | if (EVP_CipherInit_ex(m_ctx, nullptr, nullptr, CAST_BUF_CONST(m_key.data()), CAST_BUF_CONST(m_nonce.data()), 252 | encrypt) 253 | <= 0) { 254 | return {}; 255 | } 256 | } 257 | 258 | if (EVP_CIPHER_CTX_set_padding(m_ctx, m_padding) <= 0) { 259 | return {}; 260 | } 261 | 262 | if (!encrypt && !tag.empty()) { 263 | if (EVP_CIPHER_CTX_ctrl(m_ctx, EVP_CTRL_AEAD_SET_TAG, CAST_SIZE(tag.size()), tag.data()) <= 0) { 264 | return {}; 265 | } 266 | } 267 | 268 | int written1; 269 | 270 | if (!aad.empty()) { 271 | if (EVP_CipherUpdate(m_ctx, nullptr, &written1, CAST_BUF_CONST(aad.data()), CAST_SIZE(aad.size())) <= 0) { 272 | return {}; 273 | } 274 | } 275 | 276 | if (EVP_CipherUpdate(m_ctx, CAST_BUF(out.data()), &written1, CAST_BUF_CONST(in.data()), CAST_SIZE(in.size())) 277 | <= 0) { 278 | return {}; 279 | } 280 | 281 | int written2; 282 | 283 | if (EVP_CipherFinal_ex(m_ctx, CAST_BUF(out.data() + written1), &written2) <= 0) { 284 | return {}; 285 | } 286 | 287 | if (encrypt && !tag.empty()) { 288 | if (EVP_CIPHER_CTX_ctrl(m_ctx, EVP_CTRL_AEAD_GET_TAG, CAST_SIZE(tag.size()), tag.data()) <= 0) { 289 | return {}; 290 | } 291 | } 292 | 293 | assert(written1 >= 0); 294 | assert(written2 >= 0); 295 | return static_cast< std::size_t >(written1 + written2); 296 | } 297 | -------------------------------------------------------------------------------- /include/mumble/PacketDataStream.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Mumble Developers. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #ifndef MUMBLE_PACKETDATASTREAM_HPP 7 | #define MUMBLE_PACKETDATASTREAM_HPP 8 | 9 | #include "NonCopyable.hpp" 10 | #include "Types.hpp" 11 | 12 | #include 13 | #include 14 | 15 | namespace mumble { 16 | class PacketDataStream : NonCopyable { 17 | public: 18 | PacketDataStream(const BufView buf) : m_ok(true), m_buf(buf), m_seek(m_buf), m_overshoot(0) {} 19 | 20 | constexpr explicit operator bool() const { return m_ok; } 21 | 22 | constexpr BufViewConst buf() const { return m_buf; } 23 | constexpr BufView buf() { return m_buf; } 24 | 25 | constexpr BufViewConst data() const { return { m_buf.data(), m_buf.size() - m_seek.size() }; } 26 | constexpr BufView data() { return { m_buf.data(), m_buf.size() - m_seek.size() }; } 27 | 28 | constexpr BufViewConst seek() const { return m_seek; } 29 | constexpr BufView seek() { return m_seek; } 30 | 31 | constexpr uint32_t undersize() const { return m_overshoot; } 32 | 33 | constexpr void rewind() { m_seek = m_buf; } 34 | constexpr void truncate() { m_buf = m_seek; } 35 | 36 | constexpr void skip(const uint32_t size) { 37 | if (m_seek.size() >= size) { 38 | m_seek = m_seek.subspan(size); 39 | } else { 40 | m_ok = false; 41 | } 42 | } 43 | 44 | constexpr PacketDataStream &operator<<(const uint64_t value) { 45 | auto tmp = value; 46 | 47 | if ((tmp & 0x8000000000000000LL) && (~tmp < 0x100000000LL)) { 48 | // Signed number. 49 | tmp = ~tmp; 50 | if (tmp <= 0x3) { 51 | // Shortcase for -1 to -4. 52 | append(0xFC | tmp); 53 | return *this; 54 | } else { 55 | append(0xF8); 56 | } 57 | } 58 | 59 | if (tmp < 0x80) { 60 | // Need top bit clear. 61 | append(tmp); 62 | } else if (tmp < 0x4000) { 63 | // Need top two bits clear. 64 | append((tmp >> 8) | 0x80); 65 | append(tmp & 0xFF); 66 | } else if (tmp < 0x200000) { 67 | // Need top three bits clear. 68 | append((tmp >> 16) | 0xC0); 69 | append((tmp >> 8) & 0xFF); 70 | append(tmp & 0xFF); 71 | } else if (tmp < 0x10000000) { 72 | // Need top four bits clear. 73 | append((tmp >> 24) | 0xE0); 74 | append((tmp >> 16) & 0xFF); 75 | append((tmp >> 8) & 0xFF); 76 | append(tmp & 0xFF); 77 | } else if (tmp < 0x100000000LL) { 78 | // It's a full 32-bit integer. 79 | append(0xF0); 80 | append((tmp >> 24) & 0xFF); 81 | append((tmp >> 16) & 0xFF); 82 | append((tmp >> 8) & 0xFF); 83 | append(tmp & 0xFF); 84 | } else { 85 | // It's a 64-bit value. 86 | append(0xF4); 87 | append((tmp >> 56) & 0xFF); 88 | append((tmp >> 48) & 0xFF); 89 | append((tmp >> 40) & 0xFF); 90 | append((tmp >> 32) & 0xFF); 91 | append((tmp >> 24) & 0xFF); 92 | append((tmp >> 16) & 0xFF); 93 | append((tmp >> 8) & 0xFF); 94 | append(tmp & 0xFF); 95 | } 96 | 97 | return *this; 98 | } 99 | 100 | constexpr PacketDataStream &operator>>(uint64_t &value) { 101 | uint64_t tmp = next(); 102 | 103 | if ((tmp & 0x80) == 0x00) { 104 | value = (tmp & 0x7F); 105 | } else if ((tmp & 0xC0) == 0x80) { 106 | value = (tmp & 0x3F) << 8 | next(); 107 | } else if ((tmp & 0xF0) == 0xF0) { 108 | switch (tmp & 0xFC) { 109 | case 0xF0: 110 | value = next() << 24 | next() << 16 | next() << 8 | next(); 111 | break; 112 | case 0xF4: 113 | value = next() << 56 | next() << 48 | next() << 40 | next() << 32 | next() << 24 | next() << 16 114 | | next() << 8 | next(); 115 | break; 116 | case 0xF8: 117 | *this >> value; 118 | value = ~value; 119 | break; 120 | case 0xFC: 121 | value = tmp & 0x03; 122 | value = ~value; 123 | break; 124 | default: 125 | m_ok = false; 126 | value = 0; 127 | break; 128 | } 129 | } else if ((tmp & 0xF0) == 0xE0) { 130 | value = (tmp & 0x0F) << 24 | next() << 16 | next() << 8 | next(); 131 | } else if ((tmp & 0xE0) == 0xC0) { 132 | value = (tmp & 0x1F) << 16 | next() << 8 | next(); 133 | } 134 | 135 | return *this; 136 | } 137 | 138 | #define INTMAPOPERATOR(type) \ 139 | constexpr PacketDataStream &operator<<(const type value) { return *this << static_cast< uint64_t >(value); } \ 140 | constexpr PacketDataStream &operator>>(type &value) { \ 141 | uint64_t tmp = 0; \ 142 | *this >> tmp; \ 143 | value = static_cast< type >(tmp); \ 144 | return *this; \ 145 | } 146 | 147 | INTMAPOPERATOR(int32_t); 148 | INTMAPOPERATOR(uint32_t); 149 | INTMAPOPERATOR(int16_t); 150 | INTMAPOPERATOR(uint16_t); 151 | INTMAPOPERATOR(int8_t); 152 | INTMAPOPERATOR(uint8_t); 153 | 154 | constexpr PacketDataStream &operator<<(const bool value) { 155 | const uint32_t tmp = value ? 1 : 0; 156 | return *this << tmp; 157 | } 158 | 159 | constexpr PacketDataStream &operator>>(bool &value) { 160 | uint32_t tmp = 0; 161 | *this >> tmp; 162 | value = tmp ? true : false; 163 | return *this; 164 | } 165 | 166 | union double64u { 167 | uint64_t ui; 168 | double d; 169 | }; 170 | 171 | PacketDataStream &operator<<(const double value) { 172 | double64u u; 173 | u.d = value; 174 | return *this << u.ui; 175 | } 176 | 177 | PacketDataStream &operator>>(double &value) { 178 | double64u u; 179 | *this >> u.ui; 180 | value = u.d; 181 | return *this; 182 | } 183 | 184 | union float32u { 185 | uint8_t ui[4]; 186 | float f; 187 | }; 188 | 189 | PacketDataStream &operator<<(const float value) { 190 | float32u u; 191 | u.f = value; 192 | append(u.ui[0]); 193 | append(u.ui[1]); 194 | append(u.ui[2]); 195 | append(u.ui[3]); 196 | return *this; 197 | } 198 | 199 | PacketDataStream &operator>>(float &value) { 200 | float32u u; 201 | if (m_seek.size() < 4) { 202 | m_ok = false; 203 | value = 0; 204 | } 205 | u.ui[0] = next8(); 206 | u.ui[1] = next8(); 207 | u.ui[2] = next8(); 208 | u.ui[3] = next8(); 209 | value = u.f; 210 | return *this; 211 | } 212 | 213 | PacketDataStream &operator<<(const BufViewConst buf) { 214 | *this << static_cast< uint64_t >(buf.size()); 215 | append(buf); 216 | return *this; 217 | } 218 | 219 | PacketDataStream &operator>>(Buf &buf) { 220 | uint32_t size = 0; 221 | *this >> size; 222 | 223 | if (size > m_seek.size()) { 224 | size = static_cast< decltype(size) >(m_seek.size()); 225 | m_ok = false; 226 | } 227 | 228 | buf = { m_seek.begin(), m_seek.begin() + size }; 229 | m_seek = m_seek.subspan(size); 230 | 231 | return *this; 232 | } 233 | 234 | PacketDataStream &operator<<(const std::string_view str) { 235 | *this << static_cast< uint32_t >(str.size()); 236 | append({ reinterpret_cast< const std::byte * >(str.data()), str.size() }); 237 | return *this; 238 | } 239 | 240 | PacketDataStream &operator>>(std::string &str) { 241 | uint32_t size = 0; 242 | *this >> size; 243 | 244 | if (size > m_seek.size()) { 245 | size = static_cast< decltype(size) >(m_seek.size()); 246 | m_ok = false; 247 | } 248 | 249 | str = { reinterpret_cast< const char * >(m_seek.data()), size }; 250 | m_seek = m_seek.subspan(size); 251 | 252 | return *this; 253 | } 254 | 255 | private: 256 | constexpr uint64_t next() { 257 | if (!m_seek.empty()) { 258 | const auto value = std::to_integer< uint64_t >(m_seek.front()); 259 | m_seek = m_seek.subspan(sizeof(std::byte)); 260 | return value; 261 | } 262 | 263 | m_ok = false; 264 | 265 | return 0; 266 | } 267 | 268 | constexpr uint8_t next8() { 269 | if (!m_seek.empty()) { 270 | const auto value = std::to_integer< uint8_t >(m_seek.front()); 271 | m_seek = m_seek.subspan(sizeof(std::byte)); 272 | return value; 273 | } 274 | 275 | m_ok = false; 276 | 277 | return 0; 278 | } 279 | 280 | constexpr void append(const uint64_t value) { 281 | if (!m_seek.empty()) { 282 | m_seek.front() = std::byte(value); 283 | m_seek = m_seek.subspan(sizeof(std::byte)); 284 | } else { 285 | m_ok = false; 286 | ++m_overshoot; 287 | } 288 | } 289 | 290 | void append(const BufViewConst buf) { 291 | if (m_seek.size() >= buf.size()) { 292 | std::copy(buf.begin(), buf.end(), m_seek.begin()); 293 | m_seek = m_seek.subspan(buf.size()); 294 | } else { 295 | std::fill(m_seek.begin(), m_seek.end(), std::byte(0)); 296 | m_overshoot += static_cast< decltype(m_overshoot) >(buf.size() - m_seek.size()); 297 | m_seek = m_seek.last(0); 298 | m_ok = false; 299 | } 300 | } 301 | 302 | bool m_ok; 303 | BufView m_buf; 304 | BufView m_seek; 305 | uint32_t m_overshoot; 306 | }; 307 | }; // namespace mumble 308 | 309 | #endif 310 | -------------------------------------------------------------------------------- /src/Peer.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "Peer.hpp" 7 | 8 | #include "Connection.hpp" 9 | #include "Socket.hpp" 10 | #include "TCP.hpp" 11 | #include "UDP.hpp" 12 | 13 | #include "mumble/Connection.hpp" 14 | #include "mumble/Macros.hpp" 15 | #include "mumble/Message.hpp" 16 | #include "mumble/Pack.hpp" 17 | #include "mumble/Types.hpp" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include 28 | 29 | #ifndef MUMBLE_COMPILER_MSVC 30 | # include 31 | #else 32 | # pragma warning(push) 33 | # pragma warning(disable : 4244) 34 | # pragma warning(disable : 4324) 35 | # include 36 | # pragma warning(pop) 37 | #endif 38 | 39 | using namespace mumble; 40 | 41 | using P = Peer::P; 42 | 43 | Peer::Peer() : m_p(new P) { 44 | } 45 | 46 | Peer::Peer(Peer &&peer) : m_p(std::exchange(peer.m_p, nullptr)) { 47 | } 48 | 49 | Peer::~Peer() { 50 | stopTCP(); 51 | stopUDP(); 52 | } 53 | 54 | Peer &Peer::operator=(Peer &&peer) { 55 | m_p = std::exchange(peer.m_p, nullptr); 56 | return *this; 57 | } 58 | 59 | Peer::operator bool() const { 60 | return static_cast< bool >(m_p); 61 | } 62 | 63 | std::pair< Code, int32_t > Peer::connect(const Endpoint &peerEndpoint, const Endpoint &endpoint) { 64 | SocketTCP socket; 65 | 66 | auto code = Socket::osErrorToCode(socket.setEndpoint(endpoint)); 67 | if (code != Code::Success) { 68 | return { code, {} }; 69 | } 70 | 71 | code = Socket::osErrorToCode(socket.connect(peerEndpoint)); 72 | if (code != Code::Success) { 73 | return { code, {} }; 74 | } 75 | 76 | return { Code::Success, socket.stealHandle() }; 77 | } 78 | 79 | Code Peer::startTCP(const FeedbackTCP &feedback, const uint32_t threads) { 80 | return m_p->m_tcp.start(feedback, threads); 81 | } 82 | 83 | Code Peer::stopTCP() { 84 | return m_p->m_tcp.stop(); 85 | } 86 | 87 | Code Peer::startUDP(const FeedbackUDP &feedback, const uint32_t bufferSize) { 88 | return m_p->m_udp.start(feedback, bufferSize); 89 | } 90 | 91 | Code Peer::stopUDP() { 92 | return m_p->m_udp.stop(); 93 | } 94 | 95 | Code Peer::bindTCP(Endpoint &endpoint, const bool ipv6Only) { 96 | return m_p->m_tcp.bind(endpoint, ipv6Only); 97 | } 98 | 99 | Code Peer::unbindTCP() { 100 | return m_p->m_tcp.unbind(); 101 | } 102 | 103 | Code Peer::bindUDP(Endpoint &endpoint, const bool ipv6Only) { 104 | return m_p->m_udp.bind(endpoint, ipv6Only); 105 | } 106 | 107 | Code Peer::unbindUDP() { 108 | return m_p->m_udp.unbind(); 109 | } 110 | 111 | Code Peer::addTCP(const SharedConnection &connection) { 112 | auto &tcp = m_p->m_tcp; 113 | 114 | std::unique_lock< std::shared_mutex > lock(tcp.m_mutex); 115 | 116 | tcp.m_connections[connection->socketHandle()] = connection; 117 | tcp.m_monitor.add(connection->socketHandle(), true, false); 118 | 119 | return Code::Success; 120 | } 121 | 122 | Code Peer::delTCP(const SharedConnection &connection) { 123 | auto &tcp = m_p->m_tcp; 124 | 125 | std::unique_lock< std::shared_mutex > lock(tcp.m_mutex); 126 | 127 | tcp.m_monitor.del(connection->socketHandle()); 128 | tcp.m_connections.extract(connection->socketHandle()); 129 | 130 | return Code::Success; 131 | } 132 | 133 | Code Peer::sendUDP(const Endpoint &endpoint, const BufViewConst data) { 134 | if (!m_p->m_udp.m_socket) { 135 | return Code::Init; 136 | } 137 | 138 | return m_p->m_udp.m_socket->write(endpoint, data); 139 | } 140 | 141 | template< typename Feedback, typename Socket > Code P::Proto< Feedback, Socket >::stop() { 142 | if (!m_thread) { 143 | return Code::Success; 144 | } 145 | 146 | m_halt = true; 147 | m_monitor.trigger(); 148 | m_thread->join(); 149 | m_thread.reset(); 150 | 151 | return Code::Success; 152 | } 153 | 154 | template< typename Feedback, typename Socket > 155 | Code P::Proto< Feedback, Socket >::bind(Endpoint &endpoint, const bool ipv6Only) { 156 | if (m_thread) { 157 | return Code::Busy; 158 | } 159 | 160 | m_socket = std::make_unique< Socket >(); 161 | if (!*m_socket) { 162 | return Code::Open; 163 | } 164 | 165 | if (!m_monitor.add(m_socket->handle(), true, false)) { 166 | return Code::Failure; 167 | } 168 | 169 | m_socket->setBlocking(false); 170 | 171 | auto ret = m_socket->setEndpoint(endpoint, ipv6Only); 172 | if (ret != 0) { 173 | return Socket::osErrorToCode(ret); 174 | } 175 | 176 | return Code::Success; 177 | } 178 | 179 | template< typename Feedback, typename Socket > Code P::Proto< Feedback, Socket >::unbind() { 180 | if (m_thread) { 181 | return Code::Busy; 182 | } 183 | 184 | m_socket.reset(); 185 | 186 | return Code::Success; 187 | } 188 | 189 | Code P::TCP::start(const FeedbackTCP &feedback, const uint32_t threads) { 190 | if (m_thread) { 191 | return Code::Busy; 192 | } 193 | 194 | m_halt = false; 195 | m_feedback = feedback; 196 | m_thread = std::make_unique< boost::thread >(&TCP::threadFunc, this, threads); 197 | 198 | return Code::Success; 199 | } 200 | 201 | Code P::UDP::start(const FeedbackUDP &feedback, const uint32_t bufferSize) { 202 | if (m_thread) { 203 | return Code::Busy; 204 | } 205 | 206 | if (!m_socket) { 207 | return Code::Init; 208 | } 209 | 210 | m_halt = false; 211 | m_feedback = feedback; 212 | m_thread = std::make_unique< boost::thread >(&UDP::threadFunc, this, bufferSize); 213 | 214 | return Code::Success; 215 | } 216 | 217 | Code P::TCP::bind(Endpoint &endpoint, const bool ipv6Only) { 218 | const Code code = Proto::bind(endpoint, ipv6Only); 219 | if (code != Code::Success) { 220 | return code; 221 | } 222 | 223 | const auto ret = m_socket->listen(); 224 | if (ret != 0) { 225 | return Socket::osErrorToCode(ret); 226 | } 227 | 228 | return Code::Success; 229 | } 230 | 231 | void P::TCP::threadFunc(const uint32_t threads) { 232 | using Event = Monitor::Event; 233 | using Pool = quickpool::ThreadPool; 234 | 235 | if (m_feedback.started) { 236 | m_feedback.started(); 237 | } 238 | 239 | std::unique_ptr< Pool > pool; 240 | pool = threads ? std::make_unique< Pool >(threads) : std::make_unique< Pool >(); 241 | 242 | std::vector< Event > events(m_monitor.num()); 243 | 244 | uint32_t num = 0; 245 | 246 | while (!m_halt) { 247 | gsl::span< Event > view(events.data(), num); 248 | pool->parallel_for_each(view, [this](Event &event) { 249 | if (m_socket && event.fd == m_socket->handle()) { 250 | if (event.state & Event::Error) { 251 | m_feedback.failed(Code::Failure); 252 | return; 253 | } 254 | 255 | while (event.state & Event::InReady) { 256 | Endpoint endpoint; 257 | const auto ret = m_socket->accept(endpoint); 258 | 259 | const auto code = Socket::osErrorToCode(ret.first); 260 | switch (code) { 261 | case Code::Success: { 262 | if (!m_feedback.connection || !m_feedback.connection(endpoint, ret.second)) { 263 | Socket::close(ret.second); 264 | } 265 | 266 | break; 267 | } 268 | default: 269 | m_feedback.failed(code); 270 | [[fallthrough]]; 271 | case Code::Timeout: 272 | case Code::Cancel: 273 | case Code::Retry: 274 | case Code::Busy: 275 | case Code::Disconnect: 276 | event.state = Event::None; 277 | } 278 | } 279 | } else { 280 | SharedConnection connection; 281 | 282 | { 283 | std::shared_lock< std::shared_mutex > lock(m_mutex); 284 | 285 | const auto iter = m_connections.find(event.fd); 286 | if (iter != m_connections.cend()) { 287 | connection = iter->second; 288 | } else { 289 | return; 290 | } 291 | } 292 | 293 | if (event.state & Event::Disconnected || event.state & Event::Error) { 294 | connection->p()->handleState(event.state); 295 | return; 296 | } 297 | 298 | while (event.state & Event::InReady) { 299 | const auto code = connection->process(false, [this]() { return m_halt.load(); }); 300 | switch (code) { 301 | case Code::Success: 302 | break; 303 | default: 304 | event.state = Event::None; 305 | } 306 | } 307 | } 308 | }); 309 | 310 | if (events.size() != m_monitor.num()) { 311 | events.resize(m_monitor.num()); 312 | } 313 | 314 | num = m_monitor.wait(events, m_feedback.timeout ? m_feedback.timeout() : m_monitor.timeoutMax); 315 | } 316 | 317 | if (m_feedback.stopped) { 318 | m_feedback.stopped(); 319 | } 320 | } 321 | 322 | void P::UDP::threadFunc(const uint32_t bufferSize) { 323 | using namespace udp; 324 | using namespace legacy::udp; 325 | 326 | using Event = Monitor::Event; 327 | using Message = udp::Message; 328 | using Pack = udp::Pack; 329 | using Type = Message::Type; 330 | 331 | if (m_feedback.started) { 332 | m_feedback.started(); 333 | } 334 | 335 | Pack pack(NetHeader(), bufferSize ? bufferSize : 1024); 336 | Event event(m_socket->handle()); 337 | 338 | while (!m_halt) { 339 | if (event.state & Event::Error) { 340 | m_feedback.failed(Code::Failure); 341 | return; 342 | } 343 | 344 | while (event.state & Event::InReady) { 345 | Endpoint endpoint; 346 | BufView packet(pack.buf()); 347 | 348 | const auto code = m_socket->read(endpoint, packet); 349 | switch (code) { 350 | case Code::Success: { 351 | if (Message::type(pack) == Type::Ping) { 352 | Message::Ping ping; 353 | if (pack(ping, static_cast< uint32_t >(packet.size() - sizeof(NetHeader)))) { 354 | if (m_feedback.ping) { 355 | m_feedback.ping(endpoint, ping); 356 | } 357 | 358 | continue; 359 | } 360 | } 361 | 362 | if (isPlainPing(packet)) { 363 | if (m_feedback.legacyPing) { 364 | m_feedback.legacyPing(endpoint, *reinterpret_cast< Ping * >(packet.data())); 365 | } 366 | 367 | continue; 368 | } 369 | 370 | if (m_feedback.encrypted) { 371 | m_feedback.encrypted(endpoint, packet); 372 | } 373 | 374 | continue; 375 | } 376 | case Code::Timeout: 377 | case Code::Retry: 378 | case Code::Busy: 379 | event.state = Event::None; 380 | continue; 381 | default: 382 | m_feedback.failed(code); 383 | return; 384 | } 385 | } 386 | 387 | m_monitor.wait({ &event, 1 }, m_feedback.timeout ? m_feedback.timeout() : m_monitor.timeoutMax); 388 | } 389 | 390 | if (m_feedback.stopped) { 391 | m_feedback.stopped(); 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /src/CryptOCB2.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of libmumble. 2 | // Use of this source code is governed by a BSD-style license 3 | // that can be found in the LICENSE file at the root of the 4 | // Mumble source tree or at . 5 | 6 | #include "CryptOCB2.hpp" 7 | 8 | #include "mumble/Endian.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #define CHECK \ 20 | if (!*this) { \ 21 | return {}; \ 22 | } 23 | 24 | #define CAST_BUF(var) (reinterpret_cast< unsigned char * >(var)) 25 | #define CAST_BUF_CONST(var) (reinterpret_cast< const unsigned char * >(var)) 26 | #define CAST_SIZE(var) (static_cast< int >(var)) 27 | 28 | using namespace mumble; 29 | 30 | using P = CryptOCB2::P; 31 | 32 | using KeyBlock = P::KeyBlock; 33 | using KeyBlockView = P::KeyBlockView; 34 | using KeyBlockViewConst = P::KeyBlockViewConst; 35 | using SubBlock = P::SubBlock; 36 | 37 | CryptOCB2::CryptOCB2() : m_p(new P) { 38 | } 39 | 40 | CryptOCB2::~CryptOCB2() = default; 41 | 42 | CryptOCB2::operator bool() const { 43 | return m_p && m_p->m_ok; 44 | } 45 | 46 | uint32_t CryptOCB2::blockSize() const { 47 | CHECK 48 | 49 | return P::blockSize; 50 | } 51 | 52 | uint32_t CryptOCB2::keySize() const { 53 | CHECK 54 | 55 | return P::keySize; 56 | } 57 | 58 | uint32_t CryptOCB2::nonceSize() const { 59 | CHECK 60 | 61 | return P::nonceSize; 62 | } 63 | 64 | BufViewConst CryptOCB2::key() const { 65 | CHECK 66 | 67 | return m_p->m_key; 68 | } 69 | 70 | Buf CryptOCB2::genKey() const { 71 | CHECK 72 | 73 | Buf key(P::keySize); 74 | if (EVP_CIPHER_CTX_rand_key(m_p->m_ctx, CAST_BUF(key.data())) <= 0) { 75 | return {}; 76 | } 77 | 78 | return key; 79 | } 80 | 81 | bool CryptOCB2::setKey(const BufViewConst key) { 82 | CHECK 83 | 84 | if (key.size() != P::keySize) { 85 | return false; 86 | } 87 | 88 | std::copy(key.begin(), key.end(), m_p->m_key.begin()); 89 | 90 | return EVP_CipherInit_ex(m_p->m_ctx, nullptr, nullptr, CAST_BUF_CONST(key.data()), nullptr, -1) > 0; 91 | } 92 | 93 | BufViewConst CryptOCB2::nonce() const { 94 | CHECK 95 | 96 | return m_p->m_nonce; 97 | } 98 | 99 | Buf CryptOCB2::genNonce() const { 100 | CHECK 101 | 102 | Buf nonce(P::nonceSize); 103 | if (RAND_priv_bytes(CAST_BUF(nonce.data()), CAST_SIZE(nonce.size())) <= 0) { 104 | return {}; 105 | } 106 | 107 | return nonce; 108 | } 109 | 110 | bool CryptOCB2::setNonce(const BufViewConst nonce) { 111 | CHECK 112 | 113 | if (nonce.size() != P::nonceSize) { 114 | return false; 115 | } 116 | 117 | std::copy(nonce.begin(), nonce.end(), m_p->m_nonce.begin()); 118 | 119 | return true; 120 | } 121 | 122 | size_t CryptOCB2::decrypt(BufView out, BufViewConst in, const BufViewConst tag) { 123 | CHECK 124 | 125 | if (!out.size()) { 126 | return in.size(); 127 | } 128 | 129 | KeyBlock delta; 130 | const auto deltaBytes = gsl::as_writable_bytes(KeyBlockView(delta)); 131 | 132 | if (!m_p->process(true, deltaBytes, m_p->m_nonce)) { 133 | return {}; 134 | } 135 | 136 | size_t written = 0; 137 | 138 | KeyBlock checksum{}, tmp; 139 | const auto tmpBytes = gsl::as_writable_bytes(KeyBlockView(tmp)); 140 | 141 | while (in.size() > P::blockSize) { 142 | P::s2(delta); 143 | P::xorBlock(tmp, delta, P::toBlockView(in)); 144 | 145 | if (!m_p->process(false, tmpBytes, tmpBytes)) { 146 | return {}; 147 | } 148 | 149 | P::xorBlock(P::toBlockView(out), delta, tmp); 150 | written += P::blockSize; 151 | 152 | P::xorBlock(checksum, checksum, P::toBlockView(out)); 153 | 154 | in = in.subspan(P::blockSize); 155 | out = out.subspan(P::blockSize); 156 | } 157 | 158 | P::s2(delta); 159 | 160 | tmp = {}; 161 | tmp.back() = Endian::swap(static_cast< SubBlock >(in.size() * 8)); 162 | P::xorBlock(tmp, tmp, delta); 163 | 164 | KeyBlock pad; 165 | const auto padBytes = gsl::as_writable_bytes(KeyBlockView(pad)); 166 | 167 | if (!m_p->process(true, padBytes, tmpBytes)) { 168 | return {}; 169 | } 170 | 171 | tmp = {}; 172 | std::copy(in.begin(), in.end(), tmpBytes.begin()); 173 | P::xorBlock(tmp, tmp, pad); 174 | P::xorBlock(checksum, checksum, tmp); 175 | 176 | // Counter-cryptanalysis described in section 9 of https://eprint.iacr.org/2019/311 177 | // In an attack, the decrypted last block would need to equal `delta ^ len(128)`. 178 | // With a bit of luck (or many packets), smaller values than 128 (i.e. non-full blocks) are also 179 | // feasible, so we check `tmp` instead of `plain`. 180 | // Since our `len` only ever modifies the last byte, we simply check all remaining ones. 181 | if (std::equal(tmpBytes.begin(), tmpBytes.begin() + P::blockSize, deltaBytes.begin())) { 182 | return {}; 183 | } 184 | 185 | std::copy_n(tmpBytes.begin(), in.size(), out.begin()); 186 | written += in.size(); 187 | 188 | if (tag.empty()) { 189 | return written; 190 | } 191 | 192 | P::s3(delta); 193 | P::xorBlock(tmp, delta, checksum); 194 | 195 | Buf retrievedTag(P::blockSize); 196 | if (!m_p->process(true, retrievedTag, tmpBytes)) { 197 | return {}; 198 | } 199 | 200 | if (!std::equal(tag.begin(), tag.end(), retrievedTag.cbegin())) { 201 | return {}; 202 | } 203 | 204 | return written; 205 | } 206 | 207 | size_t CryptOCB2::encrypt(BufView out, BufViewConst in, const BufView tag) { 208 | CHECK 209 | 210 | if (!out.size()) { 211 | return in.size(); 212 | } 213 | 214 | if (!tag.empty() && tag.size() != P::blockSize) { 215 | return {}; 216 | } 217 | 218 | KeyBlock delta; 219 | if (!m_p->process(true, gsl::as_writable_bytes(KeyBlockView(delta)), m_p->m_nonce)) { 220 | return {}; 221 | } 222 | 223 | size_t written = 0; 224 | 225 | KeyBlock checksum{}, tmp; 226 | const auto tmpBytes = gsl::as_writable_bytes(KeyBlockView(tmp)); 227 | 228 | while (in.size() > P::blockSize) { 229 | // Counter-cryptanalysis described in section 9 of https://eprint.iacr.org/2019/311 230 | // For an attack, the second to last block (i.e. the last iteration of this loop) 231 | // must be all 0 except for the last byte (which may be 0 - 128). 232 | bool flipABit = false; 233 | 234 | if (in.size() - P::blockSize <= P::blockSize) { 235 | uint8_t sum = 0; 236 | 237 | for (uint8_t i = 0; i < P::blockSize - 1; ++i) { 238 | sum |= static_cast< uint8_t >(in[i]); 239 | } 240 | 241 | if (!sum) { 242 | // The assumption that critical packets do not turn up by pure chance turned out to be incorrect 243 | // since digital silence appears to produce them in mass. 244 | // So instead we now modify the packet in a way which should not affect the audio but will 245 | // prevent the attack. 246 | flipABit = true; 247 | } 248 | } 249 | 250 | P::s2(delta); 251 | P::xorBlock(tmp, delta, P::toBlockView(in)); 252 | 253 | if (flipABit) { 254 | *reinterpret_cast< uint8_t * >(tmp.data()) ^= 1; 255 | } 256 | 257 | if (!m_p->process(true, tmpBytes, tmpBytes)) { 258 | return {}; 259 | } 260 | 261 | P::xorBlock(P::toBlockView(out), delta, tmp); 262 | written += P::blockSize; 263 | 264 | P::xorBlock(checksum, checksum, P::toBlockView(in)); 265 | 266 | if (flipABit) { 267 | *reinterpret_cast< uint8_t * >(checksum.data()) ^= 1; 268 | } 269 | 270 | in = in.subspan(P::blockSize); 271 | out = out.subspan(P::blockSize); 272 | } 273 | 274 | P::s2(delta); 275 | 276 | tmp = {}; 277 | tmp.back() = Endian::swap(static_cast< SubBlock >(in.size() * 8)); 278 | P::xorBlock(tmp, tmp, delta); 279 | 280 | KeyBlock pad; 281 | const auto padBytes = gsl::as_writable_bytes(KeyBlockView(pad)); 282 | 283 | if (!m_p->process(true, padBytes, tmpBytes)) { 284 | return {}; 285 | } 286 | 287 | std::copy(in.begin(), in.end(), tmpBytes.begin()); 288 | std::copy_n(padBytes.begin() + static_cast< long >(in.size()), P::blockSize - in.size(), 289 | tmpBytes.begin() + static_cast< long >(in.size())); 290 | m_p->xorBlock(checksum, checksum, tmp); 291 | m_p->xorBlock(tmp, pad, tmp); 292 | 293 | std::copy_n(tmpBytes.begin(), in.size(), out.begin()); 294 | written += in.size(); 295 | 296 | if (tag.empty()) { 297 | return written; 298 | } 299 | 300 | P::s3(delta); 301 | P::xorBlock(tmp, delta, checksum); 302 | 303 | if (!m_p->process(true, tag, tmpBytes)) { 304 | return {}; 305 | } 306 | 307 | return written; 308 | } 309 | 310 | P::P() : m_ok(false), m_key(), m_nonce(), m_ctx(EVP_CIPHER_CTX_new()) { 311 | if (!m_ctx) { 312 | return; 313 | } 314 | 315 | if (EVP_CipherInit_ex(m_ctx, EVP_aes_128_ecb(), nullptr, nullptr, nullptr, -1) <= 0) { 316 | return; 317 | } 318 | 319 | if (EVP_CIPHER_CTX_block_size(m_ctx) != blockSize || EVP_CIPHER_CTX_key_length(m_ctx) != keySize) { 320 | return; 321 | } 322 | 323 | m_ok = true; 324 | } 325 | 326 | P::~P() { 327 | if (m_ctx) { 328 | EVP_CIPHER_CTX_free(m_ctx); 329 | } 330 | } 331 | 332 | size_t P::process(const bool encrypt, const BufView out, const BufViewConst in) { 333 | if (static_cast< bool >(EVP_CIPHER_CTX_encrypting(m_ctx)) == encrypt) { 334 | if (EVP_CipherInit_ex(m_ctx, nullptr, nullptr, nullptr, nullptr, encrypt) <= 0) { 335 | return {}; 336 | } 337 | } else { 338 | // The AES-128-ECB implementation requires resetting the key when switching operation mode. 339 | // Not doing so doesn't cause any apparent failure, until you realize the output is messed up. 340 | if (EVP_CipherInit_ex(m_ctx, nullptr, nullptr, CAST_BUF_CONST(m_key.data()), nullptr, encrypt) <= 0) { 341 | return {}; 342 | } 343 | } 344 | 345 | if (EVP_CIPHER_CTX_set_padding(m_ctx, 0) <= 0) { 346 | return {}; 347 | } 348 | 349 | int written1; 350 | 351 | if (EVP_CipherUpdate(m_ctx, CAST_BUF(out.data()), &written1, CAST_BUF_CONST(in.data()), CAST_SIZE(in.size())) 352 | <= 0) { 353 | return {}; 354 | } 355 | 356 | int written2; 357 | 358 | if (EVP_CipherFinal_ex(m_ctx, CAST_BUF(out.data() + written1), &written2) <= 0) { 359 | return {}; 360 | } 361 | 362 | assert(written1 >= 0); 363 | assert(written2 >= 0); 364 | return static_cast< std::size_t >(written1 + written2); 365 | } 366 | 367 | void P::xorBlock(const KeyBlockView dst, const KeyBlockViewConst a, const KeyBlockViewConst b) { 368 | for (uint8_t i = 0; i < subBlocks; ++i) { 369 | dst[i] = a[i] ^ b[i]; 370 | } 371 | } 372 | 373 | void P::s2(const KeyBlockView block) { 374 | const SubBlock carry = Endian::swap(block[0]) >> shiftBits; 375 | 376 | for (uint8_t i = 0; i < subBlocks - 1; ++i) { 377 | block[i] = Endian::swap((Endian::swap(block[i]) << 1) | (Endian::swap(block[i + 1]) >> shiftBits)); 378 | } 379 | 380 | block[subBlocks - 1] = Endian::swap((Endian::swap(block[subBlocks - 1]) << 1) ^ (carry * 0x87)); 381 | } 382 | 383 | void P::s3(const KeyBlockView block) { 384 | const SubBlock carry = Endian::swap(block[0]) >> shiftBits; 385 | 386 | for (uint8_t i = 0; i < subBlocks - 1; ++i) { 387 | block[i] ^= Endian::swap((Endian::swap(block[i]) << 1) | (Endian::swap(block[i + 1]) >> shiftBits)); 388 | } 389 | 390 | block[subBlocks - 1] ^= Endian::swap((Endian::swap(block[subBlocks - 1]) << 1) ^ (carry * 0x87)); 391 | } 392 | 393 | KeyBlockView P::toBlockView(const BufView buf) { 394 | return KeyBlockView(reinterpret_cast< SubBlock * >(buf.data()), subBlocks); 395 | } 396 | 397 | KeyBlockViewConst P::toBlockView(const BufViewConst buf) { 398 | return KeyBlockViewConst(reinterpret_cast< const SubBlock * >(buf.data()), subBlocks); 399 | } 400 | --------------------------------------------------------------------------------