/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "webauthn/cable_scanner.h" #include "webauthn/cable_core.h" #include "base/algorithm.h" #include #import #include namespace Platform::WebAuthn::Cable { namespace { struct State { std::function onAdvert; std::function onAvailability; std::set seen; bool stopped = false; }; [[nodiscard]] std::vector CableServiceData( NSDictionary *advertisementData) { auto result = std::vector(); NSDictionary *serviceData = advertisementData[CBAdvertisementDataServiceDataKey]; if (!serviceData) { return result; } CBUUID *google = [CBUUID UUIDWithString:@"FDE2"]; CBUUID *fido = [CBUUID UUIDWithString:@"FFF9"]; for (CBUUID *uuid in serviceData) { if (![uuid isEqual:google] && ![uuid isEqual:fido]) { continue; } NSData *data = serviceData[uuid]; if (!data || data.length < kAdvertSize) { continue; } result.emplace_back( reinterpret_cast(data.bytes), int(data.length)); } return result; } } // namespace } // namespace Platform::WebAuthn::Cable @interface TDCableScannerDelegate : NSObject - (instancetype)initWithState: (std::shared_ptr)state; @end @implementation TDCableScannerDelegate { std::weak_ptr _state; } - (instancetype)initWithState: (std::shared_ptr)state { if (self = [super init]) { _state = state; } return self; } - (void)centralManagerDidUpdateState:(CBCentralManager *)central { if (central.state == CBManagerStatePoweredOn) { [central scanForPeripheralsWithServices:nil options:@{ CBCentralManagerScanOptionAllowDuplicatesKey: @YES }]; crl::on_main([weak = _state] { const auto state = weak.lock(); if (state && !state->stopped && state->onAvailability) { base::take(state->onAvailability)(true); } }); return; } else if (central.state == CBManagerStateUnknown || central.state == CBManagerStateResetting) { return; } crl::on_main([weak = _state] { const auto state = weak.lock(); if (state && !state->stopped && state->onAvailability) { base::take(state->onAvailability)(false); } }); } - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { auto blobs = Platform::WebAuthn::Cable::CableServiceData(advertisementData); if (blobs.empty()) { return; } crl::on_main([weak = _state, blobs = std::move(blobs)] { const auto state = weak.lock(); if (!state || state->stopped) { return; } for (const auto &blob : blobs) { if (state->seen.emplace(blob).second && state->onAdvert) { state->onAdvert(blob); } } }); } @end namespace Platform::WebAuthn::Cable { namespace { class MacBleScanner final : public BleScanner { public: ~MacBleScanner(); bool start( std::function onAdvert, std::function onAvailability) override; void stop() override; private: std::shared_ptr _state; CBCentralManager *_central = nil; TDCableScannerDelegate *_delegate = nil; }; MacBleScanner::~MacBleScanner() { stop(); } bool MacBleScanner::start( std::function onAdvert, std::function onAvailability) { _state = std::make_shared(); _state->onAdvert = std::move(onAdvert); _state->onAvailability = std::move(onAvailability); _delegate = [[TDCableScannerDelegate alloc] initWithState:_state]; _central = [[CBCentralManager alloc] initWithDelegate:_delegate queue:nil]; return _central != nil; } void MacBleScanner::stop() { if (_state) { _state->stopped = true; } if (_central) { [_central stopScan]; _central.delegate = nil; [_central release]; _central = nil; } if (_delegate) { [_delegate release]; _delegate = nil; } _state = nullptr; } } // namespace std::unique_ptr MakeBleScanner() { return std::make_unique(); } } // namespace Platform::WebAuthn::Cable