Skip to content

Commit

Permalink
Perform more renamings
Browse files Browse the repository at this point in the history
Add tests
  • Loading branch information
fotiDim committed Feb 16, 2025
1 parent e35e7fd commit 967b0f8
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 78 deletions.
4 changes: 2 additions & 2 deletions lib/src/models/manufacturer_data_filter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ class ManufacturerDataFilter {
/// Matches as prefix the peripheral's advertised data.
Uint8List? payloadPrefix;

/// For any bit in the mask, set it to 1 if it needs to match
/// the corresponding one in manufacturer data, otherwise set it to 0.
/// 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;

Expand Down
1 change: 1 addition & 0 deletions lib/src/models/model_exports.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
86 changes: 38 additions & 48 deletions lib/src/universal_ble_filter_util.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import 'dart:typed_data';

import 'package:universal_ble/src/models/manufacturer_data_filter.dart';
import 'package:universal_ble/universal_ble.dart';

// A default Filter on dart side
// Used on Linux only
class UniversalBleFilterUtil {
ScanFilter? scanFilter;

bool filterDevice(BleDevice device) {
bool matchesDevice(BleDevice device) {
final filter = scanFilter;
if (filter == null) return true;

Expand All @@ -24,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;

Expand All @@ -39,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;

Expand All @@ -50,49 +48,41 @@ 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;

return manufacturerDataList.any((deviceMsd) => manufacturerDataFilters.any(
(filterMsd) => _isManufacturerDataMatching(filterMsd, deviceMsd),
));
}

bool _isManufacturerDataMatching(
ManufacturerDataFilter filterMsd,
ManufacturerData deviceMsd,
) {
// Early return if company identifiers don't match
if (filterMsd.companyIdentifier != deviceMsd.companyId) {
return false;
}

final payloadPrefix = filterMsd.payloadPrefix;
final payload = deviceMsd.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 = filterMsd.payloadMask;

// Choose comparison strategy based on filter mask
return filterMask != null && filterMask.length == payloadPrefix.length
? _compareWithMask(payloadPrefix, payload, filterMask)
: _compareWithoutMask(payloadPrefix, payload);
List<ManufacturerData> deviceDataList = device.manufacturerDataList;
if (deviceDataList.isEmpty) return false;

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 _compareWithMask(Uint8List prefix, Uint8List payload, Uint8List mask) {
Expand Down
8 changes: 4 additions & 4 deletions lib/src/universal_ble_linux/universal_ble_linux.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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());
}
});
Expand Down
178 changes: 178 additions & 0 deletions test/manufacturer_data_filter_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:universal_ble/src/universal_ble_filter_util.dart';
import 'dart:typed_data';

import 'package:universal_ble/universal_ble.dart';

void main() {
group('ManufacturerData Matching Tests', () {
late UniversalBleFilterUtil filterUtil;
late ScanFilter scanFilter;
late BleDevice device;

setUp(() {
filterUtil = UniversalBleFilterUtil();
scanFilter = ScanFilter(
withNamePrefix: [],
withServices: [],
withManufacturerData: [],
);
});

test('should match when no manufacturer filters are present', () {
device = BleDevice(
deviceId: '1',
name: 'Test Device',
manufacturerDataList: [],
services: [],
);

expect(filterUtil.manufacturerDataMatches(scanFilter, device), true);
});

test('should not match when device has no manufacturer data', () {
scanFilter = ScanFilter(
withNamePrefix: [],
withServices: [],
withManufacturerData: [
ManufacturerDataFilter(companyIdentifier: 0x004C),
],
);

device = BleDevice(
deviceId: '1',
name: 'Test Device',
manufacturerDataList: [],
services: [],
);

expect(filterUtil.manufacturerDataMatches(scanFilter, device), false);
});

test(
'should match when company identifiers are equal and no payload prefix',
() {
scanFilter = ScanFilter(
withNamePrefix: [],
withServices: [],
withManufacturerData: [
ManufacturerDataFilter(companyIdentifier: 0x004C),
],
);

device = BleDevice(
deviceId: '1',
name: 'Test Device',
manufacturerDataList: [
ManufacturerData(0x004C, Uint8List.fromList([])),
],
services: [],
);

expect(filterUtil.manufacturerDataMatches(scanFilter, device), true);
});

group('Payload Prefix Tests', () {
test('should match when payload prefix matches start of payload', () {
scanFilter = ScanFilter(
withNamePrefix: [],
withServices: [],
withManufacturerData: [
ManufacturerDataFilter(
companyIdentifier: 0x004C,
payloadPrefix: Uint8List.fromList([0x01, 0x02]),
),
],
);

device = BleDevice(
deviceId: '1',
name: 'Test Device',
manufacturerDataList: [
ManufacturerData(0x004C, Uint8List.fromList([0x01, 0x02, 0x03])),
],
services: [],
);

expect(filterUtil.manufacturerDataMatches(scanFilter, device), true);
});

test('should match with multiple manufacturer data entries', () {
scanFilter = ScanFilter(
withNamePrefix: [],
withServices: [],
withManufacturerData: [
ManufacturerDataFilter(
companyIdentifier: 0x004D,
payloadPrefix: Uint8List.fromList([0x01, 0x02]),
),
],
);

device = BleDevice(
deviceId: '1',
name: 'Test Device',
manufacturerDataList: [
ManufacturerData(0x004C, Uint8List.fromList([0x03, 0x04])),
ManufacturerData(0x004D, Uint8List.fromList([0x01, 0x02, 0x03])),
],
services: [],
);

expect(filterUtil.manufacturerDataMatches(scanFilter, device), true);
});
});

group('Payload Mask Tests', () {
test('should match when masked values are equal', () {
scanFilter = ScanFilter(
withNamePrefix: [],
withServices: [],
withManufacturerData: [
ManufacturerDataFilter(
companyIdentifier: 0x004C,
payloadPrefix: Uint8List.fromList([0xFF, 0xFF]),
payloadMask: Uint8List.fromList([0xF0, 0xF0]),
),
],
);

device = BleDevice(
deviceId: '1',
name: 'Test Device',
manufacturerDataList: [
ManufacturerData(0x004C, Uint8List.fromList([0xF5, 0xF8])),
],
services: [],
);

expect(filterUtil.manufacturerDataMatches(scanFilter, device), true);
});

test('should not match when masked values are different', () {
scanFilter = ScanFilter(
withNamePrefix: [],
withServices: [],
withManufacturerData: [
ManufacturerDataFilter(
companyIdentifier: 0x004C,
payloadPrefix: Uint8List.fromList([0xFF, 0xFF]),
payloadMask: Uint8List.fromList([0xF0, 0xF0]),
),
],
);

device = BleDevice(
deviceId: '1',
name: 'Test Device',
manufacturerDataList: [
ManufacturerData(0x004C, Uint8List.fromList([0xE5, 0xF8])),
],
services: [],
);

expect(filterUtil.manufacturerDataMatches(scanFilter, device), false);
});
});
});
}
Loading

0 comments on commit 967b0f8

Please sign in to comment.