TL;DR: Ако получаваш
org.bluez.Error.AuthenticationFailedсъс собствен C++/sd-bus pairing agent на BlueZ 5.66+, проблемът най-вероятно е във вътрешната регистрация на agent-а. Пусни външен Python agent (simple-agent.py) като отделен процес и имплементирай D-Bus property polling, вместо да разчиташ наPropertiesChangedсигнали. Подробностите и кодът са по-долу.
Прекарах два дни, втренчен в org.bluez.Error.AuthenticationFailed, преди да разбера какво всъщност става.
Pairing agent-ът беше регистриран. D-Bus извикванията изглеждаха правилни. busctl потвърждаваше, че всичко е на мястото си -- а BlueZ просто продължаваше да казва не. Това беше по време на работата по D2Explorer -- инструмент за сдвояване с Huawei Watch D2 под Linux -- и грешката при сдвояване блокираше всичко.
Ето какво наистина се случи и как го оправихме.
Планът: вътрешен C++ pairing agent
Идеята беше чиста и самодостатъчна. Едно C++ приложение, което управлява целия процес на сдвояване чрез sd-bus (C/C++ D-Bus bindings):
- Свържи се към system D-Bus.
- Намери Bluetooth адаптера (
org.bluez.Adapter1). - Имплементирай C++ клас, който предоставя интерфейса
org.bluez.Agent1. - Регистрирай agent-а в
org.bluez.AgentManager1чрезRegisterAgentиRequestDefaultAgent. Започнахме с capabilityDisplayYesNo, после го опростихме доNoInputNoOutput. - Открий целевото устройство (
org.bluez.Device1). - Извикай
Pair()върху D-Bus интерфейса на устройството. - Вътрешният agent обработва callbacks (
RequestConfirmation,RequestAuthorization) автоматично -- без нужда от потребителско действие. - Trust-ваш устройството, установяваш GATT connection, готово.
Един binary, без външни зависимости. Това беше планът.
Стената: org.bluez.Error.AuthenticationFailed
Всичко работеше до стъпка 6. Адаптерът беше намерен, agent-ът регистриран (D-Bus го потвърждаваше), устройството открито. Но в момента, в който извикахме Device1.Pair() чрез sd_bus_call_method -- моментален провал:
[BluetoothDevice] Calling Device1.Pair() method via D-Bus
[BluetoothDevice] Device1.Pair() method threw exception: Failed to call method 'Pair':
Input/output error - D-Bus error: org.bluez.Error.AuthenticationFailed (Authentication Failed)Опитахме всичко. Различни agent capabilities. Проверихме sd-bus vtable настройката. Уверихме се, че имплементациите на agent methods връщат успех бързо. Използвахме busctl и gdbus, за да наблюдаваме D-Bus traffic-а -- registration calls изглеждаха правилни. Pair() извикването просто продължаваше да се проваля.
Задънена улица.
Пробивът: външен Python agent
За да изолираме проблема, извадихме вътрешния C++ agent от уравнението. Пуснахме стандартния simple-agent.py на BlueZ като отделен процес преди да стартираме нашето C++ приложение (вече без собствената му agent registration):
# Terminal 1: Run the external agent
sudo python simple-agent.py NoInputNoOutput
# Terminal 2: Run our C++ app (no internal agent)
sudo ./build/huawei_pair_app <MAC> <QR_VALUE>Резултатът:
[BluetoothDevice] Calling Device1.Pair() method via D-Bus
[BluetoothDevice] Device1.Pair() method succeeded <--- SUCCESS!Консистентно. Всеки път. Грешката AuthenticationFailed изчезна напълно.
Това доказа, че проблемът не беше в самия Pair(), нито в устройството, нито в pairing capability-то на BlueZ. Беше конкретно в начина, по който нашето C++ приложение, използвайки sd-bus, се регистрираше и взаимодействаше като pairing agent. Същата логическа операция -- регистриране на NoInputNoOutput agent и извикване на Pair() -- работеше перфектно, когато agent-ът вървеше като отделен Python процес.
Това сработи.
Защо вътрешният agent се провали?
Когато за пръв път се сблъсках с това, имах само хипотези. Оттогава намерих реални документирани доказателства, че това е по-широк проблем -- не само нашият код.
BlueZ 5.70+ regression
BlueZ GitHub Issue #605 документира случаи, в които устройства се сдвояват без проблем на BlueZ 5.50, но се провалят на по-нови версии с auth failed with status 0x05. HCI logs показват Status: PIN or Key Missing (0x06) въпреки запазени link keys. Workaround-ът? Стартиране на legacy bluez-simple-agent.py script. Звучи познато?
Наличният agent е root cause-ът
Bleak Issue #1434 го прави още по-ясно: сдвояването работи само когато bluetoothctl или GNOME Bluetooth е стартиран, защото тези приложения регистрират нужния authentication agent. Без активен, правилно функциониращ agent, BlueZ вътрешно връща No agent available for request type 2 -- което на повърхността излиза като AuthenticationFailed.
Ключовият извод: не е достатъчно просто да регистрираш agent. Agent-ът трябва да отговаря на callbacks от BlueZ по начин, който bluetoothd приема за валиден. И нещо в начина, по който sd-bus прави това в същия процес, който инициира сдвояването, не удовлетворява по-новите версии на BlueZ.
Може дори да не е BlueZ
Red Hat Bug #1905671 разкрива, че някои AuthenticationFailed грешки са kernel-related, а не BlueZ-related. Kernel 5.9 е имал проблеми със сдвояването, които 5.8.18 и 5.10+ не са имали. Коментарът на maintainer-а си струва да се цитира: "Bluetooth is complex, it could be firmware, kernel, bluez, controller, end device or a combination of them all."
Agent capability mismatch
BlueZ Issue #650 документира още един ъгъл: някои устройства (особено iOS) се провалят при сдвояване с NoInputNoOutput agents, защото downgrade-ват Secure Connections към Legacy pairing, което води до Insufficient Authentication (0x05) грешки при последващ attribute access. Това е Security Manager Protocol (SMP) negotiation проблем, не agent registration проблем -- но произвежда същото error message.
Най-вероятните виновници в нашия случай
Като се има предвид evidence-ът, най-вероятните обяснения за провала на вътрешния sd-bus agent са:
- Timing --
sd-busregistration или method handling в нашия event loop не отговаряше в точния прозорец, койтоbluetoothdочакваше. - Тънкости между
sd-busиpython-dbus-- разлики в начина, по който тези libraries взаимодействат с D-Bus daemon-а или управляват object lifetimes. - По-строги изисквания в BlueZ 5.66+ -- променени вътрешни sequences за agent interaction, които
sd-bus, когато се използва в същото приложение, иницииращо сдвояването, не удовлетворява.
Втората стена: D-Bus сигналите са ненадеждни
Да минем покрай AuthenticationFailed беше голяма победа, но не беше краят. С външния agent на място Pair() успяваше -- но не можехме надеждно да засечем кога е приключил.
Разчитахме на D-Bus PropertiesChanged сигнали (чрез sd-bus), за да разберем кога Paired, Trusted, Connected и ServicesResolved стават true. Понякога сигналите пристигаха. Понякога закъсняваха. Понякога изобщо не пристигаха.
Затова имплементирахме active polling -- fallback, който query-ва property values директно, когато сигналите не се появят:
bool BluetoothDevice::isPaired() {
bool cachedValue = mockPaired_.load(); // Check signal-updated cache
if (cachedValue) return true;
// Signal didn't fire? Poll D-Bus directly.
Logger::debug("[Polling] Polling Paired property via D-Bus...");
bool polledValue = false;
adapter_.getObjectProperty<bool>(
devicePath_, "org.bluez.Device1", "Paired", polledValue
);
if (polledValue) mockPaired_.store(true); // Update cache
return polledValue;
}Всеки state transition method (isPaired(), isTrusted(), isConnected(), areServicesResolved()) следва същия pattern: първо проверява cached atomic boolean-а (обновен от signal handler-а, ако той сработи), после пада обратно към директен D-Bus Get property call.
Не е елегантно. Но е необходимо.
Това сработи.
Пълната поправка
Ето консолидираната рецепта. Ако изграждаш automated Bluetooth pairing под Linux с BlueZ 5.66+ и удряш AuthenticationFailed:
Стъпка 1: Вземи simple-agent.py
Вземи го от BlueZ source tree.
Стъпка 2: Стартирай външния agent
sudo python simple-agent.py NoInputNoOutputДръж го стартиран в отделен terminal (или като background service).
Стъпка 3: Извади вътрешния agent от приложението си
Премахни всички RegisterAgent / RequestDefaultAgent извиквания от C++ приложението си. Остави външния Python agent да обработва authentication callbacks.
Стъпка 4: Добави D-Bus property polling
Не разчитай само на PropertiesChanged сигнали. За всяко критично property (Paired, Trusted, Connected, ServicesResolved) имплементирай cache-then-poll pattern-а, показан по-горе. Poll-вай периодично от main loop-а.
Стъпка 5: Провери
- Потвърди, че външният agent е стартиран (
sudo python simple-agent.py NoInputNoOutput). - Стартирай приложението си.
Pair()трябва да успее. - Наблюдавай polling logs -- трябва да виждаш D-Bus property queries за state transitions.
- Ако
Pair()все още се проваля, провери версията на BlueZ (bluetoothd --version) и kernel версията -- проблемът може да е по-дълбок.
Какво ти струва това
Няма да се преструвам, че това е чисто решение. Не е:
- External dependency -- приложението ти вече има нужда от отделен Python процес, който да върви.
- Повече complexity -- polling logic в main loop-а, върху signal handlers.
- По-малко self-contained -- мечтата за един binary си отиде.
Но работи. Надеждно. А когато два дни си гледал AuthenticationFailed, "работи" е това, което има значение.
Референции
1. BlueZ GitHub Issue #55: Device characteristics and pairing timing -- Intermittent pairing failures related to agent timing.
2. Bluetooth Auto Pairing with NoInputNoOutput Agent Issues -- Forum discussion about headless pairing challenges.
3. BlueZ Source: test/simple-agent -- The standard Python agent.
4. BlueZ GitHub Issue #605: Pairing regression in 5.70+ -- Documented failures with newer BlueZ versions.
5. Bleak Issue #1434: Pairing requires active agent -- Evidence that agent availability is the root cause.
6. Red Hat Bug #1905671: Kernel-related pairing failures -- Not always BlueZ -- sometimes it's the kernel.
7. BlueZ GitHub Issue #650: Agent capability mismatch -- SMP negotiation failures with NoInputNoOutput.
8. BlueZ Agent API Documentation -- Official agent interface reference.
9. Kynetics: Pairing Agents in the BlueZ Stack -- Technical deep dive into agent registration.
Свързани публикации
- Huawei Watch D2 BLE Pairing: Protocol & Vendor Lock-In -- проектът, който доведе до това разследване. Watch D2 изисква proprietary application-level handshake върху стандартния BLE pairing, затова automated pairing трябваше да заработи още от самото начало.
- Fix Android Emulator Bluetooth on M1 Mac using Bumble & API 32 -- още една Bluetooth integration битка, този път bridge-ване на физическото radio на Mac към Android Emulator.

Коментари