diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3e24306 --- /dev/null +++ b/.clang-format @@ -0,0 +1,241 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 0 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: ".*" + Priority: 1 +IncludeIsMainRegex: "(Test)?$" +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseTab: Never +--- +Language: ObjC +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 0 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: ".*" + Priority: 1 +IncludeIsMainRegex: "(Test)?$" +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseTab: Never + diff --git a/CMakeLists.txt b/CMakeLists.txt index 3694b86..a742b0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.30) +cmake_minimum_required(VERSION 3.20) project(bitchat) set(CMAKE_CXX_STANDARD 20) @@ -86,10 +86,11 @@ set(SOURCES # Platform-specific source files if(PLATFORM_APPLE) - set(SOURCES ${SOURCES} src/platforms/apple/bluetooth.mm) + set(SOURCES ${SOURCES} src/platforms/apple/bluetooth.mm src/platforms/apple/bluetooth_bridge.mm) set_source_files_properties(src/platforms/apple/bluetooth.mm PROPERTIES COMPILE_FLAGS "-x objective-c++") + set_source_files_properties(src/platforms/apple/bluetooth_bridge.mm PROPERTIES COMPILE_FLAGS "-x objective-c++") elseif(PLATFORM_LINUX) - set(SOURCES ${SOURCES} src/platforms/linux/bluetooth.cpp) + set(SOURCES ${SOURCES} src/platforms/linux/bluetooth.cpp src/platforms/linux/bluetooth_factory.cpp) endif() # Create executable @@ -114,7 +115,7 @@ if(PLATFORM_APPLE) # Only compile .mm files as Objective-C++, not the entire target # target_compile_options(bitchat PRIVATE -x objective-c++) elseif(PLATFORM_LINUX) - target_link_libraries(bitchat ${BLUEZ_LIBRARIES}) + target_link_libraries(bitchat ${BLUEZ_LIBRARIES}) target_include_directories(bitchat PRIVATE ${BLUEZ_INCLUDE_DIRS}) endif() diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e68b239 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +.PHONY: build +.DEFAULT_GOAL := help + +help: + @echo "Type: make [rule]. Available options are:" + @echo "" + @echo "- help" + @echo "- format" + @echo "- windows-format" + @echo "- clean" + @echo "" + @echo "- build" + @echo "- run" + @echo "- run-windows" + @echo "" + +format: + find -E src/ include/ -regex '.*\.(cpp|hpp|cc|cxx|c|h|m|mm)' -exec clang-format -style=file -i {} \; + +windows-format: + powershell -Command "Get-ChildItem -Path src,include -Recurse -Include *.cpp,*.hpp,*.cc,*.cxx,*.c,*.h,*.m,*.mm | ForEach-Object { clang-format -style=file -i $$_.FullName }" + +clean: + rm -rf build + find . -name ".DS_Store" -delete + +build: + rm -rf build + cmake -B build . + cmake --build build -j$(or $(jobs),$(shell sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4)) + +run: + ./build/bin/bitchat + +run-windows: + powershell -Command ".\build\bin\bitchat.exe" + diff --git a/README.md b/README.md index 53b6f95..e956fea 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Bitchat creates a decentralized mesh network where devices communicate directly ``` bitchat-cpp/ -├── include/ # Public headers +├── include/ # Public headers │ ├── bitchat/ # Core library headers │ │ ├── core/ # Main application logic │ │ │ └── bitchat_manager.h @@ -56,7 +56,7 @@ bitchat-cpp/ │ │ │ └── packet_serializer.h │ │ └── platform/ # Platform abstraction layer │ │ ├── bluetooth_interface.h -│ │ └── bluetooth_bridge.h +│ │ └── bluetooth_factory.h │ └── platforms/ # Platform-specific headers │ └── apple/ # macOS/iOS CoreBluetooth │ └── bluetooth.h @@ -78,9 +78,7 @@ bitchat-cpp/ ├── cmake/ # CMake utilities │ └── CPM.cmake # CPM dependency manager ├── main.cpp # Application entry point -├── CMakeLists.txt # Build configuration -├── build.sh # Build script -└── README.md # This file +├── CMakeLists.txt # Build configuration ``` ## Key Components diff --git a/build.sh b/build.sh deleted file mode 100755 index 5615ed9..0000000 --- a/build.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# Build script for Bitchat C++ - -set -e - -echo "=== Building Bitchat C++ ===" - -# Create build directory -mkdir -p build -cd build - -# Configure with CMake -echo "Configuring with CMake..." -cmake .. - -# Build -echo "Building..." -make -j$(nproc) - -echo "=== Build complete ===" -echo "Executable: build/bin/bitchat" diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake index 2d151bf..3636ee5 100644 --- a/cmake/CPM.cmake +++ b/cmake/CPM.cmake @@ -45,7 +45,7 @@ endif() if(DEFINED EXTRACTED_CPM_VERSION) set(CURRENT_CPM_VERSION "${EXTRACTED_CPM_VERSION}${CPM_DEVELOPMENT}") else() - set(CURRENT_CPM_VERSION 1.0.0-development-version) + set(CURRENT_CPM_VERSION 0.42.0) endif() get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH) diff --git a/include/bitchat/compression/compression_manager.h b/include/bitchat/compression/compression_manager.h index 4ce1fc8..1f3cfdc 100644 --- a/include/bitchat/compression/compression_manager.h +++ b/include/bitchat/compression/compression_manager.h @@ -1,26 +1,28 @@ #pragma once -#include #include #include +#include -namespace bitchat { +namespace bitchat +{ // CompressionManager: handles LZ4 compression and decompression -class CompressionManager { +class CompressionManager +{ public: CompressionManager(); ~CompressionManager() = default; // Compress data using LZ4 - std::vector compressData(const std::vector& data); - + std::vector compressData(const std::vector &data); + // Decompress data using LZ4 - std::vector decompressData(const std::vector& compressedData, size_t originalSize); - + std::vector decompressData(const std::vector &compressedData, size_t originalSize); + // Check if data should be compressed - bool shouldCompress(const std::vector& data) const; - + bool shouldCompress(const std::vector &data) const; + // Calculate compression bound for given data size int calculateCompressionBound(size_t dataSize) const; @@ -28,4 +30,4 @@ class CompressionManager { static constexpr size_t COMPRESSION_THRESHOLD = 100; // bytes }; -} // namespace bitchat \ No newline at end of file +} // namespace bitchat \ No newline at end of file diff --git a/include/bitchat/core/bitchat_manager.h b/include/bitchat/core/bitchat_manager.h index 960982a..bd3d05d 100644 --- a/include/bitchat/core/bitchat_manager.h +++ b/include/bitchat/core/bitchat_manager.h @@ -1,66 +1,68 @@ #pragma once -#include "bitchat/platform/bluetooth_interface.h" -#include "bitchat/crypto/crypto_manager.h" #include "bitchat/compression/compression_manager.h" -#include "bitchat/protocol/packet_serializer.h" +#include "bitchat/crypto/crypto_manager.h" +#include "bitchat/platform/bluetooth_interface.h" #include "bitchat/protocol/packet.h" -#include +#include "bitchat/protocol/packet_serializer.h" +#include #include -#include +#include #include +#include #include -#include -namespace bitchat { +namespace bitchat +{ -// BitchatManager: main class that orchestrates the entire application -class BitchatManager { +// BitchatManager is the main class that orchestrates the entire application +class BitchatManager +{ public: BitchatManager(); ~BitchatManager(); // Initialize the manager bool initialize(); - + // Start the manager (start Bluetooth, etc.) bool start(); - + // Stop the manager void stop(); - + // Send a message to the current channel - bool sendMessage(const std::string& content); - + bool sendMessage(const std::string &content); + // Join a channel - void joinChannel(const std::string& channel); - + void joinChannel(const std::string &channel); + // Set nickname - void setNickname(const std::string& nickname); - + void setNickname(const std::string &nickname); + // Get current channel std::string getCurrentChannel() const; - + // Get nickname std::string getNickname() const; - + // Get peer ID std::string getPeerId() const; - + // Get online peers std::map getOnlinePeers() const; - + // Get message history std::vector getMessageHistory() const; - + // Check if manager is ready bool isReady() const; - + // Set callbacks for UI updates - using MessageCallback = std::function; - using PeerCallback = std::function; - using StatusCallback = std::function; - + using MessageCallback = std::function; + using PeerCallback = std::function; + using StatusCallback = std::function; + void setMessageCallback(MessageCallback callback); void setPeerJoinedCallback(PeerCallback callback); void setPeerLeftCallback(PeerCallback callback); @@ -69,12 +71,12 @@ class BitchatManager { private: // Bluetooth interface std::unique_ptr bluetooth; - + // Managers std::unique_ptr cryptoManager; std::unique_ptr compressionManager; std::unique_ptr packetSerializer; - + // State std::string peerId; std::string nickname; @@ -82,47 +84,42 @@ class BitchatManager { std::map onlinePeers; std::vector messageHistory; std::set processedMessages; - + // Threading std::atomic shouldExit; std::thread announceThread; std::thread cleanupThread; - + // Mutexes mutable std::mutex peersMutex; mutable std::mutex messagesMutex; mutable std::mutex processedMutex; - + // Callbacks MessageCallback messageCallback; PeerCallback peerJoinedCallback; PeerCallback peerLeftCallback; StatusCallback statusCallback; - + // Bluetooth event handlers - void onPeerConnected(const std::string& peerId, const std::string& nickname); - void onPeerDisconnected(const std::string& peerId); - void onMessageReceived(const BitchatMessage& message); - void onPacketReceived(const BitchatPacket& packet); - + void onPeerConnected(const std::string &peerId, const std::string &nickname); + void onPeerDisconnected(const std::string &peerId); + void onMessageReceived(const BitchatMessage &message); + void onPacketReceived(const BitchatPacket &packet); + // Internal methods void announceLoop(); void cleanupLoop(); void cleanupStalePeers(); - void processPacket(const BitchatPacket& packet); - void relayPacket(const BitchatPacket& packet); - bool wasMessageProcessed(const std::string& messageId); - void markMessageProcessed(const std::string& messageId); - void logMessage(const std::string& message); + void processPacket(const BitchatPacket &packet); + void relayPacket(const BitchatPacket &packet); + bool wasMessageProcessed(const std::string &messageId); + void markMessageProcessed(const std::string &messageId); // Constants static constexpr int ANNOUNCE_INTERVAL = 15; // seconds - static constexpr int CLEANUP_INTERVAL = 30; // seconds - static constexpr int PEER_TIMEOUT = 180; // seconds + static constexpr int CLEANUP_INTERVAL = 30; // seconds + static constexpr int PEER_TIMEOUT = 180; // seconds }; -// Factory function for creating platform-specific Bluetooth interface -std::unique_ptr createAppleBluetoothBridge(); -std::unique_ptr createLinuxBluetoothBridge(); - -} // namespace bitchat \ No newline at end of file +} // namespace bitchat diff --git a/include/bitchat/core/constants.h b/include/bitchat/core/constants.h new file mode 100644 index 0000000..ae13b6d --- /dev/null +++ b/include/bitchat/core/constants.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +namespace bitchat +{ + +namespace constants +{ + +// Service and Characteristic UUIDs for Bitchat BLE Protocol +const std::string BLE_SERVICE_UUID = "F47B5E2D-4A9E-4C5A-9B3F-8E1D2C3A4B5C"; +const std::string BLE_CHARACTERISTIC_UUID = "A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D"; + +// BLE Configuration Constants +const double BLE_SCAN_INTERVAL_SECONDS = 0.1; +const double BLE_CONNECTION_TIMEOUT_SECONDS = 10.0; + +// Packet validation constants +const size_t BLE_MIN_PACKET_SIZE_BYTES = 21; +const size_t BLE_MAX_PACKET_SIZE_BYTES = 512; + +// Peer ID generation constants +const size_t BLE_PEER_ID_LENGTH_CHARS = 8; + +// BLE Service Properties +const uint32_t BLE_CHARACTERISTIC_PROPERTIES = + 0x02 | // Read + 0x08 | // Write + 0x04 | // Write Without Response + 0x10; // Notify + +// BLE Service Permissions +const uint32_t BLE_CHARACTERISTIC_PERMISSIONS = + 0x01 | // Readable + 0x02; // Writeable + +} // namespace constants + +} // namespace bitchat diff --git a/include/bitchat/crypto/crypto_manager.h b/include/bitchat/crypto/crypto_manager.h index 200a20c..83e8c4a 100644 --- a/include/bitchat/crypto/crypto_manager.h +++ b/include/bitchat/crypto/crypto_manager.h @@ -1,63 +1,65 @@ #pragma once -#include -#include #include #include #include +#include +#include // Forward declaration for OpenSSL types typedef struct evp_pkey_st EVP_PKEY; -namespace bitchat { +namespace bitchat +{ // CryptoManager: handles encryption, signatures, and key management -class CryptoManager { +class CryptoManager +{ public: CryptoManager(); ~CryptoManager(); // Initialize crypto subsystem bool initialize(); - + // Cleanup crypto resources void cleanup(); // Generate or load signing key pair - bool generateOrLoadKeyPair(const std::string& keyFile = "bitchat_keypair.pem"); - + bool generateOrLoadKeyPair(const std::string &keyFile = "bitchat_keypair.pem"); + // Sign data with private key - std::vector signData(const std::vector& data); - + std::vector signData(const std::vector &data); + // Verify signature with peer's public key - bool verifySignature(const std::vector& data, - const std::vector& signature, - const std::string& peerId); - + bool verifySignature(const std::vector &data, + const std::vector &signature, + const std::string &peerId); + // Add peer's public key (combined format from Swift - 96 bytes) - bool addPeerPublicKey(const std::string& peerId, const std::vector& combinedKeyData); - + bool addPeerPublicKey(const std::string &peerId, const std::vector &combinedKeyData); + // Get combined public key data (Swift format - 96 bytes) std::vector getCombinedPublicKeyData() const; - + // Get public key bytes std::vector getPublicKeyBytes() const; - + // Save peer public key to file - void savePeerPublicKey(const std::string& peerId, const std::vector& pubkey); - + void savePeerPublicKey(const std::string &peerId, const std::vector &pubkey); + // Check if we have a key for a peer - bool hasPeerKey(const std::string& peerId) const; + bool hasPeerKey(const std::string &peerId) const; private: - EVP_PKEY* signingPrivateKey; - std::map peerSigningKeys; + EVP_PKEY *signingPrivateKey; + std::map peerSigningKeys; mutable std::mutex cryptoMutex; - + // Helper functions - EVP_PKEY* loadPrivateKey(const std::string& filename); - void savePrivateKey(EVP_PKEY* pkey, const std::string& filename); - std::vector getPublicKeyBytes(EVP_PKEY* pkey) const; + EVP_PKEY *loadPrivateKey(const std::string &filename); + void savePrivateKey(EVP_PKEY *pkey, const std::string &filename); + std::vector getPublicKeyBytes(EVP_PKEY *pkey) const; }; -} // namespace bitchat \ No newline at end of file +} // namespace bitchat \ No newline at end of file diff --git a/include/bitchat/platform/bluetooth_bridge.h b/include/bitchat/platform/bluetooth_bridge.h deleted file mode 100644 index 800344d..0000000 --- a/include/bitchat/platform/bluetooth_bridge.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include "bitchat/platform/bluetooth_interface.h" -#include - -namespace bitchat { - std::unique_ptr createAppleBluetoothBridge(); -} \ No newline at end of file diff --git a/include/bitchat/platform/bluetooth_factory.h b/include/bitchat/platform/bluetooth_factory.h new file mode 100644 index 0000000..59a385c --- /dev/null +++ b/include/bitchat/platform/bluetooth_factory.h @@ -0,0 +1,13 @@ +#pragma once + +#include "bitchat/platform/bluetooth_interface.h" +#include + +namespace bitchat +{ + +// Factory function that creates the appropriate Bluetooth interface for the current platform +// Each platform implements this function to return their specific implementation +std::unique_ptr createBluetoothInterface(); + +} // namespace bitchat diff --git a/include/bitchat/platform/bluetooth_interface.h b/include/bitchat/platform/bluetooth_interface.h index 9058c86..87c6553 100644 --- a/include/bitchat/platform/bluetooth_interface.h +++ b/include/bitchat/platform/bluetooth_interface.h @@ -1,53 +1,55 @@ #pragma once -#include -#include #include #include +#include +#include -namespace bitchat { +namespace bitchat +{ // Forward declarations struct BitchatPacket; struct BitchatMessage; // Callback types for Bluetooth transport events - PURE TRANSPORT ONLY -using PeerDisconnectedCallback = std::function; -using PacketReceivedCallback = std::function; +using PeerDisconnectedCallback = std::function; +using PacketReceivedCallback = std::function; // Abstract Bluetooth interface that platforms must implement - PURE TRANSPORT ONLY // This interface handles only BLE transport, all business logic is in BitchatManager -class BluetoothInterface { +class BluetoothInterface +{ public: virtual ~BluetoothInterface() = default; // Initialize Bluetooth subsystem virtual bool initialize() = 0; - + // Start advertising and scanning virtual bool start() = 0; - + // Stop all Bluetooth operations virtual void stop() = 0; - + // Send packet to all connected peers - PURE TRANSPORT - virtual bool sendPacket(const BitchatPacket& packet) = 0; - + virtual bool sendPacket(const BitchatPacket &packet) = 0; + // Send packet to specific peer - PURE TRANSPORT - virtual bool sendPacketToPeer(const BitchatPacket& packet, const std::string& peerId) = 0; - + virtual bool sendPacketToPeer(const BitchatPacket &packet, const std::string &peerId) = 0; + // Check if Bluetooth is ready virtual bool isReady() const = 0; - + // Get local peer ID virtual std::string getLocalPeerId() const = 0; - + // Set callbacks - PURE TRANSPORT ONLY virtual void setPeerDisconnectedCallback(PeerDisconnectedCallback callback) = 0; virtual void setPacketReceivedCallback(PacketReceivedCallback callback) = 0; - + // Get connected peers count virtual size_t getConnectedPeersCount() const = 0; }; -} // namespace bitchat \ No newline at end of file +} // namespace bitchat \ No newline at end of file diff --git a/include/bitchat/protocol/packet.h b/include/bitchat/protocol/packet.h index a1d65a1..46458ee 100644 --- a/include/bitchat/protocol/packet.h +++ b/include/bitchat/protocol/packet.h @@ -1,10 +1,11 @@ #pragma once #include -#include #include +#include -namespace bitchat { +namespace bitchat +{ // Packet type constants constexpr uint8_t PKT_VERSION = 1; @@ -30,7 +31,8 @@ constexpr uint8_t FLAG_IS_COMPRESSED = 0x04; constexpr uint8_t PKT_TTL = 7; // BitchatPacket: represents a protocol packet sent via Bluetooth -struct BitchatPacket { +struct BitchatPacket +{ uint8_t version = PKT_VERSION; uint8_t type = 0; uint8_t ttl = PKT_TTL; @@ -46,7 +48,8 @@ struct BitchatPacket { }; // BitchatMessage: represents a chat message -struct BitchatMessage { +struct BitchatMessage +{ std::string id; std::string sender; std::string content; @@ -65,7 +68,8 @@ struct BitchatMessage { }; // OnlinePeer: represents an online peer in the network -struct OnlinePeer { +struct OnlinePeer +{ std::string nick; std::string canal; std::vector peerid; @@ -79,13 +83,13 @@ struct OnlinePeer { // Utility functions std::string packetTypeToString(uint8_t type); -std::string toHex(const std::vector& data); -std::string toHexCompact(const std::vector& data); -std::vector stringToVector(const std::string& str); -std::string vectorToString(const std::vector& vec); -std::string normalizePeerId(const std::string& peerId); +std::string toHex(const std::vector &data); +std::string toHexCompact(const std::vector &data); +std::vector stringToVector(const std::string &str); +std::string vectorToString(const std::vector &vec); +std::string normalizePeerId(const std::string &peerId); std::string randomPeerId(); std::string uuidv4(); std::string randomNickname(); -} // namespace bitchat \ No newline at end of file +} // namespace bitchat \ No newline at end of file diff --git a/include/bitchat/protocol/packet_serializer.h b/include/bitchat/protocol/packet_serializer.h index d5059fb..6d7f905 100644 --- a/include/bitchat/protocol/packet_serializer.h +++ b/include/bitchat/protocol/packet_serializer.h @@ -3,50 +3,52 @@ #include "packet.h" #include -namespace bitchat { +namespace bitchat +{ // PacketSerializer: handles serialization and deserialization of packets -class PacketSerializer { +class PacketSerializer +{ public: PacketSerializer(); ~PacketSerializer() = default; // Serialize packet to binary data - std::vector serializePacket(const BitchatPacket& packet); - + std::vector serializePacket(const BitchatPacket &packet); + // Deserialize binary data to packet - BitchatPacket deserializePacket(const std::vector& data); - + BitchatPacket deserializePacket(const std::vector &data); + // Create message payload - std::vector makeMessagePayload(const BitchatMessage& message); - + std::vector makeMessagePayload(const BitchatMessage &message); + // Parse message payload - BitchatMessage parseMessagePayload(const std::vector& payload); - + BitchatMessage parseMessagePayload(const std::vector &payload); + // Create announce payload - std::vector makeAnnouncePayload(const std::string& nickname); - + std::vector makeAnnouncePayload(const std::string &nickname); + // Parse announce payload - void parseAnnouncePayload(const std::vector& payload, std::string& nickname); - + void parseAnnouncePayload(const std::vector &payload, std::string &nickname); + // Create packet with proper fields - BitchatPacket makePacket(uint8_t type, const std::vector& payload, - bool hasRecipient = false, bool hasSignature = false, - const std::string& senderId = ""); + BitchatPacket makePacket(uint8_t type, const std::vector &payload, + bool hasRecipient = false, bool hasSignature = false, + const std::string &senderId = ""); private: // Helper functions for serialization - void writeUint64(std::vector& data, uint64_t value); - void writeUint16(std::vector& data, uint16_t value); - void writeUint8(std::vector& data, uint8_t value); - + void writeUint64(std::vector &data, uint64_t value); + void writeUint16(std::vector &data, uint16_t value); + void writeUint8(std::vector &data, uint8_t value); + // Helper functions for deserialization - uint64_t readUint64(const std::vector& data, size_t& offset); - uint16_t readUint16(const std::vector& data, size_t& offset); - uint8_t readUint8(const std::vector& data, size_t& offset); - + uint64_t readUint64(const std::vector &data, size_t &offset); + uint16_t readUint16(const std::vector &data, size_t &offset); + uint8_t readUint8(const std::vector &data, size_t &offset); + // Validate packet size - bool validatePacketSize(const std::vector& data, size_t expectedSize); + bool validatePacketSize(const std::vector &data, size_t expectedSize); }; -} // namespace bitchat \ No newline at end of file +} // namespace bitchat \ No newline at end of file diff --git a/include/platforms/apple/bluetooth.h b/include/platforms/apple/bluetooth.h index ade78df..ee9b3ae 100644 --- a/include/platforms/apple/bluetooth.h +++ b/include/platforms/apple/bluetooth.h @@ -1,28 +1,28 @@ #pragma once -#import #import +#import // Objective-C interface for Apple Bluetooth implementation - PURE BLE ONLY // This header should ONLY be included by .mm or .m files @interface AppleBluetooth : NSObject // Properties -@property (nonatomic, strong) CBCentralManager *centralManager; -@property (nonatomic, strong) CBPeripheralManager *peripheralManager; -@property (nonatomic, strong) CBMutableCharacteristic *mutableCharacteristic; -@property (nonatomic, strong) NSMutableArray *discoveredPeripherals; -@property (nonatomic, strong) NSMutableDictionary *connectedPeripherals; -@property (nonatomic, strong) NSMutableDictionary *peripheralCharacteristics; -@property (nonatomic, strong) NSMutableArray *subscribedCentrals; -@property (nonatomic, assign) BOOL ready; -@property (nonatomic, strong) NSLock *lock; -@property (nonatomic, strong) dispatch_queue_t bleQueue; -@property (nonatomic, strong) NSString *localPeerId; +@property(nonatomic, strong) CBCentralManager *centralManager; +@property(nonatomic, strong) CBPeripheralManager *peripheralManager; +@property(nonatomic, strong) CBMutableCharacteristic *mutableCharacteristic; +@property(nonatomic, strong) NSMutableArray *discoveredPeripherals; +@property(nonatomic, strong) NSMutableDictionary *connectedPeripherals; +@property(nonatomic, strong) NSMutableDictionary *peripheralCharacteristics; +@property(nonatomic, strong) NSMutableArray *subscribedCentrals; +@property(nonatomic, assign) BOOL ready; +@property(nonatomic, strong) NSLock *lock; +@property(nonatomic, strong) dispatch_queue_t bleQueue; +@property(nonatomic, strong) NSString *localPeerId; // Callback properties - PURE BLE ONLY -@property (nonatomic, copy) void (^peerDisconnectedCallback)(NSString*); -@property (nonatomic, copy) void (^packetReceivedCallback)(NSData*); +@property(nonatomic, copy) void (^peerDisconnectedCallback)(NSString *); +@property(nonatomic, copy) void (^packetReceivedCallback)(NSData *); // Initialization - (instancetype)init; @@ -48,4 +48,4 @@ + (NSString *)serviceUUID; + (NSString *)characteristicUUID; -@end \ No newline at end of file +@end \ No newline at end of file diff --git a/include/platforms/linux/bluetooth.h b/include/platforms/linux/bluetooth.h index 99077f4..3e2c060 100644 --- a/include/platforms/linux/bluetooth.h +++ b/include/platforms/linux/bluetooth.h @@ -1,19 +1,19 @@ #pragma once #include "bitchat/platform/bluetooth_interface.h" -#include -#include -#include -#include #include +#include #include #include -#include -#include +#include +#include +#include -namespace bitchat { +namespace bitchat +{ -class LinuxBluetooth : public BluetoothInterface { +class LinuxBluetooth : public BluetoothInterface +{ public: LinuxBluetooth(); ~LinuxBluetooth() override; @@ -21,8 +21,8 @@ class LinuxBluetooth : public BluetoothInterface { bool initialize() override; bool start() override; void stop() override; - bool sendPacket(const BitchatPacket& packet) override; - bool sendPacketToPeer(const BitchatPacket& packet, const std::string& peerId) override; + bool sendPacket(const BitchatPacket &packet) override; + bool sendPacketToPeer(const BitchatPacket &packet, const std::string &peerId) override; bool isReady() const override; std::string getLocalPeerId() const override; void setPeerDisconnectedCallback(PeerDisconnectedCallback callback) override; @@ -30,25 +30,24 @@ class LinuxBluetooth : public BluetoothInterface { size_t getConnectedPeersCount() const override; private: - void scan_thread_func(); - void reader_thread_func(const std::string& device_id, int socket); - void accept_thread_func(); + void scanThreadFunc(); + void readerThreadFunc(const std::string &deviceId, int socket); + void acceptThreadFunc(); - int dev_id_; - int hci_socket_; - int rfcomm_socket_; - std::string local_peer_id_; + int deviceId; + int hciSocket; + int rfcommSocket; + std::string localPeerId; - std::thread scan_thread_; - std::thread accept_thread_; - std::atomic stop_threads_; + std::thread scanThread; + std::thread acceptThread; + std::atomic stopThreads; - PacketReceivedCallback packet_received_callback_; - PeerDisconnectedCallback peer_disconnected_callback_; + PacketReceivedCallback packetReceivedCallback; + PeerDisconnectedCallback peerDisconnectedCallback; - std::map connected_sockets_; - mutable std::mutex sockets_mutex_; - std::shared_ptr logger_; + std::map connectedSockets; + mutable std::mutex socketsMutex; }; -} // namespace bitchat \ No newline at end of file +} // namespace bitchat diff --git a/main.cpp b/main.cpp index bea4ad9..764cad3 100644 --- a/main.cpp +++ b/main.cpp @@ -7,7 +7,7 @@ #include // Global manager instance -std::unique_ptr g_manager; +std::unique_ptr manager; // Callback functions for UI updates void onMessageReceived(const bitchat::BitchatMessage& message) { @@ -16,7 +16,7 @@ void onMessageReceived(const bitchat::BitchatMessage& message) { char timebuf[10]; std::tm* tinfo = std::localtime(×tamp); std::strftime(timebuf, sizeof(timebuf), "%H:%M", tinfo); - + // Display message spdlog::info("[{}] {}: {}", timebuf, message.sender, message.content); } @@ -34,24 +34,24 @@ void onStatusUpdate(const std::string& status) { } void showOnlinePeers() { - if (!g_manager) return; - - auto peers = g_manager->getOnlinePeers(); + if (!manager) return; + + auto peers = manager->getOnlinePeers(); spdlog::info("\nPeople online:"); - + time_t now = time(nullptr); bool found = false; - + for (const auto& [peerId, peer] : peers) { // Show all peers that have been seen recently (within 3 minutes) if ((now - peer.lastSeen) < 180) { std::string peerInfo = "- " + peer.nick; - + // Check if this is us (by comparing peer ID) - if (peerId == g_manager->getPeerId()) { + if (peerId == manager->getPeerId()) { peerInfo += " (you)"; } - + if (!peer.canal.empty()) { peerInfo += " (channel: " + peer.canal + ")"; } @@ -62,7 +62,7 @@ void showOnlinePeers() { found = true; } } - + if (!found) { spdlog::info("No one online at the moment."); } @@ -82,9 +82,9 @@ void showHelp() { void clearScreen() { #ifdef _WIN32 - system("cls"); + (void)system("cls"); #else - system("clear"); + (void)system("clear"); #endif } @@ -94,34 +94,34 @@ int main() { auto logger = std::make_shared("bitchat", console_sink); spdlog::set_default_logger(logger); spdlog::set_pattern("[%H:%M:%S] %v"); - + spdlog::info("=== Bitchat Terminal Client ==="); - + // Create and initialize manager - g_manager = std::make_unique(); - + manager = std::make_unique(); + // Set callbacks - g_manager->setMessageCallback(onMessageReceived); - g_manager->setPeerJoinedCallback(onPeerJoined); - g_manager->setPeerLeftCallback(onPeerLeft); - g_manager->setStatusCallback(onStatusUpdate); - + manager->setMessageCallback(onMessageReceived); + manager->setPeerJoinedCallback(onPeerJoined); + manager->setPeerLeftCallback(onPeerLeft); + manager->setStatusCallback(onStatusUpdate); + // Initialize - if (!g_manager->initialize()) { + if (!manager->initialize()) { spdlog::error("Failed to initialize BitchatManager"); return 1; } - + // Start - if (!g_manager->start()) { + if (!manager->start()) { spdlog::error("Failed to start BitchatManager"); return 1; } - + spdlog::info("Connected! Type /help for commands."); - spdlog::info("Peer ID: {}", g_manager->getPeerId()); - spdlog::info("Nickname: {}", g_manager->getNickname()); - + spdlog::info("Peer ID: {}", manager->getPeerId()); + spdlog::info("Nickname: {}", manager->getNickname()); + // Main command loop std::string line; while (true) { @@ -132,14 +132,14 @@ int main() { showHelp(); } else if (line.rfind("/j ", 0) == 0) { std::string channel = line.substr(3); - g_manager->joinChannel(channel); + manager->joinChannel(channel); spdlog::info("Joined channel: {}", channel); } else if (line == "/j") { - g_manager->joinChannel("#general"); + manager->joinChannel("#general"); spdlog::info("Joined general chat"); } else if (line.rfind("/nick ", 0) == 0) { std::string nickname = line.substr(6); - g_manager->setNickname(nickname); + manager->setNickname(nickname); spdlog::info("Nickname changed to: {}", nickname); } else if (line == "/w") { showOnlinePeers(); @@ -151,7 +151,7 @@ int main() { spdlog::warn("Unknown command. Type /help for available commands."); } else { // Send message - if (g_manager->sendMessage(line)) { + if (manager->sendMessage(line)) { time_t now = time(nullptr); char timebuf[10]; std::tm* tinfo = std::localtime(&now); @@ -162,15 +162,15 @@ int main() { } } } - + // Small delay to prevent busy waiting std::this_thread::sleep_for(std::chrono::milliseconds(10)); } - + // Cleanup - g_manager->stop(); - g_manager.reset(); - + manager->stop(); + manager.reset(); + spdlog::info("Disconnected."); return 0; -} \ No newline at end of file +} diff --git a/src/bitchat/compression/compression_manager.cpp b/src/bitchat/compression/compression_manager.cpp index dedfd5d..bb8d8f2 100644 --- a/src/bitchat/compression/compression_manager.cpp +++ b/src/bitchat/compression/compression_manager.cpp @@ -1,21 +1,25 @@ #include "bitchat/compression/compression_manager.h" #include "lz4.h" -#include #include +#include -namespace bitchat { +namespace bitchat +{ CompressionManager::CompressionManager() = default; -std::vector CompressionManager::compressData(const std::vector& data) { +std::vector CompressionManager::compressData(const std::vector &data) +{ // Skip compression for small data - if (data.size() < COMPRESSION_THRESHOLD) { + if (data.size() < COMPRESSION_THRESHOLD) + { return data; } // Calculate maximum compressed size int maxCompressedSize = calculateCompressionBound(data.size()); - if (maxCompressedSize <= 0) { + if (maxCompressedSize <= 0) + { return data; // Return original on error } @@ -24,18 +28,19 @@ std::vector CompressionManager::compressData(const std::vector // Compress using LZ4 int compressedSize = LZ4_compress_default( - reinterpret_cast(data.data()), - reinterpret_cast(compressed.data()), + reinterpret_cast(data.data()), + reinterpret_cast(compressed.data()), static_cast(data.size()), - maxCompressedSize - ); + maxCompressedSize); - if (compressedSize <= 0) { + if (compressedSize <= 0) + { return data; // Return original on error } // Only return compressed if it's actually smaller - if (compressedSize < static_cast(data.size())) { + if (compressedSize < static_cast(data.size())) + { compressed.resize(compressedSize); return compressed; } @@ -43,24 +48,26 @@ std::vector CompressionManager::compressData(const std::vector return data; // Return original if compression didn't help } -std::vector CompressionManager::decompressData(const std::vector& compressedData, - size_t originalSize) { +std::vector CompressionManager::decompressData(const std::vector &compressedData, + size_t originalSize) +{ // Allocate buffer for decompressed data std::vector decompressed(originalSize); // Decompress using LZ4 int decompressedSize = LZ4_decompress_safe( - reinterpret_cast(compressedData.data()), - reinterpret_cast(decompressed.data()), + reinterpret_cast(compressedData.data()), + reinterpret_cast(decompressed.data()), static_cast(compressedData.size()), - static_cast(originalSize) - ); + static_cast(originalSize)); - if (decompressedSize <= 0) { + if (decompressedSize <= 0) + { return compressedData; // Return original on error } - if (decompressedSize != static_cast(originalSize)) { + if (decompressedSize != static_cast(originalSize)) + { // Size mismatch, resize to actual size decompressed.resize(decompressedSize); } @@ -68,9 +75,11 @@ std::vector CompressionManager::decompressData(const std::vector& data) const { +bool CompressionManager::shouldCompress(const std::vector &data) const +{ // Don't compress if data is too small - if (data.size() < COMPRESSION_THRESHOLD) { + if (data.size() < COMPRESSION_THRESHOLD) + { return false; } @@ -78,13 +87,14 @@ bool CompressionManager::shouldCompress(const std::vector& data) const std::set uniqueBytes(data.begin(), data.end()); // If we have very high byte diversity, data is likely already compressed - double uniqueByteRatio = static_cast(uniqueBytes.size()) / + double uniqueByteRatio = static_cast(uniqueBytes.size()) / std::min(data.size(), static_cast(256)); return uniqueByteRatio < 0.9; // Compress if less than 90% unique bytes } -int CompressionManager::calculateCompressionBound(size_t dataSize) const { +int CompressionManager::calculateCompressionBound(size_t dataSize) const +{ return LZ4_compressBound(static_cast(dataSize)); } -} // namespace bitchat \ No newline at end of file +} // namespace bitchat \ No newline at end of file diff --git a/src/bitchat/core/bitchat_manager.cpp b/src/bitchat/core/bitchat_manager.cpp index b4112ff..6cbc69f 100644 --- a/src/bitchat/core/bitchat_manager.cpp +++ b/src/bitchat/core/bitchat_manager.cpp @@ -1,21 +1,25 @@ #include "bitchat/core/bitchat_manager.h" -#include "bitchat/platform/bluetooth_interface.h" -#include -#include +#include "bitchat/platform/bluetooth_factory.h" #include #include +#include +#include -namespace bitchat { +namespace bitchat +{ -BitchatManager::BitchatManager() - : shouldExit(false) { +BitchatManager::BitchatManager() + : shouldExit(false) +{ } -BitchatManager::~BitchatManager() { +BitchatManager::~BitchatManager() +{ stop(); } -bool BitchatManager::initialize() { +bool BitchatManager::initialize() +{ // Generate peer ID and random nickname peerId = randomPeerId(); nickname = randomNickname(); @@ -27,61 +31,62 @@ bool BitchatManager::initialize() { packetSerializer = std::make_unique(); // Initialize crypto - if (!cryptoManager->initialize()) { - std::cerr << "Failed to initialize crypto manager" << std::endl; + if (!cryptoManager->initialize()) + { + spdlog::error("Failed to initialize crypto manager"); return false; } - if (!cryptoManager->generateOrLoadKeyPair()) { - std::cerr << "Failed to generate or load key pair" << std::endl; + if (!cryptoManager->generateOrLoadKeyPair()) + { + spdlog::error("Failed to generate or load key pair"); return false; } // Create Bluetooth interface - try { -#if __APPLE__ - bluetooth = createAppleBluetoothBridge(); -#elif _WIN32 || _WIN64 - // TODO: Initialize Windows Bluetooth interface - throw std::runtime_error("Windows Bluetooth interface not implemented"); -#elif __linux__ - bluetooth = createLinuxBluetoothBridge(); -#else - std::cerr << "Unsupported platform for Bluetooth interface" << std::endl; - throw std::runtime_error("Unsupported platform for Bluetooth interface"); -#endif - } catch (const std::exception& e) { - std::cerr << "Failed to create Bluetooth interface: " << e.what() << std::endl; - return false; + try + { + bluetooth = createBluetoothInterface(); + if (!bluetooth) + { + throw std::runtime_error("Failed to create Bluetooth interface for current platform"); + } + } + catch (const std::exception &e) + { + spdlog::error("Failed to create Bluetooth interface: {}", e.what()); + return false; } // Set Bluetooth callbacks - bluetooth->setPeerDisconnectedCallback([this](const std::string& peerId) { - onPeerDisconnected(peerId); - }); + bluetooth->setPeerDisconnectedCallback([this](const std::string &peerId) + { onPeerDisconnected(peerId); }); - bluetooth->setPacketReceivedCallback([this](const BitchatPacket& packet) { - onPacketReceived(packet); - }); + bluetooth->setPacketReceivedCallback([this](const BitchatPacket &packet) + { onPacketReceived(packet); }); return true; } -bool BitchatManager::start() { - if (!bluetooth) { - std::cerr << "Bluetooth interface not initialized" << std::endl; +bool BitchatManager::start() +{ + if (!bluetooth) + { + spdlog::error("Bluetooth interface not initialized"); return false; } // Initialize Bluetooth - if (!bluetooth->initialize()) { - std::cerr << "Failed to initialize Bluetooth" << std::endl; + if (!bluetooth->initialize()) + { + spdlog::error("Failed to initialize Bluetooth"); return false; } // Start Bluetooth - if (!bluetooth->start()) { - std::cerr << "Failed to start Bluetooth" << std::endl; + if (!bluetooth->start()) + { + spdlog::error("Failed to start Bluetooth"); return false; } @@ -93,25 +98,31 @@ bool BitchatManager::start() { return true; } -void BitchatManager::stop() { +void BitchatManager::stop() +{ shouldExit = true; // Wait for threads to finish - if (announceThread.joinable()) { + if (announceThread.joinable()) + { announceThread.join(); } - if (cleanupThread.joinable()) { + if (cleanupThread.joinable()) + { cleanupThread.join(); } // Stop Bluetooth - if (bluetooth) { + if (bluetooth) + { bluetooth->stop(); } } -bool BitchatManager::sendMessage(const std::string& content) { - if (content.empty()) { +bool BitchatManager::sendMessage(const std::string &content) +{ + if (content.empty()) + { return false; } @@ -120,16 +131,19 @@ bool BitchatManager::sendMessage(const std::string& content) { message.sender = nickname; message.content = content; message.timestamp = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); - + std::chrono::system_clock::now().time_since_epoch()) + .count(); + // Only set channel if we're actually in a channel (not in general/main chat) - if (currentChannel != "#general") { + if (currentChannel != "#general") + { message.channel = currentChannel; - if (!message.channel.empty() && message.channel[0] != '#') { + if (!message.channel.empty() && message.channel[0] != '#') + { message.channel = "#" + message.channel; } } - + // Use peer ID string for Swift compatibility - convert to vector message.senderPeerID = stringToVector(peerId); @@ -138,15 +152,17 @@ bool BitchatManager::sendMessage(const std::string& content) { // Add signature auto signature = cryptoManager->signData(payload); - if (signature.empty()) { - std::cerr << "Failed to sign message" << std::endl; + if (signature.empty()) + { + spdlog::error("Failed to sign message"); return false; } packet.signature = signature; // Send packet - if (!bluetooth->sendPacket(packet)) { - std::cerr << "Failed to send message packet" << std::endl; + if (!bluetooth->sendPacket(packet)) + { + spdlog::error("Failed to send message packet"); return false; } @@ -154,75 +170,87 @@ bool BitchatManager::sendMessage(const std::string& content) { { std::lock_guard lock(messagesMutex); messageHistory.push_back(message); - if (messageHistory.size() > 100) { + if (messageHistory.size() > 100) + { messageHistory.erase(messageHistory.begin()); } } - // Log message - logMessage("You: " + content); - return true; } -void BitchatManager::joinChannel(const std::string& channel) { +void BitchatManager::joinChannel(const std::string &channel) +{ currentChannel = channel; - if (!currentChannel.empty() && currentChannel[0] != '#') { + if (!currentChannel.empty() && currentChannel[0] != '#') + { currentChannel = "#" + currentChannel; } } -void BitchatManager::setNickname(const std::string& nickname) { +void BitchatManager::setNickname(const std::string &nickname) +{ this->nickname = nickname; } -std::string BitchatManager::getCurrentChannel() const { +std::string BitchatManager::getCurrentChannel() const +{ return currentChannel; } -std::string BitchatManager::getNickname() const { +std::string BitchatManager::getNickname() const +{ return nickname; } -std::string BitchatManager::getPeerId() const { +std::string BitchatManager::getPeerId() const +{ return peerId; } -std::map BitchatManager::getOnlinePeers() const { +std::map BitchatManager::getOnlinePeers() const +{ std::lock_guard lock(peersMutex); return onlinePeers; } -std::vector BitchatManager::getMessageHistory() const { +std::vector BitchatManager::getMessageHistory() const +{ std::lock_guard lock(messagesMutex); return messageHistory; } -bool BitchatManager::isReady() const { +bool BitchatManager::isReady() const +{ return bluetooth && bluetooth->isReady(); } -void BitchatManager::setMessageCallback(MessageCallback callback) { +void BitchatManager::setMessageCallback(MessageCallback callback) +{ messageCallback = callback; } -void BitchatManager::setPeerJoinedCallback(PeerCallback callback) { +void BitchatManager::setPeerJoinedCallback(PeerCallback callback) +{ peerJoinedCallback = callback; } -void BitchatManager::setPeerLeftCallback(PeerCallback callback) { +void BitchatManager::setPeerLeftCallback(PeerCallback callback) +{ peerLeftCallback = callback; } -void BitchatManager::setStatusCallback(StatusCallback callback) { +void BitchatManager::setStatusCallback(StatusCallback callback) +{ statusCallback = callback; } // Bluetooth event handlers -void BitchatManager::onPeerConnected(const std::string& peerId, const std::string& nickname) { +void BitchatManager::onPeerConnected(const std::string &peerId, const std::string &nickname) +{ std::lock_guard lock(peersMutex); - - OnlinePeer& peer = onlinePeers[peerId]; + + OnlinePeer &peer = onlinePeers[peerId]; bool isNewPeer = !peer.hasAnnounced; peer.nick = nickname; peer.canal = ""; @@ -230,44 +258,53 @@ void BitchatManager::onPeerConnected(const std::string& peerId, const std::strin peer.lastSeen = time(nullptr); peer.hasAnnounced = true; - if (isNewPeer && peerId != this->peerId && peerJoinedCallback) { + if (isNewPeer && peerId != this->peerId && peerJoinedCallback) + { peerJoinedCallback(peerId, nickname); } } -void BitchatManager::onPeerDisconnected(const std::string& peerId) { +void BitchatManager::onPeerDisconnected(const std::string &peerId) +{ std::string nickname; - + { std::lock_guard lock(peersMutex); auto it = onlinePeers.find(peerId); - if (it != onlinePeers.end()) { + if (it != onlinePeers.end()) + { nickname = it->second.nick; onlinePeers.erase(it); } } - if (!nickname.empty() && peerLeftCallback) { + if (!nickname.empty() && peerLeftCallback) + { peerLeftCallback(peerId, nickname); } } -void BitchatManager::onMessageReceived(const BitchatMessage& message) { +void BitchatManager::onMessageReceived(const BitchatMessage &message) +{ // Check if message is for current channel or is broadcast std::string messageChannel = message.channel; std::string currentChannelNormalized = currentChannel; - if (!messageChannel.empty() && messageChannel[0] == '#') { + if (!messageChannel.empty() && messageChannel[0] == '#') + { messageChannel = messageChannel.substr(1); } - if (!currentChannelNormalized.empty() && currentChannelNormalized[0] == '#') { + if (!currentChannelNormalized.empty() && currentChannelNormalized[0] == '#') + { currentChannelNormalized = currentChannelNormalized.substr(1); } bool isBroadcast = true; // For now, treat all messages as broadcast - if (isBroadcast || messageChannel == currentChannelNormalized || message.channel.empty()) { + if (isBroadcast || messageChannel == currentChannelNormalized || message.channel.empty()) + { // Skip empty messages - if (message.content.empty()) { + if (message.content.empty()) + { return; } @@ -275,22 +312,26 @@ void BitchatManager::onMessageReceived(const BitchatMessage& message) { { std::lock_guard lock(messagesMutex); messageHistory.push_back(message); - if (messageHistory.size() > 100) { + if (messageHistory.size() > 100) + { messageHistory.erase(messageHistory.begin()); } } // Update peer info std::string senderId; - if (!message.senderPeerID.empty()) { + if (!message.senderPeerID.empty()) + { senderId = normalizePeerId(toHexCompact(message.senderPeerID)); - } else { + } + else + { senderId = "unknown"; } { std::lock_guard lock(peersMutex); - OnlinePeer& peer = onlinePeers[senderId]; + OnlinePeer &peer = onlinePeers[senderId]; peer.nick = message.sender; peer.canal = message.channel; peer.peerid = message.senderPeerID.empty() ? stringToVector(senderId) : message.senderPeerID; @@ -298,19 +339,19 @@ void BitchatManager::onMessageReceived(const BitchatMessage& message) { } // Call message callback - if (messageCallback) { + if (messageCallback) + { messageCallback(message); } - - // Log message - logMessage(message.sender + ": " + message.content); } } -void BitchatManager::onPacketReceived(const BitchatPacket& packet) { +void BitchatManager::onPacketReceived(const BitchatPacket &packet) +{ std::string messageId = std::to_string(packet.timestamp) + "-" + vectorToString(packet.senderID); - if (wasMessageProcessed(messageId)) { + if (wasMessageProcessed(messageId)) + { return; // Already processed } @@ -319,126 +360,139 @@ void BitchatManager::onPacketReceived(const BitchatPacket& packet) { } // Internal methods -void BitchatManager::announceLoop() { - while (!shouldExit) { +void BitchatManager::announceLoop() +{ + while (!shouldExit) + { auto payload = packetSerializer->makeAnnouncePayload(nickname); auto packet = packetSerializer->makePacket(PKT_TYPE_ANNOUNCE, payload, true, true, peerId); // Add signature auto signature = cryptoManager->signData(payload); - if (!signature.empty()) { + if (!signature.empty()) + { packet.signature = signature; } bluetooth->sendPacket(packet); // Sleep for announce interval - for (int i = 0; i < ANNOUNCE_INTERVAL && !shouldExit; ++i) { + for (int i = 0; i < ANNOUNCE_INTERVAL && !shouldExit; ++i) + { std::this_thread::sleep_for(std::chrono::seconds(1)); } } } -void BitchatManager::cleanupLoop() { - while (!shouldExit) { +void BitchatManager::cleanupLoop() +{ + while (!shouldExit) + { cleanupStalePeers(); - + // Sleep for cleanup interval - for (int i = 0; i < CLEANUP_INTERVAL && !shouldExit; ++i) { + for (int i = 0; i < CLEANUP_INTERVAL && !shouldExit; ++i) + { std::this_thread::sleep_for(std::chrono::seconds(1)); } } } -void BitchatManager::cleanupStalePeers() { +void BitchatManager::cleanupStalePeers() +{ std::lock_guard lock(peersMutex); time_t now = time(nullptr); - for (auto it = onlinePeers.begin(); it != onlinePeers.end();) { - if (now - it->second.lastSeen > PEER_TIMEOUT) { + for (auto it = onlinePeers.begin(); it != onlinePeers.end();) + { + if (now - it->second.lastSeen > PEER_TIMEOUT) + { it = onlinePeers.erase(it); - } else { + } + else + { ++it; } } } -void BitchatManager::processPacket(const BitchatPacket& packet) { - switch (packet.type) { - case PKT_TYPE_MESSAGE: { - // Verify if it's our own message - std::string senderId = normalizePeerId(vectorToString(packet.senderID)); - if (senderId == peerId) { - return; // Ignore our own messages - } +void BitchatManager::processPacket(const BitchatPacket &packet) +{ + switch (packet.type) + { + case PKT_TYPE_MESSAGE: + { + // Verify if it's our own message + std::string senderId = normalizePeerId(vectorToString(packet.senderID)); + if (senderId == peerId) + { + return; // Ignore our own messages + } - // Verify signature if present - if (packet.flags & FLAG_HAS_SIGNATURE && !packet.signature.empty()) { - if (!cryptoManager->verifySignature(packet.payload, packet.signature, senderId)) { - // Signature verification failed, but continue processing - // Key might not be available yet - } + // Verify signature if present + if (packet.flags & FLAG_HAS_SIGNATURE && !packet.signature.empty()) + { + if (!cryptoManager->verifySignature(packet.payload, packet.signature, senderId)) + { + // Signature verification failed, but continue processing + // Key might not be available yet } - - // Parse message - BitchatMessage message = packetSerializer->parseMessagePayload(packet.payload); - onMessageReceived(message); - break; } - case PKT_TYPE_ANNOUNCE: { - std::string nickname; - packetSerializer->parseAnnouncePayload(packet.payload, nickname); - std::string peerId = normalizePeerId(vectorToString(packet.senderID)); - onPeerConnected(peerId, nickname); - break; - } + // Parse message + BitchatMessage message = packetSerializer->parseMessagePayload(packet.payload); + onMessageReceived(message); + break; + } - case PKT_TYPE_KEYEXCHANGE: { - std::string peerId = normalizePeerId(vectorToString(packet.senderID)); - cryptoManager->addPeerPublicKey(peerId, packet.payload); - break; - } + case PKT_TYPE_ANNOUNCE: + { + std::string nickname; + packetSerializer->parseAnnouncePayload(packet.payload, nickname); + std::string peerId = normalizePeerId(vectorToString(packet.senderID)); + onPeerConnected(peerId, nickname); + break; + } - case PKT_TYPE_LEAVE: { - std::string senderId = normalizePeerId(vectorToString(packet.senderID)); - onPeerDisconnected(senderId); - break; - } + case PKT_TYPE_KEYEXCHANGE: + { + std::string peerId = normalizePeerId(vectorToString(packet.senderID)); + cryptoManager->addPeerPublicKey(peerId, packet.payload); + break; + } + + case PKT_TYPE_LEAVE: + { + std::string senderId = normalizePeerId(vectorToString(packet.senderID)); + onPeerDisconnected(senderId); + break; + } } // Relay if TTL > 0 - if (packet.ttl > 1) { + if (packet.ttl > 1) + { relayPacket(packet); } } -void BitchatManager::relayPacket(const BitchatPacket& packet) { +void BitchatManager::relayPacket(const BitchatPacket &packet) +{ BitchatPacket relayPacket = packet; relayPacket.ttl--; bluetooth->sendPacket(relayPacket); } -bool BitchatManager::wasMessageProcessed(const std::string& messageId) { +bool BitchatManager::wasMessageProcessed(const std::string &messageId) +{ std::lock_guard lock(processedMutex); return processedMessages.find(messageId) != processedMessages.end(); } -void BitchatManager::markMessageProcessed(const std::string& messageId) { +void BitchatManager::markMessageProcessed(const std::string &messageId) +{ std::lock_guard lock(processedMutex); processedMessages.insert(messageId); } -void BitchatManager::logMessage(const std::string& message) { - std::ofstream logFile("bitchat.log", std::ios::app); - if (logFile.is_open()) { - auto now = std::chrono::system_clock::now(); - auto time_t = std::chrono::system_clock::to_time_t(now); - std::string timestamp = std::ctime(&time_t); - timestamp.pop_back(); // Remove \n - - logFile << "[" << timestamp << "] " << message << std::endl; - } -} - -} // namespace bitchat \ No newline at end of file +} // namespace bitchat diff --git a/src/bitchat/crypto/crypto_manager.cpp b/src/bitchat/crypto/crypto_manager.cpp index 2f41af2..7e79fbe 100644 --- a/src/bitchat/crypto/crypto_manager.cpp +++ b/src/bitchat/crypto/crypto_manager.cpp @@ -1,268 +1,312 @@ #include "bitchat/crypto/crypto_manager.h" +#include +#include +#include +#include +#include #include #include -#include #include -#include -#include -#include -#include -#include +#include -namespace bitchat { +namespace bitchat +{ -CryptoManager::CryptoManager() : signingPrivateKey(nullptr) { +CryptoManager::CryptoManager() + : signingPrivateKey(nullptr) +{ } -CryptoManager::~CryptoManager() { +CryptoManager::~CryptoManager() +{ cleanup(); } -bool CryptoManager::initialize() { +bool CryptoManager::initialize() +{ OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); return true; } -void CryptoManager::cleanup() { +void CryptoManager::cleanup() +{ std::lock_guard lock(cryptoMutex); - - if (signingPrivateKey) { + + if (signingPrivateKey) + { EVP_PKEY_free(signingPrivateKey); signingPrivateKey = nullptr; } - + // Clean up peer keys - for (auto& pair : peerSigningKeys) { - if (pair.second) { + for (auto &pair : peerSigningKeys) + { + if (pair.second) + { EVP_PKEY_free(pair.second); } } peerSigningKeys.clear(); - + // Clean up OpenSSL EVP_cleanup(); ERR_free_strings(); } -bool CryptoManager::generateOrLoadKeyPair(const std::string& keyFile) { +bool CryptoManager::generateOrLoadKeyPair(const std::string &keyFile) +{ std::lock_guard lock(cryptoMutex); - + // Try to load existing key signingPrivateKey = loadPrivateKey(keyFile); - if (signingPrivateKey) { + if (signingPrivateKey) + { return true; } - + // Generate new Ed25519 key pair - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr); - if (!ctx) { - std::cerr << "Error creating Ed25519 key context" << std::endl; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr); + if (!ctx) + { + spdlog::error("Error creating Ed25519 key context"); return false; } - - if (EVP_PKEY_keygen_init(ctx) <= 0) { - std::cerr << "Error initializing key generation" << std::endl; + + if (EVP_PKEY_keygen_init(ctx) <= 0) + { + spdlog::error("Error initializing key generation"); EVP_PKEY_CTX_free(ctx); return false; } - - if (EVP_PKEY_keygen(ctx, &signingPrivateKey) <= 0) { - std::cerr << "Error generating private key" << std::endl; + + if (EVP_PKEY_keygen(ctx, &signingPrivateKey) <= 0) + { + spdlog::error("Error generating private key"); EVP_PKEY_CTX_free(ctx); return false; } - + EVP_PKEY_CTX_free(ctx); - + // Save the key savePrivateKey(signingPrivateKey, keyFile); - + return true; } -std::vector CryptoManager::signData(const std::vector& data) { +std::vector CryptoManager::signData(const std::vector &data) +{ std::lock_guard lock(cryptoMutex); - - if (!signingPrivateKey) { - std::cerr << "Private key not available for signing" << std::endl; + + if (!signingPrivateKey) + { + spdlog::error("Private key not available for signing"); return std::vector(); } - - EVP_MD_CTX* ctx = EVP_MD_CTX_new(); - if (!ctx) { - std::cerr << "Error creating signature context" << std::endl; + + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (!ctx) + { + spdlog::error("Error creating signature context"); return std::vector(); } - + size_t sigLen = 0; - if (EVP_DigestSignInit(ctx, nullptr, nullptr, nullptr, signingPrivateKey) <= 0) { - std::cerr << "Error initializing signature" << std::endl; + if (EVP_DigestSignInit(ctx, nullptr, nullptr, nullptr, signingPrivateKey) <= 0) + { + spdlog::error("Error initializing signature"); EVP_MD_CTX_free(ctx); return std::vector(); } - + // Calculate signature size - if (EVP_DigestSign(ctx, nullptr, &sigLen, data.data(), data.size()) <= 0) { - std::cerr << "Error calculating signature size" << std::endl; + if (EVP_DigestSign(ctx, nullptr, &sigLen, data.data(), data.size()) <= 0) + { + spdlog::error("Error calculating signature size"); EVP_MD_CTX_free(ctx); return std::vector(); } - + // Create signature std::vector signature(sigLen); - if (EVP_DigestSign(ctx, signature.data(), &sigLen, data.data(), data.size()) <= 0) { - std::cerr << "Error creating signature" << std::endl; + if (EVP_DigestSign(ctx, signature.data(), &sigLen, data.data(), data.size()) <= 0) + { + spdlog::error("Error creating signature"); EVP_MD_CTX_free(ctx); return std::vector(); } - + EVP_MD_CTX_free(ctx); - + // Resize to exact size (Ed25519 = 64 bytes) signature.resize(64); return signature; } -bool CryptoManager::verifySignature(const std::vector& data, - const std::vector& signature, - const std::string& peerId) { +bool CryptoManager::verifySignature(const std::vector &data, + const std::vector &signature, + const std::string &peerId) +{ std::lock_guard lock(cryptoMutex); - + auto it = peerSigningKeys.find(peerId); - if (it == peerSigningKeys.end()) { + if (it == peerSigningKeys.end()) + { return false; // Key not available yet } - - EVP_PKEY* peerKey = it->second; - if (!peerKey) { + + EVP_PKEY *peerKey = it->second; + if (!peerKey) + { return false; } - - EVP_MD_CTX* ctx = EVP_MD_CTX_new(); - if (!ctx) { - std::cerr << "Error creating verification context" << std::endl; + + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (!ctx) + { + spdlog::error("Error creating verification context"); return false; } - - if (EVP_DigestVerifyInit(ctx, nullptr, nullptr, nullptr, peerKey) <= 0) { - std::cerr << "Error initializing verification" << std::endl; + + if (EVP_DigestVerifyInit(ctx, nullptr, nullptr, nullptr, peerKey) <= 0) + { + spdlog::error("Error initializing verification"); EVP_MD_CTX_free(ctx); return false; } - + int result = EVP_DigestVerify(ctx, signature.data(), signature.size(), - data.data(), data.size()); + data.data(), data.size()); EVP_MD_CTX_free(ctx); - + return result == 1; } -bool CryptoManager::addPeerPublicKey(const std::string& peerId, const std::vector& combinedKeyData) { - if (combinedKeyData.size() != 96) { - std::cerr << "Invalid combined key data size: " << combinedKeyData.size() << " (expected 96)" << std::endl; +bool CryptoManager::addPeerPublicKey(const std::string &peerId, const std::vector &combinedKeyData) +{ + if (combinedKeyData.size() != 96) + { + spdlog::error("Invalid combined key data size: {} (expected 96)", combinedKeyData.size()); return false; } - + // Extract signing key (middle 32 bytes) std::vector signingKeyData(combinedKeyData.begin() + 32, combinedKeyData.begin() + 64); - + std::lock_guard lock(cryptoMutex); - + // Replace existing key if present - if (peerSigningKeys.find(peerId) != peerSigningKeys.end()) { + if (peerSigningKeys.find(peerId) != peerSigningKeys.end()) + { EVP_PKEY_free(peerSigningKeys[peerId]); } - + // Load received public key - EVP_PKEY* peerKey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, nullptr, + EVP_PKEY *peerKey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, nullptr, signingKeyData.data(), signingKeyData.size()); - if (peerKey) { + if (peerKey) + { peerSigningKeys[peerId] = peerKey; return true; } - + return false; } -std::vector CryptoManager::getCombinedPublicKeyData() const { +std::vector CryptoManager::getCombinedPublicKeyData() const +{ std::vector data; - + // For Swift compatibility, use the same key for all three purposes // Swift uses: 32 bytes (key agreement) + 32 bytes (signing) + 32 bytes (identity) std::vector pubkey = getPublicKeyBytes(); - + // Repeat the same key for the three fields (96 bytes total) data.insert(data.end(), pubkey.begin(), pubkey.end()); // Key agreement (32 bytes) data.insert(data.end(), pubkey.begin(), pubkey.end()); // Signing (32 bytes) data.insert(data.end(), pubkey.begin(), pubkey.end()); // Identity (32 bytes) - + return data; } -std::vector CryptoManager::getPublicKeyBytes() const { - if (!signingPrivateKey) { +std::vector CryptoManager::getPublicKeyBytes() const +{ + if (!signingPrivateKey) + { return std::vector(); } return getPublicKeyBytes(signingPrivateKey); } -void CryptoManager::savePeerPublicKey(const std::string& peerId, const std::vector& pubkey) { +void CryptoManager::savePeerPublicKey(const std::string &peerId, const std::vector &pubkey) +{ std::ofstream file("peers_keys.txt", std::ios::app); - if (file.is_open()) { + if (file.is_open()) + { file << peerId << " "; - for (auto byte : pubkey) { + for (auto byte : pubkey) + { file << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); } file << std::endl; } } -bool CryptoManager::hasPeerKey(const std::string& peerId) const { +bool CryptoManager::hasPeerKey(const std::string &peerId) const +{ std::lock_guard lock(cryptoMutex); return peerSigningKeys.find(peerId) != peerSigningKeys.end(); } // Private helper functions -EVP_PKEY* CryptoManager::loadPrivateKey(const std::string& filename) { +EVP_PKEY *CryptoManager::loadPrivateKey(const std::string &filename) +{ std::ifstream file(filename, std::ios::binary); - if (!file.is_open()) { + if (!file.is_open()) + { return nullptr; } - + std::string pemData((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - - BIO* bio = BIO_new_mem_buf(pemData.c_str(), pemData.length()); - if (!bio) { + + BIO *bio = BIO_new_mem_buf(pemData.c_str(), pemData.length()); + if (!bio) + { return nullptr; } - - EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr); + + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr); BIO_free(bio); - + return pkey; } -void CryptoManager::savePrivateKey(EVP_PKEY* pkey, const std::string& filename) { - if (!pkey) { +void CryptoManager::savePrivateKey(EVP_PKEY *pkey, const std::string &filename) +{ + if (!pkey) + { return; } - - BIO* bio = BIO_new_file(filename.c_str(), "wb"); - if (!bio) { + + BIO *bio = BIO_new_file(filename.c_str(), "wb"); + if (!bio) + { return; } - + PEM_write_bio_PrivateKey(bio, pkey, nullptr, nullptr, 0, nullptr, nullptr); BIO_free(bio); } -std::vector CryptoManager::getPublicKeyBytes(EVP_PKEY* pkey) const { - if (!pkey) { +std::vector CryptoManager::getPublicKeyBytes(EVP_PKEY *pkey) const +{ + if (!pkey) + { return std::vector(); } - + std::vector pubkey(32); size_t len = pubkey.size(); EVP_PKEY_get_raw_public_key(pkey, pubkey.data(), &len); @@ -270,4 +314,4 @@ std::vector CryptoManager::getPublicKeyBytes(EVP_PKEY* pkey) const { return pubkey; } -} // namespace bitchat \ No newline at end of file +} // namespace bitchat diff --git a/src/bitchat/protocol/packet_serializer.cpp b/src/bitchat/protocol/packet_serializer.cpp index fd9d443..f6d169e 100644 --- a/src/bitchat/protocol/packet_serializer.cpp +++ b/src/bitchat/protocol/packet_serializer.cpp @@ -2,13 +2,15 @@ #include "bitchat/compression/compression_manager.h" #include "bitchat/crypto/crypto_manager.h" #include -#include +#include -namespace bitchat { +namespace bitchat +{ PacketSerializer::PacketSerializer() = default; -std::vector PacketSerializer::serializePacket(const BitchatPacket& packet) { +std::vector PacketSerializer::serializePacket(const BitchatPacket &packet) +{ std::vector data; // Try to compress payload if beneficial @@ -17,9 +19,11 @@ std::vector PacketSerializer::serializePacket(const BitchatPacket& pack bool isCompressed = false; CompressionManager compressionManager; - if (compressionManager.shouldCompress(packet.payload)) { + if (compressionManager.shouldCompress(packet.payload)) + { std::vector compressedPayload = compressionManager.compressData(packet.payload); - if (compressedPayload.size() < packet.payload.size()) { + if (compressedPayload.size() < packet.payload.size()) + { originalPayloadSize = packet.payload.size(); payload = compressedPayload; isCompressed = true; @@ -34,7 +38,8 @@ std::vector PacketSerializer::serializePacket(const BitchatPacket& pack // Flags (include compression flag if needed) uint8_t flags = packet.flags; - if (isCompressed) { + if (isCompressed) + { flags |= FLAG_IS_COMPRESSED; } writeUint8(data, flags); @@ -49,21 +54,24 @@ std::vector PacketSerializer::serializePacket(const BitchatPacket& pack data.insert(data.end(), senderID.begin(), senderID.end()); // RecipientID (8 bytes, if present) - if (packet.flags & FLAG_HAS_RECIPIENT) { + if (packet.flags & FLAG_HAS_RECIPIENT) + { std::vector recipientID = packet.recipientID; recipientID.resize(8, 0); data.insert(data.end(), recipientID.begin(), recipientID.end()); } // Payload (with original size prepended if compressed) - if (isCompressed) { + if (isCompressed) + { // Prepend original size (2 bytes, big-endian) writeUint16(data, originalPayloadSize); } data.insert(data.end(), payload.begin(), payload.end()); // Signature (64 bytes, if present) - if (packet.flags & FLAG_HAS_SIGNATURE) { + if (packet.flags & FLAG_HAS_SIGNATURE) + { std::vector signature = packet.signature; signature.resize(64, 0); data.insert(data.end(), signature.begin(), signature.end()); @@ -72,13 +80,15 @@ std::vector PacketSerializer::serializePacket(const BitchatPacket& pack return data; } -BitchatPacket PacketSerializer::deserializePacket(const std::vector& data) { +BitchatPacket PacketSerializer::deserializePacket(const std::vector &data) +{ BitchatPacket packet; size_t offset = 0; // Verify minimum size: headerSize (13) + senderIDSize (8) = 21 bytes - if (data.size() < 21) { - std::cerr << "ERROR: Packet too short: " << data.size() << " bytes (minimum 21)" << std::endl; + if (data.size() < 21) + { + spdlog::error("Packet too short: {} bytes (minimum 21)", data.size()); return packet; } @@ -97,17 +107,19 @@ BitchatPacket PacketSerializer::deserializePacket(const std::vector& da // Calculate expected total size size_t expectedSize = 21; // headerSize + senderIDSize - if (packet.flags & FLAG_HAS_RECIPIENT) { + if (packet.flags & FLAG_HAS_RECIPIENT) + { expectedSize += 8; // recipientIDSize } - if (packet.flags & FLAG_HAS_SIGNATURE) { + if (packet.flags & FLAG_HAS_SIGNATURE) + { expectedSize += 64; // signatureSize } expectedSize += packet.payloadLength; - if (!validatePacketSize(data, expectedSize)) { - std::cerr << "ERROR: Packet size mismatch. Expected: " << expectedSize - << ", got: " << data.size() << std::endl; + if (!validatePacketSize(data, expectedSize)) + { + spdlog::error("Packet size mismatch. Expected: {}, got: {}", expectedSize, data.size()); return packet; } @@ -116,16 +128,19 @@ BitchatPacket PacketSerializer::deserializePacket(const std::vector& da offset += 8; // RecipientID (8 bytes, if present) - if (packet.flags & FLAG_HAS_RECIPIENT) { + if (packet.flags & FLAG_HAS_RECIPIENT) + { packet.recipientID.assign(data.begin() + offset, data.begin() + offset + 8); offset += 8; } // Payload (with decompression if needed) - if (isCompressed) { + if (isCompressed) + { // First 2 bytes are original size - if (packet.payloadLength < 2) { - std::cerr << "ERROR: Compressed payload too small for size header" << std::endl; + if (packet.payloadLength < 2) + { + spdlog::error("Compressed payload too small for size header"); return packet; } @@ -139,36 +154,50 @@ BitchatPacket PacketSerializer::deserializePacket(const std::vector& da // Decompress CompressionManager compressionManager; packet.payload = compressionManager.decompressData(compressedPayload, originalSize); - } else { + } + else + { // Normal payload - if (offset + packet.payloadLength <= data.size()) { + if (offset + packet.payloadLength <= data.size()) + { packet.payload.assign(data.begin() + offset, - data.begin() + offset + packet.payloadLength); + data.begin() + offset + packet.payloadLength); offset += packet.payloadLength; } } // Signature (64 bytes, if present) - if ((packet.flags & FLAG_HAS_SIGNATURE) && offset + 64 <= data.size()) { + if ((packet.flags & FLAG_HAS_SIGNATURE) && offset + 64 <= data.size()) + { packet.signature.assign(data.begin() + offset, data.begin() + offset + 64); } return packet; } -std::vector PacketSerializer::makeMessagePayload(const BitchatMessage& message) { +std::vector PacketSerializer::makeMessagePayload(const BitchatMessage &message) +{ std::vector data; // Flags - calculate based on present fields uint8_t flags = 0; - if (message.isRelay) flags |= 0x01; - if (message.isPrivate) flags |= 0x02; - if (!message.originalSender.empty()) flags |= 0x04; - if (!message.recipientNickname.empty()) flags |= 0x08; - if (!message.senderPeerID.empty()) flags |= 0x10; - if (!message.mentions.empty()) flags |= 0x20; - if (!message.channel.empty()) flags |= 0x40; - if (message.isEncrypted) flags |= 0x80; + if (message.isRelay) + flags |= 0x01; + if (message.isPrivate) + flags |= 0x02; + if (!message.originalSender.empty()) + flags |= 0x04; + if (!message.recipientNickname.empty()) + flags |= 0x08; + if (!message.senderPeerID.empty()) + flags |= 0x10; + if (!message.mentions.empty()) + flags |= 0x20; + if (!message.channel.empty()) + flags |= 0x40; + if (message.isEncrypted) + flags |= 0x80; + writeUint8(data, flags); // Timestamp (8 bytes, milliseconds) @@ -192,19 +221,22 @@ std::vector PacketSerializer::makeMessagePayload(const BitchatMessage& message.content.begin() + contentLength); // Optional fields based on flags - if (!message.originalSender.empty()) { + if (!message.originalSender.empty()) + { writeUint8(data, static_cast(std::min(static_cast(255), message.originalSender.size()))); data.insert(data.end(), message.originalSender.begin(), message.originalSender.begin() + std::min(static_cast(255), message.originalSender.size())); } - if (!message.recipientNickname.empty()) { + if (!message.recipientNickname.empty()) + { writeUint8(data, static_cast(std::min(static_cast(255), message.recipientNickname.size()))); data.insert(data.end(), message.recipientNickname.begin(), message.recipientNickname.begin() + std::min(static_cast(255), message.recipientNickname.size())); } - if (!message.senderPeerID.empty()) { + if (!message.senderPeerID.empty()) + { // Convert peer ID bytes to hex string for Swift compatibility std::string peerIDHex = toHexCompact(message.senderPeerID); writeUint8(data, static_cast(std::min(static_cast(255), peerIDHex.size()))); @@ -213,9 +245,11 @@ std::vector PacketSerializer::makeMessagePayload(const BitchatMessage& } // Mentions array - if (!message.mentions.empty()) { + if (!message.mentions.empty()) + { writeUint8(data, static_cast(std::min(static_cast(255), message.mentions.size()))); - for (const auto& mention : message.mentions) { + for (const auto &mention : message.mentions) + { writeUint8(data, static_cast(std::min(static_cast(255), mention.size()))); data.insert(data.end(), mention.begin(), mention.begin() + std::min(static_cast(255), mention.size())); @@ -223,7 +257,8 @@ std::vector PacketSerializer::makeMessagePayload(const BitchatMessage& } // Channel (only if present) - if (!message.channel.empty()) { + if (!message.channel.empty()) + { writeUint8(data, static_cast(std::min(static_cast(255), message.channel.size()))); data.insert(data.end(), message.channel.begin(), message.channel.begin() + std::min(static_cast(255), message.channel.size())); @@ -232,13 +267,15 @@ std::vector PacketSerializer::makeMessagePayload(const BitchatMessage& return data; } -BitchatMessage PacketSerializer::parseMessagePayload(const std::vector& payload) { +BitchatMessage PacketSerializer::parseMessagePayload(const std::vector &payload) +{ BitchatMessage message; size_t offset = 0; // Minimum size: flags(1) + timestamp(8) + id_len(1) + sender_len(1) + content_len(2) = 13 bytes - if (payload.size() < 13) { - std::cerr << "ERROR: Payload too small: " << payload.size() << " < 13" << std::endl; + if (payload.size() < 13) + { + spdlog::error("Payload too small: {} < 13", payload.size()); return message; } @@ -260,8 +297,9 @@ BitchatMessage PacketSerializer::parseMessagePayload(const std::vector& auto idLen = readUint8(payload, offset); // Message ID (variable length) - if (offset + idLen > payload.size()) { - std::cerr << "ERROR: Buffer overflow reading ID data" << std::endl; + if (offset + idLen > payload.size()) + { + spdlog::error("Buffer overflow reading ID data"); return message; } message.id = std::string(payload.begin() + offset, payload.begin() + offset + idLen); @@ -271,8 +309,9 @@ BitchatMessage PacketSerializer::parseMessagePayload(const std::vector& auto senderLen = readUint8(payload, offset); // Sender (variable length) - if (offset + senderLen > payload.size()) { - std::cerr << "ERROR: Buffer overflow reading sender data" << std::endl; + if (offset + senderLen > payload.size()) + { + spdlog::error("Buffer overflow reading sender data"); return message; } message.sender = std::string(payload.begin() + offset, payload.begin() + offset + senderLen); @@ -282,50 +321,65 @@ BitchatMessage PacketSerializer::parseMessagePayload(const std::vector& uint16_t contentLen = readUint16(payload, offset); // Content (variable length) - if (offset + contentLen > payload.size()) { - std::cerr << "ERROR: Buffer overflow reading content data" << std::endl; + if (offset + contentLen > payload.size()) + { + spdlog::error("Buffer overflow reading content data"); return message; } - if (message.isEncrypted) { + if (message.isEncrypted) + { // Store encrypted content as bytes message.encryptedContent.assign(payload.begin() + offset, payload.begin() + offset + contentLen); message.content = ""; // Empty placeholder - } else { + } + else + { // Normal string content message.content = std::string(payload.begin() + offset, payload.begin() + offset + contentLen); } offset += contentLen; // Optional fields based on flags - if (hasOriginalSender && offset < payload.size()) { + if (hasOriginalSender && offset < payload.size()) + { auto len = readUint8(payload, offset); - if (offset + len <= payload.size()) { + if (offset + len <= payload.size()) + { message.originalSender = std::string(payload.begin() + offset, payload.begin() + offset + len); offset += len; } } - if (hasRecipientNickname && offset < payload.size()) { + if (hasRecipientNickname && offset < payload.size()) + { auto len = readUint8(payload, offset); - if (offset + len <= payload.size()) { + if (offset + len <= payload.size()) + { message.recipientNickname = std::string(payload.begin() + offset, payload.begin() + offset + len); offset += len; } } - if (hasSenderPeerID && offset < payload.size()) { + if (hasSenderPeerID && offset < payload.size()) + { auto len = readUint8(payload, offset); - if (offset + len <= payload.size()) { + if (offset + len <= payload.size()) + { std::string peerIDHex = std::string(payload.begin() + offset, payload.begin() + offset + len); // Convert hex string back to bytes message.senderPeerID.clear(); - for (size_t i = 0; i < len; i += 2) { - if (i + 1 < len) { + for (size_t i = 0; i < len; i += 2) + { + if (i + 1 < len) + { std::string byteStr = peerIDHex.substr(i, 2); - try { + try + { message.senderPeerID.push_back(static_cast(std::stoi(byteStr, nullptr, 16))); - } catch (const std::exception& e) { + } + catch (const std::exception &e) + { // Skip invalid hex bytes continue; } @@ -336,11 +390,14 @@ BitchatMessage PacketSerializer::parseMessagePayload(const std::vector& } // Mentions array - if (hasMentions && offset < payload.size()) { + if (hasMentions && offset < payload.size()) + { auto mentionCount = readUint8(payload, offset); - for (uint8_t i = 0; i < mentionCount && offset < payload.size(); ++i) { + for (uint8_t i = 0; i < mentionCount && offset < payload.size(); ++i) + { auto len = readUint8(payload, offset); - if (offset + len <= payload.size()) { + if (offset + len <= payload.size()) + { message.mentions.push_back(std::string(payload.begin() + offset, payload.begin() + offset + len)); offset += len; } @@ -348,9 +405,11 @@ BitchatMessage PacketSerializer::parseMessagePayload(const std::vector& } // Channel - if (hasChannel && offset < payload.size()) { + if (hasChannel && offset < payload.size()) + { auto len = readUint8(payload, offset); - if (offset + len <= payload.size()) { + if (offset + len <= payload.size()) + { message.channel = std::string(payload.begin() + offset, payload.begin() + offset + len); } } @@ -358,21 +417,25 @@ BitchatMessage PacketSerializer::parseMessagePayload(const std::vector& return message; } -std::vector PacketSerializer::makeAnnouncePayload(const std::string& nickname) { +std::vector PacketSerializer::makeAnnouncePayload(const std::string &nickname) +{ return std::vector(nickname.begin(), nickname.end()); } -void PacketSerializer::parseAnnouncePayload(const std::vector& payload, std::string& nickname) { +void PacketSerializer::parseAnnouncePayload(const std::vector &payload, std::string &nickname) +{ nickname = std::string(payload.begin(), payload.end()); } -BitchatPacket PacketSerializer::makePacket(uint8_t type, const std::vector& payload, - bool hasRecipient, bool hasSignature, const std::string& senderId) { +BitchatPacket PacketSerializer::makePacket(uint8_t type, const std::vector &payload, + bool hasRecipient, bool hasSignature, const std::string &senderId) +{ BitchatPacket packet; packet.type = type; packet.timestamp = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); - + std::chrono::system_clock::now().time_since_epoch()) + .count(); + // Convert string to UTF-8 bytes for Swift compatibility std::vector senderID(senderId.begin(), senderId.end()); senderID.resize(8, 0); // Pad to 8 bytes @@ -383,11 +446,14 @@ BitchatPacket PacketSerializer::makePacket(uint8_t type, const std::vector(8, 0xFF); // Broadcast to all } @@ -395,42 +461,51 @@ BitchatPacket PacketSerializer::makePacket(uint8_t type, const std::vector& data, uint64_t value) { - for (int i = 7; i >= 0; --i) { +void PacketSerializer::writeUint64(std::vector &data, uint64_t value) +{ + for (int i = 7; i >= 0; --i) + { data.push_back(static_cast((value >> (i * 8)) & 0xFF)); } } -void PacketSerializer::writeUint16(std::vector& data, uint16_t value) { +void PacketSerializer::writeUint16(std::vector &data, uint16_t value) +{ data.push_back(static_cast((value >> 8) & 0xFF)); data.push_back(static_cast(value & 0xFF)); } -void PacketSerializer::writeUint8(std::vector& data, uint8_t value) { +void PacketSerializer::writeUint8(std::vector &data, uint8_t value) +{ data.push_back(value); } // Helper functions for deserialization -uint64_t PacketSerializer::readUint64(const std::vector& data, size_t& offset) { +uint64_t PacketSerializer::readUint64(const std::vector &data, size_t &offset) +{ uint64_t value = 0; - for (int i = 0; i < 8; ++i) { + for (int i = 0; i < 8; ++i) + { value = (value << 8) | data[offset++]; } return value; } -uint16_t PacketSerializer::readUint16(const std::vector& data, size_t& offset) { +uint16_t PacketSerializer::readUint16(const std::vector &data, size_t &offset) +{ uint16_t value = static_cast((data[offset] << 8) | data[offset + 1]); offset += 2; return value; } -uint8_t PacketSerializer::readUint8(const std::vector& data, size_t& offset) { +uint8_t PacketSerializer::readUint8(const std::vector &data, size_t &offset) +{ return data[offset++]; } -bool PacketSerializer::validatePacketSize(const std::vector& data, size_t expectedSize) { +bool PacketSerializer::validatePacketSize(const std::vector &data, size_t expectedSize) +{ return data.size() >= expectedSize; } -} // namespace bitchat \ No newline at end of file +} // namespace bitchat diff --git a/src/bitchat/protocol/packet_utils.cpp b/src/bitchat/protocol/packet_utils.cpp index 41acc3e..f76bad5 100644 --- a/src/bitchat/protocol/packet_utils.cpp +++ b/src/bitchat/protocol/packet_utils.cpp @@ -1,96 +1,111 @@ #include "bitchat/protocol/packet.h" -#include -#include -#include #include #include +#include +#include +#include #ifdef __APPLE__ #include #endif -namespace bitchat { - -std::string packetTypeToString(uint8_t type) { - switch (type) { - case PKT_TYPE_ANNOUNCE: - return "ANNOUNCE"; - case PKT_TYPE_KEYEXCHANGE: - return "KEYEXCHANGE"; - case PKT_TYPE_MESSAGE: - return "MESSAGE"; - case PKT_TYPE_LEAVE: - return "LEAVE"; - case PKT_TYPE_FRAGMENT_START: - return "FRAGMENT_START"; - case PKT_TYPE_FRAGMENT_CONTINUE: - return "FRAGMENT_CONTINUE"; - case PKT_TYPE_FRAGMENT_END: - return "FRAGMENT_END"; - case PKT_TYPE_CHANNEL_ANNOUNCE: - return "CHANNEL_ANNOUNCE"; - case PKT_TYPE_CHANNEL_RETENTION: - return "CHANNEL_RETENTION"; - case PKT_TYPE_DELIVERY_ACK: - return "DELIVERY_ACK"; - case PKT_TYPE_DELIVERY_STATUS_REQUEST: - return "DELIVERY_STATUS_REQUEST"; - case PKT_TYPE_READ_RECEIPT: - return "READ_RECEIPT"; - default: - return "UNKNOWN"; +namespace bitchat +{ + +std::string packetTypeToString(uint8_t type) +{ + switch (type) + { + case PKT_TYPE_ANNOUNCE: + return "ANNOUNCE"; + case PKT_TYPE_KEYEXCHANGE: + return "KEYEXCHANGE"; + case PKT_TYPE_MESSAGE: + return "MESSAGE"; + case PKT_TYPE_LEAVE: + return "LEAVE"; + case PKT_TYPE_FRAGMENT_START: + return "FRAGMENT_START"; + case PKT_TYPE_FRAGMENT_CONTINUE: + return "FRAGMENT_CONTINUE"; + case PKT_TYPE_FRAGMENT_END: + return "FRAGMENT_END"; + case PKT_TYPE_CHANNEL_ANNOUNCE: + return "CHANNEL_ANNOUNCE"; + case PKT_TYPE_CHANNEL_RETENTION: + return "CHANNEL_RETENTION"; + case PKT_TYPE_DELIVERY_ACK: + return "DELIVERY_ACK"; + case PKT_TYPE_DELIVERY_STATUS_REQUEST: + return "DELIVERY_STATUS_REQUEST"; + case PKT_TYPE_READ_RECEIPT: + return "READ_RECEIPT"; + default: + return "UNKNOWN"; } } -std::string toHex(const std::vector& data) { +std::string toHex(const std::vector &data) +{ std::stringstream ss; - for (size_t i = 0; i < data.size(); ++i) { - if (i > 0) ss << " "; + for (size_t i = 0; i < data.size(); ++i) + { + if (i > 0) + ss << " "; ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(data[i]); } return ss.str(); } -std::string toHexCompact(const std::vector& data) { +std::string toHexCompact(const std::vector &data) +{ std::stringstream ss; - for (uint8_t byte : data) { + for (uint8_t byte : data) + { ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); } return ss.str(); } -std::vector stringToVector(const std::string& str) { +std::vector stringToVector(const std::string &str) +{ return std::vector(str.begin(), str.end()); } -std::string vectorToString(const std::vector& vec) { +std::string vectorToString(const std::vector &vec) +{ return std::string(vec.begin(), vec.end()); } -std::string normalizePeerId(const std::string& peerId) { +std::string normalizePeerId(const std::string &peerId) +{ std::string normalized = peerId; normalized.erase(std::remove(normalized.begin(), normalized.end(), '\0'), normalized.end()); return normalized; } -std::string randomPeerId() { +std::string randomPeerId() +{ std::vector peerid(4); std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(0, 255); - - for (auto& byte : peerid) { + + for (auto &byte : peerid) + { byte = static_cast(dis(gen)); } std::stringstream ss; - for (uint8_t byte : peerid) { + for (uint8_t byte : peerid) + { ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); } return ss.str(); } -std::string uuidv4() { +std::string uuidv4() +{ #ifdef __APPLE__ uuid_t uuid; char uuid_str[37]; @@ -102,45 +117,30 @@ std::string uuidv4() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(0, 15); - + std::stringstream ss; ss << std::hex; - - for (int i = 0; i < 32; ++i) { - if (i == 8 || i == 12 || i == 16 || i == 20) { + + for (int i = 0; i < 32; ++i) + { + if (i == 8 || i == 12 || i == 16 || i == 20) + { ss << "-"; } ss << dis(gen); } - + return ss.str(); #endif } -std::string randomNickname() { - std::vector adjectives = { - "Swift", "Fast", "Quick", "Bright", "Smart", "Clever", "Wise", "Sharp", - "Bold", "Brave", "Calm", "Cool", "Dark", "Deep", "Fair", "Fine", - "Free", "Good", "Great", "High", "Kind", "Light", "Long", "Loud", - "New", "Nice", "Old", "Open", "Pure", "Rich", "Safe", "Soft", - "Sweet", "Tall", "True", "Warm", "Wild", "Young", "Zesty", "Vivid" - }; - - std::vector nouns = { - "Fox", "Wolf", "Bear", "Eagle", "Hawk", "Lion", "Tiger", "Dragon", - "Phoenix", "Raven", "Swan", "Falcon", "Owl", "Shark", "Whale", "Dolphin", - "Panda", "Koala", "Kangaroo", "Elephant", "Giraffe", "Zebra", "Cheetah", - "Leopard", "Jaguar", "Panther", "Lynx", "Bobcat", "Coyote", "Jackal", - "Viper", "Cobra", "Python", "Anaconda", "Rattlesnake", "Mamba", "Asp", - "Shark", "Orca", "Narwhal", "Seahorse", "Starfish", "Jellyfish" - }; - +std::string randomNickname() +{ std::random_device rd; std::mt19937 gen(rd()); - std::uniform_int_distribution<> adjDis(0, adjectives.size() - 1); - std::uniform_int_distribution<> nounDis(0, nouns.size() - 1); - - return adjectives[adjDis(gen)] + nouns[nounDis(gen)]; + std::uniform_int_distribution<> dis(1000, 9999); + + return "anon" + std::to_string(dis(gen)); } -} // namespace bitchat \ No newline at end of file +} // namespace bitchat diff --git a/src/platforms/apple/bluetooth.mm b/src/platforms/apple/bluetooth.mm index fc3b6ee..1abc2c2 100644 --- a/src/platforms/apple/bluetooth.mm +++ b/src/platforms/apple/bluetooth.mm @@ -1,461 +1,693 @@ -#include "bitchat/platform/bluetooth_interface.h" -#include "bitchat/protocol/packet_serializer.h" #import "platforms/apple/bluetooth.h" -#include -#include -#include - -namespace bitchat { - -// Bridge class that implements the C++ interface and forwards to Objective-C -class AppleBluetoothBridge : public BluetoothInterface { -private: - AppleBluetooth* impl; - std::string localPeerId; - PacketSerializer serializer; - - // Callbacks - PeerDisconnectedCallback peerDisconnectedCallback; - PacketReceivedCallback packetReceivedCallback; - -public: - AppleBluetoothBridge() : impl(nil) { - impl = [[AppleBluetooth alloc] init]; - if (impl) { - // Set up callback bridges - [impl setPeerDisconnectedCallback:^(NSString *peerId) { - if (peerDisconnectedCallback) { - std::string cppPeerId = [peerId UTF8String]; - peerDisconnectedCallback(cppPeerId); - } - }]; - - [impl setPacketReceivedCallback:^(NSData *packetData) { - if (packetReceivedCallback) { - // Convert NSData to BitchatPacket - std::vector data((uint8_t*)packetData.bytes, - (uint8_t*)packetData.bytes + packetData.length); - BitchatPacket packet = serializer.deserializePacket(data); - packetReceivedCallback(packet); - } - }]; - } - } - - ~AppleBluetoothBridge() { - if (impl) { - [impl release]; - } - } - - bool initialize() override { - if (!impl) return false; - return [impl initialize]; - } - - bool start() override { - if (!impl) return false; - return [impl start]; - } - - void stop() override { - if (impl) { - [impl stop]; - } - } - - bool sendPacket(const BitchatPacket& packet) override { - if (!impl) return false; - - std::vector data = serializer.serializePacket(packet); - NSData* nsData = [NSData dataWithBytes:data.data() length:data.size()]; - return [impl sendPacket:nsData]; - } - - bool sendPacketToPeer(const BitchatPacket& packet, const std::string& peerId) override { - if (!impl) return false; - - std::vector data = serializer.serializePacket(packet); - NSData* nsData = [NSData dataWithBytes:data.data() length:data.size()]; - NSString* nsPeerId = [NSString stringWithUTF8String:peerId.c_str()]; - return [impl sendPacket:nsData toPeer:nsPeerId]; - } - - bool isReady() const override { - if (!impl) return false; - return [impl isReady]; - } - - std::string getLocalPeerId() const override { - if (!impl) return ""; - NSString* peerId = [impl getLocalPeerId]; - return peerId ? [peerId UTF8String] : ""; - } - - - void setPeerDisconnectedCallback(PeerDisconnectedCallback callback) override { - peerDisconnectedCallback = callback; - } - - void setPacketReceivedCallback(PacketReceivedCallback callback) override { - packetReceivedCallback = callback; - } - - size_t getConnectedPeersCount() const override { - if (!impl) return 0; - return [impl getConnectedPeersCount]; - } -}; - -// Factory function -std::unique_ptr createAppleBluetoothBridge() { - return std::make_unique(); -} - -} // namespace bitchat +#include "bitchat/core/constants.h" // ============================================================================ -// Objective-C Implementation - PURE BLE ONLY +// Objective-C Implementation - Pure BLE Implementation // ============================================================================ -// Constants -static NSString* const SERVICE_UUID = @"F47B5E2D-4A9E-4C5A-9B3F-8E1D2C3A4B5C"; -static NSString* const CHARACTERISTIC_UUID = @"A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D"; +// Platform-specific constants (using C++ constants) +static NSString *const SERVICE_UUID = @(bitchat::constants::BLE_SERVICE_UUID.c_str()); +static NSString *const CHARACTERISTIC_UUID = @(bitchat::constants::BLE_CHARACTERISTIC_UUID.c_str()); -@implementation AppleBluetooth { +@implementation AppleBluetooth +{ // Instance variables for callback properties - void (^_peerDisconnectedCallback)(NSString*); - void (^_packetReceivedCallback)(NSData*); + void (^_peerDisconnectedCallback)(NSString *); // Callback when a peer disconnects + void (^_packetReceivedCallback)(NSData *); // Callback when a packet is received } -// Callback properties - implement getters and setters manually -- (void (^)(NSString*))peerDisconnectedCallback { +// ============================================================================ +// Callback Properties - Manual getters and setters +// ============================================================================ + +/** + * @brief Getter for peer disconnection callback + * @return The stored callback block + */ +- (void (^)(NSString *))peerDisconnectedCallback +{ return _peerDisconnectedCallback; } -- (void)setPeerDisconnectedCallback:(void (^)(NSString*))callback { - _peerDisconnectedCallback = [callback copy]; +/** + * @brief Setter for peer disconnection callback + * @param callback The callback block to store + */ +- (void)setPeerDisconnectedCallback:(void (^)(NSString *))callback +{ + _peerDisconnectedCallback = [callback copy]; // Copy to ensure block survives } -- (void (^)(NSData*))packetReceivedCallback { +/** + * @brief Getter for packet received callback + * @return The stored callback block + */ +- (void (^)(NSData *))packetReceivedCallback +{ return _packetReceivedCallback; } -- (void)setPacketReceivedCallback:(void (^)(NSData*))callback { - _packetReceivedCallback = [callback copy]; +/** + * @brief Setter for packet received callback + * @param callback The callback block to store + */ +- (void)setPacketReceivedCallback:(void (^)(NSData *))callback +{ + _packetReceivedCallback = [callback copy]; // Copy to ensure block survives } -- (instancetype)init { +// ============================================================================ +// Initialization and Lifecycle +// ============================================================================ + +/** + * @brief Initialize the AppleBluetooth instance + * + * Sets up the Core Bluetooth managers (Central and Peripheral) and initializes + * all necessary data structures for managing BLE connections. + */ +- (instancetype)init +{ self = [super init]; - if (self) { - self.ready = NO; - self.lock = [[NSLock alloc] init]; - self.bleQueue = dispatch_queue_create("com.bitchat.ble", DISPATCH_QUEUE_SERIAL); - - // Initialize managers on main queue + if (self) + { + self.ready = NO; // Not ready until managers are powered on + self.lock = [[NSLock alloc] init]; // Thread safety lock + self.bleQueue = dispatch_queue_create("com.bitchat.ble", DISPATCH_QUEUE_SERIAL); // Serial queue for BLE operations + + // Initialize managers on main queue (required for Core Bluetooth) dispatch_async(dispatch_get_main_queue(), ^{ + // Central manager handles scanning and connecting to other devices self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:self.bleQueue]; + // Peripheral manager handles advertising and accepting connections self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:self.bleQueue]; }); - - self.discoveredPeripherals = [[NSMutableArray alloc] init]; - self.connectedPeripherals = [[NSMutableDictionary alloc] init]; - self.peripheralCharacteristics = [[NSMutableDictionary alloc] init]; - self.subscribedCentrals = [[NSMutableArray alloc] init]; + + // Initialize collections for managing BLE connections + self.discoveredPeripherals = [[NSMutableArray alloc] init]; // Devices found during scanning + self.connectedPeripherals = [[NSMutableDictionary alloc] init]; // Currently connected devices + self.peripheralCharacteristics = [[NSMutableDictionary alloc] init]; // Characteristics for each peripheral + self.subscribedCentrals = [[NSMutableArray alloc] init]; // Centrals subscribed to our service } return self; } -- (BOOL)initialize { - // Wait for both managers to be ready +/** + * @brief Initialize the Bluetooth system + * + * Waits for both Central and Peripheral managers to be powered on before + * marking the system as ready for operations. + * + * @return YES if initialization successful, NO otherwise + */ +- (BOOL)initialize +{ + // Wait for both managers to be ready (this can take a few seconds) while (self.centralManager.state != CBManagerStatePoweredOn || - self.peripheralManager.state != CBManagerStatePoweredOn) { - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode - beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + self.peripheralManager.state != CBManagerStatePoweredOn) + { + // Run the run loop to allow state updates to process + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:[NSDate dateWithTimeIntervalSinceNow:bitchat::constants::BLE_SCAN_INTERVAL_SECONDS]]; } - - self.ready = YES; + + self.ready = YES; // Mark system as ready for operations return YES; } -- (BOOL)start { - if (!self.ready) { +/** + * @brief Start Bluetooth operations + * + * Once called, the system will automatically start scanning for other devices + * and advertising its own presence based on delegate callbacks. + * + * @return YES if started successfully, NO if not ready + */ +- (BOOL)start +{ + if (!self.ready) + { return NO; } - + // Start scanning and advertising will be handled by delegate methods + // when the managers reach powered on state return YES; } -- (void)stop { - if (self.centralManager) { - [self.centralManager stopScan]; +/** + * @brief Stop all Bluetooth operations + * + * Stops scanning for devices and stops advertising this device's presence. + */ +- (void)stop +{ + if (self.centralManager) + { + [self.centralManager stopScan]; // Stop scanning for other devices } - if (self.peripheralManager) { - [self.peripheralManager stopAdvertising]; + + if (self.peripheralManager) + { + [self.peripheralManager stopAdvertising]; // Stop advertising this device } } -- (BOOL)sendPacket:(NSData *)packetData { - if (!self.ready) return NO; - - // Send to all connected peripherals - for (CBPeripheral *peripheral in self.connectedPeripherals.allValues) { +// ============================================================================ +// Packet Sending Methods +// ============================================================================ + +/** + * @brief Send a packet to all connected peers + * + * Broadcasts the packet to all connected peripherals and subscribed centrals. + * + * @param packetData The packet data to send + * @return YES if sent successfully, NO if not ready + */ +- (BOOL)sendPacket:(NSData *)packetData +{ + if (!self.ready) + return NO; + + // Send to all connected peripherals (devices we're connected to) + for (CBPeripheral *peripheral in self.connectedPeripherals.allValues) + { CBCharacteristic *characteristic = [self.peripheralCharacteristics objectForKey:peripheral]; - if (characteristic && peripheral.state == CBPeripheralStateConnected) { + + if (characteristic && peripheral.state == CBPeripheralStateConnected) + { + // Write packet to the characteristic without waiting for response [peripheral writeValue:packetData forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse]; } } - - // Send to subscribed centrals - if (self.mutableCharacteristic && self.subscribedCentrals.count > 0) { + + // Send to subscribed centrals (devices connected to us) + if (self.mutableCharacteristic && self.subscribedCentrals.count > 0) + { + // Update the characteristic value for all subscribed centrals [self.peripheralManager updateValue:packetData forCharacteristic:self.mutableCharacteristic onSubscribedCentrals:self.subscribedCentrals]; } - + return YES; } -- (BOOL)sendPacket:(NSData *)packetData toPeripheral:(CBPeripheral *)peripheral { - if (!self.ready || !peripheral) return NO; - +/** + * @brief Send a packet to a specific peripheral + * + * @param packetData The packet data to send + * @param peripheral The target peripheral device + * @return YES if sent successfully, NO if not ready or peripheral not connected + */ +- (BOOL)sendPacket:(NSData *)packetData toPeripheral:(CBPeripheral *)peripheral +{ + if (!self.ready || !peripheral) + { + return NO; + } + CBCharacteristic *characteristic = [self.peripheralCharacteristics objectForKey:peripheral]; - if (characteristic && peripheral.state == CBPeripheralStateConnected) { + + if (characteristic && peripheral.state == CBPeripheralStateConnected) + { + // Write packet to the specific peripheral's characteristic [peripheral writeValue:packetData forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse]; + return YES; } + return NO; } -- (BOOL)sendPacket:(NSData *)packetData toPeer:(NSString *)peerId { +/** + * @brief Send a packet to a specific peer by ID + * + * @param packetData The packet data to send + * @param peerId The target peer's identifier + * @return YES if sent successfully, NO if peer not found + */ +- (BOOL)sendPacket:(NSData *)packetData toPeer:(NSString *)peerId +{ // Find peripheral for this peer ID - for (NSString *peerIDKey in self.connectedPeripherals.allKeys) { - if ([peerIDKey isEqualToString:peerId]) { + for (NSString *peerIDKey in self.connectedPeripherals.allKeys) + { + if ([peerIDKey isEqualToString:peerId]) + { CBPeripheral *peripheral = [self.connectedPeripherals objectForKey:peerIDKey]; return [self sendPacket:packetData toPeripheral:peripheral]; } } - return NO; + + return NO; // Peer not found } -- (BOOL)isReady { +// ============================================================================ +// State and Information Methods +// ============================================================================ + +/** + * @brief Check if the Bluetooth system is ready for operations + * @return YES if ready, NO otherwise + */ +- (BOOL)isReady +{ return self.ready; } -- (NSString *)getLocalPeerId { +/** + * @brief Get the local device's peer identifier + * + * Generates a random 8-character hex ID if not already set. + * This ID is used to identify this device to other peers. + * + * @return The local peer ID as a string + */ +- (NSString *)getLocalPeerId +{ // Generate a random peer ID if not set - if (!self.localPeerId) { + if (!self.localPeerId) + { // Simple random ID generation (8 hex characters) NSMutableString *peerId = [NSMutableString string]; - for (int i = 0; i < 8; i++) { - [peerId appendFormat:@"%02x", arc4random_uniform(256)]; + + for (size_t i = 0; i < bitchat::constants::BLE_PEER_ID_LENGTH_CHARS; i++) + { + [peerId appendFormat:@"%02x", arc4random_uniform(256)]; // Generate random hex byte } + self.localPeerId = [peerId copy]; } + return self.localPeerId; } -- (NSUInteger)getConnectedPeersCount { +/** + * @brief Get the number of currently connected peers + * @return Number of connected peripherals + */ +- (NSUInteger)getConnectedPeersCount +{ return self.connectedPeripherals.count; } +// ============================================================================ // Constants -+ (NSString *)serviceUUID { +// ============================================================================ + +/** + * @brief Get the BLE service UUID + * @return The service UUID string + */ ++ (NSString *)serviceUUID +{ return SERVICE_UUID; } -+ (NSString *)characteristicUUID { +/** + * @brief Get the BLE characteristic UUID + * @return The characteristic UUID string + */ ++ (NSString *)characteristicUUID +{ return CHARACTERISTIC_UUID; } // ============================================================================ -// CBCentralManagerDelegate - PURE BLE ONLY +// CBCentralManagerDelegate - Central Role (Scanner/Client) // ============================================================================ -- (void)centralManagerDidUpdateState:(CBCentralManager *)central { - if (central.state == CBManagerStatePoweredOn) { +/** + * @brief Called when the central manager's state changes + * + * When the central manager is powered on, it automatically starts scanning + * for other devices advertising our service. + * + * @param central The central manager instance + */ +- (void)centralManagerDidUpdateState:(CBCentralManager *)central +{ + if (central.state == CBManagerStatePoweredOn) + { dispatch_async(dispatch_get_main_queue(), ^{ - [self startScanning]; + [self startScanning]; // Start scanning for other devices }); } } +/** + * @brief Called when a peripheral is discovered during scanning + * + * Automatically attempts to connect to discovered peripherals that + * are advertising our service. + * + * @param central The central manager + * @param peripheral The discovered peripheral + * @param advertisementData Advertisement data from the peripheral + * @param RSSI Signal strength indicator + */ - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData - RSSI:(NSNumber *)RSSI { - if (![self.discoveredPeripherals containsObject:peripheral]) { - [self.discoveredPeripherals addObject:peripheral]; - peripheral.delegate = self; - [self.centralManager connectPeripheral:peripheral options:nil]; - } -} - + RSSI:(NSNumber *)RSSI +{ + if (![self.discoveredPeripherals containsObject:peripheral]) + { + [self.discoveredPeripherals addObject:peripheral]; // Track discovered peripheral + peripheral.delegate = self; // Set ourselves as delegate for peripheral events + [self.centralManager connectPeripheral:peripheral options:nil]; // Attempt connection + } +} + +/** + * @brief Called when successfully connected to a peripheral + * + * Stores the peripheral and starts service discovery to find our + * communication characteristic. + * + * @param central The central manager + * @param peripheral The connected peripheral + */ - (void)centralManager:(CBCentralManager *)central - didConnectPeripheral:(CBPeripheral *)peripheral { - NSString *tempID = peripheral.identifier.UUIDString; - [self.connectedPeripherals setObject:peripheral forKey:tempID]; - [peripheral discoverServices:@[ [CBUUID UUIDWithString:SERVICE_UUID] ]]; -} - + didConnectPeripheral:(CBPeripheral *)peripheral +{ + NSString *tempID = peripheral.identifier.UUIDString; // Use UUID as peer ID + [self.connectedPeripherals setObject:peripheral forKey:tempID]; // Store connected peripheral + [peripheral discoverServices:@[ [CBUUID UUIDWithString:SERVICE_UUID] ]]; // Discover our service +} + +/** + * @brief Called when a peripheral disconnects + * + * Removes the peripheral from tracking and notifies the C++ bridge + * about the disconnection. + * + * @param central The central manager + * @param peripheral The disconnected peripheral + * @param error Disconnection error (if any) + */ - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral - error:(NSError *)error { + error:(NSError *)error +{ NSString *tempID = peripheral.identifier.UUIDString; - [self.connectedPeripherals removeObjectForKey:tempID]; - [self.peripheralCharacteristics removeObjectForKey:peripheral]; - - if (self.peerDisconnectedCallback) { + [self.connectedPeripherals removeObjectForKey:tempID]; // Remove from connected devices + [self.peripheralCharacteristics removeObjectForKey:peripheral]; // Remove characteristic reference + + // Notify C++ bridge about disconnection + if (self.peerDisconnectedCallback) + { self.peerDisconnectedCallback(tempID); } } +/** + * @brief Called when connection to a peripheral fails + * + * Removes the peripheral from discovered devices list. + * + * @param central The central manager + * @param peripheral The peripheral that failed to connect + * @param error Connection error + */ - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral - error:(NSError *)error { - [self.discoveredPeripherals removeObject:peripheral]; + error:(NSError *)error +{ + [self.discoveredPeripherals removeObject:peripheral]; // Remove from discovered devices } // ============================================================================ -// CBPeripheralManagerDelegate - PURE BLE ONLY +// CBPeripheralManagerDelegate - Peripheral Role (Advertiser/Server) // ============================================================================ -- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral { - if (peripheral.state == CBManagerStatePoweredOn) { +/** + * @brief Called when the peripheral manager's state changes + * + * When the peripheral manager is powered on, it sets up the service + * and starts advertising this device's presence. + * + * @param peripheral The peripheral manager + */ +- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral +{ + if (peripheral.state == CBManagerStatePoweredOn) + { dispatch_async(dispatch_get_main_queue(), ^{ - [self setupPeripheral]; - [self startAdvertising]; + [self setupPeripheral]; // Set up our service and characteristic + [self startAdvertising]; // Start advertising our presence }); } } +/** + * @brief Called when a central writes to our characteristic + * + * Receives incoming packets from connected centrals and forwards + * them to the C++ bridge for processing. + * + * @param peripheral The peripheral manager + * @param requests Array of write requests from centrals + */ - (void)peripheralManager:(CBPeripheralManager *)peripheral - didReceiveWriteRequests:(NSArray *)requests { - for (CBATTRequest *request in requests) { - if (request.value && request.value.length >= 21) { - // PURE BLE: Just forward the raw packet data to C++ - if (self.packetReceivedCallback) { + didReceiveWriteRequests:(NSArray *)requests +{ + for (CBATTRequest *request in requests) + { + if (request.value && request.value.length >= bitchat::constants::BLE_MIN_PACKET_SIZE_BYTES) + { + // Forward the raw packet data to C++ bridge for processing + if (self.packetReceivedCallback) + { self.packetReceivedCallback(request.value); } } - [peripheral respondToRequest:request withResult:CBATTErrorSuccess]; + [peripheral respondToRequest:request withResult:CBATTErrorSuccess]; // Acknowledge the write } } +/** + * @brief Called when a central subscribes to our characteristic + * + * Tracks subscribed centrals so we can send them updates. + * + * @param peripheral The peripheral manager + * @param central The subscribing central + * @param characteristic The characteristic being subscribed to + */ - (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central - didSubscribeToCharacteristic:(CBCharacteristic *)characteristic { - if (![self.subscribedCentrals containsObject:central]) { - [self.subscribedCentrals addObject:central]; - } -} - + didSubscribeToCharacteristic:(CBCharacteristic *)characteristic +{ + if (![self.subscribedCentrals containsObject:central]) + { + [self.subscribedCentrals addObject:central]; // Track subscribed central + } +} + +/** + * @brief Called when a central unsubscribes from our characteristic + * + * Removes the central from our subscribed list. + * + * @param peripheral The peripheral manager + * @param central The unsubscribing central + * @param characteristic The characteristic being unsubscribed from + */ - (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central - didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic { - [self.subscribedCentrals removeObject:central]; + didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic +{ + [self.subscribedCentrals removeObject:central]; // Remove from subscribed list } +/** + * @brief Called when advertising starts + * + * @param peripheral The peripheral manager + * @param error Advertising error (if any) + */ - (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral - error:(NSError *)error { + error:(NSError *)error +{ // Advertising started successfully } +/** + * @brief Called when a service is added + * + * @param peripheral The peripheral manager + * @param service The added service + * @param error Service addition error (if any) + */ - (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service - error:(NSError *)error { + error:(NSError *)error +{ // Service added successfully } // ============================================================================ -// CBPeripheralDelegate - PURE BLE ONLY +// CBPeripheralDelegate - Peripheral Discovery and Communication // ============================================================================ +/** + * @brief Called when services are discovered on a peripheral + * + * Looks for our specific service and starts characteristic discovery. + * + * @param peripheral The peripheral + * @param error Service discovery error (if any) + */ - (void)peripheral:(CBPeripheral *)peripheral - didDiscoverServices:(NSError *)error { - for (CBService *service in peripheral.services) { - if ([service.UUID isEqual:[CBUUID UUIDWithString:SERVICE_UUID]]) { + didDiscoverServices:(NSError *)error +{ + for (CBService *service in peripheral.services) + { + if ([service.UUID isEqual:[CBUUID UUIDWithString:SERVICE_UUID]]) + { + // Found our service, now discover the characteristic [peripheral discoverCharacteristics:@[ [CBUUID UUIDWithString:CHARACTERISTIC_UUID] ] forService:service]; } } } +/** + * @brief Called when characteristics are discovered on a service + * + * Stores the characteristic reference and enables notifications + * to receive updates from the peripheral. + * + * @param peripheral The peripheral + * @param service The service containing the characteristics + * @param error Characteristic discovery error (if any) + */ - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service - error:(NSError *)error { - for (CBCharacteristic *characteristic in service.characteristics) { - if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_UUID]]) { - [self.peripheralCharacteristics setObject:characteristic forKey:peripheral]; - [peripheral setNotifyValue:YES forCharacteristic:characteristic]; + error:(NSError *)error +{ + for (CBCharacteristic *characteristic in service.characteristics) + { + if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_UUID]]) + { + [self.peripheralCharacteristics setObject:characteristic forKey:peripheral]; // Store characteristic + [peripheral setNotifyValue:YES forCharacteristic:characteristic]; // Enable notifications } } } +/** + * @brief Called when a characteristic value is updated + * + * Receives incoming packets from peripherals and forwards them + * to the C++ bridge for processing. + * + * @param peripheral The peripheral + * @param characteristic The updated characteristic + * @param error Update error (if any) + */ - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic - error:(NSError *)error { + error:(NSError *)error +{ NSData *data = characteristic.value; - if (!data || data.length < 21) { - return; + + if (!data || data.length < bitchat::constants::BLE_MIN_PACKET_SIZE_BYTES) + { + return; // Ignore invalid or too small packets } - - // PURE BLE: Just forward the raw packet data to C++ - if (self.packetReceivedCallback) { + + // Forward the raw packet data to C++ bridge for processing + if (self.packetReceivedCallback) + { self.packetReceivedCallback(data); } } +/** + * @brief Called when notification state changes for a characteristic + * + * @param peripheral The peripheral + * @param characteristic The characteristic + * @param error State change error (if any) + */ - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic - error:(NSError *)error { + error:(NSError *)error +{ // Notification state updated } // ============================================================================ -// Private methods - PURE BLE ONLY +// Private Helper Methods // ============================================================================ -- (void)startScanning { - if (self.centralManager.state == CBManagerStatePoweredOn) { - NSDictionary *options = @{CBCentralManagerScanOptionAllowDuplicatesKey : @YES}; +/** + * @brief Start scanning for other devices + * + * Scans for peripherals advertising our service UUID. + */ +- (void)startScanning +{ + if (self.centralManager.state == CBManagerStatePoweredOn) + { + NSDictionary *options = @{CBCentralManagerScanOptionAllowDuplicatesKey : @YES}; // Allow duplicate advertisements [self.centralManager scanForPeripheralsWithServices:@[ [CBUUID UUIDWithString:SERVICE_UUID] ] - options:options]; + options:options]; } } -- (void)setupPeripheral { +/** + * @brief Set up the peripheral service and characteristic + * + * Creates the BLE service and characteristic that other devices + * can connect to and communicate through. + */ +- (void)setupPeripheral +{ + // Create the characteristic with read, write, and notify properties self.mutableCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:CHARACTERISTIC_UUID] - properties:CBCharacteristicPropertyRead | CBCharacteristicPropertyWrite | + properties:CBCharacteristicPropertyRead | CBCharacteristicPropertyWrite | CBCharacteristicPropertyWriteWithoutResponse | CBCharacteristicPropertyNotify - value:nil - permissions:CBAttributePermissionsReadable | CBAttributePermissionsWriteable]; + value:nil + permissions:CBAttributePermissionsReadable | CBAttributePermissionsWriteable]; + // Create the service and add the characteristic CBMutableService *service = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:SERVICE_UUID] primary:YES]; service.characteristics = @[ self.mutableCharacteristic ]; - [self.peripheralManager addService:service]; + [self.peripheralManager addService:service]; // Add service to peripheral manager } -- (void)startAdvertising { - if (self.peripheralManager.state == CBManagerStatePoweredOn) { - NSString *localName = [self getLocalPeerId]; +/** + * @brief Start advertising this device's presence + * + * Advertises the service UUID and local peer ID so other devices + * can discover and connect to this device. + */ +- (void)startAdvertising +{ + if (self.peripheralManager.state == CBManagerStatePoweredOn) + { + NSString *localName = [self getLocalPeerId]; // Use peer ID as local name + + // Set up advertisement data NSDictionary *advertisementData = @{ - CBAdvertisementDataServiceUUIDsKey : @[ [CBUUID UUIDWithString:SERVICE_UUID] ], - CBAdvertisementDataLocalNameKey : localName + CBAdvertisementDataServiceUUIDsKey : @[ [CBUUID UUIDWithString:SERVICE_UUID] ], // Advertise our service + CBAdvertisementDataLocalNameKey : localName // Advertise our peer ID }; - [self.peripheralManager startAdvertising:advertisementData]; + + [self.peripheralManager startAdvertising:advertisementData]; // Start advertising } } - - -@end \ No newline at end of file +@end diff --git a/src/platforms/apple/bluetooth_bridge.mm b/src/platforms/apple/bluetooth_bridge.mm new file mode 100644 index 0000000..a74cb87 --- /dev/null +++ b/src/platforms/apple/bluetooth_bridge.mm @@ -0,0 +1,242 @@ +#include "bitchat/platform/bluetooth_interface.h" +#include "bitchat/protocol/packet_serializer.h" +#include "platforms/apple/bluetooth.h" +#include +#include +#include + +namespace bitchat +{ + +// ============================================================================ +// C++ Bridge Layer - Implements BluetoothInterface and forwards to Objective-C +// ============================================================================ + +/** + * @brief Bridge class that implements the C++ BluetoothInterface + * + * This class acts as a bridge between the C++ codebase and the Objective-C + * Bluetooth implementation. It translates C++ calls to Objective-C method calls + * and handles callback conversions between the two languages. + */ +class AppleBluetoothBridge : public BluetoothInterface +{ +private: + AppleBluetooth *impl; // Objective-C implementation instance + std::string localPeerId; // Local device peer identifier + PacketSerializer serializer; // Handles packet serialization/deserialization + + // Callback function pointers for C++ interface + PeerDisconnectedCallback peerDisconnectedCallback; // Called when a peer disconnects + PacketReceivedCallback packetReceivedCallback; // Called when a packet is received + +public: + /** + * @brief Constructor - Initializes the bridge and sets up callback translations + */ + AppleBluetoothBridge() + : impl(nil) + { + // Create the Objective-C Bluetooth implementation + impl = [[AppleBluetooth alloc] init]; + if (impl) + { + // Set up callback bridges to translate Objective-C callbacks to C++ + + // Bridge for peer disconnection events + [impl setPeerDisconnectedCallback:^(NSString *peerId) { + if (peerDisconnectedCallback) + { + // Convert NSString to std::string for C++ callback + std::string cppPeerId = [peerId UTF8String]; + peerDisconnectedCallback(cppPeerId); + } + }]; + + // Bridge for packet reception events + [impl setPacketReceivedCallback:^(NSData *packetData) { + if (packetReceivedCallback) + { + // Convert NSData to std::vector for C++ processing + std::vector data((uint8_t *)packetData.bytes, + (uint8_t *)packetData.bytes + packetData.length); + // Deserialize the raw data into a BitchatPacket object + BitchatPacket packet = serializer.deserializePacket(data); + packetReceivedCallback(packet); + } + }]; + } + } + + /** + * @brief Destructor - Clean up Objective-C object + */ + ~AppleBluetoothBridge() + { + if (impl) + { + [impl release]; // Release Objective-C object memory + } + } + + /** + * @brief Initialize the Bluetooth system + * @return true if initialization successful, false otherwise + */ + bool initialize() override + { + if (!impl) + { + return false; + } + + return [impl initialize]; // Forward to Objective-C implementation + } + + /** + * @brief Start Bluetooth scanning and advertising + * @return true if started successfully, false otherwise + */ + bool start() override + { + if (!impl) + { + return false; + } + + return [impl start]; // Forward to Objective-C implementation + } + + /** + * @brief Stop Bluetooth operations + */ + void stop() override + { + if (impl) + { + [impl stop]; // Forward to Objective-C implementation + } + } + + /** + * @brief Send a packet to all connected peers + * @param packet The packet to send + * @return true if sent successfully, false otherwise + */ + bool sendPacket(const BitchatPacket &packet) override + { + if (!impl) + { + return false; + } + + // Serialize C++ packet to raw bytes + std::vector data = serializer.serializePacket(packet); + // Convert to NSData for Objective-C + NSData *nsData = [NSData dataWithBytes:data.data() length:data.size()]; + return [impl sendPacket:nsData]; // Forward to Objective-C implementation + } + + /** + * @brief Send a packet to a specific peer + * @param packet The packet to send + * @param peerId The target peer's identifier + * @return true if sent successfully, false otherwise + */ + bool sendPacketToPeer(const BitchatPacket &packet, const std::string &peerId) override + { + if (!impl) + { + return false; + } + + // Serialize C++ packet to raw bytes + std::vector data = serializer.serializePacket(packet); + // Convert to NSData for Objective-C + NSData *nsData = [NSData dataWithBytes:data.data() length:data.size()]; + // Convert std::string to NSString for Objective-C + NSString *nsPeerId = [NSString stringWithUTF8String:peerId.c_str()]; + return [impl sendPacket:nsData toPeer:nsPeerId]; // Forward to Objective-C implementation + } + + /** + * @brief Check if Bluetooth system is ready for operations + * @return true if ready, false otherwise + */ + bool isReady() const override + { + if (!impl) + { + return false; + } + + return [impl isReady]; // Forward to Objective-C implementation + } + + /** + * @brief Get the local device's peer identifier + * @return Local peer ID as string + */ + std::string getLocalPeerId() const override + { + if (!impl) + { + return ""; + } + + // Get NSString from Objective-C and convert to std::string + NSString *peerId = [impl getLocalPeerId]; + return peerId ? [peerId UTF8String] : ""; + } + + /** + * @brief Set callback for peer disconnection events + * @param callback Function to call when a peer disconnects + */ + void setPeerDisconnectedCallback(PeerDisconnectedCallback callback) override + { + peerDisconnectedCallback = callback; // Store C++ callback function + } + + /** + * @brief Set callback for packet reception events + * @param callback Function to call when a packet is received + */ + void setPacketReceivedCallback(PacketReceivedCallback callback) override + { + packetReceivedCallback = callback; // Store C++ callback function + } + + /** + * @brief Get the number of currently connected peers + * @return Number of connected peers + */ + size_t getConnectedPeersCount() const override + { + if (!impl) + { + return 0; + } + + return [impl getConnectedPeersCount]; // Forward to Objective-C implementation + } +}; + +// ============================================================================ +// Factory Function - Creates the C++ bridge instance +// ============================================================================ + +/** + * @brief Factory function to create a Bluetooth interface instance + * + * This function is called by the BluetoothFactory to create a platform-specific + * Bluetooth implementation. It returns a unique_ptr to ensure proper memory management. + * + * @return Unique pointer to the Bluetooth interface implementation + */ +std::unique_ptr createBluetoothInterface() +{ + return std::make_unique(); +} + +} // namespace bitchat diff --git a/src/platforms/linux/bluetooth.cpp b/src/platforms/linux/bluetooth.cpp index 84694fd..f114485 100644 --- a/src/platforms/linux/bluetooth.cpp +++ b/src/platforms/linux/bluetooth.cpp @@ -1,283 +1,423 @@ #include "platforms/linux/bluetooth.h" #include "bitchat/protocol/packet.h" #include "bitchat/protocol/packet_serializer.h" -#include -#include -#include -#include +#include #include #include #include +#include #include #include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include -namespace bitchat { - -LinuxBluetooth::LinuxBluetooth() : dev_id_(-1), hci_socket_(-1), rfcomm_socket_(-1), stop_threads_(false) { - logger_ = spdlog::stdout_color_mt("linux_bluetooth"); - logger_->set_level(spdlog::level::debug); - - dev_id_ = hci_get_route(nullptr); - if (dev_id_ < 0) { - logger_->error("No Bluetooth adapter found"); +namespace bitchat +{ + +LinuxBluetooth::LinuxBluetooth() + : deviceId(-1) + , hciSocket(-1) + , rfcommSocket(-1) + , stopThreads(false) + , peerDisconnectedCallback(nullptr) + , packetReceivedCallback(nullptr) +{ + deviceId = hci_get_route(nullptr); + if (deviceId < 0) + { + spdlog::error("No Bluetooth adapter found"); throw std::runtime_error("No Bluetooth adapter found"); } - hci_socket_ = hci_open_dev(dev_id_); - if (hci_socket_ < 0) { - logger_->error("Failed to open HCI socket"); + hciSocket = hci_open_dev(deviceId); + if (hciSocket < 0) + { + spdlog::error("Failed to open HCI socket"); throw std::runtime_error("Failed to open HCI socket"); } bdaddr_t bdaddr; - hci_read_bd_addr(hci_socket_, &bdaddr, 1000); + hci_read_bd_addr(hciSocket, &bdaddr, 1000); char addr[19]; ba2str(&bdaddr, addr); - local_peer_id_ = addr; - logger_->info("Local Bluetooth adapter address: {}", local_peer_id_); + localPeerId = addr; + spdlog::info("Local Bluetooth adapter address: {}", localPeerId); } -LinuxBluetooth::~LinuxBluetooth() { +LinuxBluetooth::~LinuxBluetooth() +{ stop(); - if (hci_socket_ >= 0) { - close(hci_socket_); - logger_->info("Closed HCI socket."); + + if (hciSocket >= 0) + { + close(hciSocket); + spdlog::info("Closed HCI socket."); } - if (rfcomm_socket_ >= 0) { - close(rfcomm_socket_); - logger_->info("Closed RFCOMM socket."); + + if (rfcommSocket >= 0) + { + close(rfcommSocket); + spdlog::info("Closed RFCOMM socket."); } } -bool LinuxBluetooth::initialize() { - logger_->info("LinuxBluetooth initialized."); +bool LinuxBluetooth::initialize() +{ + spdlog::info("LinuxBluetooth initialized."); return true; } -bool LinuxBluetooth::start() { - stop_threads_ = false; - scan_thread_ = std::thread(&LinuxBluetooth::scan_thread_func, this); - accept_thread_ = std::thread(&LinuxBluetooth::accept_thread_func, this); - logger_->info("Bluetooth scanning and acceptance threads started."); +bool LinuxBluetooth::start() +{ + stopThreads = false; + scanThread = std::thread(&LinuxBluetooth::scanThreadFunc, this); + acceptThread = std::thread(&LinuxBluetooth::acceptThreadFunc, this); + spdlog::info("Bluetooth scanning and acceptance threads started."); + return true; } -void LinuxBluetooth::stop() { - stop_threads_ = true; - logger_->info("Stopping Bluetooth threads..."); - if (scan_thread_.joinable()) { - scan_thread_.join(); +void LinuxBluetooth::stop() +{ + stopThreads = true; + spdlog::info("Stopping Bluetooth threads..."); + + if (scanThread.joinable()) + { + scanThread.join(); } - if (accept_thread_.joinable()) { - accept_thread_.join(); + + if (acceptThread.joinable()) + { + acceptThread.join(); } - std::lock_guard lock(sockets_mutex_); - for (auto const& [key, val] : connected_sockets_) { + + std::lock_guard lock(socketsMutex); + for (auto const &[key, val] : connectedSockets) + { close(val); - logger_->info("Closed socket for peer: {}", key); + spdlog::info("Closed socket for peer: {}", key); } - connected_sockets_.clear(); - logger_->info("Bluetooth threads stopped and sockets closed."); + + connectedSockets.clear(); + spdlog::info("Bluetooth threads stopped and sockets closed."); } -bool LinuxBluetooth::sendPacket(const BitchatPacket& packet) { +bool LinuxBluetooth::sendPacket(const BitchatPacket &packet) +{ PacketSerializer serializer; std::vector data = serializer.serializePacket(packet); - std::lock_guard lock(sockets_mutex_); - if (connected_sockets_.empty()) { - logger_->warn("No connected peers to send packet to."); + std::lock_guard lock(socketsMutex); + + if (connectedSockets.empty()) + { + spdlog::warn("No connected peers to send packet to."); return false; } - for (auto const& [key, val] : connected_sockets_) { - if (write(val, data.data(), data.size()) < 0) { - logger_->error("Failed to write to socket for peer {}: {}", key, strerror(errno)); - return false; + + bool sentToAny = false; + for (auto const &[key, val] : connectedSockets) + { + if (write(val, data.data(), data.size()) < 0) + { + spdlog::error("Failed to write to socket for peer {}: {}", key, strerror(errno)); + // Don't return false here, try to send to other peers + continue; } - logger_->debug("Sent packet to peer: {}", key); + + spdlog::debug("Sent packet to peer: {}", key); + sentToAny = true; } - return true; + + return sentToAny; } -bool LinuxBluetooth::sendPacketToPeer(const BitchatPacket& packet, const std::string& peerId) { +bool LinuxBluetooth::sendPacketToPeer(const BitchatPacket &packet, const std::string &peerId) +{ PacketSerializer serializer; std::vector data = serializer.serializePacket(packet); - std::lock_guard lock(sockets_mutex_); - auto it = connected_sockets_.find(peerId); - if (it != connected_sockets_.end()) { - if (write(it->second, data.data(), data.size()) < 0) { - logger_->error("Failed to write to socket for peer {}: {}", peerId, strerror(errno)); + std::lock_guard lock(socketsMutex); + auto it = connectedSockets.find(peerId); + + if (it != connectedSockets.end()) + { + if (write(it->second, data.data(), data.size()) < 0) + { + spdlog::error("Failed to write to socket for peer {}: {}", peerId, strerror(errno)); return false; } - logger_->debug("Sent packet to specific peer: {}", peerId); + spdlog::debug("Sent packet to specific peer: {}", peerId); return true; } - logger_->warn("Peer {} not found in connected sockets.", peerId); + + spdlog::warn("Peer {} not found in connected sockets.", peerId); + return false; } -bool LinuxBluetooth::isReady() const { - return dev_id_ >= 0 && hci_socket_ >= 0; +bool LinuxBluetooth::isReady() const +{ + return deviceId >= 0 && hciSocket >= 0; } -std::string LinuxBluetooth::getLocalPeerId() const { - return local_peer_id_; +std::string LinuxBluetooth::getLocalPeerId() const +{ + return localPeerId; } -void LinuxBluetooth::setPeerDisconnectedCallback(PeerDisconnectedCallback callback) { - peer_disconnected_callback_ = callback; +void LinuxBluetooth::setPeerDisconnectedCallback(PeerDisconnectedCallback callback) +{ + peerDisconnectedCallback = callback; } -void LinuxBluetooth::setPacketReceivedCallback(PacketReceivedCallback callback) { - packet_received_callback_ = callback; +void LinuxBluetooth::setPacketReceivedCallback(PacketReceivedCallback callback) +{ + packetReceivedCallback = callback; } -size_t LinuxBluetooth::getConnectedPeersCount() const { - std::lock_guard lock(sockets_mutex_); - return connected_sockets_.size(); +size_t LinuxBluetooth::getConnectedPeersCount() const +{ + std::lock_guard lock(socketsMutex); + return connectedSockets.size(); } -void LinuxBluetooth::scan_thread_func() { - inquiry_info* ii = new inquiry_info[255]; - int max_rsp = 255; - int num_rsp{}; +void LinuxBluetooth::scanThreadFunc() +{ + inquiry_info *ii = new inquiry_info[255]; + int maxRsp = 255; + int numRsp{}; int flags = IREQ_CACHE_FLUSH; - char addr[19] = { 0 }; + char addr[19] = {0}; + + spdlog::info("Bluetooth scan thread started."); - logger_->info("Bluetooth scan thread started."); + while (!stopThreads) + { + numRsp = hci_inquiry(deviceId, 8, maxRsp, nullptr, &ii, flags); - while (!stop_threads_) { - num_rsp = hci_inquiry(dev_id_, 8, max_rsp, nullptr, &ii, flags); - if (num_rsp < 0) { - logger_->error("hci_inquiry failed: {}", strerror(errno)); + if (numRsp < 0) + { + spdlog::error("HCI inquiry failed: {}", strerror(errno)); break; } - for (int i = 0; i < num_rsp; i++) { + for (int i = 0; i < numRsp; i++) + { ba2str(&(ii[i].bdaddr), addr); - std::string device_id = addr; + std::string deviceId = addr; { - std::lock_guard lock(sockets_mutex_); - if (connected_sockets_.find(device_id) != connected_sockets_.end()) { - logger_->debug("Device {} already connected, skipping.", device_id); + std::lock_guard lock(socketsMutex); + if (connectedSockets.find(deviceId) != connectedSockets.end()) + { + spdlog::debug("Device {} is already connected, skipping.", deviceId); continue; } } - struct sockaddr_rc sock_addr; - memset(&sock_addr, 0, sizeof(sock_addr)); + struct sockaddr_rc sockAddr; + memset(&sockAddr, 0, sizeof(sockAddr)); int s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); - if (s < 0) { - logger_->error("Failed to create RFCOMM socket: {}", strerror(errno)); + + if (s < 0) + { + spdlog::error("Failed to create RFCOMM socket: {}", strerror(errno)); continue; } - sock_addr.rc_family = AF_BLUETOOTH; - sock_addr.rc_channel = (uint8_t) 1; - str2ba(addr, &sock_addr.rc_bdaddr); - - if (connect(s, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == 0) { - std::lock_guard lock(sockets_mutex_); - connected_sockets_[device_id] = s; - logger_->info("Connected to device: {}", device_id); - std::thread(&LinuxBluetooth::reader_thread_func, this, device_id, s).detach(); - } else { - logger_->warn("Failed to connect to device {}: {}", device_id, strerror(errno)); + + sockAddr.rc_family = AF_BLUETOOTH; + sockAddr.rc_channel = (uint8_t)1; + str2ba(addr, &sockAddr.rc_bdaddr); + + if (connect(s, (struct sockaddr *)&sockAddr, sizeof(sockAddr)) == 0) + { + std::lock_guard lock(socketsMutex); + connectedSockets[deviceId] = s; + spdlog::info("Connected to device: {}", deviceId); + std::thread(&LinuxBluetooth::readerThreadFunc, this, deviceId, s).detach(); + } + else + { + spdlog::warn("Failed to connect to device {}: {}", deviceId, strerror(errno)); close(s); } } - std::this_thread::sleep_for(std::chrono::seconds(10)); // Scan every 10 seconds + + // Scan every 10 seconds + std::this_thread::sleep_for(std::chrono::seconds(10)); } - logger_->info("Bluetooth scan thread stopped."); + + spdlog::info("Bluetooth scan thread stopped."); + delete[] ii; } -void LinuxBluetooth::accept_thread_func() { - struct sockaddr_rc loc_addr, rem_addr; - memset(&loc_addr, 0, sizeof(loc_addr)); - memset(&rem_addr, 0, sizeof(rem_addr)); - char buf[256] = { 0 }; +void LinuxBluetooth::acceptThreadFunc() +{ + struct sockaddr_rc locAddr, remAddr; + memset(&locAddr, 0, sizeof(locAddr)); + memset(&remAddr, 0, sizeof(remAddr)); + char buf[256] = {0}; int client; - socklen_t opt = sizeof(rem_addr); + socklen_t opt = sizeof(remAddr); - rfcomm_socket_ = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); - if (rfcomm_socket_ < 0) { - logger_->error("Failed to create RFCOMM socket for accepting connections: {}", strerror(errno)); + rfcommSocket = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (rfcommSocket < 0) + { + spdlog::error("Failed to create RFCOMM socket for accepting connections: {}", strerror(errno)); return; } - loc_addr.rc_family = AF_BLUETOOTH; - bdaddr_t any_bdaddr = {0}; // Initialize to all zeros - bacpy(&loc_addr.rc_bdaddr, &any_bdaddr); - loc_addr.rc_channel = (uint8_t) 1; - if (bind(rfcomm_socket_, (struct sockaddr *)&loc_addr, sizeof(loc_addr)) < 0) { - logger_->error("Failed to bind RFCOMM socket: {}", strerror(errno)); - close(rfcomm_socket_); - rfcomm_socket_ = -1; + locAddr.rc_family = AF_BLUETOOTH; + bdaddr_t anyBdaddr = {0}; // Initialize to all zeros + bacpy(&locAddr.rc_bdaddr, &anyBdaddr); + locAddr.rc_channel = (uint8_t)1; + + if (bind(rfcommSocket, (struct sockaddr *)&locAddr, sizeof(locAddr)) < 0) + { + spdlog::error("Failed to bind RFCOMM socket: {}", strerror(errno)); + close(rfcommSocket); + rfcommSocket = -1; return; } - listen(rfcomm_socket_, 1); - logger_->info("Listening for incoming Bluetooth connections on channel 1."); - while (!stop_threads_) { - client = accept(rfcomm_socket_, (struct sockaddr *)&rem_addr, &opt); - if (client < 0) { - if (errno == EINTR) { // Interrupted system call, e.g., by signal + // Listen for incoming connections on channel 1 + listen(rfcommSocket, 1); + spdlog::info("Listening for incoming Bluetooth connections on channel 1."); + + while (!stopThreads) + { + client = accept(rfcommSocket, (struct sockaddr *)&remAddr, &opt); + if (client < 0) + { + if (errno == EINTR) + { + // Interrupted system call, e.g., by signal continue; } - logger_->error("Failed to accept connection: {}", strerror(errno)); + + spdlog::error("Failed to accept connection: {}", strerror(errno)); continue; } - ba2str(&rem_addr.rc_bdaddr, buf); - std::string device_id = buf; + ba2str(&remAddr.rc_bdaddr, buf); + std::string deviceId = buf; - std::lock_guard lock(sockets_mutex_); - connected_sockets_[device_id] = client; - logger_->info("Accepted connection from device: {}", device_id); - std::thread(&LinuxBluetooth::reader_thread_func, this, device_id, client).detach(); + std::lock_guard lock(socketsMutex); + connectedSockets[deviceId] = client; + spdlog::info("Accepted connection from device: {}", deviceId); + std::thread(&LinuxBluetooth::readerThreadFunc, this, deviceId, client).detach(); } - logger_->info("Bluetooth accept thread stopped."); + + spdlog::info("Bluetooth accept thread stopped."); } -void LinuxBluetooth::reader_thread_func(const std::string& device_id, int socket) { +void LinuxBluetooth::readerThreadFunc(const std::string &deviceId, int socket) +{ char buf[4096]; - ssize_t bytes_read; + ssize_t bytesRead; + std::vector accumulatedData; + PacketSerializer serializer; + const size_t maxPacketSize = 65536; // 64KB max packet size + + spdlog::info("Reader thread started for device: {}", deviceId); + + while ((bytesRead = read(socket, buf, sizeof(buf))) > 0) + { + // Add received data to accumulated buffer + accumulatedData.insert(accumulatedData.end(), buf, buf + bytesRead); + + // Process complete packets from accumulated data + while (accumulatedData.size() >= 21) // Minimum packet size (header + senderID) + { + // Read payload length from the packet header (offset 12-13) + uint16_t payloadLength = (accumulatedData[12] << 8) | accumulatedData[13]; + uint8_t flags = accumulatedData[11]; // flags byte + + // Calculate total expected packet size + size_t expectedSize = 21; // header + senderID + if (flags & FLAG_HAS_RECIPIENT) + { + expectedSize += 8; // recipientID + } + expectedSize += payloadLength; // payload + if (flags & FLAG_HAS_SIGNATURE) + { + expectedSize += 64; // signature + } - logger_->info("Reader thread started for device: {}", device_id); + // Check for invalid or too large packets + if (expectedSize > maxPacketSize || payloadLength > maxPacketSize - 21) + { + spdlog::error("Invalid or too large packet from device: {} (size: {})", deviceId, expectedSize); + accumulatedData.clear(); + break; + } + + // Check if we have enough data for the complete packet + if (accumulatedData.size() < expectedSize) + { + // Not enough data for complete packet, wait for more + break; + } + + // Try to deserialize the packet + try + { + BitchatPacket packet = serializer.deserializePacket(accumulatedData); + + // Validate the packet + if (packet.version == 0 || packet.version > 1) + { + spdlog::warn("Invalid packet version {} from device: {}", packet.version, deviceId); + accumulatedData.erase(accumulatedData.begin()); + continue; + } + + if (packetReceivedCallback) + { + packetReceivedCallback(packet); + spdlog::debug("Received packet from device: {}", deviceId); + } - while ((bytes_read = read(socket, buf, sizeof(buf))) > 0) { - if (packet_received_callback_) { - std::vector data(buf, buf + bytes_read); - PacketSerializer serializer; - BitchatPacket packet = serializer.deserializePacket(data); - packet_received_callback_(packet); - logger_->debug("Received packet from device: {}", device_id); + // Remove the consumed packet from accumulated data + accumulatedData.erase(accumulatedData.begin(), accumulatedData.begin() + expectedSize); + } + catch (const std::exception &e) + { + spdlog::error("Failed to deserialize packet from device {}: {}", deviceId, e.what()); + // Remove one byte and try again + accumulatedData.erase(accumulatedData.begin()); + } } } - if (bytes_read == 0) { - logger_->info("Device {} disconnected gracefully.", device_id); - } else if (bytes_read < 0) { - logger_->error("Error reading from device {}: {}", device_id, strerror(errno)); + if (bytesRead == 0) + { + spdlog::info("Device {} disconnected gracefully.", deviceId); + } + else if (bytesRead < 0) + { + spdlog::error("Failed to read from device {}: {}", deviceId, strerror(errno)); } - if (peer_disconnected_callback_) { - peer_disconnected_callback_(device_id); - logger_->info("Peer disconnected callback invoked for device: {}", device_id); + // Notify about disconnection + if (peerDisconnectedCallback) + { + peerDisconnectedCallback(deviceId); + spdlog::info("Peer disconnected callback invoked for device: {}", deviceId); } - std::lock_guard lock(sockets_mutex_); - connected_sockets_.erase(device_id); + // Clean up socket + std::lock_guard lock(socketsMutex); + connectedSockets.erase(deviceId); close(socket); - logger_->info("Reader thread for device {} finished. Socket closed and removed from map.", device_id); -} - -std::unique_ptr createLinuxBluetoothBridge() { - return std::make_unique(); + spdlog::info("Reader thread for device {} finished. Socket closed and removed from map.", deviceId); } -} // namespace bitchat \ No newline at end of file +} // namespace bitchat diff --git a/src/platforms/linux/bluetooth_factory.cpp b/src/platforms/linux/bluetooth_factory.cpp new file mode 100644 index 0000000..babf3c0 --- /dev/null +++ b/src/platforms/linux/bluetooth_factory.cpp @@ -0,0 +1,13 @@ +#include "bitchat/platform/bluetooth_factory.h" +#include "platforms/linux/bluetooth.h" +#include + +namespace bitchat +{ + +std::unique_ptr createBluetoothInterface() +{ + return std::make_unique(); +} + +} // namespace bitchat