Preserve WinRT BLE event context and diagnose API failures

This commit is contained in:
evgeny committed 2026-10-29 22:38:08 +01:00
1 parent b1a272fa23
commit dc70174af9
1 file changed
+175 -61
+175 -61
View File
@@ -7,6 +7,8 @@
#include "../debug_config.h"
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Foundation.Metadata.h>
#include <winrt/Windows.System.Profile.h>
#include <winrt/Windows.Devices.Bluetooth.h>
#include <winrt/Windows.Devices.Bluetooth.Advertisement.h>
#include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h>
@@ -80,11 +82,19 @@ struct WriteDeferral {
~WriteDeferral() { try { value.Complete(); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("write deferral").c_str()); } }
};
struct WinState : std::enable_shared_from_this<WinState> {
/* Имена имеют статическое время жизни; контекст ошибки сохраняется независимо от уровня лога. */
struct Event {
const char* name;
ble_link_id id;
std::function<void(WinState&)> run;
};
struct UASYNC* ua = nullptr;
struct ble_transport* owner = nullptr;
uint16_t company = 0;
std::mutex mutex;
std::deque<std::function<void(WinState&)>> events;
std::deque<Event> events;
const char* operation = "idle"; /* Только uasync; Windows callbacks передают контекст через Event. */
ble_link_id operation_link = 0;
struct posted_task* posted = nullptr;
bool stopped = false, failed = false, overflow = false, apartment = false, scan_ready = false, service_ready = false;
IAsyncInfo startup{nullptr};
@@ -103,27 +113,39 @@ struct WinState : std::enable_shared_from_this<WinState> {
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);
}
static void dispatch(void* arg) {
auto& s = *static_cast<WinState*>(arg);
std::deque<std::function<void(WinState&)>> batch;
std::deque<Event> 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()); }
s.step(event.name, event.id);
try { event.run(s); }
catch (...) {
auto reason = exception_reason(s.operation, s.operation_link);
DEBUG_ERROR(CAT, "BLE WinRT event=%s event_link=%llu: %s", event.name,
(unsigned long long)event.id, reason.c_str());
if (event.id) s.error(event.id, reason.c_str());
else s.fail(reason.c_str());
}
}
}
void post(std::function<void(WinState&)> event) {
void post(const char* name, ble_link_id id, std::function<void(WinState&)> 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));
else events.push_back({name, id, std::move(event)});
if (posted) return;
posted = static_cast<struct posted_task*>(u_calloc(1, sizeof(struct posted_task)));
if (!posted) { overflow = true; DEBUG_ERROR(CAT, "BLE WinRT event allocation failed"); return; }
@@ -132,6 +154,8 @@ struct WinState : std::enable_shared_from_this<WinState> {
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);
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) {
@@ -145,13 +169,14 @@ struct WinState : std::enable_shared_from_this<WinState> {
}
template<typename Result, typename Callback>
void await(const char* phase, const IAsyncOperation<Result>& operation, ble_link_id id, Callback callback) {
step(phase, id);
const bool fragment = !std::strcmp(phase, "write frame") || !std::strcmp(phase, "notify 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<Result>& op, AsyncStatus status) {
if (auto s = weak.lock()) s->post([id, op, status, phase, fragment, callback](WinState& self) {
if (auto s = weak.lock()) s->post(phase, id, [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);
@@ -166,7 +191,10 @@ struct WinState : std::enable_shared_from_this<WinState> {
return;
}
if (!fragment) DEBUG_DEBUG(CAT, "BLE WinRT async complete phase=%s link=%llu", phase, (unsigned long long)id);
callback(self, op.GetResults());
self.step("IAsyncOperation.GetResults", id);
auto result = op.GetResults();
self.step(phase, id);
callback(self, result);
});
});
}
@@ -202,7 +230,7 @@ struct WinState : std::enable_shared_from_this<WinState> {
if (!api) { DEBUG_INFO(CAT, "BLE WinRT speed API unavailable link=%llu; using OS parameters", (unsigned long long)l.id); return; }
auto weak = weak_from_this(); auto id = l.id;
auto changed = [weak, id](auto const&, auto const&) {
if (auto s = weak.lock()) s->post([id](WinState& s) { s.log_connection(id); });
if (auto s = weak.lock()) s->post("connection parameters/PHY changed", id, [id](WinState& s) { s.log_connection(id); });
};
l.parameters_changed = api.ConnectionParametersChanged(auto_revoke, changed);
l.phy_changed = api.ConnectionPhyChanged(auto_revoke, changed);
@@ -215,22 +243,26 @@ struct WinState : std::enable_shared_from_this<WinState> {
}
void watch_session(WinLink& l) {
auto weak = weak_from_this(); auto id = l.id;
step("GattSession.SessionStatusChanged registration (auto_revoke)", 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) {
if (auto s = weak.lock()) s->post("GattSession.SessionStatusChanged", id, [id, a](WinState& s) {
auto status = a.Status(); auto error = a.Error();
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");
});
});
step("GattSession.MaxPduSizeChanged registration (auto_revoke)", id);
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&) {
if (auto s = weak.lock()) s->post("GattSession.MaxPduSizeChanged", id, [id, session](WinState& s) {
if (!s.link(id)) return;
auto mtu = session.MaxPduSize();
DEBUG_DEBUG(CAT, "BLE WinRT negotiated PDU changed link=%llu mtu=%u", (unsigned long long)id, (unsigned)mtu);
});
});
}
WinLink* incoming(const GattSession& session) {
step("incoming GattSession.DeviceId");
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);
@@ -245,15 +277,26 @@ struct WinState : std::enable_shared_from_this<WinState> {
}
void subscriptions() {
std::vector<ble_link_id> active;
step("GattLocalCharacteristic.SubscribedClients");
auto invitations = local_invite.SubscribedClients(), acknowledgements = local_ack.SubscribedClients();
for (auto const& client : local_tx.SubscribedClients()) {
auto clients = local_tx.SubscribedClients();
DEBUG_DEBUG(CAT, "BLE WinRT subscriptions DATA=%u INVITE=%u ACK=%u links=%zu",
clients.Size(), invitations.Size(), acknowledgements.Size(), links.size());
for (auto const& client : clients) {
step("GattSubscribedClient.Session/DeviceId");
auto same_device = [&client](auto const& other) { return client.Session().DeviceId().Id() == other.Session().DeviceId().Id(); };
auto invitation = std::find_if(invitations.begin(), invitations.end(), same_device);
auto acknowledgement = std::find_if(acknowledgements.begin(), acknowledgements.end(), same_device);
if (invitation == invitations.end() || acknowledgement == acknowledgements.end()) continue;
if (invitation == invitations.end() || acknowledgement == acknowledgements.end()) {
DEBUG_DEBUG(CAT, "BLE WinRT subscriptions incomplete device=%s DATA=1 INVITE=%d ACK=%d",
to_string(client.Session().DeviceId().Id()).c_str(),
invitation != invitations.end(), acknowledgement != acknowledgements.end());
continue;
}
auto* l = incoming(client.Session()); if (!l) continue;
l->subscriber = client; l->invite_subscriber = *invitation; l->ack_subscriber = *acknowledgement; active.push_back(l->id);
if (!l->opened) {
step("GattSubscribedClient.MaxNotificationSize", l->id);
auto capacity = std::min({client.MaxNotificationSize(), (*invitation).MaxNotificationSize(), (*acknowledgement).MaxNotificationSize()});
DEBUG_DEBUG(CAT, "BLE WinRT subscribed DATA/INVITE/ACK link=%llu capacity=%u", (unsigned long long)l->id, capacity);
l->opened = true; ble_backend_opened(owner, l->id, capacity + 3);
@@ -308,17 +351,26 @@ struct WinState : std::enable_shared_from_this<WinState> {
}
GattLocalCharacteristic::WriteRequested_revoker watch_write(GattLocalCharacteristic const& characteristic, enum ble_wire_channel channel) {
auto weak = weak_from_this();
step("GattLocalCharacteristic.WriteRequested registration (auto_revoke)");
DEBUG_DEBUG(CAT, "BLE WinRT register WriteRequested channel=%d", channel);
return characteristic.WriteRequested(auto_revoke, [weak, channel](auto const&, GattWriteRequestedEventArgs const& args) {
const char* phase = "WriteRequested.GetDeferral";
try {
auto deferral = std::make_shared<WriteDeferral>(args.GetDeferral());
auto request = args.GetRequestAsync(); auto session = args.Session();
phase = "WriteRequested.GetRequestAsync"; auto request = args.GetRequestAsync();
phase = "WriteRequested.Session"; auto session = args.Session();
phase = "WriteRequested.GetRequestAsync.Completed registration";
request.Completed([weak, deferral, session, channel](auto const& operation, AsyncStatus status) {
if (auto s = weak.lock()) s->post([deferral, session, operation, status, channel](WinState& s) {
if (auto s = weak.lock()) s->post("GattLocalCharacteristic.WriteRequested", 0,
[deferral, session, operation, status, channel](WinState& s) {
if (status != AsyncStatus::Completed) {
DEBUG_WARN(CAT, "BLE WinRT GetRequest status=%d HRESULT=0x%08x", (int)status, (unsigned)operation.ErrorCode().value);
DEBUG_WARN(CAT, "BLE WinRT GetRequest channel=%d status=%d HRESULT=0x%08x",
channel, (int)status, (unsigned)operation.ErrorCode().value);
return;
}
s.step("GattWriteRequestedEventArgs.GetRequestAsync.GetResults");
auto request = operation.GetResults(); if (!request) { DEBUG_WARN(CAT, "BLE WinRT expired write request"); return; }
s.step("GattWriteRequest.Option/Offset/Value");
bool response = request.Option() == GattWriteOption::WriteWithResponse;
auto expected = channel == BLE_WIRE_DATA ? GattWriteOption::WriteWithoutResponse : GattWriteOption::WriteWithResponse;
if (request.Offset() || request.Option() != expected || !request.Value().Length() || request.Value().Length() > BLE_VALUE_MAX) {
@@ -338,15 +390,18 @@ struct WinState : std::enable_shared_from_this<WinState> {
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());
auto id = l->id;
s.step("GattWriteRequest.Value/DataReader", id); auto data = bytes(request.Value());
DEBUG_TRACE(CAT, "BLE WinRT RX write link=%llu channel=%d bytes=%zu response=%d",
(unsigned long long)id, channel, data.size(), response);
ble_backend_received(s.owner, id, channel, data.data(), data.size());
s.step("GattWriteRequest.Respond", id);
if (response) { 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()); });
auto reason = exception_reason(phase);
if (auto s = weak.lock()) s->post("WriteRequested failure", 0, [reason](WinState& s) { s.fail(reason.c_str()); });
}
});
}
@@ -355,18 +410,22 @@ struct WinState : std::enable_shared_from_this<WinState> {
write_requested = watch_write(local_rx, BLE_WIRE_DATA);
invite_write_requested = watch_write(local_invite, BLE_WIRE_INVITE);
ack_write_requested = watch_write(local_ack, BLE_WIRE_ACK);
step("ACK.SubscribedClientsChanged registration (auto_revoke)");
ack_subscriptions_changed = local_ack.SubscribedClientsChanged(auto_revoke, [weak](auto const&, auto const&) {
if (auto s = weak.lock()) s->post([](WinState& s) { s.subscriptions(); });
if (auto s = weak.lock()) s->post("ACK.SubscribedClientsChanged", 0, [](WinState& s) { s.subscriptions(); });
});
step("INVITE.SubscribedClientsChanged registration (auto_revoke)");
invite_subscriptions_changed = local_invite.SubscribedClientsChanged(auto_revoke, [weak](auto const&, auto const&) {
if (auto s = weak.lock()) s->post([](WinState& s) { s.subscriptions(); });
if (auto s = weak.lock()) s->post("INVITE.SubscribedClientsChanged", 0, [](WinState& s) { s.subscriptions(); });
});
step("DATA.SubscribedClientsChanged registration (auto_revoke)");
subscriptions_changed = local_tx.SubscribedClientsChanged(auto_revoke, [weak](auto const&, auto const&) {
if (auto s = weak.lock()) s->post([](WinState& s) { s.subscriptions(); });
if (auto s = weak.lock()) s->post("DATA.SubscribedClientsChanged", 0, [](WinState& s) { s.subscriptions(); });
});
step("GattServiceProvider.AdvertisementStatusChanged registration (auto_revoke)");
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) {
if (auto s = weak.lock()) s->post("GattServiceProvider.AdvertisementStatusChanged", 0, [args](WinState& s) {
auto status = args.Status(); auto error = args.Error();
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);
@@ -375,35 +434,56 @@ struct WinState : std::enable_shared_from_this<WinState> {
});
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);
step("GattServiceProvider.StartAdvertising"); provider.StartAdvertising(parameters);
step("BluetoothLEAdvertisementWatcher construction/ScanningMode");
watcher = BluetoothLEAdvertisementWatcher(); watcher.ScanningMode(BluetoothLEScanningMode::Active);
step("Watcher.Received registration (auto_revoke)");
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) {
try {
bool matching = false;
for (auto const& value : args.Advertisement().ManufacturerData()) if (value.CompanyId() == state->company) matching = true;
if (!matching) return;
} catch (...) {
auto reason = exception_reason("Received manufacturer 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 || 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());
s.step("Received.QueryInterface(IBluetoothLEAdvertisementReceivedEventArgs2)");
auto address_api = args.try_as<IBluetoothLEAdvertisementReceivedEventArgs2>();
if (!address_api) {
s.fail("Received: IBluetoothLEAdvertisementReceivedEventArgs2 unavailable; Windows 10 2004+ required"); return;
}
s.step("Received.BluetoothAddressType"); auto type = address_api.BluetoothAddressType();
s.step("Received.BluetoothAddress"); auto address = args.BluetoothAddress();
std::snprintf(ep.id, sizeof(ep.id), "%012llx/%d", (unsigned long long)address, (int)type);
s.step("Received manufacturer buffer/DataReader");
auto data = bytes(value.Data());
s.step("Received.RawSignalStrengthInDBm"); auto rssi = args.RawSignalStrengthInDBm();
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());
data.size(), rssi);
s.step("ble_backend_broadcast");
ble_backend_broadcast(s.owner, &ep, data.data(), data.size(), rssi);
}
});
});
step("Watcher.Stopped registration (auto_revoke)");
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);
if (auto s = weak.lock()) s->post("Watcher.Stopped", 0, [args](WinState& s) {
s.radio_error("scan stopped", args.Error());
});
});
watcher.Start();
step("Watcher.Start"); 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() {
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 (publisher) {
@@ -415,24 +495,34 @@ struct WinState : std::enable_shared_from_this<WinState> {
publish_changed.revoke(); publisher = nullptr;
}
if (!broadcast_enabled) 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);
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) {
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;
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();
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");
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");
});
});
publisher.Start();
step("Publisher.Start"); 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);
@@ -497,10 +587,14 @@ struct WinState : std::enable_shared_from_this<WinState> {
}
GattCharacteristic::ValueChanged_revoker watch_value(ble_link_id id, GattCharacteristic const& characteristic, enum ble_wire_channel channel) {
auto weak = weak_from_this();
step("GattCharacteristic.ValueChanged registration (auto_revoke)", id);
DEBUG_DEBUG(CAT, "BLE WinRT register ValueChanged link=%llu channel=%d", (unsigned long long)id, channel);
return characteristic.ValueChanged(auto_revoke, [weak, id, channel](auto const&, GattValueChangedEventArgs const& args) {
if (auto s = weak.lock()) s->post([id, channel, args](WinState& s) {
if (auto s = weak.lock()) s->post("GattCharacteristic.ValueChanged", id, [id, channel, args](WinState& s) {
if (!s.link(id)) return;
s.step("ValueChanged.CharacteristicValue/DataReader", id);
auto data = bytes(args.CharacteristicValue());
DEBUG_TRACE(CAT, "BLE WinRT RX value link=%llu channel=%d bytes=%zu", (unsigned long long)id, channel, data.size());
ble_backend_received(s.owner, id, channel, data.data(), data.size());
});
});
@@ -510,12 +604,15 @@ struct WinState : std::enable_shared_from_this<WinState> {
auto characteristic = channel == BLE_WIRE_ACK ? l->ack : channel == BLE_WIRE_DATA ? l->tx : l->invite;
auto mode = channel == BLE_WIRE_DATA ? GattClientCharacteristicConfigurationDescriptorValue::Notify :
GattClientCharacteristicConfigurationDescriptorValue::Indicate;
await("subscribe channel", characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(mode), id,
[id, channel](WinState& s, GattCommunicationStatus status) {
step("GattCharacteristic.WriteCCCDWithResultAsync", id);
DEBUG_DEBUG(CAT, "BLE WinRT subscribe request link=%llu channel=%d mode=%d", (unsigned long long)id, channel, (int)mode);
await("subscribe channel", characteristic.WriteClientCharacteristicConfigurationDescriptorWithResultAsync(mode), id,
[id, channel](WinState& s, GattWriteResult const& result) {
auto status = result.Status(); auto protocol = result.ProtocolError();
DEBUG_DEBUG(CAT, "BLE WinRT subscribe link=%llu channel=%d status=%d", (unsigned long long)id, channel, (int)status);
if (status != GattCommunicationStatus::Success) {
DEBUG_WARN(CAT, "BLE WinRT subscription failed link=%llu channel=%d status=%d",
(unsigned long long)id, channel, (int)status);
DEBUG_WARN(CAT, "BLE WinRT subscription failed link=%llu channel=%d status=%d ATT_error=%d",
(unsigned long long)id, channel, (int)status, protocol ? int(protocol.Value()) : -1);
s.error(id, "GATT subscription failed"); return;
}
if (channel == BLE_WIRE_DATA) { s.subscribe_channel(id, BLE_WIRE_INVITE); return; }
@@ -543,7 +640,7 @@ struct WinState : std::enable_shared_from_this<WinState> {
}
void shutdown() {
struct posted_task* task;
std::deque<std::function<void(WinState&)>> discarded;
std::deque<Event> 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();
@@ -568,6 +665,16 @@ extern "C" struct ble_backend* ble_backend_create(struct ble_transport* owner, s
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->step("ApiInformation.IsPropertyPresent(BluetoothAddressType)");
bool address_type = Windows::Foundation::Metadata::ApiInformation::IsPropertyPresent(
L"Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementReceivedEventArgs", L"BluetoothAddressType");
DEBUG_INFO(CAT, "BLE WinRT API BluetoothAddressType=%d required=Windows10-2004 wire=3 channels=DATA/INVITE/ACK", address_type);
if (!address_type) throw hresult_error(E_NOINTERFACE, L"BLE requires BluetoothAddressType (Windows 10 2004 / build 19041 or newer)");
try {
auto version = Windows::System::Profile::AnalyticsInfo::VersionInfo();
DEBUG_INFO(CAT, "BLE WinRT OS family=%s version=%s", to_string(version.DeviceFamily()).c_str(),
to_string(version.DeviceFamilyVersion()).c_str());
} catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("OS version diagnostics").c_str()); }
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(),
@@ -589,7 +696,7 @@ extern "C" struct ble_backend* ble_backend_create(struct ble_transport* owner, s
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; }
} catch (...) { DEBUG_ERROR(CAT, "%s", exception_reason(state->operation).c_str()); state->shutdown(); return nullptr; }
}
extern "C" void ble_backend_destroy(struct ble_backend* b) {
if (!b) return;
@@ -600,7 +707,7 @@ extern "C" int ble_backend_advertise(struct ble_backend* b, const uint8_t* data,
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; }
} 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) {
unsigned long long address; int type, consumed = 0;
@@ -621,31 +728,38 @@ extern "C" int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_
return BLE_ERROR;
}
try {
DEBUG_TRACE(CAT, "BLE WinRT TX fragment link=%llu outgoing=%d bytes=%zu", (unsigned long long)id, l->outgoing, len);
DEBUG_TRACE(CAT, "BLE WinRT TX fragment link=%llu outgoing=%d channel=%d bytes=%zu",
(unsigned long long)id, l->outgoing, channel, len);
s.step("TX DataWriter buffer", id);
l->sending = true; auto buffer = WinState::buffer(data, len);
auto option = channel == BLE_WIRE_DATA ? GattWriteOption::WriteWithoutResponse : GattWriteOption::WriteWithResponse;
uint64_t started = get_time_us();
s.step(l->outgoing ? "GattCharacteristic.WriteValueWithResultAsync" : "GattLocalCharacteristic.NotifyValueAsync", id);
if (l->outgoing) s.await("write frame", (channel == BLE_WIRE_ACK ? l->ack : channel == BLE_WIRE_INVITE ? l->invite : l->rx)
.WriteValueWithResultAsync(buffer, option), id,
[id, len](WinState& s, GattWriteResult const& result) {
[id, len, channel, started](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);
DEBUG_WARN(CAT, "BLE WinRT write failed link=%llu channel=%d bytes=%zu status=%d ATT_error=%d", (unsigned long long)id,
channel, len, (int)result.Status(), protocol ? int(protocol.Value()) : -1);
else DEBUG_TRACE(CAT, "BLE WinRT write complete link=%llu channel=%d bytes=%zu elapsed_us=%llu",
(unsigned long long)id, channel, len, (unsigned long long)(get_time_us() - started));
ble_backend_sent(s.owner, id, result.Status() == GattCommunicationStatus::Success);
});
else s.await("notify frame", (channel == BLE_WIRE_ACK ? s.local_ack : channel == BLE_WIRE_INVITE ? s.local_invite : s.local_tx)
.NotifyValueAsync(buffer, channel == BLE_WIRE_ACK ? l->ack_subscriber :
channel == BLE_WIRE_INVITE ? l->invite_subscriber : l->subscriber), id,
[id, len](WinState& s, GattClientNotificationResult const& result) {
[id, len, channel, started](WinState& s, GattClientNotificationResult const& result) {
auto* l = s.link(id); l->sending = false;
s.step("GattClientNotificationResult.Status/BytesSent", id);
if (result.Status() != GattCommunicationStatus::Success || result.BytesSent() != len)
DEBUG_WARN(CAT, "BLE WinRT notification 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 notification complete link=%llu bytes=%zu", (unsigned long long)id, len);
DEBUG_WARN(CAT, "BLE WinRT notification failed link=%llu channel=%d expected=%zu sent=%u status=%d", (unsigned long long)id,
channel, len, result.BytesSent(), (int)result.Status());
else DEBUG_TRACE(CAT, "BLE WinRT notification complete link=%llu channel=%d bytes=%zu elapsed_us=%llu",
(unsigned long long)id, channel, len, (unsigned long long)(get_time_us() - started));
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; }
} catch (...) { l->sending = false; DEBUG_ERROR(CAT, "%s", exception_reason(s.operation, id).c_str()); return BLE_ERROR; }
}