Improve ManufacturerDataFilter (#139)

* Improve-ManufacturerDataFilter-docs

* Improve ManufacturerDataFilter namings and code

* Perform more renamings
Add tests
This commit is contained in:
Foti Dim
2025-02-17 06:42:28 +01:00
committed by GitHub
parent 15256283e3
commit 2c0d36b9d1
12 changed files with 302 additions and 105 deletions
@@ -0,0 +1,26 @@
import 'dart:typed_data';
class ManufacturerDataFilter {
/// Must be of integer type, in hex or decimal form (e.g. 0x004c or 76).
int companyIdentifier;
/// Matches as prefix the peripheral's advertised data.
Uint8List? payloadPrefix;
/// For each bit in the mask, set it to 1 if it needs to match
/// the corresponding one in manufacturer data, or otherwise set it to 0.
/// The 'mask' must have the same length as the payload.
Uint8List? payloadMask;
/// Filter manufacturer data by company identifier, payload prefix, or payload mask.
ManufacturerDataFilter({
required this.companyIdentifier,
this.payloadPrefix,
this.payloadMask,
});
@override
String toString() {
return 'ManufacturerDataFilter(companyIdentifier: $companyIdentifier, payloadPrefix: $payloadPrefix, mask: $payloadMask)';
}
}
+1
View File
@@ -4,6 +4,7 @@ export 'package:universal_ble/src/models/platform_config.dart';
export 'package:universal_ble/src/models/queue_type.dart';
export 'package:universal_ble/src/models/ble_uuid_parser.dart';
export 'package:universal_ble/src/models/scan_filter.dart';
export 'package:universal_ble/src/models/manufacturer_data_filter.dart';
export 'package:universal_ble/src/models/ble_property.dart';
export 'package:universal_ble/src/models/ble_service.dart';
export 'package:universal_ble/src/models/availability_state.dart';
+1 -24
View File
@@ -1,4 +1,4 @@
import 'dart:typed_data';
import 'package:universal_ble/src/models/manufacturer_data_filter.dart';
class ScanFilter {
List<String> withServices;
@@ -16,26 +16,3 @@ class ScanFilter {
return 'ScanFilter(withServices: $withServices, withManufacturerData: $withManufacturerData, withNamePrefix: $withNamePrefix)';
}
}
class ManufacturerDataFilter {
int companyIdentifier;
// Mask and data must be of same length
Uint8List? payload;
/// For any bit in the mask, set it the 1 if it needs to match
/// the one in manufacturer data, otherwise set it to 0.
/// The 'mask' must have the same length as 'data'.
Uint8List? mask;
ManufacturerDataFilter({
required this.companyIdentifier,
this.payload,
this.mask,
});
@override
String toString() {
return 'ManufacturerDataFilter(companyIdentifier: $companyIdentifier, payload: $payload, mask: $mask)';
}
}
+49 -42
View File
@@ -7,7 +7,7 @@ import 'package:universal_ble/universal_ble.dart';
class UniversalBleFilterUtil {
ScanFilter? scanFilter;
bool filterDevice(BleDevice device) {
bool matchesDevice(BleDevice device) {
final filter = scanFilter;
if (filter == null) return true;
@@ -23,13 +23,12 @@ class UniversalBleFilterUtil {
}
// Else check one of the filter passes
return hasNamePrefixFilter && isNameMatchingFilters(filter, device) ||
hasServiceFilter && isServicesMatchingFilters(filter, device) ||
hasManufacturerDataFilter &&
isManufacturerDataMatchingFilters(filter, device);
return hasNamePrefixFilter && nameMatches(filter, device) ||
hasServiceFilter && servicesMatch(filter, device) ||
hasManufacturerDataFilter && manufacturerDataMatches(filter, device);
}
bool isNameMatchingFilters(ScanFilter scanFilter, BleDevice device) {
bool nameMatches(ScanFilter scanFilter, BleDevice device) {
var namePrefixFilter = scanFilter.withNamePrefix;
if (namePrefixFilter.isEmpty) return true;
@@ -38,7 +37,7 @@ class UniversalBleFilterUtil {
return namePrefixFilter.any(name.startsWith);
}
bool isServicesMatchingFilters(ScanFilter scanFilter, BleDevice device) {
bool servicesMatch(ScanFilter scanFilter, BleDevice device) {
var serviceFilters = scanFilter.withServices;
if (serviceFilters.isEmpty) return true;
@@ -49,48 +48,56 @@ class UniversalBleFilterUtil {
return serviceFilters.any(serviceUuids.contains);
}
bool isManufacturerDataMatchingFilters(
ScanFilter scanFilter,
BleDevice device,
) {
bool manufacturerDataMatches(ScanFilter scanFilter, BleDevice device) {
final manufacturerDataFilters = scanFilter.withManufacturerData;
if (manufacturerDataFilters.isEmpty) return true;
List<ManufacturerData> manufacturerDataList = device.manufacturerDataList;
if (manufacturerDataList.isEmpty) return false;
List<ManufacturerData> deviceDataList = device.manufacturerDataList;
if (deviceDataList.isEmpty) return false;
return manufacturerDataList.any((deviceMsd) => manufacturerDataFilters.any(
(filterMsd) => _isManufacturerDataMatch(filterMsd, deviceMsd),
));
return deviceDataList
.any((deviceData) => manufacturerDataFilters.any((filter) {
// Early return if company identifiers don't match
if (filter.companyIdentifier != deviceData.companyId) {
return false;
}
final payloadPrefix = filter.payloadPrefix;
final payload = deviceData.payload;
// Handle cases where payload prefix is null or empty
if (payloadPrefix == null || payloadPrefix.isEmpty) {
return true;
}
// Validate payload lengths
if (payload.isEmpty || payloadPrefix.length > payload.length) {
return false;
}
final filterMask = filter.payloadMask;
// Choose comparison strategy based on filter mask
return filterMask != null &&
filterMask.length == payloadPrefix.length
? _compareWithMask(payloadPrefix, payload, filterMask)
: _compareWithoutMask(payloadPrefix, payload);
}));
}
bool _isManufacturerDataMatch(
ManufacturerDataFilter filterMsd,
ManufacturerData deviceMsd,
) {
if (filterMsd.companyIdentifier != deviceMsd.companyId) return false;
Uint8List? filterPayload = filterMsd.payload;
Uint8List devicePayload = deviceMsd.payload;
if (filterPayload == null || filterPayload.isEmpty) return true;
if (devicePayload.isEmpty) return false;
if (filterPayload.length > devicePayload.length) return false;
Uint8List? filterMask = filterMsd.mask;
if (filterMask != null && filterMask.length == filterPayload.length) {
for (int i = 0; i < filterPayload.length; i++) {
if ((filterPayload[i] & filterMask[i]) !=
(devicePayload[i] & filterMask[i])) {
return false;
}
bool _compareWithMask(Uint8List prefix, Uint8List payload, Uint8List mask) {
for (int i = 0; i < prefix.length; i++) {
if ((prefix[i] & mask[i]) != (payload[i] & mask[i])) {
return false;
}
} else {
for (int i = 0; i < filterPayload.length; i++) {
if (filterPayload[i] != devicePayload[i]) {
return false;
}
}
return true;
}
bool _compareWithoutMask(Uint8List prefix, Uint8List payload) {
for (int i = 0; i < prefix.length; i++) {
if (prefix[i] != payload[i]) {
return false;
}
}
return true;
@@ -107,12 +107,12 @@ class UniversalBleLinux extends UniversalBlePlatform {
_deviceAdded = null;
_deviceRemoved = null;
// Stop Disovery
// Stop Discovery
if (_activeAdapter?.discovering == true) {
await _activeAdapter?.stopDiscovery();
}
// Clean all advertiseemnt listeners
// Clean all advertisement listeners
_deviceAdvertisementSubscriptions.removeWhere((e, value) {
value.cancel();
return true;
@@ -455,7 +455,7 @@ class UniversalBleLinux extends UniversalBlePlatform {
void _onDeviceAdd(BlueZDevice device) {
BleDevice bleDevice = device.toBleDevice();
if (!_bleFilter.filterDevice(bleDevice)) {
if (!_bleFilter.matchesDevice(bleDevice)) {
return;
}
@@ -475,7 +475,7 @@ class UniversalBleLinux extends UniversalBlePlatform {
e.contains(BluezProperty.manufacturerData) ||
e.contains(BluezProperty.uuids))
.listen((_) {
if (_bleFilter.filterDevice(bleDevice)) {
if (_bleFilter.matchesDevice(bleDevice)) {
updateScanResult(device.toBleDevice());
}
});
@@ -245,8 +245,8 @@ extension _ScanFilterExtension on ScanFilter? {
?.withManufacturerData
.map((e) => UniversalManufacturerDataFilter(
companyIdentifier: e.companyIdentifier,
data: e.payload,
mask: e.mask,
data: e.payloadPrefix,
mask: e.payloadMask,
))
.toList();
@@ -369,8 +369,8 @@ class UniversalBleWeb extends UniversalBlePlatform {
manufacturerData: [
ManufacturerDataFilterBuilder(
companyIdentifier: manufacturerData.companyIdentifier,
dataPrefix: manufacturerData.payload,
mask: manufacturerData.mask,
dataPrefix: manufacturerData.payloadPrefix,
mask: manufacturerData.payloadMask,
),
],
),