per Codex:jp_native works: jp central -> T-Beam peripheral
jp_native_peripheral works: T-Beam central -> jp peripheral jp_native_dual builds, but needs role arbitration to avoid central/peripheral race
This commit is contained in:
parent
83b122df15
commit
0c15cf7219
3 changed files with 692 additions and 11 deletions
|
|
@ -88,9 +88,9 @@ jp_native_dual
|
|||
|
||||
`jp_native` builds a Linux console program instead of ESP32 firmware. It uses the host Bluetooth adapter through BlueZ D-Bus, skips the OLED path, and prints received text to stdout. The current jp payload is `texts/little_boy_blue.txt`.
|
||||
|
||||
`jp_native_peripheral` is the first Linux peripheral/server scaffold. It builds a separate binary that checks for BlueZ `GattManager1` and `LEAdvertisingManager1` support on the host adapter. It does not yet register the full Exercise 306 GATT service or accept a T-Beam connection.
|
||||
`jp_native_peripheral` builds a Linux BLE peripheral/server. It registers the Exercise 306 GATT service through BlueZ, advertises the Reticulum service UUID, accepts T-Beam central connections, receives writes on RX, and notifies outgoing fragments on TX.
|
||||
|
||||
`jp_native_dual` registers both host interfaces in one process: the proven Linux central/client path and the current Linux peripheral/server scaffold. Today it can still use the central path to pair with a T-Beam, while also reporting whether BlueZ exposes the services needed for future host-native peripheral work.
|
||||
`jp_native_dual` registers both host interfaces in one process: the Linux central/client path and the Linux peripheral/server path.
|
||||
|
||||
## Building
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ Run it from the repository root:
|
|||
exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native_peripheral/program
|
||||
```
|
||||
|
||||
This build is expected to report whether the current BlueZ adapter exposes the GATT server and LE advertising managers needed for true Linux peripheral mode.
|
||||
This build is expected to register the GATT server, advertise the Exercise 306 service, and exchange Reticulum file-transfer traffic when a T-Beam connects as the BLE central.
|
||||
|
||||
Build the jp dual-role test binary with:
|
||||
|
||||
|
|
@ -215,7 +215,9 @@ Run it from the repository root:
|
|||
exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native_dual/program
|
||||
```
|
||||
|
||||
For the current T-Beam test, start `jp_native` or `jp_native_dual` first and wait for `BLE linux-central: scanning for Reticulum service`, then RESET the T-Beam. The dual binary is not fully ambidextrous until the Linux peripheral/server scaffold registers the real GATT service and advertisement.
|
||||
For a central-mode host test, start `jp_native` or `jp_native_dual` first and wait for `BLE linux-central: scanning for Reticulum service`, then RESET the T-Beam.
|
||||
|
||||
For a peripheral-mode host test, start `jp_native_peripheral` first and wait for `BLE linux-peripheral: advertising Reticulum service; waiting for central`, then RESET the T-Beam. In this order, the T-Beam can connect as the BLE central and jp receives an inbound Reticulum link.
|
||||
|
||||
### AMD64
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,39 @@
|
|||
|
||||
#include <Utilities/OS.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
using namespace RNS;
|
||||
|
||||
static uint64_t now_ms() {
|
||||
return RNS::Utilities::OS::ltime();
|
||||
}
|
||||
|
||||
static void put_u16_be(uint8_t* data, uint16_t value) {
|
||||
data[0] = (uint8_t)((value >> 8) & 0xFF);
|
||||
data[1] = (uint8_t)(value & 0xFF);
|
||||
}
|
||||
|
||||
static void put_u32_be(uint8_t* data, uint32_t value) {
|
||||
data[0] = (uint8_t)((value >> 24) & 0xFF);
|
||||
data[1] = (uint8_t)((value >> 16) & 0xFF);
|
||||
data[2] = (uint8_t)((value >> 8) & 0xFF);
|
||||
data[3] = (uint8_t)(value & 0xFF);
|
||||
}
|
||||
|
||||
static uint16_t get_u16_be(const uint8_t* data) {
|
||||
return ((uint16_t)data[0] << 8) | data[1];
|
||||
}
|
||||
|
||||
static uint32_t get_u32_be(const uint8_t* data) {
|
||||
return ((uint32_t)data[0] << 24) |
|
||||
((uint32_t)data[1] << 16) |
|
||||
((uint32_t)data[2] << 8) |
|
||||
(uint32_t)data[3];
|
||||
}
|
||||
|
||||
static void log_error(const char* what, GError* error) {
|
||||
if (error) {
|
||||
std::fprintf(stderr, "%s: %s\n", what, error->message);
|
||||
|
|
@ -15,17 +44,96 @@ static void log_error(const char* what, GError* error) {
|
|||
}
|
||||
}
|
||||
|
||||
static GVariant* bytes_variant(const RNS::Bytes& bytes) {
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE("ay"));
|
||||
for (size_t i = 0; i < bytes.size(); ++i) {
|
||||
g_variant_builder_add(&builder, "y", bytes[i]);
|
||||
}
|
||||
return g_variant_builder_end(&builder);
|
||||
}
|
||||
|
||||
static GVariant* flags_variant(std::initializer_list<const char*> flags) {
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE("as"));
|
||||
for (const char* flag : flags) {
|
||||
g_variant_builder_add(&builder, "s", flag);
|
||||
}
|
||||
return g_variant_builder_end(&builder);
|
||||
}
|
||||
|
||||
static const char* OBJECT_MANAGER_XML =
|
||||
"<node>"
|
||||
" <interface name='org.freedesktop.DBus.ObjectManager'>"
|
||||
" <method name='GetManagedObjects'>"
|
||||
" <arg name='objects' type='a{oa{sa{sv}}}' direction='out'/>"
|
||||
" </method>"
|
||||
" </interface>"
|
||||
"</node>";
|
||||
|
||||
static const char* GATT_SERVICE_XML =
|
||||
"<node>"
|
||||
" <interface name='org.bluez.GattService1'>"
|
||||
" <property name='UUID' type='s' access='read'/>"
|
||||
" <property name='Primary' type='b' access='read'/>"
|
||||
" </interface>"
|
||||
"</node>";
|
||||
|
||||
static const char* GATT_CHARACTERISTIC_XML =
|
||||
"<node>"
|
||||
" <interface name='org.bluez.GattCharacteristic1'>"
|
||||
" <method name='ReadValue'>"
|
||||
" <arg name='options' type='a{sv}' direction='in'/>"
|
||||
" <arg name='value' type='ay' direction='out'/>"
|
||||
" </method>"
|
||||
" <method name='WriteValue'>"
|
||||
" <arg name='value' type='ay' direction='in'/>"
|
||||
" <arg name='options' type='a{sv}' direction='in'/>"
|
||||
" </method>"
|
||||
" <method name='StartNotify'/>"
|
||||
" <method name='StopNotify'/>"
|
||||
" <property name='UUID' type='s' access='read'/>"
|
||||
" <property name='Service' type='o' access='read'/>"
|
||||
" <property name='Value' type='ay' access='read'/>"
|
||||
" <property name='Notifying' type='b' access='read'/>"
|
||||
" <property name='Flags' type='as' access='read'/>"
|
||||
" </interface>"
|
||||
"</node>";
|
||||
|
||||
static const char* ADVERTISEMENT_XML =
|
||||
"<node>"
|
||||
" <interface name='org.bluez.LEAdvertisement1'>"
|
||||
" <method name='Release'/>"
|
||||
" <property name='Type' type='s' access='read'/>"
|
||||
" <property name='ServiceUUIDs' type='as' access='read'/>"
|
||||
" <property name='LocalName' type='s' access='read'/>"
|
||||
" </interface>"
|
||||
"</node>";
|
||||
|
||||
HostBluezPeripheralInterface::HostBluezPeripheralInterface(const std::string& node_label,
|
||||
const char* name)
|
||||
: InterfaceImpl(name), node_label_(node_label) {
|
||||
_IN = true;
|
||||
_OUT = true;
|
||||
_bitrate = 1000000;
|
||||
_HW_MTU = 168;
|
||||
_HW_MTU = BLE_PAYLOAD_SIZE;
|
||||
identity_value_ = local_identity_hash();
|
||||
}
|
||||
|
||||
HostBluezPeripheralInterface::~HostBluezPeripheralInterface() {
|
||||
stop();
|
||||
if (object_manager_node_) {
|
||||
g_dbus_node_info_unref(object_manager_node_);
|
||||
}
|
||||
if (gatt_service_node_) {
|
||||
g_dbus_node_info_unref(gatt_service_node_);
|
||||
}
|
||||
if (gatt_characteristic_node_) {
|
||||
g_dbus_node_info_unref(gatt_characteristic_node_);
|
||||
}
|
||||
if (advertisement_node_) {
|
||||
g_dbus_node_info_unref(advertisement_node_);
|
||||
}
|
||||
if (bus_) {
|
||||
g_object_unref(bus_);
|
||||
}
|
||||
|
|
@ -50,14 +158,15 @@ bool HostBluezPeripheralInterface::start() {
|
|||
return true;
|
||||
}
|
||||
|
||||
std::printf("BLE linux-peripheral: BlueZ reports GATT server and LE advertising managers are present\n");
|
||||
std::printf("BLE linux-peripheral: GATT object registration scaffold builds; runtime service registration is the next step\n");
|
||||
register_gatt_application();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HostBluezPeripheralInterface::stop() {
|
||||
unregister_bluez_objects();
|
||||
online_ = false;
|
||||
connected_ = false;
|
||||
notifying_ = false;
|
||||
_online = false;
|
||||
}
|
||||
|
||||
|
|
@ -132,6 +241,123 @@ bool HostBluezPeripheralInterface::adapter_supports_peripheral() const {
|
|||
return has_gatt_manager_ && has_advertising_manager_;
|
||||
}
|
||||
|
||||
bool HostBluezPeripheralInterface::register_gatt_application() {
|
||||
GError* error = nullptr;
|
||||
object_manager_node_ = g_dbus_node_info_new_for_xml(OBJECT_MANAGER_XML, &error);
|
||||
if (!object_manager_node_) {
|
||||
log_error("BLE linux-peripheral: ObjectManager XML parse failed", error);
|
||||
return false;
|
||||
}
|
||||
gatt_service_node_ = g_dbus_node_info_new_for_xml(GATT_SERVICE_XML, &error);
|
||||
if (!gatt_service_node_) {
|
||||
log_error("BLE linux-peripheral: GattService XML parse failed", error);
|
||||
return false;
|
||||
}
|
||||
gatt_characteristic_node_ = g_dbus_node_info_new_for_xml(GATT_CHARACTERISTIC_XML, &error);
|
||||
if (!gatt_characteristic_node_) {
|
||||
log_error("BLE linux-peripheral: GattCharacteristic XML parse failed", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
static const GDBusInterfaceVTable vtable = {
|
||||
HostBluezPeripheralInterface::method_call,
|
||||
HostBluezPeripheralInterface::property_get,
|
||||
nullptr,
|
||||
{nullptr}};
|
||||
|
||||
auto register_one = [&](const char* path, GDBusNodeInfo* node) -> bool {
|
||||
guint id = g_dbus_connection_register_object(bus_, path, node->interfaces[0], &vtable, this, nullptr, &error);
|
||||
if (id == 0) {
|
||||
log_error("BLE linux-peripheral: D-Bus object registration failed", error);
|
||||
return false;
|
||||
}
|
||||
object_registration_ids_.push_back(id);
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!register_one(APP_PATH, object_manager_node_) ||
|
||||
!register_one(SERVICE_PATH, gatt_service_node_) ||
|
||||
!register_one(TX_PATH, gatt_characteristic_node_) ||
|
||||
!register_one(RX_PATH, gatt_characteristic_node_) ||
|
||||
!register_one(IDENTITY_PATH, gatt_characteristic_node_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
g_dbus_connection_call(bus_, BLUEZ_BUS, adapter_path_.c_str(),
|
||||
"org.bluez.GattManager1", "RegisterApplication",
|
||||
g_variant_new("(oa{sv})", APP_PATH, nullptr),
|
||||
nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr,
|
||||
register_application_done, this);
|
||||
std::printf("BLE linux-peripheral: GATT application registration requested\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostBluezPeripheralInterface::register_advertisement() {
|
||||
GError* error = nullptr;
|
||||
advertisement_node_ = g_dbus_node_info_new_for_xml(ADVERTISEMENT_XML, &error);
|
||||
if (!advertisement_node_) {
|
||||
log_error("BLE linux-peripheral: Advertisement XML parse failed", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
static const GDBusInterfaceVTable vtable = {
|
||||
HostBluezPeripheralInterface::method_call,
|
||||
HostBluezPeripheralInterface::property_get,
|
||||
nullptr,
|
||||
{nullptr}};
|
||||
|
||||
guint id = g_dbus_connection_register_object(bus_, ADV_PATH, advertisement_node_->interfaces[0],
|
||||
&vtable, this, nullptr, &error);
|
||||
if (id == 0) {
|
||||
log_error("BLE linux-peripheral: advertisement object registration failed", error);
|
||||
return false;
|
||||
}
|
||||
object_registration_ids_.push_back(id);
|
||||
|
||||
g_dbus_connection_call(bus_, BLUEZ_BUS, adapter_path_.c_str(),
|
||||
"org.bluez.LEAdvertisingManager1", "RegisterAdvertisement",
|
||||
g_variant_new("(oa{sv})", ADV_PATH, nullptr),
|
||||
nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr,
|
||||
register_advertisement_done, this);
|
||||
std::printf("BLE linux-peripheral: advertisement registration requested name=RNS-%s\n", node_label_.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void HostBluezPeripheralInterface::unregister_bluez_objects() {
|
||||
if (!bus_) {
|
||||
return;
|
||||
}
|
||||
if (advertisement_registered_) {
|
||||
GError* error = nullptr;
|
||||
GVariant* result = g_dbus_connection_call_sync(
|
||||
bus_, BLUEZ_BUS, adapter_path_.c_str(), "org.bluez.LEAdvertisingManager1", "UnregisterAdvertisement",
|
||||
g_variant_new("(o)", ADV_PATH), nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
||||
if (!result) {
|
||||
log_error("BLE linux-peripheral: UnregisterAdvertisement failed", error);
|
||||
} else {
|
||||
g_variant_unref(result);
|
||||
}
|
||||
advertisement_registered_ = false;
|
||||
}
|
||||
if (gatt_registered_) {
|
||||
GError* error = nullptr;
|
||||
GVariant* result = g_dbus_connection_call_sync(
|
||||
bus_, BLUEZ_BUS, adapter_path_.c_str(), "org.bluez.GattManager1", "UnregisterApplication",
|
||||
g_variant_new("(o)", APP_PATH), nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
||||
if (!result) {
|
||||
log_error("BLE linux-peripheral: UnregisterApplication failed", error);
|
||||
} else {
|
||||
g_variant_unref(result);
|
||||
}
|
||||
gatt_registered_ = false;
|
||||
}
|
||||
|
||||
for (guint id : object_registration_ids_) {
|
||||
g_dbus_connection_unregister_object(bus_, id);
|
||||
}
|
||||
object_registration_ids_.clear();
|
||||
}
|
||||
|
||||
void HostBluezPeripheralInterface::loop() {
|
||||
if (!online_) {
|
||||
return;
|
||||
|
|
@ -139,6 +365,11 @@ void HostBluezPeripheralInterface::loop() {
|
|||
while (g_main_context_iteration(nullptr, FALSE)) {
|
||||
}
|
||||
|
||||
if (reassembly_started_ms_ != 0 && now_ms() - reassembly_started_ms_ > REASSEMBLY_TIMEOUT_MS) {
|
||||
std::fprintf(stderr, "BLE linux-peripheral: reassembly timeout; dropping partial packet\n");
|
||||
reset_reassembly();
|
||||
}
|
||||
|
||||
RNS::Bytes packet({RNS::Type::NONE});
|
||||
while (dequeue_packet(packet)) {
|
||||
InterfaceImpl::handle_incoming(packet);
|
||||
|
|
@ -146,16 +377,124 @@ void HostBluezPeripheralInterface::loop() {
|
|||
}
|
||||
|
||||
void HostBluezPeripheralInterface::send_outgoing(const RNS::Bytes& data) {
|
||||
if (!online_ || !connected_) {
|
||||
if (!online_ || !connected_ || !notifying_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Full TX notify support depends on BlueZ GATT characteristic object
|
||||
// registration. This scaffold intentionally compiles separately from the
|
||||
// proven central path so adapter capability can be tested first.
|
||||
size_t total = (data.size() + BLE_PAYLOAD_SIZE - 1) / BLE_PAYLOAD_SIZE;
|
||||
if (total == 0 || total > 65535) {
|
||||
std::fprintf(stderr, "BLE linux-peripheral: cannot fragment packet len=%zu fragments=%zu\n", data.size(), total);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t msg_id = ++tx_message_id_;
|
||||
for (size_t i = 0; i < total; ++i) {
|
||||
uint8_t fragment[BLE_VALUE_SIZE];
|
||||
uint8_t fragment_type = FRAG_CONTINUE;
|
||||
if (i == 0) {
|
||||
fragment_type = FRAG_START;
|
||||
} else if (i == total - 1) {
|
||||
fragment_type = FRAG_END;
|
||||
}
|
||||
|
||||
size_t offset = i * BLE_PAYLOAD_SIZE;
|
||||
size_t chunk = std::min(BLE_PAYLOAD_SIZE, data.size() - offset);
|
||||
fragment[0] = fragment_type;
|
||||
fragment[1] = FRAG_HEADER_VERSION;
|
||||
put_u16_be(fragment + 2, (uint16_t)i);
|
||||
put_u16_be(fragment + 4, (uint16_t)total);
|
||||
put_u32_be(fragment + 6, msg_id);
|
||||
put_u32_be(fragment + 10, (uint32_t)data.size());
|
||||
std::memcpy(fragment + FRAG_HEADER_SIZE, data.data() + offset, chunk);
|
||||
send_fragment(fragment, FRAG_HEADER_SIZE + chunk);
|
||||
RNS::Utilities::OS::sleep(0.020);
|
||||
}
|
||||
|
||||
InterfaceImpl::handle_outgoing(data);
|
||||
}
|
||||
|
||||
void HostBluezPeripheralInterface::send_fragment(const uint8_t* data, size_t len) {
|
||||
tx_value_.assign(data, len);
|
||||
emit_tx_value_changed();
|
||||
}
|
||||
|
||||
void HostBluezPeripheralInterface::emit_tx_value_changed() {
|
||||
if (!bus_ || !notifying_) {
|
||||
return;
|
||||
}
|
||||
|
||||
GVariantBuilder changed;
|
||||
g_variant_builder_init(&changed, G_VARIANT_TYPE("a{sv}"));
|
||||
g_variant_builder_add(&changed, "{sv}", "Value", bytes_variant(tx_value_));
|
||||
GVariantBuilder invalidated;
|
||||
g_variant_builder_init(&invalidated, G_VARIANT_TYPE("as"));
|
||||
g_dbus_connection_emit_signal(bus_, nullptr, TX_PATH,
|
||||
"org.freedesktop.DBus.Properties", "PropertiesChanged",
|
||||
g_variant_new("(sa{sv}as)", "org.bluez.GattCharacteristic1", &changed, &invalidated),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void HostBluezPeripheralInterface::handle_fragment(const uint8_t* data, size_t len) {
|
||||
if (len == 16) {
|
||||
connected_ = true;
|
||||
std::printf("BLE linux-peripheral: identity handshake received\n");
|
||||
return;
|
||||
}
|
||||
if (len < FRAG_HEADER_SIZE) {
|
||||
std::fprintf(stderr, "BLE linux-peripheral: fragment too short len=%zu\n", len);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t fragment_type = data[0];
|
||||
uint8_t version = data[1];
|
||||
uint16_t sequence = get_u16_be(data + 2);
|
||||
uint16_t total = get_u16_be(data + 4);
|
||||
uint32_t msg_id = get_u32_be(data + 6);
|
||||
uint32_t msg_len = get_u32_be(data + 10);
|
||||
const uint8_t* payload = data + FRAG_HEADER_SIZE;
|
||||
size_t payload_len = len - FRAG_HEADER_SIZE;
|
||||
uint32_t expected_fragments = (msg_len + BLE_PAYLOAD_SIZE - 1) / BLE_PAYLOAD_SIZE;
|
||||
size_t expected_offset = (size_t)sequence * BLE_PAYLOAD_SIZE;
|
||||
|
||||
if ((fragment_type != FRAG_START && fragment_type != FRAG_CONTINUE && fragment_type != FRAG_END) ||
|
||||
version != FRAG_HEADER_VERSION ||
|
||||
total == 0 || sequence >= total ||
|
||||
msg_id == 0 || msg_len == 0 ||
|
||||
expected_fragments == 0 || expected_fragments != total ||
|
||||
expected_offset >= msg_len || payload_len > (msg_len - expected_offset)) {
|
||||
std::fprintf(stderr, "BLE linux-peripheral: invalid fragment header\n");
|
||||
reset_reassembly();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sequence == 0) {
|
||||
reset_reassembly();
|
||||
expected_total_ = total;
|
||||
received_fragments_ = 0;
|
||||
expected_message_len_ = msg_len;
|
||||
reassembly_started_ms_ = now_ms();
|
||||
current_rx_message_id_ = msg_id;
|
||||
} else if (reassembly_started_ms_ == 0 || msg_id != current_rx_message_id_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (expected_total_ != total || expected_message_len_ != msg_len || sequence != received_fragments_) {
|
||||
std::fprintf(stderr, "BLE linux-peripheral: out-of-order fragment; dropping partial packet\n");
|
||||
reset_reassembly();
|
||||
return;
|
||||
}
|
||||
|
||||
reassembly_buffer_.append(payload, payload_len);
|
||||
received_fragments_++;
|
||||
|
||||
if (received_fragments_ == expected_total_) {
|
||||
if (reassembly_buffer_.size() == expected_message_len_) {
|
||||
enqueue_packet(reassembly_buffer_);
|
||||
}
|
||||
reset_reassembly();
|
||||
}
|
||||
}
|
||||
|
||||
void HostBluezPeripheralInterface::enqueue_packet(const RNS::Bytes& packet) {
|
||||
incoming_packets_.push_back(packet);
|
||||
}
|
||||
|
|
@ -168,3 +507,271 @@ bool HostBluezPeripheralInterface::dequeue_packet(RNS::Bytes& packet) {
|
|||
incoming_packets_.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HostBluezPeripheralInterface::reset_reassembly() {
|
||||
reassembly_buffer_.clear();
|
||||
expected_total_ = 0;
|
||||
received_fragments_ = 0;
|
||||
expected_message_len_ = 0;
|
||||
reassembly_started_ms_ = 0;
|
||||
current_rx_message_id_ = 0;
|
||||
}
|
||||
|
||||
RNS::Bytes HostBluezPeripheralInterface::local_identity_hash() const {
|
||||
std::string material = std::string("microReticulum BLE ") + node_label_;
|
||||
return RNS::Identity::full_hash(RNS::bytesFromString(material.c_str())).left(16);
|
||||
}
|
||||
|
||||
GVariant* HostBluezPeripheralInterface::get_managed_objects() const {
|
||||
GVariantBuilder objects;
|
||||
g_variant_builder_init(&objects, G_VARIANT_TYPE("a{oa{sa{sv}}}"));
|
||||
|
||||
GVariantBuilder service_ifaces;
|
||||
g_variant_builder_init(&service_ifaces, G_VARIANT_TYPE("a{sa{sv}}"));
|
||||
GVariantBuilder service_props;
|
||||
g_variant_builder_init(&service_props, G_VARIANT_TYPE("a{sv}"));
|
||||
g_variant_builder_add(&service_props, "{sv}", "UUID", g_variant_new_string(SERVICE_UUID));
|
||||
g_variant_builder_add(&service_props, "{sv}", "Primary", g_variant_new_boolean(TRUE));
|
||||
g_variant_builder_add(&service_ifaces, "{sa{sv}}", "org.bluez.GattService1", &service_props);
|
||||
g_variant_builder_add(&objects, "{oa{sa{sv}}}", SERVICE_PATH, &service_ifaces);
|
||||
|
||||
auto add_characteristic = [&](const char* path, const char* uuid, GVariant* flags, const RNS::Bytes& value, bool notifying) {
|
||||
GVariantBuilder ifaces;
|
||||
g_variant_builder_init(&ifaces, G_VARIANT_TYPE("a{sa{sv}}"));
|
||||
GVariantBuilder props;
|
||||
g_variant_builder_init(&props, G_VARIANT_TYPE("a{sv}"));
|
||||
g_variant_builder_add(&props, "{sv}", "UUID", g_variant_new_string(uuid));
|
||||
g_variant_builder_add(&props, "{sv}", "Service", g_variant_new_object_path(SERVICE_PATH));
|
||||
g_variant_builder_add(&props, "{sv}", "Flags", flags);
|
||||
g_variant_builder_add(&props, "{sv}", "Value", bytes_variant(value));
|
||||
g_variant_builder_add(&props, "{sv}", "Notifying", g_variant_new_boolean(notifying));
|
||||
g_variant_builder_add(&ifaces, "{sa{sv}}", "org.bluez.GattCharacteristic1", &props);
|
||||
g_variant_builder_add(&objects, "{oa{sa{sv}}}", path, &ifaces);
|
||||
};
|
||||
|
||||
add_characteristic(TX_PATH, TX_UUID, flags_variant({"read", "notify"}), tx_value_, notifying_);
|
||||
add_characteristic(RX_PATH, RX_UUID, flags_variant({"write", "write-without-response"}), RNS::Bytes(), false);
|
||||
add_characteristic(IDENTITY_PATH, IDENTITY_UUID, flags_variant({"read"}), identity_value_, false);
|
||||
|
||||
return g_variant_new("(a{oa{sa{sv}}})", &objects);
|
||||
}
|
||||
|
||||
GVariant* HostBluezPeripheralInterface::get_property(const char* object_path,
|
||||
const char* interface_name,
|
||||
const char* property_name) const {
|
||||
if (std::strcmp(interface_name, "org.bluez.GattService1") == 0) {
|
||||
if (std::strcmp(property_name, "UUID") == 0) {
|
||||
return g_variant_new_string(SERVICE_UUID);
|
||||
}
|
||||
if (std::strcmp(property_name, "Primary") == 0) {
|
||||
return g_variant_new_boolean(TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
if (std::strcmp(interface_name, "org.bluez.GattCharacteristic1") == 0) {
|
||||
bool is_tx = std::strcmp(object_path, TX_PATH) == 0;
|
||||
bool is_rx = std::strcmp(object_path, RX_PATH) == 0;
|
||||
bool is_identity = std::strcmp(object_path, IDENTITY_PATH) == 0;
|
||||
|
||||
if (std::strcmp(property_name, "UUID") == 0) {
|
||||
return g_variant_new_string(is_tx ? TX_UUID : (is_rx ? RX_UUID : IDENTITY_UUID));
|
||||
}
|
||||
if (std::strcmp(property_name, "Service") == 0) {
|
||||
return g_variant_new_object_path(SERVICE_PATH);
|
||||
}
|
||||
if (std::strcmp(property_name, "Flags") == 0) {
|
||||
if (is_tx) {
|
||||
return flags_variant({"read", "notify"});
|
||||
}
|
||||
if (is_rx) {
|
||||
return flags_variant({"write", "write-without-response"});
|
||||
}
|
||||
return flags_variant({"read"});
|
||||
}
|
||||
if (std::strcmp(property_name, "Value") == 0) {
|
||||
return bytes_variant(is_identity ? identity_value_ : tx_value_);
|
||||
}
|
||||
if (std::strcmp(property_name, "Notifying") == 0) {
|
||||
return g_variant_new_boolean(is_tx && notifying_);
|
||||
}
|
||||
}
|
||||
|
||||
if (std::strcmp(interface_name, "org.bluez.LEAdvertisement1") == 0) {
|
||||
if (std::strcmp(property_name, "Type") == 0) {
|
||||
return g_variant_new_string("peripheral");
|
||||
}
|
||||
if (std::strcmp(property_name, "ServiceUUIDs") == 0) {
|
||||
return flags_variant({SERVICE_UUID});
|
||||
}
|
||||
if (std::strcmp(property_name, "LocalName") == 0) {
|
||||
std::string name = std::string("RNS-") + node_label_;
|
||||
return g_variant_new_string(name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void HostBluezPeripheralInterface::handle_method_call(const char* object_path,
|
||||
const char* interface_name,
|
||||
const char* method_name,
|
||||
GVariant* parameters,
|
||||
GDBusMethodInvocation* invocation) {
|
||||
if (std::strcmp(interface_name, "org.freedesktop.DBus.ObjectManager") == 0 &&
|
||||
std::strcmp(method_name, "GetManagedObjects") == 0) {
|
||||
g_dbus_method_invocation_return_value(invocation, get_managed_objects());
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp(interface_name, "org.bluez.LEAdvertisement1") == 0 &&
|
||||
std::strcmp(method_name, "Release") == 0) {
|
||||
advertisement_registered_ = false;
|
||||
g_dbus_method_invocation_return_value(invocation, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp(interface_name, "org.bluez.GattCharacteristic1") != 0) {
|
||||
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "Unknown method");
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp(method_name, "ReadValue") == 0) {
|
||||
GVariant* options = nullptr;
|
||||
g_variant_get(parameters, "(@a{sv})", &options);
|
||||
if (options) {
|
||||
g_variant_unref(options);
|
||||
}
|
||||
if (std::strcmp(object_path, IDENTITY_PATH) == 0) {
|
||||
g_dbus_method_invocation_return_value(invocation, g_variant_new("(@ay)", bytes_variant(identity_value_)));
|
||||
return;
|
||||
}
|
||||
if (std::strcmp(object_path, TX_PATH) == 0) {
|
||||
g_dbus_method_invocation_return_value(invocation, g_variant_new("(@ay)", bytes_variant(tx_value_)));
|
||||
return;
|
||||
}
|
||||
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "ReadValue not supported");
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp(method_name, "WriteValue") == 0) {
|
||||
if (std::strcmp(object_path, RX_PATH) != 0) {
|
||||
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "WriteValue not supported");
|
||||
return;
|
||||
}
|
||||
GVariant* value = nullptr;
|
||||
GVariant* options = nullptr;
|
||||
g_variant_get(parameters, "(@ay@a{sv})", &value, &options);
|
||||
gsize len = 0;
|
||||
const uint8_t* data = static_cast<const uint8_t*>(g_variant_get_fixed_array(value, &len, sizeof(uint8_t)));
|
||||
if (data && len > 0) {
|
||||
handle_fragment(data, len);
|
||||
}
|
||||
if (value) {
|
||||
g_variant_unref(value);
|
||||
}
|
||||
if (options) {
|
||||
g_variant_unref(options);
|
||||
}
|
||||
g_dbus_method_invocation_return_value(invocation, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp(method_name, "StartNotify") == 0) {
|
||||
if (std::strcmp(object_path, TX_PATH) != 0) {
|
||||
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "Notify not supported");
|
||||
return;
|
||||
}
|
||||
notifying_ = true;
|
||||
connected_ = true;
|
||||
std::printf("BLE linux-peripheral: central subscribed to TX notifications\n");
|
||||
g_dbus_method_invocation_return_value(invocation, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp(method_name, "StopNotify") == 0) {
|
||||
notifying_ = false;
|
||||
connected_ = false;
|
||||
reset_reassembly();
|
||||
std::printf("BLE linux-peripheral: central unsubscribed from TX notifications\n");
|
||||
g_dbus_method_invocation_return_value(invocation, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "Unknown method");
|
||||
}
|
||||
|
||||
void HostBluezPeripheralInterface::method_call(GDBusConnection* connection,
|
||||
const gchar* sender,
|
||||
const gchar* object_path,
|
||||
const gchar* interface_name,
|
||||
const gchar* method_name,
|
||||
GVariant* parameters,
|
||||
GDBusMethodInvocation* invocation,
|
||||
gpointer user_data) {
|
||||
(void)connection;
|
||||
(void)sender;
|
||||
auto* self = static_cast<HostBluezPeripheralInterface*>(user_data);
|
||||
if (!self) {
|
||||
g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "No peripheral instance");
|
||||
return;
|
||||
}
|
||||
self->handle_method_call(object_path, interface_name, method_name, parameters, invocation);
|
||||
}
|
||||
|
||||
GVariant* HostBluezPeripheralInterface::property_get(GDBusConnection* connection,
|
||||
const gchar* sender,
|
||||
const gchar* object_path,
|
||||
const gchar* interface_name,
|
||||
const gchar* property_name,
|
||||
GError** error,
|
||||
gpointer user_data) {
|
||||
(void)connection;
|
||||
(void)sender;
|
||||
auto* self = static_cast<HostBluezPeripheralInterface*>(user_data);
|
||||
if (!self) {
|
||||
g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "No peripheral instance");
|
||||
return nullptr;
|
||||
}
|
||||
GVariant* value = self->get_property(object_path, interface_name, property_name);
|
||||
if (!value) {
|
||||
g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Unknown property");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void HostBluezPeripheralInterface::register_application_done(GObject* source_object,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
auto* self = static_cast<HostBluezPeripheralInterface*>(user_data);
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
GError* error = nullptr;
|
||||
GVariant* reply = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), result, &error);
|
||||
if (!reply) {
|
||||
log_error("BLE linux-peripheral: RegisterApplication failed", error);
|
||||
return;
|
||||
}
|
||||
g_variant_unref(reply);
|
||||
self->gatt_registered_ = true;
|
||||
std::printf("BLE linux-peripheral: GATT application registered\n");
|
||||
self->register_advertisement();
|
||||
}
|
||||
|
||||
void HostBluezPeripheralInterface::register_advertisement_done(GObject* source_object,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
auto* self = static_cast<HostBluezPeripheralInterface*>(user_data);
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
GError* error = nullptr;
|
||||
GVariant* reply = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), result, &error);
|
||||
if (!reply) {
|
||||
log_error("BLE linux-peripheral: RegisterAdvertisement failed", error);
|
||||
return;
|
||||
}
|
||||
g_variant_unref(reply);
|
||||
self->advertisement_registered_ = true;
|
||||
std::printf("BLE linux-peripheral: advertising Reticulum service; waiting for central\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class HostBluezPeripheralInterface : public RNS::InterfaceImpl {
|
||||
public:
|
||||
|
|
@ -27,18 +28,89 @@ private:
|
|||
bool connect_bus();
|
||||
bool find_adapter();
|
||||
bool adapter_supports_peripheral() const;
|
||||
bool register_gatt_application();
|
||||
bool register_advertisement();
|
||||
void unregister_bluez_objects();
|
||||
void emit_tx_value_changed();
|
||||
void send_fragment(const uint8_t* data, size_t len);
|
||||
void handle_fragment(const uint8_t* data, size_t len);
|
||||
void enqueue_packet(const RNS::Bytes& packet);
|
||||
bool dequeue_packet(RNS::Bytes& packet);
|
||||
void reset_reassembly();
|
||||
RNS::Bytes local_identity_hash() const;
|
||||
|
||||
GVariant* get_managed_objects() const;
|
||||
GVariant* get_property(const char* object_path, const char* interface_name, const char* property_name) const;
|
||||
void handle_method_call(const char* object_path,
|
||||
const char* interface_name,
|
||||
const char* method_name,
|
||||
GVariant* parameters,
|
||||
GDBusMethodInvocation* invocation);
|
||||
|
||||
static void method_call(GDBusConnection* connection,
|
||||
const gchar* sender,
|
||||
const gchar* object_path,
|
||||
const gchar* interface_name,
|
||||
const gchar* method_name,
|
||||
GVariant* parameters,
|
||||
GDBusMethodInvocation* invocation,
|
||||
gpointer user_data);
|
||||
static GVariant* property_get(GDBusConnection* connection,
|
||||
const gchar* sender,
|
||||
const gchar* object_path,
|
||||
const gchar* interface_name,
|
||||
const gchar* property_name,
|
||||
GError** error,
|
||||
gpointer user_data);
|
||||
static void register_application_done(GObject* source_object, GAsyncResult* result, gpointer user_data);
|
||||
static void register_advertisement_done(GObject* source_object, GAsyncResult* result, gpointer user_data);
|
||||
|
||||
static constexpr const char* BLUEZ_BUS = "org.bluez";
|
||||
static constexpr const char* APP_PATH = "/com/microreticulum/ex306";
|
||||
static constexpr const char* SERVICE_PATH = "/com/microreticulum/ex306/service0";
|
||||
static constexpr const char* TX_PATH = "/com/microreticulum/ex306/service0/tx";
|
||||
static constexpr const char* RX_PATH = "/com/microreticulum/ex306/service0/rx";
|
||||
static constexpr const char* IDENTITY_PATH = "/com/microreticulum/ex306/service0/identity";
|
||||
static constexpr const char* ADV_PATH = "/com/microreticulum/ex306/advertisement0";
|
||||
static constexpr const char* SERVICE_UUID = "37145b00-442d-4a94-917f-8f42c5da28e3";
|
||||
static constexpr const char* TX_UUID = "37145b00-442d-4a94-917f-8f42c5da28e4";
|
||||
static constexpr const char* RX_UUID = "37145b00-442d-4a94-917f-8f42c5da28e5";
|
||||
static constexpr const char* IDENTITY_UUID = "37145b00-442d-4a94-917f-8f42c5da28e6";
|
||||
|
||||
static constexpr uint8_t FRAG_START = 0x01;
|
||||
static constexpr uint8_t FRAG_CONTINUE = 0x02;
|
||||
static constexpr uint8_t FRAG_END = 0x03;
|
||||
static constexpr uint8_t FRAG_HEADER_VERSION = 0x02;
|
||||
static constexpr size_t FRAG_HEADER_SIZE = 14;
|
||||
static constexpr size_t BLE_ATT_MTU = 185;
|
||||
static constexpr size_t BLE_VALUE_SIZE = BLE_ATT_MTU - 3;
|
||||
static constexpr size_t BLE_PAYLOAD_SIZE = BLE_VALUE_SIZE - FRAG_HEADER_SIZE;
|
||||
static constexpr uint64_t REASSEMBLY_TIMEOUT_MS = 30000;
|
||||
|
||||
std::string node_label_;
|
||||
GDBusConnection* bus_ = nullptr;
|
||||
std::string adapter_path_;
|
||||
GDBusNodeInfo* object_manager_node_ = nullptr;
|
||||
GDBusNodeInfo* gatt_service_node_ = nullptr;
|
||||
GDBusNodeInfo* gatt_characteristic_node_ = nullptr;
|
||||
GDBusNodeInfo* advertisement_node_ = nullptr;
|
||||
std::vector<guint> object_registration_ids_;
|
||||
bool online_ = false;
|
||||
bool connected_ = false;
|
||||
bool notifying_ = false;
|
||||
bool gatt_registered_ = false;
|
||||
bool advertisement_registered_ = false;
|
||||
bool has_gatt_manager_ = false;
|
||||
bool has_advertising_manager_ = false;
|
||||
uint32_t tx_message_id_ = 0;
|
||||
RNS::Bytes tx_value_;
|
||||
RNS::Bytes identity_value_;
|
||||
|
||||
RNS::Bytes reassembly_buffer_;
|
||||
uint16_t expected_total_ = 0;
|
||||
uint16_t received_fragments_ = 0;
|
||||
uint32_t expected_message_len_ = 0;
|
||||
uint64_t reassembly_started_ms_ = 0;
|
||||
uint32_t current_rx_message_id_ = 0;
|
||||
std::deque<RNS::Bytes> incoming_packets_;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue