mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-10-23 06:12:25 +00:00
Initial commit: standalone Pyxis T-Deck firmware
Split T-Deck firmware from microReticulum examples/lxmf_tdeck/ into its own repo. microReticulum is consumed as a git submodule dependency pinned to feat/t-deck. All include paths updated from relative symlinks to bare includes resolved via library build flags. Both tdeck (NimBLE) and tdeck-bluedroid environments compile successfully. Licensed under AGPLv3. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
ac6ceca9f8
90 files changed
+28320
No files matched your search
@@ -0,0 +1,352 @@
|
||||
/**
|
||||
* @file BLEIdentityManager.h
|
||||
* @brief BLE-Reticulum Protocol v2.2 identity handshake manager
|
||||
*
|
||||
* Manages the identity handshake protocol and address-to-identity mapping.
|
||||
*
|
||||
* Handshake Protocol (v2.2):
|
||||
* 1. Central connects to peripheral
|
||||
* 2. Central writes 16-byte identity to RX characteristic
|
||||
* 3. Peripheral detects handshake: exactly 16 bytes AND no existing identity for that address
|
||||
* 4. Both sides now have bidirectional identity mapping
|
||||
*
|
||||
* The identity is the first 16 bytes of the Reticulum transport identity hash,
|
||||
* which remains stable across MAC address rotations.
|
||||
*
|
||||
* Uses fixed-size pools instead of STL containers to eliminate heap fragmentation.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "BLETypes.h"
|
||||
#include "Bytes.h"
|
||||
#include "Utilities/OS.h"
|
||||
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
|
||||
namespace RNS { namespace BLE {
|
||||
|
||||
class BLEIdentityManager {
|
||||
public:
|
||||
//=========================================================================
|
||||
// Pool Configuration
|
||||
//=========================================================================
|
||||
static constexpr size_t ADDRESS_IDENTITY_POOL_SIZE = 16;
|
||||
static constexpr size_t HANDSHAKE_POOL_SIZE = 4;
|
||||
/**
|
||||
* @brief Callback when handshake completes successfully
|
||||
*
|
||||
* @param mac_address The peer's current MAC address
|
||||
* @param peer_identity The peer's 16-byte identity hash
|
||||
* @param is_central true if we are the central (we initiated)
|
||||
*/
|
||||
using HandshakeCompleteCallback = std::function<void(
|
||||
const Bytes& mac_address,
|
||||
const Bytes& peer_identity,
|
||||
bool is_central)>;
|
||||
|
||||
/**
|
||||
* @brief Callback when handshake fails
|
||||
*
|
||||
* @param mac_address The peer's MAC address
|
||||
* @param reason Description of the failure
|
||||
*/
|
||||
using HandshakeFailedCallback = std::function<void(
|
||||
const Bytes& mac_address,
|
||||
const std::string& reason)>;
|
||||
|
||||
/**
|
||||
* @brief Callback when MAC rotation is detected
|
||||
*
|
||||
* Called when an identity we already know appears from a different MAC address.
|
||||
* This is common on Android which rotates BLE MAC addresses every ~15 minutes.
|
||||
*
|
||||
* @param old_mac The previous MAC address for this identity
|
||||
* @param new_mac The new MAC address
|
||||
* @param identity The stable 16-byte identity
|
||||
*/
|
||||
using MacRotationCallback = std::function<void(
|
||||
const Bytes& old_mac,
|
||||
const Bytes& new_mac,
|
||||
const Bytes& identity)>;
|
||||
|
||||
public:
|
||||
BLEIdentityManager();
|
||||
|
||||
/**
|
||||
* @brief Set our local identity (from RNS::Identity)
|
||||
*
|
||||
* Must be called before any handshakes. The identity should be the
|
||||
* first 16 bytes of the transport identity hash.
|
||||
*
|
||||
* @param identity_hash The 16-byte identity hash
|
||||
*/
|
||||
void setLocalIdentity(const Bytes& identity_hash);
|
||||
|
||||
/**
|
||||
* @brief Get our local identity hash
|
||||
*/
|
||||
const Bytes& getLocalIdentity() const { return _local_identity; }
|
||||
|
||||
/**
|
||||
* @brief Check if local identity is set
|
||||
*/
|
||||
bool hasLocalIdentity() const { return _local_identity.size() == Limits::IDENTITY_SIZE; }
|
||||
|
||||
/**
|
||||
* @brief Set callback for successful handshakes
|
||||
*/
|
||||
void setHandshakeCompleteCallback(HandshakeCompleteCallback callback);
|
||||
|
||||
/**
|
||||
* @brief Set callback for failed handshakes
|
||||
*/
|
||||
void setHandshakeFailedCallback(HandshakeFailedCallback callback);
|
||||
|
||||
/**
|
||||
* @brief Set callback for MAC rotation detection
|
||||
*/
|
||||
void setMacRotationCallback(MacRotationCallback callback);
|
||||
|
||||
//=========================================================================
|
||||
// Handshake Operations
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* @brief Start handshake as central (initiator)
|
||||
*
|
||||
* Called after BLE connection is established. Returns the identity
|
||||
* bytes that should be written to the peer's RX characteristic.
|
||||
*
|
||||
* @param mac_address Peer's MAC address
|
||||
* @return The 16-byte identity to write to peer's RX characteristic
|
||||
*/
|
||||
Bytes initiateHandshake(const Bytes& mac_address);
|
||||
|
||||
/**
|
||||
* @brief Process received data to detect/complete handshake
|
||||
*
|
||||
* This should be called for all received data. The function detects
|
||||
* whether the data is an identity handshake or regular data.
|
||||
*
|
||||
* @param mac_address Source MAC address
|
||||
* @param data Received data (may be identity or regular packet)
|
||||
* @param is_central true if we are central role for this connection
|
||||
* @return true if this was a handshake message (consumed), false if regular data
|
||||
*/
|
||||
bool processReceivedData(const Bytes& mac_address, const Bytes& data, bool is_central);
|
||||
|
||||
/**
|
||||
* @brief Check if data looks like an identity handshake
|
||||
*
|
||||
* A handshake is detected if:
|
||||
* - Data is exactly 16 bytes
|
||||
* - No existing identity mapping exists for this MAC address
|
||||
*
|
||||
* @param data The received data
|
||||
* @param mac_address The sender's MAC
|
||||
* @return true if this appears to be a handshake
|
||||
*/
|
||||
bool isHandshakeData(const Bytes& data, const Bytes& mac_address) const;
|
||||
|
||||
/**
|
||||
* @brief Mark handshake as complete for a peer
|
||||
*
|
||||
* Called after receiving identity from peer or after writing our identity.
|
||||
*
|
||||
* @param mac_address The peer's MAC address
|
||||
* @param peer_identity The peer's 16-byte identity
|
||||
* @param is_central true if we are the central
|
||||
*/
|
||||
void completeHandshake(const Bytes& mac_address, const Bytes& peer_identity, bool is_central);
|
||||
|
||||
/**
|
||||
* @brief Check for timed-out handshakes
|
||||
*/
|
||||
void checkTimeouts();
|
||||
|
||||
//=========================================================================
|
||||
// Identity Mapping
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* @brief Get identity for a MAC address
|
||||
* @return Identity bytes or empty if not known
|
||||
*/
|
||||
Bytes getIdentityForMac(const Bytes& mac_address) const;
|
||||
|
||||
/**
|
||||
* @brief Get MAC address for an identity
|
||||
* @return MAC address or empty if not known
|
||||
*/
|
||||
Bytes getMacForIdentity(const Bytes& identity) const;
|
||||
|
||||
/**
|
||||
* @brief Check if we have completed handshake with a MAC
|
||||
*/
|
||||
bool hasIdentity(const Bytes& mac_address) const;
|
||||
|
||||
/**
|
||||
* @brief Find identity that matches a prefix (for MAC rotation detection)
|
||||
*
|
||||
* Used when scanning detects a device name with identity prefix (Protocol v2.2).
|
||||
* If found, returns the full identity so we can recognize the rotated peer.
|
||||
*
|
||||
* @param prefix First N bytes of identity (typically 3 bytes from device name)
|
||||
* @return Full identity bytes if found, empty otherwise
|
||||
*/
|
||||
Bytes findIdentityByPrefix(const Bytes& prefix) const;
|
||||
|
||||
/**
|
||||
* @brief Update MAC address for a known identity (MAC rotation)
|
||||
*
|
||||
* @param identity The stable identity
|
||||
* @param new_mac The new MAC address
|
||||
*/
|
||||
void updateMacForIdentity(const Bytes& identity, const Bytes& new_mac);
|
||||
|
||||
/**
|
||||
* @brief Remove identity mapping (on disconnect)
|
||||
*/
|
||||
void removeMapping(const Bytes& mac_address);
|
||||
|
||||
/**
|
||||
* @brief Clear all mappings
|
||||
*/
|
||||
void clearAllMappings();
|
||||
|
||||
/**
|
||||
* @brief Get count of known peer identities
|
||||
*/
|
||||
size_t knownPeerCount() const;
|
||||
|
||||
/**
|
||||
* @brief Check if handshake is in progress for a MAC
|
||||
*/
|
||||
bool isHandshakeInProgress(const Bytes& mac_address) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Handshake state tracking
|
||||
*/
|
||||
enum class HandshakeState {
|
||||
NONE, // No handshake in progress
|
||||
INITIATED, // We sent our identity (as central)
|
||||
RECEIVED_IDENTITY, // We received peer's identity
|
||||
COMPLETE // Bidirectional identity exchange done
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief State for an in-progress handshake
|
||||
*/
|
||||
struct HandshakeSession {
|
||||
bool in_use = false;
|
||||
Bytes mac_address;
|
||||
Bytes peer_identity;
|
||||
HandshakeState state = HandshakeState::NONE;
|
||||
bool is_central = false;
|
||||
double started_at = 0.0;
|
||||
|
||||
void clear() {
|
||||
in_use = false;
|
||||
mac_address.clear();
|
||||
peer_identity.clear();
|
||||
state = HandshakeState::NONE;
|
||||
is_central = false;
|
||||
started_at = 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Slot for address-to-identity mapping
|
||||
*/
|
||||
struct AddressIdentitySlot {
|
||||
bool in_use = false;
|
||||
Bytes mac_address; // 6-byte MAC key
|
||||
Bytes identity; // 16-byte identity value
|
||||
|
||||
void clear() {
|
||||
in_use = false;
|
||||
mac_address.clear();
|
||||
identity.clear();
|
||||
}
|
||||
};
|
||||
|
||||
//=========================================================================
|
||||
// Pool Helper Methods - Address to Identity Mapping
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* @brief Find slot by MAC address
|
||||
*/
|
||||
AddressIdentitySlot* findAddressToIdentitySlot(const Bytes& mac);
|
||||
const AddressIdentitySlot* findAddressToIdentitySlot(const Bytes& mac) const;
|
||||
|
||||
/**
|
||||
* @brief Find slot by identity
|
||||
*/
|
||||
AddressIdentitySlot* findIdentityToAddressSlot(const Bytes& identity);
|
||||
const AddressIdentitySlot* findIdentityToAddressSlot(const Bytes& identity) const;
|
||||
|
||||
/**
|
||||
* @brief Find an empty slot in the address-identity pool
|
||||
*/
|
||||
AddressIdentitySlot* findEmptyAddressIdentitySlot();
|
||||
|
||||
/**
|
||||
* @brief Add or update address-identity mapping
|
||||
* @return true if successful, false if pool is full
|
||||
*/
|
||||
bool setAddressIdentityMapping(const Bytes& mac, const Bytes& identity);
|
||||
|
||||
/**
|
||||
* @brief Remove mapping by MAC address
|
||||
*/
|
||||
void removeAddressIdentityMapping(const Bytes& mac);
|
||||
|
||||
//=========================================================================
|
||||
// Pool Helper Methods - Handshake Sessions
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* @brief Find handshake session by MAC
|
||||
*/
|
||||
HandshakeSession* findHandshakeSession(const Bytes& mac);
|
||||
const HandshakeSession* findHandshakeSession(const Bytes& mac) const;
|
||||
|
||||
/**
|
||||
* @brief Find an empty handshake session slot
|
||||
*/
|
||||
HandshakeSession* findEmptyHandshakeSlot();
|
||||
|
||||
/**
|
||||
* @brief Get or create a handshake session for a MAC
|
||||
*/
|
||||
HandshakeSession* getOrCreateSession(const Bytes& mac_address);
|
||||
|
||||
/**
|
||||
* @brief Remove handshake session by MAC
|
||||
*/
|
||||
void removeHandshakeSession(const Bytes& mac);
|
||||
|
||||
//=========================================================================
|
||||
// Fixed-size Pool Storage
|
||||
//=========================================================================
|
||||
|
||||
// Our local identity hash (16 bytes)
|
||||
Bytes _local_identity;
|
||||
|
||||
// Bidirectional mappings (survive MAC rotation via identity)
|
||||
// Note: We use the same pool for both directions since they share the same data
|
||||
AddressIdentitySlot _address_identity_pool[ADDRESS_IDENTITY_POOL_SIZE];
|
||||
|
||||
// Active handshake sessions (keyed by MAC)
|
||||
HandshakeSession _handshakes_pool[HANDSHAKE_POOL_SIZE];
|
||||
|
||||
// Callbacks
|
||||
HandshakeCompleteCallback _handshake_complete_callback = nullptr;
|
||||
HandshakeFailedCallback _handshake_failed_callback = nullptr;
|
||||
MacRotationCallback _mac_rotation_callback = nullptr;
|
||||
};
|
||||
|
||||
}} // namespace RNS::BLE
|
||||
Reference in new issue
Block a user