/* WinRT backend: события Windows сериализуются в uasync через ограниченный mailbox. * Каждая отправка адресована link/subscribedClient; одна незавершённая операция на link. * Слабые ссылки в WinRT handlers и отменяемый posted_task исключают обращения к уничтоженному core. */ #include "ble_backend.h" #include "../u_async.h" #include "../mem.h" #include "../debug_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace winrt; using namespace Windows::Foundation; using namespace Windows::Devices::Bluetooth; using namespace Windows::Devices::Bluetooth::Advertisement; using namespace Windows::Devices::Bluetooth::GenericAttributeProfile; using namespace Windows::Storage::Streams; static constexpr auto CAT = DEBUG_CATEGORY_DISCOVERY; static const char* bluetooth_error(BluetoothError error) { switch (error) { case BluetoothError::Success: return "Success"; case BluetoothError::RadioNotAvailable: return "RadioNotAvailable"; case BluetoothError::ResourceInUse: return "ResourceInUse"; case BluetoothError::DeviceNotConnected: return "DeviceNotConnected"; case BluetoothError::DisabledByPolicy: return "DisabledByPolicy"; case BluetoothError::NotSupported: return "NotSupported"; case BluetoothError::DisabledByUser: return "DisabledByUser"; case BluetoothError::ConsentRequired: return "ConsentRequired"; case BluetoothError::TransportNotSupported: return "TransportNotSupported"; default: return "OtherError"; } } /* Вызывать только из catch; сохраняем контекст и HRESULT вместо немого catch(...). */ static std::string exception_reason(const char* phase, ble_link_id id = 0) { char prefix[192]; try { throw; } catch (const hresult_error& e) { std::snprintf(prefix, sizeof(prefix), "BLE WinRT phase=%s link=%llu HRESULT=0x%08x: ", phase, (unsigned long long)id, (unsigned)e.code().value); return prefix + to_string(e.message()); } catch (const std::exception& e) { return std::string("BLE WinRT phase=") + phase + ": " + e.what(); } catch (...) { return std::string("BLE WinRT phase=") + phase + ": unknown exception"; } } struct WinState; struct WinLink { ble_link_id id = 0; std::string endpoint; hstring device_id; bool outgoing = false, opened = false, sending = false; BluetoothLEDevice device{nullptr}; GattDeviceService service{nullptr}; GattCharacteristic rx{nullptr}, tx{nullptr}; GattSession session{nullptr}; GattSubscribedClient subscriber{nullptr}; IAsyncInfo operation{nullptr}; GattCharacteristic::ValueChanged_revoker value_changed; GattSession::SessionStatusChanged_revoker session_changed; GattSession::MaxPduSizeChanged_revoker pdu_changed; }; struct WriteDeferral { Deferral value; explicit WriteDeferral(Deferral v) : value(v) {} ~WriteDeferral() { try { value.Complete(); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("write deferral").c_str()); } } }; struct WinState : std::enable_shared_from_this { struct UASYNC* ua = nullptr; struct ble_transport* owner = nullptr; uint16_t company = 0; std::mutex mutex; std::deque> events; struct posted_task* posted = nullptr; bool stopped = false, failed = false, overflow = false, apartment = false, scan_ready = false, service_ready = false; IAsyncInfo startup{nullptr}; BluetoothLEAdvertisementWatcher watcher{nullptr}; BluetoothLEAdvertisementPublisher publisher{nullptr}; GattServiceProvider provider{nullptr}; GattLocalCharacteristic local_rx{nullptr}, local_tx{nullptr}; BluetoothLEAdvertisementWatcher::Received_revoker received; BluetoothLEAdvertisementWatcher::Stopped_revoker scan_stopped; BluetoothLEAdvertisementPublisher::StatusChanged_revoker publish_changed; GattServiceProvider::AdvertisementStatusChanged_revoker service_changed; GattLocalCharacteristic::WriteRequested_revoker write_requested; GattLocalCharacteristic::SubscribedClientsChanged_revoker subscriptions_changed; std::map> links; std::array broadcast{}; size_t broadcast_size = 0; bool broadcast_enabled = false; uint64_t publish_generation = 0; static void dispatch(void* arg) { auto& s = *static_cast(arg); std::deque> batch; bool overloaded; { std::lock_guard guard(s.mutex); s.posted = nullptr; batch.swap(s.events); overloaded = s.overflow; s.overflow = false; } if (overloaded) { s.fail("WinRT event queue overflow"); return; } DEBUG_TRACE(CAT, "BLE WinRT dispatch events=%zu links=%zu", batch.size(), s.links.size()); for (auto& event : batch) { if (s.stopped || s.failed) break; try { event(s); } catch (...) { s.fail(exception_reason("event dispatch").c_str()); } } } void post(std::function event) { std::lock_guard guard(mutex); if (stopped) return; if (events.size() >= 256) { if (!overflow) DEBUG_ERROR(CAT, "BLE WinRT event mailbox full queued=%zu limit=256", events.size()); overflow = true; } else events.push_back(std::move(event)); if (posted) return; posted = static_cast(u_calloc(1, sizeof(struct posted_task))); if (!posted) { overflow = true; DEBUG_ERROR(CAT, "BLE WinRT event allocation failed"); return; } posted->callback = dispatch; posted->arg = this; uasync_post_reserved(ua, posted); } WinLink* link(ble_link_id id) { auto it = links.find(id); return it == links.end() ? nullptr : it->second.get(); } void fail(const char* reason) { if (failed) return; failed = true; DEBUG_ERROR(CAT, "BLE WinRT failed: %s", reason); ble_backend_status(owner, 0, reason); } void error(ble_link_id id, const char* reason) { DEBUG_WARN(CAT, "BLE WinRT link=%llu failed: %s", (unsigned long long)id, reason); ble_backend_closed(owner, id, reason); } void radio_error(const char* phase, BluetoothError error) { char reason[192]; std::snprintf(reason, sizeof(reason), "%s: BluetoothError=%d (%s)", phase, (int)error, bluetooth_error(error)); fail(reason); } template void await(const char* phase, const IAsyncOperation& operation, ble_link_id id, Callback callback) { const bool fragment = !std::strcmp(phase, "write frame") || !std::strcmp(phase, "indicate frame"); if (!fragment) DEBUG_DEBUG(CAT, "BLE WinRT async begin phase=%s link=%llu", phase, (unsigned long long)id); if (id) { auto* l = link(id); if (!l) { operation.Cancel(); return; } l->operation = operation; } else startup = operation; auto weak = weak_from_this(); operation.Completed([weak, id, phase, fragment, callback](const IAsyncOperation& op, AsyncStatus status) { if (auto s = weak.lock()) s->post([id, op, status, phase, fragment, callback](WinState& self) { auto* l = id ? self.link(id) : nullptr; if (id && !l) { DEBUG_DEBUG(CAT, "BLE WinRT stale completion phase=%s link=%llu status=%d", phase, (unsigned long long)id, (int)status); return; } if (l) l->operation = nullptr; else self.startup = nullptr; if (status != AsyncStatus::Completed) { char reason[192]; std::snprintf(reason, sizeof(reason), "phase=%s async_status=%d HRESULT=0x%08x", phase, (int)status, (unsigned)op.ErrorCode().value); if (id) self.error(id, reason); else self.fail(reason); return; } if (!fragment) DEBUG_DEBUG(CAT, "BLE WinRT async complete phase=%s link=%llu", phase, (unsigned long long)id); callback(self, op.GetResults()); }); }); } static IBuffer buffer(const uint8_t* data, size_t len) { DataWriter writer; writer.WriteBytes(array_view(data, data + len)); return writer.DetachBuffer(); } static std::vector bytes(const IBuffer& buffer) { if (buffer.Length() > BLE_VALUE_MAX) throw hresult_invalid_argument(); std::vector result(buffer.Length()); DataReader::FromBuffer(buffer).ReadBytes(result); return result; } void ready() { DEBUG_DEBUG(CAT, "BLE WinRT readiness scan=%d service=%d", scan_ready, service_ready); if (scan_ready && service_ready) ble_backend_status(owner, 1, "WinRT scan/GATT ready"); } void watch_session(WinLink& l) { auto weak = weak_from_this(); auto id = l.id; l.session_changed = l.session.SessionStatusChanged(auto_revoke, [weak, id](auto const&, GattSessionStatusChangedEventArgs const& a) { auto status = a.Status(); auto error = a.Error(); if (auto s = weak.lock()) s->post([id, status, error](WinState& s) { DEBUG_DEBUG(CAT, "BLE WinRT session link=%llu status=%d error=%d (%s)", (unsigned long long)id, (int)status, (int)error, bluetooth_error(error)); if (status == GattSessionStatus::Closed && s.link(id)) s.error(id, "GATT session closed"); }); }); l.pdu_changed = l.session.MaxPduSizeChanged(auto_revoke, [weak, id](auto const& session, auto const&) { auto mtu = session.MaxPduSize(); if (auto s = weak.lock()) s->post([id, mtu](WinState&) { DEBUG_DEBUG(CAT, "BLE WinRT negotiated PDU changed link=%llu mtu=%u", (unsigned long long)id, (unsigned)mtu); }); }); } WinLink* incoming(const GattSession& session) { hstring key = session.DeviceId().Id(); for (auto& [id, l] : links) if (l->device_id == key) return l->outgoing ? nullptr : l.get(); std::string endpoint = to_string(key); if (endpoint.size() >= BLE_ENDPOINT_MAX || links.size() >= BLE_LINKS_MAX) { DEBUG_WARN(CAT, "BLE WinRT incoming endpoint/connection limit"); return nullptr; } struct ble_endpoint ep{}; std::memcpy(ep.id, endpoint.c_str(), endpoint.size() + 1); ble_link_id id = ble_backend_incoming(owner, &ep); if (!id) return nullptr; auto l = std::make_unique(); l->id = id; l->endpoint = endpoint; l->device_id = key; l->session = session; DEBUG_DEBUG(CAT, "BLE WinRT incoming link=%llu endpoint=%s mtu=%u", (unsigned long long)id, endpoint.c_str(), session.MaxPduSize()); auto* result = l.get(); links.emplace(id, std::move(l)); watch_session(*result); return result; } void subscriptions() { DEBUG_DEBUG(CAT, "BLE WinRT subscriptions clients=%u links=%zu", local_tx.SubscribedClients().Size(), links.size()); std::vector active; for (auto const& client : local_tx.SubscribedClients()) { auto* l = incoming(client.Session()); if (!l) continue; l->subscriber = client; active.push_back(l->id); if (!l->opened) { DEBUG_DEBUG(CAT, "BLE WinRT subscribed link=%llu notification_max=%u", (unsigned long long)l->id, client.MaxNotificationSize()); l->opened = true; ble_backend_opened(owner, l->id, client.MaxNotificationSize() + 3); } } std::vector gone; for (auto& [id, l] : links) if (!l->outgoing && l->opened && std::find(active.begin(), active.end(), id) == active.end()) gone.push_back(id); for (auto id : gone) error(id, "indication subscription removed"); } void make_server() { await("create GATT service", GattServiceProvider::CreateAsync(guid(BLE_SERVICE_UUID)), 0, [](WinState& s, GattServiceProviderResult const& result) { if (result.Error() != BluetoothError::Success) { s.radio_error("create GATT service", result.Error()); return; } s.provider = result.ServiceProvider(); GattLocalCharacteristicParameters parameters; parameters.CharacteristicProperties(GattCharacteristicProperties::Write); parameters.WriteProtectionLevel(GattProtectionLevel::Plain); s.await("create RX characteristic", s.provider.Service().CreateCharacteristicAsync(guid(BLE_RX_UUID), parameters), 0, [](WinState& s, GattLocalCharacteristicResult const& result) { if (result.Error() != BluetoothError::Success) { s.radio_error("create RX characteristic", result.Error()); return; } s.local_rx = result.Characteristic(); s.make_tx(); }); }); } void make_tx() { GattLocalCharacteristicParameters parameters; parameters.CharacteristicProperties(GattCharacteristicProperties::Indicate); parameters.ReadProtectionLevel(GattProtectionLevel::Plain); await("create TX characteristic", provider.Service().CreateCharacteristicAsync(guid(BLE_TX_UUID), parameters), 0, [](WinState& s, GattLocalCharacteristicResult const& result) { if (result.Error() != BluetoothError::Success) { s.radio_error("create TX characteristic", result.Error()); return; } s.local_tx = result.Characteristic(); s.start_server(); }); } void start_server() { auto weak = weak_from_this(); write_requested = local_rx.WriteRequested(auto_revoke, [weak](auto const&, GattWriteRequestedEventArgs const& args) { try { auto deferral = std::make_shared(args.GetDeferral()); auto request = args.GetRequestAsync(); auto session = args.Session(); request.Completed([weak, deferral, session](auto const& operation, AsyncStatus status) { if (auto s = weak.lock()) s->post([deferral, session, operation, status](WinState& s) { if (status != AsyncStatus::Completed) { DEBUG_WARN(CAT, "BLE WinRT GetRequest status=%d HRESULT=0x%08x", (int)status, (unsigned)operation.ErrorCode().value); return; } auto request = operation.GetResults(); if (!request) { DEBUG_WARN(CAT, "BLE WinRT expired write request"); return; } if (request.Offset() || request.Option() != GattWriteOption::WriteWithResponse || request.Value().Length() > BLE_VALUE_MAX) { DEBUG_WARN(CAT, "BLE WinRT invalid write offset=%u option=%d bytes=%u", request.Offset(), (int)request.Option(), request.Value().Length()); request.RespondWithProtocolError(0x06); return; } auto* l = s.incoming(session); if (!l) { request.RespondWithProtocolError(0x08); return; } auto id = l->id; auto data = bytes(request.Value()); DEBUG_TRACE(CAT, "BLE WinRT RX write link=%llu bytes=%zu", (unsigned long long)id, data.size()); ble_backend_received(s.owner, id, data.data(), data.size()); if (s.link(id)) request.Respond(); else request.RespondWithProtocolError(0x08); }); }); } catch (...) { auto reason = exception_reason("WriteRequested"); if (auto s = weak.lock()) s->post([reason](WinState& s) { s.fail(reason.c_str()); }); } }); subscriptions_changed = local_tx.SubscribedClientsChanged(auto_revoke, [weak](auto const&, auto const&) { if (auto s = weak.lock()) s->post([](WinState& s) { s.subscriptions(); }); }); service_changed = provider.AdvertisementStatusChanged(auto_revoke, [weak](auto const&, auto const& args) { auto status = args.Status(); auto error = args.Error(); if (auto s = weak.lock()) s->post([status, error](WinState& s) { DEBUG_DEBUG(CAT, "BLE WinRT service advertising state=%d error=%d (%s)", (int)status, (int)error, bluetooth_error(error)); if (status == GattServiceProviderAdvertisementStatus::Started) { s.service_ready = true; s.ready(); } else if (status == GattServiceProviderAdvertisementStatus::Aborted) s.radio_error("GATT advertising aborted", error); else DEBUG_INFO(CAT, "BLE WinRT service advertising state=%d", (int)status); }); }); GattServiceProviderAdvertisingParameters parameters; parameters.IsConnectable(true); parameters.IsDiscoverable(true); DEBUG_DEBUG(CAT, "BLE WinRT start server service=%s connectable=1 discoverable=1", BLE_SERVICE_UUID); provider.StartAdvertising(parameters); watcher = BluetoothLEAdvertisementWatcher(); watcher.ScanningMode(BluetoothLEScanningMode::Active); received = watcher.Received(auto_revoke, [weak](auto const&, BluetoothLEAdvertisementReceivedEventArgs const& args) { auto state = weak.lock(); if (!state) return; bool matching = false; for (auto const& value : args.Advertisement().ManufacturerData()) if (value.CompanyId() == state->company) matching = true; if (!matching) return; state->post([args](WinState& s) { for (auto const& value : args.Advertisement().ManufacturerData()) { if (value.CompanyId() != s.company || value.Data().Length() > BLE_BROADCAST_MAX) continue; struct ble_endpoint ep{}; std::snprintf(ep.id, sizeof(ep.id), "%012llx/%d", (unsigned long long)args.BluetoothAddress(), (int)args.BluetoothAddressType()); auto data = bytes(value.Data()); DEBUG_TRACE(CAT, "BLE WinRT broadcast endpoint=%s company=%04x bytes=%zu rssi=%d", ep.id, s.company, data.size(), args.RawSignalStrengthInDBm()); ble_backend_broadcast(s.owner, &ep, data.data(), data.size(), args.RawSignalStrengthInDBm()); } }); }); scan_stopped = watcher.Stopped(auto_revoke, [weak](auto const&, auto const& args) { auto error = args.Error(); if (auto s = weak.lock()) s->post([error](WinState& s) { s.radio_error("scan stopped", error); }); }); watcher.Start(); DEBUG_DEBUG(CAT, "BLE WinRT scan requested mode=active status=%d company=%04x", (int)watcher.Status(), company); scan_ready = true; ready(); } void publish() { DEBUG_DEBUG(CAT, "BLE WinRT publish requested enabled=%d bytes=%zu generation=%llu", broadcast_enabled, broadcast_size, (unsigned long long)publish_generation); if (publisher) { auto status = publisher.Status(); if (status == BluetoothLEAdvertisementPublisherStatus::Started || status == BluetoothLEAdvertisementPublisherStatus::Waiting) { publisher.Stop(); return; } if (status == BluetoothLEAdvertisementPublisherStatus::Stopping) return; publish_changed.revoke(); publisher = nullptr; } if (!broadcast_enabled) return; BluetoothLEAdvertisement advertisement; BluetoothLEManufacturerData data; data.CompanyId(company); data.Data(buffer(broadcast.data(), broadcast_size)); advertisement.ManufacturerData().Append(data); publisher = BluetoothLEAdvertisementPublisher(advertisement); auto weak = weak_from_this(); auto generation = ++publish_generation; publish_changed = publisher.StatusChanged(auto_revoke, [weak, generation](auto const&, auto const& args) { auto status = args.Status(); auto error = args.Error(); if (auto s = weak.lock()) s->post([status, error, generation](WinState& s) { if (generation != s.publish_generation) return; DEBUG_DEBUG(CAT, "BLE WinRT broadcast state=%d error=%d (%s) generation=%llu", (int)status, (int)error, bluetooth_error(error), (unsigned long long)generation); if (status == BluetoothLEAdvertisementPublisherStatus::Aborted) s.radio_error("broadcast publication aborted", error); else if (status == BluetoothLEAdvertisementPublisherStatus::Stopped) { ble_backend_advertising(s.owner, BLE_CLOSED, "publisher stopped"); s.publish(); } else if (status == BluetoothLEAdvertisementPublisherStatus::Started) ble_backend_advertising(s.owner, BLE_READY, "publisher started"); else ble_backend_advertising(s.owner, BLE_STARTING, "publisher waiting/stopping"); }); }); publisher.Start(); } void connect(ble_link_id id, uint64_t address, BluetoothAddressType type) { DEBUG_DEBUG(CAT, "BLE WinRT connect link=%llu address=%012llx type=%d", (unsigned long long)id, (unsigned long long)address, (int)type); await("open BLE device", BluetoothLEDevice::FromBluetoothAddressAsync(address, type), id, [id](WinState& s, BluetoothLEDevice const& device) { auto* l = s.link(id); if (!device) { s.error(id, "BLE device unavailable"); return; } l->device = device; l->device_id = device.BluetoothDeviceId().Id(); DEBUG_DEBUG(CAT, "BLE WinRT device link=%llu endpoint=%s status=%d", (unsigned long long)id, to_string(l->device_id).c_str(), (int)device.ConnectionStatus()); s.await("discover GATT service", device.GetGattServicesForUuidAsync(guid(BLE_SERVICE_UUID), BluetoothCacheMode::Uncached), id, [id](WinState& s, GattDeviceServicesResult const& result) { auto protocol = result.ProtocolError(); DEBUG_DEBUG(CAT, "BLE WinRT services link=%llu status=%d ATT_error=%d count=%u", (unsigned long long)id, (int)result.Status(), protocol ? int(protocol.Value()) : -1, result.Status() == GattCommunicationStatus::Success ? result.Services().Size() : 0); if (result.Status() != GattCommunicationStatus::Success || result.Services().Size() != 1) { s.error(id, "VibeChat GATT service unavailable/ambiguous"); return; } auto* l = s.link(id); l->service = result.Services().GetAt(0); s.await("discover GATT characteristics", l->service.GetCharacteristicsAsync(BluetoothCacheMode::Uncached), id, [id](WinState& s, GattCharacteristicsResult const& result) { auto protocol = result.ProtocolError(); DEBUG_DEBUG(CAT, "BLE WinRT characteristics link=%llu status=%d ATT_error=%d count=%u", (unsigned long long)id, (int)result.Status(), protocol ? int(protocol.Value()) : -1, result.Status() == GattCommunicationStatus::Success ? result.Characteristics().Size() : 0); if (result.Status() != GattCommunicationStatus::Success) { s.error(id, "GATT discovery failed"); return; } auto* l = s.link(id); for (auto const& ch : result.Characteristics()) { DEBUG_DEBUG(CAT, "BLE WinRT characteristic link=%llu uuid=%s properties=0x%x", (unsigned long long)id, to_string(to_hstring(ch.Uuid())).c_str(), (unsigned)ch.CharacteristicProperties()); if (ch.Uuid() == guid(BLE_RX_UUID)) l->rx = ch; if (ch.Uuid() == guid(BLE_TX_UUID)) l->tx = ch; } if (!l->rx || !l->tx) { s.error(id, "RX/TX characteristics missing"); return; } s.subscribe(id); }); }); }); } void subscribe(ble_link_id id) { auto* l = link(id); await("open GATT session", GattSession::FromDeviceIdAsync(l->device.BluetoothDeviceId()), id, [id](WinState& s, GattSession const& session) { auto* l = s.link(id); if (!session) { s.error(id, "GATT session unavailable"); return; } l->session = session; session.MaintainConnection(true); s.watch_session(*l); DEBUG_DEBUG(CAT, "BLE WinRT session opened link=%llu mtu=%u maintain_connection=1", (unsigned long long)id, session.MaxPduSize()); auto weak = s.weak_from_this(); l->value_changed = l->tx.ValueChanged(auto_revoke, [weak, id](auto const&, GattValueChangedEventArgs const& args) { if (auto s = weak.lock()) s->post([id, args](WinState& s) { if (!s.link(id)) return; auto data = bytes(args.CharacteristicValue()); DEBUG_TRACE(CAT, "BLE WinRT RX indication link=%llu bytes=%zu", (unsigned long long)id, data.size()); ble_backend_received(s.owner, id, data.data(), data.size()); }); }); s.await("subscribe indications", l->tx.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue::Indicate), id, [id](WinState& s, GattCommunicationStatus status) { DEBUG_DEBUG(CAT, "BLE WinRT subscribe link=%llu status=%d", (unsigned long long)id, (int)status); if (status != GattCommunicationStatus::Success) { s.error(id, "GATT subscription failed"); return; } auto* l = s.link(id); l->opened = true; ble_backend_opened(s.owner, id, l->session.MaxPduSize()); }); }); } void close(ble_link_id id) { auto it = links.find(id); if (it == links.end()) return; auto l = std::move(it->second); links.erase(it); DEBUG_DEBUG(CAT, "BLE WinRT close link=%llu outgoing=%d opened=%d sending=%d pending=%d", (unsigned long long)id, l->outgoing, l->opened, l->sending, bool(l->operation)); l->value_changed.revoke(); l->session_changed.revoke(); l->pdu_changed.revoke(); try { if (l->operation) l->operation.Cancel(); if (l->outgoing) { if (l->session) { l->session.MaintainConnection(false); l->session.Close(); } if (l->service) l->service.Close(); if (l->device) l->device.Close(); } } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("close", id).c_str()); } } void shutdown() { struct posted_task* task; std::deque> discarded; { std::lock_guard guard(mutex); stopped = true; task = posted; posted = nullptr; discarded.swap(events); } DEBUG_DEBUG(CAT, "BLE WinRT shutdown links=%zu discarded_events=%zu pending_dispatch=%d", links.size(), discarded.size(), task != nullptr); discarded.clear(); if (task) uasync_cancel_post(ua, task); received.revoke(); scan_stopped.revoke(); publish_changed.revoke(); service_changed.revoke(); write_requested.revoke(); subscriptions_changed.revoke(); while (!links.empty()) close(links.begin()->first); try { if (startup) startup.Cancel(); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("cancel startup").c_str()); } try { if (watcher) watcher.Stop(); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("stop scan").c_str()); } try { if (publisher) publisher.Stop(); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("stop publisher").c_str()); } try { if (provider) provider.StopAdvertising(); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("stop server").c_str()); } watcher = nullptr; publisher = nullptr; provider = nullptr; local_rx = nullptr; local_tx = nullptr; startup = nullptr; if (apartment) { uninit_apartment(); apartment = false; } } }; struct ble_backend { std::shared_ptr state; }; extern "C" struct ble_backend* ble_backend_create(struct ble_transport* owner, struct UASYNC* ua, const struct ble_config* cfg) { if (cfg->adapter) { DEBUG_ERROR(CAT, "BLE WinRT explicit adapter selection unsupported"); return nullptr; } auto state = std::make_shared(); state->owner = owner; state->ua = ua; state->company = cfg->company_id; try { DEBUG_DEBUG(CAT, "BLE WinRT initialize company=%04x node=%016llx apartment=MTA", cfg->company_id, (unsigned long long)cfg->node_id); init_apartment(apartment_type::multi_threaded); state->apartment = true; state->await("get default adapter", BluetoothAdapter::GetDefaultAsync(), 0, [](WinState& s, BluetoothAdapter const& adapter) { if (!adapter) { s.fail("no default Bluetooth adapter"); return; } DEBUG_DEBUG(CAT, "BLE WinRT adapter id=%s address=%012llx BLE=%d central=%d peripheral=%d", to_string(adapter.DeviceId()).c_str(), (unsigned long long)adapter.BluetoothAddress(), adapter.IsLowEnergySupported(), adapter.IsCentralRoleSupported(), adapter.IsPeripheralRoleSupported()); try { auto advertising = adapter.try_as(); auto phy = adapter.try_as(); DEBUG_DEBUG(CAT, "BLE WinRT adapter extended_advertising=%d advertisement_max=%d PHY_2M_supported=%d PHY_coded_supported=%d (-1=API unavailable)", advertising ? int(advertising.IsExtendedAdvertisingSupported()) : -1, advertising ? int(advertising.MaxAdvertisementDataLength()) : -1, phy ? int(phy.IsLowEnergyUncoded2MPhySupported()) : -1, phy ? int(phy.IsLowEnergyCodedPhySupported()) : -1); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("optional adapter capabilities").c_str()); } if (!adapter.IsLowEnergySupported() || !adapter.IsCentralRoleSupported() || !adapter.IsPeripheralRoleSupported()) { s.fail("adapter lacks BLE central/peripheral support"); return; } s.make_server(); }); void* memory = u_malloc(sizeof(ble_backend)); if (!memory) { DEBUG_ERROR(CAT, "BLE WinRT allocation failed"); state->shutdown(); return nullptr; } return new(memory) ble_backend{state}; } catch (...) { DEBUG_ERROR(CAT, "%s", exception_reason("initialize").c_str()); state->shutdown(); return nullptr; } } extern "C" void ble_backend_destroy(struct ble_backend* b) { if (!b) return; b->state->shutdown(); b->~ble_backend(); u_free(b); } extern "C" int ble_backend_advertise(struct ble_backend* b, const uint8_t* data, size_t len) { try { auto& s = *b->state; s.broadcast_enabled = data != nullptr; s.broadcast_size = len; if (len) std::memcpy(s.broadcast.data(), data, len); s.publish(); return BLE_OK; } catch (...) { DEBUG_ERROR(CAT, "%s", exception_reason("advertise").c_str()); return BLE_ERROR; } } extern "C" int ble_backend_connect(struct ble_backend* b, ble_link_id id, const struct ble_endpoint* ep) { unsigned long long address; int type, consumed = 0; if (std::sscanf(ep->id, "%llx/%d%n", &address, &type, &consumed) != 2 || ep->id[consumed] || address >> 48 || type < 0 || type > 2) { DEBUG_ERROR(CAT, "BLE invalid WinRT endpoint=%s", ep->id); return BLE_ERROR; } try { auto& s = *b->state; auto l = std::make_unique(); l->id = id; l->endpoint = ep->id; l->outgoing = true; s.links.emplace(id, std::move(l)); s.connect(id, address, static_cast(type)); return BLE_OK; } catch (...) { DEBUG_ERROR(CAT, "%s", exception_reason("connect", id).c_str()); b->state->close(id); return BLE_ERROR; } } extern "C" void ble_backend_close(struct ble_backend* b, ble_link_id id) { b->state->close(id); } extern "C" int ble_backend_send(struct ble_backend* b, ble_link_id id, const uint8_t* data, size_t len) { auto& s = *b->state; auto* l = s.link(id); if (!l || !l->opened || l->sending) { DEBUG_ERROR(CAT, "BLE invalid WinRT send link=%llu exists=%d opened=%d sending=%d bytes=%zu", (unsigned long long)id, l != nullptr, l && l->opened, l && l->sending, len); return BLE_ERROR; } try { DEBUG_TRACE(CAT, "BLE WinRT TX fragment link=%llu outgoing=%d bytes=%zu", (unsigned long long)id, l->outgoing, len); l->sending = true; auto buffer = WinState::buffer(data, len); if (l->outgoing) s.await("write frame", l->rx.WriteValueWithResultAsync(buffer, GattWriteOption::WriteWithResponse), id, [id, len](WinState& s, GattWriteResult const& result) { auto* l = s.link(id); l->sending = false; auto protocol = result.ProtocolError(); if (result.Status() != GattCommunicationStatus::Success) DEBUG_WARN(CAT, "BLE WinRT write failed link=%llu bytes=%zu status=%d ATT_error=%d", (unsigned long long)id, len, (int)result.Status(), protocol ? int(protocol.Value()) : -1); else DEBUG_TRACE(CAT, "BLE WinRT write complete link=%llu bytes=%zu", (unsigned long long)id, len); ble_backend_sent(s.owner, id, result.Status() == GattCommunicationStatus::Success); }); else s.await("indicate frame", s.local_tx.NotifyValueAsync(buffer, l->subscriber), id, [id, len](WinState& s, GattClientNotificationResult const& result) { auto* l = s.link(id); l->sending = false; if (result.Status() != GattCommunicationStatus::Success || result.BytesSent() != len) DEBUG_WARN(CAT, "BLE WinRT indication failed link=%llu expected=%zu sent=%u status=%d", (unsigned long long)id, len, result.BytesSent(), (int)result.Status()); else DEBUG_TRACE(CAT, "BLE WinRT indication complete link=%llu bytes=%zu", (unsigned long long)id, len); ble_backend_sent(s.owner, id, result.Status() == GattCommunicationStatus::Success && result.BytesSent() == len); }); return BLE_OK; } catch (...) { l->sending = false; DEBUG_ERROR(CAT, "%s", exception_reason("send", id).c_str()); return BLE_ERROR; } }