| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/bluetooth/advertisement_client.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "content/browser/bluetooth/bluetooth_blocklist.h" |
| #include "content/browser/bluetooth/bluetooth_metrics.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| using ::device::BluetoothUUID; |
| |
| } |
| |
| WebBluetoothServiceImpl::AdvertisementClient::AdvertisementClient( |
| WebBluetoothServiceImpl* service, |
| mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> |
| client_remote, |
| RequestCallback callback) |
| : client_remote_(std::move(client_remote)), |
| web_contents_(static_cast<WebContentsImpl*>( |
| WebContents::FromRenderFrameHost(&service->render_frame_host()))), |
| service_(service), |
| callback_(std::move(callback)) { |
| // Using base::Unretained() is safe here because all instances of this class |
| // will be owned by |service|. |
| client_remote_.set_disconnect_handler( |
| base::BindOnce(&WebBluetoothServiceImpl::RemoveDisconnectedClients, |
| base::Unretained(service))); |
| web_contents_->IncrementBluetoothScanningSessionsCount(); |
| } |
| |
| WebBluetoothServiceImpl::AdvertisementClient::~AdvertisementClient() { |
| web_contents_->DecrementBluetoothScanningSessionsCount(); |
| } |
| |
| WebBluetoothServiceImpl::WatchAdvertisementsClient::WatchAdvertisementsClient( |
| WebBluetoothServiceImpl* service, |
| mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> |
| client_remote, |
| blink::WebBluetoothDeviceId device_id, |
| RequestCallback callback) |
| : AdvertisementClient(service, |
| std::move(client_remote), |
| std::move(callback)), |
| device_id_(device_id) { |
| DCHECK(device_id_.IsValid()); |
| } |
| |
| WebBluetoothServiceImpl::WatchAdvertisementsClient:: |
| ~WatchAdvertisementsClient() = default; |
| |
| void WebBluetoothServiceImpl::WatchAdvertisementsClient::SendEvent( |
| const blink::mojom::WebBluetoothAdvertisingEvent& event) { |
| if (event.device->id != device_id_) { |
| return; |
| } |
| |
| auto filtered_event = event.Clone(); |
| std::erase_if(filtered_event->uuids, [this](const BluetoothUUID& uuid) { |
| return !service_->IsAllowedToAccessService(device_id_, uuid); |
| }); |
| base::EraseIf( |
| filtered_event->service_data, |
| [this](const std::pair<BluetoothUUID, std::vector<uint8_t>>& entry) { |
| return !service_->IsAllowedToAccessService(device_id_, entry.first); |
| }); |
| base::EraseIf(filtered_event->manufacturer_data, |
| [this](const std::pair<uint16_t, std::vector<uint8_t>>& entry) { |
| return !service_->IsAllowedToAccessManufacturerData( |
| device_id_, entry.first) || |
| BluetoothBlocklist::Get().IsExcluded(entry.first, |
| entry.second); |
| }); |
| client_remote_->AdvertisingEvent(std::move(filtered_event)); |
| } |
| |
| WebBluetoothServiceImpl::ScanningClient::ScanningClient( |
| WebBluetoothServiceImpl* service, |
| mojo::PendingAssociatedRemote<blink::mojom::WebBluetoothAdvertisementClient> |
| client_remote, |
| blink::mojom::WebBluetoothRequestLEScanOptionsPtr options, |
| RequestCallback callback) |
| : AdvertisementClient(service, |
| std::move(client_remote), |
| std::move(callback)), |
| options_(std::move(options)) { |
| DCHECK(options_->filters.has_value() || options_->accept_all_advertisements); |
| } |
| |
| WebBluetoothServiceImpl::ScanningClient::~ScanningClient() = default; |
| |
| void WebBluetoothServiceImpl::ScanningClient::SendEvent( |
| const blink::mojom::WebBluetoothAdvertisingEvent& event) { |
| // TODO(crbug.com/40707749): Filter out advertisement data if not |
| // included in the filters, optionalServices, or optionalManufacturerData. |
| auto filtered_event = event.Clone(); |
| if (options_->accept_all_advertisements) { |
| if (prompt_controller_) { |
| AddFilteredDeviceToPrompt(filtered_event->device->id.str(), |
| filtered_event->name); |
| } |
| |
| if (allow_send_event_) { |
| client_remote_->AdvertisingEvent(std::move(filtered_event)); |
| } |
| |
| return; |
| } |
| |
| DCHECK(options_->filters.has_value()); |
| |
| // For every filter, we're going to check to see if a |name|, |name_prefix|, |
| // or |services| have been set. If one of these is set, we will check the |
| // scan result to see if it matches the filter's value. If it doesn't, |
| // we'll just continue with the next filter. If all of the properties in a |
| // filter have a match, we can post the AdvertisingEvent. Otherwise, we are |
| // going to drop it. This logic can be reduced a bit, but I think clarity |
| // will decrease. |
| for (auto& filter : options_->filters.value()) { |
| // Check to see if there is a direct match against the advertisement name |
| if (filter->name.has_value()) { |
| if (!filtered_event->name.has_value() || |
| filter->name.value() != filtered_event->name.value()) { |
| continue; |
| } |
| } |
| |
| // Check if there is a name prefix match |
| if (filter->name_prefix.has_value()) { |
| if (!filtered_event->name.has_value() || |
| !base::StartsWith(filtered_event->name.value(), |
| filter->name_prefix.value(), |
| base::CompareCase::SENSITIVE)) { |
| continue; |
| } |
| } |
| |
| // Check to see if there is a service uuid match |
| if (filter->services.has_value()) { |
| if (base::ranges::none_of( |
| filter->services.value(), |
| [&filtered_event](const BluetoothUUID& filter_uuid) { |
| return base::Contains(filtered_event->uuids, filter_uuid); |
| })) { |
| continue; |
| } |
| } |
| |
| // TODO(crbug.com/41310835): Support manufacturerData and serviceData |
| // filters. |
| |
| if (prompt_controller_) { |
| AddFilteredDeviceToPrompt(filtered_event->device->id.str(), |
| filtered_event->name); |
| } |
| |
| if (allow_send_event_) { |
| client_remote_->AdvertisingEvent(std::move(filtered_event)); |
| } |
| return; |
| } |
| } |
| |
| } // namespace content |