From 8a5a423da94f247d7b57b6059e9b186562cf3dde Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 31 Oct 2026 00:33:44 +0100 Subject: [PATCH] Use a single connectable node service advertisement on Windows --- lib/ble/ble_winrt.cpp | 242 +++++++++++++----------------------------- 1 file changed, 72 insertions(+), 170 deletions(-) diff --git a/lib/ble/ble_winrt.cpp b/lib/ble/ble_winrt.cpp index d8bbc7a8..9dbb3007 100644 --- a/lib/ble/ble_winrt.cpp +++ b/lib/ble/ble_winrt.cpp @@ -94,6 +94,7 @@ struct WinState : std::enable_shared_from_this { struct UASYNC* ua = nullptr; struct ble_transport* owner = nullptr; uint16_t company = 0; + std::string service_uuid; std::mutex mutex; std::deque events; const char* operation = "idle"; /* Только uasync; Windows callbacks передают контекст через Event. */ @@ -102,23 +103,18 @@ struct WinState : std::enable_shared_from_this { bool stopped = false, failed = false, overflow = false, apartment = false, scan_ready = false, service_ready = false; int received_address_type_api = -1; /* -1: ещё не было объявления; 0/1: фактический интерфейс EventArgs2. */ /* Windows callbacks считают все события, включая отсеянные до mailbox. Остальные поля — только uasync. */ - std::atomic scan_events{0}, scan_no_manufacturer{0}, scan_other_company{0}, scan_matching{0}; + std::atomic scan_events{0}, scan_foreign{0}, scan_matching{0}; uint64_t scan_forwarded = 0, scan_oversized = 0; void* diagnostics_timer = nullptr; - int publisher_status = -1; /* Последний StatusChanged; -1: publisher ещё не создан. */ - uint64_t publisher_wait_since = 0; - bool publisher_wait_warned = false; bool adapter_ready = false, server_created = false, ready_reported = false; - bool service_requested = false, service_stop_pending = false, publication_pending = false; - uint64_t publication_deadline = 0; /* Ограничивает запуск/замену publisher, включая освобождение GATT-рекламы. */ + bool service_requested = false, service_stop_pending = false; + uint64_t publication_deadline = 0; /* Ограничивает запуск/смену discoverability единственного GATT advertising. */ IAsyncInfo startup{nullptr}; BluetoothLEAdvertisementWatcher watcher{nullptr}; - BluetoothLEAdvertisementPublisher publisher{nullptr}; GattServiceProvider provider{nullptr}; GattLocalCharacteristic local_rx{nullptr}, local_tx{nullptr}, local_invite{nullptr}, local_ack{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, invite_write_requested, ack_write_requested; GattLocalCharacteristic::SubscribedClientsChanged_revoker subscriptions_changed, invite_subscriptions_changed, ack_subscriptions_changed; @@ -126,18 +122,15 @@ struct WinState : std::enable_shared_from_this { std::array broadcast{}; size_t broadcast_size = 0; bool broadcast_enabled = false; - uint64_t publish_generation = 0; /* Отмечает конкретный вызов до обращения к WinRT, включая синхронные исключения. */ void step(const char* name, ble_link_id id = 0) { operation = name; operation_link = id; DEBUG_TRACE(CAT, "BLE WinRT operation=%s link=%llu", name, (unsigned long long)id); } /* Читаем ОС, а не сохранённые callbacks. -1: объекта нет, -2: чтение завершилось ошибкой. */ - struct RadioStatus { int publisher = -1, service = -1, scan = -1; }; + struct RadioStatus { int service = -1, scan = -1; }; RadioStatus radio_status() { RadioStatus result; - try { if (publisher) result.publisher = int(publisher.Status()); } - catch (...) { result.publisher = -2; DEBUG_WARN(CAT, "%s", exception_reason("diagnostics Publisher.Status").c_str()); } try { if (provider) result.service = int(provider.AdvertisementStatus()); } catch (...) { result.service = -2; DEBUG_WARN(CAT, "%s", exception_reason("diagnostics GATT.AdvertisementStatus").c_str()); } try { if (watcher) result.scan = int(watcher.Status()); } @@ -145,38 +138,19 @@ struct WinState : std::enable_shared_from_this { return result; } void discovery_stats(const char* phase) { - auto wait_ms = publisher_wait_since ? (get_time_us() - publisher_wait_since) / 1000 : 0; - DEBUG_DEBUG(CAT, "BLE WinRT scan totals phase=%s scan_ready=%d service_ready=%d failed=%d company=%04x " - "events=%llu no_manufacturer=%llu other_company=%llu matching_events=%llu forwarded_fields=%llu oversized_fields=%llu " - "publisher_state=%d waiting_ms=%llu", - phase, scan_ready, service_ready, failed, company, (unsigned long long)scan_events.load(), - (unsigned long long)scan_no_manufacturer.load(), (unsigned long long)scan_other_company.load(), - (unsigned long long)scan_matching.load(), (unsigned long long)scan_forwarded, - (unsigned long long)scan_oversized, publisher_status, (unsigned long long)wait_ms); - if (publisher_wait_since) { - auto actual = radio_status(); - DEBUG_DEBUG(CAT, "BLE WinRT radio snapshot phase=%s publisher_actual=%d publisher_event=%d service_actual=%d scan_actual=%d " - "generation=%llu payload_bytes=%zu manufacturer_ad_bytes=%zu links=%zu " - "(publisher:1=Waiting,2=Started; service:1=Stopped,2=Started; scan:1=Started; -1=absent,-2=read_error)", - phase, actual.publisher, publisher_status, actual.service, actual.scan, - (unsigned long long)publish_generation, broadcast_size, broadcast_size + 4, links.size()); - } - if (wait_ms >= 10000 && !publisher_wait_warned) { - publisher_wait_warned = true; - DEBUG_WARN(CAT, "BLE WinRT publisher still waiting for radio resources elapsed_ms=%llu generation=%llu " - "scan_ready=%d service_ready=%d; manufacturer advertisement has not started", - (unsigned long long)wait_ms, (unsigned long long)publish_generation, scan_ready, service_ready); - DEBUG_WARN(CAT, "BLE WinRT GATT and manufacturer publisher share Windows advertising resources; " - "close VibeChat and run ble_advertising_probe.exe to compare GATT on/off/restored; " - "Waiting is not proof of GATT contention or of transmission"); - } + auto actual = radio_status(); + DEBUG_DEBUG(CAT, "BLE WinRT scan totals phase=%s ready=%d/%d events=%llu foreign=%llu matching=%llu forwarded=%llu " + "service_actual=%d scan_actual=%d discoverable=%d UUID=%s links=%zu", + phase, scan_ready, service_ready, (unsigned long long)scan_events.load(), (unsigned long long)scan_foreign.load(), + (unsigned long long)scan_matching.load(), (unsigned long long)scan_forwarded, actual.service, actual.scan, + broadcast_enabled, service_uuid.c_str(), links.size()); } static void diagnostics_tick(void* arg) { auto& s = *static_cast(arg); s.diagnostics_timer = nullptr; s.discovery_stats("periodic"); s.publication_timeout(); if (s.stopped || s.failed) return; - uint64_t delay = 100000; /* Сводка раз в 10s, но deadline publisher обслуживается без лишних 10s ожидания. TB=0.1ms. */ + uint64_t delay = 100000; /* Сводка раз в10s; deadline advertising обслуживается без лишнего ожидания. TB=0.1ms. */ if (s.publication_deadline) { auto now = get_time_us(); delay = std::min(delay, s.publication_deadline > now ? (s.publication_deadline - now + 99) / 100 : uint64_t(1)); @@ -220,8 +194,8 @@ struct WinState : std::enable_shared_from_this { 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; - DEBUG_ERROR(CAT, "BLE WinRT radio failure scan_ready=%d service_ready=%d links=%zu publisher_generation=%llu", - scan_ready, service_ready, links.size(), (unsigned long long)publish_generation); + DEBUG_ERROR(CAT, "BLE WinRT radio failure scan_ready=%d service_ready=%d links=%zu UUID=%s", + scan_ready, service_ready, links.size(), service_uuid.c_str()); 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) { @@ -296,40 +270,39 @@ struct WinState : std::enable_shared_from_this { void publication_timeout() { if (!publication_deadline || get_time_us() < publication_deadline || failed || stopped) return; publication_deadline = 0; - DEBUG_ERROR(CAT, "BLE WinRT publication timeout publisher=%d service_requested=%d stop_pending=%d generation=%llu", - publisher_status, service_requested, service_stop_pending, (unsigned long long)publish_generation); - ble_backend_advertising(owner, BLE_CLOSED, "Windows BLE publisher did not start within 20 seconds"); - fail("Windows BLE publication timed out; GATT startup/update incomplete"); + DEBUG_ERROR(CAT, "BLE WinRT GATT advertisement timeout requested=%d stop_pending=%d UUID=%s", + service_requested, service_stop_pending, service_uuid.c_str()); + ble_backend_advertising(owner, BLE_CLOSED, "GATT advertisement timed out"); fail("GATT advertisement timed out"); } void start_service_advertising() { - GattServiceProviderAdvertisingParameters parameters; parameters.IsConnectable(true); parameters.IsDiscoverable(true); - DEBUG_INFO(CAT, "BLE WinRT start GATT advertisement service=%s publisher=%d generation=%llu connectable=1 discoverable=1", - BLE_SERVICE_UUID, publisher_status, (unsigned long long)publish_generation); - service_requested = true; + GattServiceProviderAdvertisingParameters parameters; + parameters.IsConnectable(true); parameters.IsDiscoverable(broadcast_enabled); + DEBUG_INFO(CAT, "BLE WinRT start single GATT advertisement UUID=%s connectable=1 discoverable=%d", + service_uuid.c_str(), broadcast_enabled); + service_requested = true; publication_deadline = get_time_us() + 20000000; step("GattServiceProvider.StartAdvertising"); provider.StartAdvertising(parameters); } - /* Первичная регистрация — только после Started publisher либо при выключенном объявлении. */ + /* Один GATT provider обслуживает и обнаружение, и подключения; отдельного publisher нет. */ void ensure_service() { if (!adapter_ready || failed || stopped || service_stop_pending) return; - if (broadcast_enabled && (publication_pending || publisher_status != int(BluetoothLEAdvertisementPublisherStatus::Started))) return; - if (!server_created) { - server_created = true; - DEBUG_INFO(CAT, "BLE WinRT create GATT after publication enabled=%d publisher=%d", broadcast_enabled, publisher_status); - make_server(); - } else if (provider && !service_requested) start_service_advertising(); + if (!server_created) { server_created = true; make_server(); } + else if (provider && !service_requested) start_service_advertising(); } void service_status(GattServiceProviderAdvertisementStatus status, BluetoothError error) { - DEBUG_DEBUG(CAT, "BLE WinRT service advertising state=%d error=%d (%s) requested=%d stop_pending=%d", - int(status), int(error), bluetooth_error(error), service_requested, service_stop_pending); + DEBUG_DEBUG(CAT, "BLE WinRT GATT advertising state=%d error=%d (%s) requested=%d stop_pending=%d discoverable=%d", + int(status), int(error), bluetooth_error(error), service_requested, service_stop_pending, broadcast_enabled); if (error != BluetoothError::Success) { radio_error("GATT advertising failed", error); return; } - if (service_stop_pending && !service_requested && (status == GattServiceProviderAdvertisementStatus::Stopped || - status == GattServiceProviderAdvertisementStatus::Aborted)) { - service_stop_pending = false; service_ready = false; - DEBUG_INFO(CAT, "BLE WinRT requested GATT stop confirmed state=%d; updating manufacturer advertisement", int(status)); - publish(); - } else if (status == GattServiceProviderAdvertisementStatus::Started) { service_ready = true; ready(); } - else if (status == GattServiceProviderAdvertisementStatus::Aborted) radio_error("GATT advertising aborted", error); - else DEBUG_INFO(CAT, "BLE WinRT service advertising state=%d", int(status)); + if (service_stop_pending) { + if (status == GattServiceProviderAdvertisementStatus::Stopped || status == GattServiceProviderAdvertisementStatus::Aborted) { + service_stop_pending = false; service_ready = false; ensure_service(); + } + return; + } + if (status == GattServiceProviderAdvertisementStatus::Started) { + publication_deadline = 0; service_ready = true; + ble_backend_advertising(owner, broadcast_enabled ? BLE_READY : BLE_CLOSED, "GATT discovery publication applied"); ready(); + } else if (status == GattServiceProviderAdvertisementStatus::Aborted) radio_error("GATT advertising aborted", error); + else if (service_requested) fail("GATT advertisement stopped unexpectedly"); } /* Только доступные Win11 API: фактические параметры отделены от запрошенного профиля. */ void log_connection(ble_link_id id) { @@ -473,7 +446,7 @@ struct WinState : std::enable_shared_from_this { for (auto id : gone) error(id, "notification subscription removed"); } void make_server() { - await("create GATT service", GattServiceProvider::CreateAsync(guid(BLE_SERVICE_UUID)), 0, [](WinState& s, GattServiceProviderResult const& result) { + await("create GATT service", GattServiceProvider::CreateAsync(guid(service_uuid.c_str())), 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; @@ -602,39 +575,27 @@ struct WinState : std::enable_shared_from_this { auto state = weak.lock(); if (!state) return; ++state->scan_events; try { - auto fields = args.Advertisement().ManufacturerData(); - if (!fields.Size()) { ++state->scan_no_manufacturer; return; } bool matching = false; - for (auto const& value : fields) if (value.CompanyId() == state->company) matching = true; - if (!matching) { ++state->scan_other_company; return; } + auto type = args.AdvertisementType(); + if (type != BluetoothLEAdvertisementType::ScannableUndirected && type != BluetoothLEAdvertisementType::NonConnectableUndirected) + for (auto const& uuid : args.Advertisement().ServiceUuids()) { + uint64_t peer; if (!ble_service_uuid_decode(to_string(uuid).c_str(), &peer)) { matching = true; break; } + } + if (!matching) { ++state->scan_foreign; return; } ++state->scan_matching; } catch (...) { - auto reason = exception_reason("Received manufacturer filter"); - state->post("Watcher.Received filter failure", 0, [reason](WinState& s) { s.fail(reason.c_str()); }); - return; + auto reason = exception_reason("Received service UUID filter"); + state->post("Watcher.Received filter failure", 0, [reason](WinState& s) { s.fail(reason.c_str()); }); return; } state->post("Watcher.Received", 0, [args](WinState& s) { - s.step("Received.Advertisement.ManufacturerData"); - for (auto const& value : args.Advertisement().ManufacturerData()) { - if (value.CompanyId() != s.company) continue; - auto ep = s.advertisement_endpoint(args); - s.step("Received.RawSignalStrengthInDBm"); auto rssi = args.RawSignalStrengthInDBm(); - s.step("Received manufacturer length"); auto length = value.Data().Length(); - if (length > BLE_BROADCAST_MAX) { - ++s.scan_oversized; - if (s.scan_oversized <= 3) - DEBUG_WARN(CAT, "BLE WinRT broadcast rejected endpoint=%s rssi=%d company=%04x bytes=%u max=%u reason=oversized", - ep.id, rssi, s.company, length, BLE_BROADCAST_MAX); - else DEBUG_TRACE(CAT, "BLE WinRT broadcast rejected endpoint=%s rssi=%d bytes=%u reason=oversized", ep.id, rssi, length); - continue; - } - s.step("Received manufacturer buffer/DataReader"); - 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(), rssi); - s.step("ble_backend_broadcast"); + auto advertisement = args.Advertisement(); + for (auto const& uuid : advertisement.ServiceUuids()) { + uint64_t peer = 0; if (ble_service_uuid_decode(to_string(uuid).c_str(), &peer)) continue; + auto ep = s.advertisement_endpoint(args); auto name = to_string(advertisement.LocalName()); + uint8_t data[BLE_BROADCAST_MAX]; int len = ble_identity_encode(data, peer, name.c_str()); + if (len < 0) len = ble_identity_encode(data, peer, ""); ++s.scan_forwarded; - ble_backend_broadcast(s.owner, &ep, data.data(), data.size(), rssi); + ble_backend_broadcast(s.owner, &ep, data, size_t(len), args.RawSignalStrengthInDBm()); } }); }); @@ -649,75 +610,13 @@ struct WinState : std::enable_shared_from_this { scan_ready = true; ready(); } void publish() { - if (failed || stopped) return; - step("Publisher.Status"); - DEBUG_DEBUG(CAT, "BLE WinRT publish requested enabled=%d bytes=%zu generation=%llu", broadcast_enabled, - broadcast_size, (unsigned long long)publish_generation); - if (!publication_deadline) publication_deadline = get_time_us() + 20000000; - if (service_stop_pending) return; - if (provider && service_requested && broadcast_enabled) { + if (stopped || failed || service_stop_pending) return; + publication_deadline = get_time_us() + 20000000; + ble_backend_advertising(owner, BLE_STARTING, "updating GATT discoverability"); + if (provider && service_requested) { service_requested = false; service_stop_pending = true; - DEBUG_INFO(CAT, "BLE WinRT pause GATT advertisement before publisher update links=%zu generation=%llu", - links.size(), (unsigned long long)publish_generation); - step("GattServiceProvider.StopAdvertising for publisher"); provider.StopAdvertising(); return; - } - 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; - publisher_status = -1; publisher_wait_since = 0; publisher_wait_warned = false; - } - if (!broadcast_enabled) { - publication_pending = false; publication_deadline = 0; ensure_service(); return; - } - step("Publisher construction/manufacturer data"); - BluetoothLEAdvertisement advertisement; - BluetoothLEManufacturerData data; data.CompanyId(company); data.Data(buffer(broadcast.data(), broadcast_size)); - advertisement.ManufacturerData().Append(data); publisher = BluetoothLEAdvertisementPublisher(advertisement); - publication_pending = false; - publisher_status = int(BluetoothLEAdvertisementPublisherStatus::Created); - auto weak = weak_from_this(); auto generation = ++publish_generation; - step("Publisher.StatusChanged registration (auto_revoke)"); - publish_changed = publisher.StatusChanged(auto_revoke, [weak, generation](auto const&, auto const& args) { - if (auto s = weak.lock()) s->post("Publisher.StatusChanged", 0, [args, generation](WinState& s) { - if (generation != s.publish_generation) { - DEBUG_DEBUG(CAT, "BLE WinRT stale publisher event generation=%llu current=%llu", - (unsigned long long)generation, (unsigned long long)s.publish_generation); - return; - } - auto status = args.Status(); auto error = args.Error(); - s.publisher_status = int(status); - if (status == BluetoothLEAdvertisementPublisherStatus::Waiting) { - if (!s.publisher_wait_since) s.publisher_wait_since = get_time_us(); - } else { - if (s.publisher_wait_since) - DEBUG_INFO(CAT, "BLE WinRT publisher left Waiting elapsed_ms=%llu state=%d generation=%llu", - (unsigned long long)((get_time_us() - s.publisher_wait_since) / 1000), (int)status, - (unsigned long long)generation); - s.publisher_wait_since = 0; s.publisher_wait_warned = false; - } - 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"); - if (!s.publication_pending && s.publisher.Status() == BluetoothLEAdvertisementPublisherStatus::Started) { - s.publication_deadline = 0; s.ensure_service(); - } - } - else if (status == BluetoothLEAdvertisementPublisherStatus::Waiting) - ble_backend_advertising(s.owner, BLE_STARTING, "publisher waiting for radio resources"); - else if (status == BluetoothLEAdvertisementPublisherStatus::Stopping) - ble_backend_advertising(s.owner, BLE_STARTING, "publisher stopping"); - else ble_backend_advertising(s.owner, BLE_STARTING, "publisher created"); - }); - }); - step("Publisher.Start"); publisher.Start(); + step("GattServiceProvider.StopAdvertising for discovery update"); provider.StopAdvertising(); + } else ensure_service(); } void connect(ble_link_id id, uint64_t address, BluetoothAddressType type) { bool cached = type == BluetoothAddressType::Unspecified; @@ -738,7 +637,9 @@ struct WinState : std::enable_shared_from_this { 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, + char peer_uuid[37]; + if (ble_service_uuid_encode(peer_uuid, ble_backend_peer(s.owner, id))) { s.error(id, "missing peer service identity"); return; } + s.await("discover GATT service", device.GetGattServicesForUuidAsync(guid(peer_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, @@ -865,17 +766,16 @@ struct WinState : std::enable_shared_from_this { if (diagnostics_timer) { uasync_cancel_timeout(ua, diagnostics_timer); diagnostics_timer = nullptr; } discarded.clear(); if (task) uasync_cancel_post(ua, task); - received.revoke(); scan_stopped.revoke(); publish_changed.revoke(); service_changed.revoke(); + received.revoke(); scan_stopped.revoke(); service_changed.revoke(); discovery_stats("shutdown"); write_requested.revoke(); subscriptions_changed.revoke(); invite_write_requested.revoke(); invite_subscriptions_changed.revoke(); ack_write_requested.revoke(); ack_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 && service_requested) provider.StopAdvertising(); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("stop server").c_str()); } - watcher = nullptr; publisher = nullptr; provider = nullptr; + watcher = nullptr; provider = nullptr; local_rx = nullptr; local_tx = nullptr; local_invite = nullptr; local_ack = nullptr; startup = nullptr; if (apartment) { uninit_apartment(); apartment = false; } } @@ -885,7 +785,8 @@ extern "C" struct ble_backend* ble_backend_create(struct ble_transport* owner, s 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; state->broadcast_size = cfg->initial_advertisement_size; - state->broadcast_enabled = state->publication_pending = state->broadcast_size != 0; + state->broadcast_enabled = state->broadcast_size != 0; + char uuid[37]; if (ble_service_uuid_encode(uuid, cfg->node_id)) return nullptr; state->service_uuid = uuid; if (state->broadcast_size > state->broadcast.size()) { DEBUG_ERROR(CAT, "BLE WinRT initial advertisement too large"); return nullptr; } if (state->broadcast_size) std::memcpy(state->broadcast.data(), cfg->initial_advertisement, state->broadcast_size); try { @@ -921,7 +822,7 @@ extern "C" struct ble_backend* ble_backend_create(struct ble_transport* owner, s s.fail("adapter lacks BLE central/peripheral support"); return; } s.adapter_ready = true; WinState::diagnostics_tick(&s); - if (s.broadcast_enabled) s.publish(); else s.ensure_service(); + s.ensure_service(); }); void* memory = u_malloc(sizeof(ble_backend)); if (!memory) { DEBUG_ERROR(CAT, "BLE WinRT allocation failed"); state->shutdown(); return nullptr; } @@ -938,12 +839,13 @@ extern "C" int ble_backend_advertise(struct ble_backend* b, const uint8_t* data, if (s.failed || s.stopped) { DEBUG_WARN(CAT, "BLE WinRT advertisement requested on closed backend"); return BLE_NOT_READY; } if (len > s.broadcast.size() || (len && !data)) { DEBUG_ERROR(CAT, "BLE WinRT invalid advertisement bytes=%zu", len); return BLE_ERROR; } if (s.broadcast_enabled == (data != nullptr) && s.broadcast_size == len && (!len || !std::memcmp(s.broadcast.data(), data, len))) { - DEBUG_DEBUG(CAT, "BLE WinRT advertisement unchanged bytes=%zu generation=%llu", len, (unsigned long long)s.publish_generation); + DEBUG_DEBUG(CAT, "BLE WinRT discovery metadata unchanged bytes=%zu", len); return BLE_OK; } - s.broadcast_enabled = data != nullptr; s.broadcast_size = len; s.publication_pending = true; + bool changed = s.broadcast_enabled != (data != nullptr); + s.broadcast_enabled = data != nullptr; s.broadcast_size = len; if (len) std::memcpy(s.broadcast.data(), data, len); - s.publish(); return BLE_OK; + if (changed) s.publish(); return BLE_OK; } catch (...) { DEBUG_ERROR(CAT, "%s", exception_reason(b->state->operation).c_str()); return BLE_ERROR; } } extern "C" int ble_backend_connect(struct ble_backend* b, ble_link_id id, const struct ble_endpoint* ep) {