diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADeviceManagerTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADeviceManagerTests.cs index 6447a9a44..2bcd1f8a6 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADeviceManagerTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/CaDA/CaDADeviceManagerTests.cs @@ -1,6 +1,7 @@ using BrickController2.DeviceManagement; using BrickController2.DeviceManagement.CaDA; using BrickController2.PlatformServices.BluetoothLE; +using BrickController2.UI.Services.AppIdentifier; using BrickController2.UI.Services.Preferences; using FluentAssertions; using Moq; @@ -17,10 +18,11 @@ public class CaDADeviceManagerTests public CaDADeviceManagerTests() { - _preferencesService.Setup(x => x.ContainsKey("AppID", "CaDA")).Returns(true); - _preferencesService.Setup(x => x.Get("AppID", "", "CaDA")).Returns("YWJj"); + _preferencesService.Setup(x => x.ContainsKey("Identifier", "App")).Returns(true); + _preferencesService.Setup(x => x.Get("Identifier", "", "App")).Returns("YWJj"); - _manager = new CaDADeviceManager(_preferencesService.Object, _cadaPlatformService.Object); + IAppIdentifierService appIdentifierService = new AppIdentifierService(_preferencesService.Object); + _manager = new CaDADeviceManager(appIdentifierService, _cadaPlatformService.Object); } [Fact] diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs index 6055d75d5..2b82de40e 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs @@ -1,21 +1,261 @@ using Autofac; using BrickController2.DeviceManagement; using BrickController2.DeviceManagement.DI; +using BrickController2.DeviceManagement.JieStar; using BrickController2.DeviceManagement.MouldKing; using BrickController2.PlatformServices.BluetoothLE; -using BrickController2.Settings; +using BrickController2.UI.Services.AppIdentifier; using FluentAssertions; using Moq; +using System; using Xunit; - +using JieStarVendor = BrickController2.DeviceManagement.JieStar.JieStar; using MouldKingVendor = BrickController2.DeviceManagement.MouldKing.MouldKing; namespace BrickController2.Tests.DeviceManagement.DI; public class VendorBuilderTests { - [Fact] - public void RegisterDevice_MK6_ReturnedDevice() + [Theory] + [InlineData("Device1")] + [InlineData("Device2")] + [InlineData("Device3")] + public void RegisterDevice_JieStar_SCM4_ReturnedDevice(string address) + { + // Arrange + var builder = new ContainerBuilder(); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + + Mock appIdentifierService = new(); + appIdentifierService.Setup(x => x.GetAppId(2)).Returns(new byte[] { 0x01, 0x02 }); + + builder.RegisterInstance(appIdentifierService.Object); + builder.RegisterType(); + + var vendorBuilder = new VendorBuilder(builder, new JieStarVendor()); + + // Act + var deviceBuilder = vendorBuilder.RegisterDevice(); + var container = builder.Build(); + + // Assert + deviceBuilder.Should().BeOfType>(); + + string name = "TestDevice"; + byte[] deviceData = [1, 2, 3]; + var device = container.ResolveKeyed(DeviceType.JieStarSCM4, + new NamedParameter(nameof(name), name), + new NamedParameter(nameof(address), address), + new NamedParameter(nameof(deviceData), deviceData)); + + device.Should().NotBeNull(); + device.Should().BeOfType(); + } + + [Theory] + [InlineData("Device")] + [InlineData("IllegalDevice")] + public void RegisterDevice_JieStar_SCM4_IllegalDevice(string address) + { + // Arrange + var builder = new ContainerBuilder(); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + + Mock appIdentifierService = new(); + appIdentifierService.Setup(x => x.GetAppId(2)).Returns(new byte[] { 0x01, 0x02 }); + + builder.RegisterInstance(appIdentifierService.Object); + builder.RegisterType(); + + var vendorBuilder = new VendorBuilder(builder, new JieStarVendor()); + + // Act + var deviceBuilder = vendorBuilder.RegisterDevice(); + var container = builder.Build(); + + // Assert + deviceBuilder.Should().BeOfType>(); + + string name = "TestDevice"; + byte[] deviceData = [1, 2, 3]; + + Action act = () => container.ResolveKeyed(DeviceType.JieStarSCM4, + new NamedParameter(nameof(name), name), + new NamedParameter(nameof(address), address), + new NamedParameter(nameof(deviceData), deviceData)); + + act.Should().Throw() + .WithInnerException() + .WithInnerException() + .Which.ParamName.Should().Be(nameof(address)); + } + + [Theory] + [InlineData("Device1")] + [InlineData("Device2")] + [InlineData("Device3")] + public void RegisterDevice_JieStar_SCM8_ReturnedDevice(string address) + { + // Arrange + var builder = new ContainerBuilder(); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + + Mock appIdentifierService = new(); + appIdentifierService.Setup(x => x.GetAppId(2)).Returns(new byte[] { 0x01, 0x02 }); + + builder.RegisterInstance(appIdentifierService.Object); + builder.RegisterType(); + + var vendorBuilder = new VendorBuilder(builder, new JieStarVendor()); + + // Act + var deviceBuilder = vendorBuilder.RegisterDevice(); + var container = builder.Build(); + + // Assert + deviceBuilder.Should().BeOfType>(); + + string name = "TestDevice"; + byte[] deviceData = [1, 2, 3]; + var device = container.ResolveKeyed(DeviceType.JieStarSCM8, + new NamedParameter(nameof(name), name), + new NamedParameter(nameof(address), address), + new NamedParameter(nameof(deviceData), deviceData)); + + device.Should().NotBeNull(); + device.Should().BeOfType(); + } + + [Theory] + [InlineData("Device")] + [InlineData("IllegalDevice")] + public void RegisterDevice_JieStar_SCM8_IllegalDevice(string address) + { + // Arrange + var builder = new ContainerBuilder(); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + + Mock appIdentifierService = new(); + appIdentifierService.Setup(x => x.GetAppId(2)).Returns(new byte[] { 0x01, 0x02 }); + + builder.RegisterInstance(appIdentifierService.Object); + builder.RegisterType(); + + var vendorBuilder = new VendorBuilder(builder, new JieStarVendor()); + + // Act + var deviceBuilder = vendorBuilder.RegisterDevice(); + var container = builder.Build(); + + // Assert + deviceBuilder.Should().BeOfType>(); + + string name = "TestDevice"; + byte[] deviceData = [1, 2, 3]; + + Action act = () => container.ResolveKeyed(DeviceType.JieStarSCM8, + new NamedParameter(nameof(name), name), + new NamedParameter(nameof(address), address), + new NamedParameter(nameof(deviceData), deviceData)); + + act.Should().Throw() + .WithInnerException() + .WithInnerException() + .Which.ParamName.Should().Be(nameof(address)); + } + + [Theory] + [InlineData("Device")] // The address is not relevant for this device + [InlineData("IllegalDevice")] // The address is not relevant for this device + public void RegisterDevice_MK3_8_ReturnedDevice(string address) + { + // Arrange + var builder = new ContainerBuilder(); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + + Mock appIdentifierService = new(); + appIdentifierService.Setup(x => x.GetAppId(2)).Returns(new byte[] { 0x01, 0x02 }); + + builder.RegisterInstance(appIdentifierService.Object); + builder.RegisterType() + .SingleInstance(); + + var vendorBuilder = new VendorBuilder(builder, new MouldKingVendor()); + + // Act + var deviceBuilder = vendorBuilder.RegisterDevice(); + var container = builder.Build(); + + // Assert + deviceBuilder.Should().BeOfType>(); + + string name = "TestDevice"; + byte[] deviceData = [1, 2, 3]; + var device = container.ResolveKeyed(DeviceType.MK3_8, + new NamedParameter(nameof(name), name), + new NamedParameter(nameof(address), address), + new NamedParameter(nameof(deviceData), deviceData)); + + device.Should().NotBeNull(); + device.Should().BeOfType(); + } + + [Theory] + [InlineData("Device")] + [InlineData("IllegalDevice")] + public void RegisterDevice_MK4_IllegalDevice(string address) + { + // Arrange + var builder = new ContainerBuilder(); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + + Mock appIdentifierService = new(); + appIdentifierService.Setup(x => x.GetAppId(2)).Returns(new byte[] { 0x01, 0x02 }); + + builder.RegisterInstance(appIdentifierService.Object); + builder.RegisterType() + .SingleInstance(); + + var vendorBuilder = new VendorBuilder(builder, new MouldKingVendor()); + + // Act + var deviceBuilder = vendorBuilder.RegisterDevice(); + var container = builder.Build(); + + // Assert + deviceBuilder.Should().BeOfType>(); + + string name = "TestDevice"; + byte[] deviceData = [1, 2, 3]; + + Action act = () => container.ResolveKeyed(DeviceType.MK4, + new NamedParameter(nameof(name), name), + new NamedParameter(nameof(address), address), + new NamedParameter(nameof(deviceData), deviceData)); + + act.Should().Throw() + .WithInnerException() + .WithInnerException() + .Which.ParamName.Should().Be(nameof(address)); + } + + [Theory] + [InlineData("Device1")] + [InlineData("Device2")] + [InlineData("Device3")] + public void RegisterDevice_MK4_ReturnedDevice(string address) { // Arrange var builder = new ContainerBuilder(); @@ -23,6 +263,90 @@ public void RegisterDevice_MK6_ReturnedDevice() builder.RegisterInstance(Mock.Of()); builder.RegisterInstance(Mock.Of()); + Mock appIdentifierService = new(); + appIdentifierService.Setup(x => x.GetAppId(2)).Returns(new byte[] { 0x01, 0x02 }); + + builder.RegisterInstance(appIdentifierService.Object); + builder.RegisterType() + .SingleInstance(); + + var vendorBuilder = new VendorBuilder(builder, new MouldKingVendor()); + + // Act + var deviceBuilder = vendorBuilder.RegisterDevice(); + var container = builder.Build(); + + // Assert + deviceBuilder.Should().BeOfType>(); + + string name = "TestDevice"; + byte[] deviceData = [1, 2, 3]; + var device = container.ResolveKeyed(DeviceType.MK4, + new NamedParameter(nameof(name), name), + new NamedParameter(nameof(address), address), + new NamedParameter(nameof(deviceData), deviceData)); + + device.Should().NotBeNull(); + device.Should().BeOfType(); + } + + [Theory] + [InlineData("Device")] // The address is not relevant for this device + [InlineData("IllegalDevice")] // The address is not relevant for this device + public void RegisterDevice_MK5_ReturnedDevice(string address) + { + // Arrange + var builder = new ContainerBuilder(); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + + Mock appIdentifierService = new(); + appIdentifierService.Setup(x => x.GetAppId(2)).Returns(new byte[] { 0x01, 0x02 }); + + builder.RegisterInstance(appIdentifierService.Object); + builder.RegisterType() + .SingleInstance(); + + var vendorBuilder = new VendorBuilder(builder, new MouldKingVendor()); + + // Act + var deviceBuilder = vendorBuilder.RegisterDevice(); + var container = builder.Build(); + + // Assert + deviceBuilder.Should().BeOfType>(); + + string name = "TestDevice"; + byte[] deviceData = [1, 2, 3]; + var device = container.ResolveKeyed(DeviceType.MK5, + new NamedParameter(nameof(name), name), + new NamedParameter(nameof(address), address), + new NamedParameter(nameof(deviceData), deviceData)); + + device.Should().NotBeNull(); + device.Should().BeOfType(); + } + + [Theory] + [InlineData("Device1")] + [InlineData("Device2")] + [InlineData("Device3")] + public void RegisterDevice_MK6_ReturnedDevice(string address) + { + // Arrange + var builder = new ContainerBuilder(); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + + Mock appIdentifierService = new(); + appIdentifierService.Setup(x => x.GetAppId(2)).Returns(new byte[] { 0x01, 0x02 }); + + builder.RegisterInstance(appIdentifierService.Object); + builder.RegisterType() + .SingleInstance(); + var vendorBuilder = new VendorBuilder(builder, new MouldKingVendor()); // Act @@ -32,7 +356,6 @@ public void RegisterDevice_MK6_ReturnedDevice() // Assert deviceBuilder.Should().BeOfType>(); - string address = "Device2"; string name = "TestDevice"; byte[] deviceData = [1, 2, 3]; var device = container.ResolveKeyed(DeviceType.MK6, @@ -43,4 +366,45 @@ public void RegisterDevice_MK6_ReturnedDevice() device.Should().NotBeNull(); device.Should().BeOfType(); } + + [Theory] + [InlineData("Device")] + [InlineData("IllegalDevice")] + public void RegisterDevice_MK6_IllegalDevice(string address) + { + // Arrange + var builder = new ContainerBuilder(); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + + Mock appIdentifierService = new(); + appIdentifierService.Setup(x => x.GetAppId(2)).Returns(new byte[] { 0x01, 0x02 }); + + builder.RegisterInstance(appIdentifierService.Object); + builder.RegisterType() + .SingleInstance(); + + var vendorBuilder = new VendorBuilder(builder, new MouldKingVendor()); + + // Act + var deviceBuilder = vendorBuilder.RegisterDevice(); + var container = builder.Build(); + + // Assert + deviceBuilder.Should().BeOfType>(); + + string name = "TestDevice"; + byte[] deviceData = [1, 2, 3]; + + Action act = () => container.ResolveKeyed(DeviceType.MK6, + new NamedParameter(nameof(name), name), + new NamedParameter(nameof(address), address), + new NamedParameter(nameof(deviceData), deviceData)); + + act.Should().Throw() + .WithInnerException() + .WithInnerException() + .Which.ParamName.Should().Be(nameof(address)); + } } diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDeviceManagerTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDeviceManagerTests.cs new file mode 100644 index 000000000..abc90ace9 --- /dev/null +++ b/BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDeviceManagerTests.cs @@ -0,0 +1,32 @@ +using BrickController2.DeviceManagement.JieStar; +using BrickController2.UI.Services.AppIdentifier; +using BrickController2.UI.Services.Preferences; +using FluentAssertions; +using Moq; +using Xunit; + +namespace BrickController2.Tests.DeviceManagement.JieStar; + +public class JieStarDeviceManagerTests +{ + private readonly JieStarDeviceManager _manager; + private readonly Mock _preferencesService = new(MockBehavior.Strict); + + public JieStarDeviceManagerTests() + { + _preferencesService.Setup(x => x.ContainsKey("Identifier", "App")).Returns(true); + _preferencesService.Setup(x => x.Get("Identifier", "", "App")).Returns("YWJj"); + + IAppIdentifierService appIdentifierService = new AppIdentifierService(_preferencesService.Object); + _manager = new JieStarDeviceManager(appIdentifierService); + } + + [Fact] + public void AppId_TwoBytesInPreferences_AllBytes() + { + var appId = _manager.GetAppId(); + appId.Length.Should().Be(2); + appId.Span[0].Should().Be(0x61); // 'a' = 0x61 + appId.Span[1].Should().Be(0x62); // 'b' = 0x62 + } +} diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDeviceManagerTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDeviceManagerTests.cs index 4183220dc..c590aeef2 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDeviceManagerTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDeviceManagerTests.cs @@ -1,17 +1,38 @@ using BrickController2.DeviceManagement; using BrickController2.DeviceManagement.MouldKing; +using BrickController2.PlatformServices.BluetoothLE; +using BrickController2.UI.Services.AppIdentifier; +using BrickController2.UI.Services.Preferences; using FluentAssertions; +using Moq; +using System.Collections.Generic; using Xunit; namespace BrickController2.Tests.DeviceManagement.MouldKing; -public class MouldKingDeviceManagerTests : DeviceManagerTestBase +public class MouldKingDeviceManagerTests { + private readonly MouldKingDeviceManager _manager; + private readonly Mock _preferencesService = new(MockBehavior.Strict); + + public MouldKingDeviceManagerTests() + { + _preferencesService.Setup(x => x.ContainsKey("Identifier", "App")).Returns(true); + _preferencesService.Setup(x => x.Get("Identifier", "", "App")).Returns("YWJj"); + + IAppIdentifierService appIdentifierService = new AppIdentifierService(_preferencesService.Object); + _manager = new MouldKingDeviceManager(appIdentifierService); + } + [Fact] public void TryGetDevice_MouldKingManufacturerId_ReturnsMouldKingDiyDevice() { byte[] manufacturerData = [0x33, 0xac]; - var scanResult = CreateScanResult(deviceName: default, manufacturerData: manufacturerData); + + var scanResult = new ScanResult(default, default, new Dictionary() + { + { 0xFF, manufacturerData } + }); var result = _manager.TryGetDevice(scanResult, out var device); @@ -28,11 +49,23 @@ public void TryGetDevice_MouldKingManufacturerId_ReturnsMouldKingDiyDevice() [Fact] public void TryGetDevice_WrongManufacturerId_ReturnsFalse() { - var scanResult = CreateScanResult("WrongManufacturerId", manufacturerData: [0x0f, 0xff]); + var scanResult = new ScanResult("WrongManufacturerId", default, new Dictionary() + { + { 0xFF, [0x0f, 0xff] } + }); var result = _manager.TryGetDevice(scanResult, out var device); result.Should().BeFalse(); device.DeviceType.Should().Be(DeviceType.Unknown); } + + [Fact] + public void AppId_TwoBytesInPreferences_AllBytes() + { + var appId = _manager.GetAppId(); + appId.Length.Should().Be(2); + appId.Span[0].Should().Be(0x61); // 'a' = 0x61 + appId.Span[1].Should().Be(0x62); // 'b' = 0x62 + } } diff --git a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs index 9e63f4864..ef567b5dc 100644 --- a/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/CaDA/CaDADeviceManager.cs @@ -1,7 +1,7 @@ using System; using BrickController2.PlatformServices.BluetoothLE; using BrickController2.Protocols; -using BrickController2.UI.Services.Preferences; +using BrickController2.UI.Services.AppIdentifier; namespace BrickController2.DeviceManagement.CaDA; @@ -11,18 +11,16 @@ namespace BrickController2.DeviceManagement.CaDA; public class CaDADeviceManager : BluetoothDeviceManagerBase, IBluetoothLEAdvertiserDeviceScanInfo, IBluetoothLEDeviceManager, ICaDADeviceManager { - private const string SECTION = "CaDA"; - private const string APPIDKEY = "AppID"; - + private const int AppIdentifierLength = 3; // CaDA protocol defines 3 bytes for the app identifier private readonly ICaDAPlatformService _cadaPlatformService; // this identifier is patched into the advertising data to identify the app - private readonly byte[] _appIdChecksumMaskArray; + private readonly byte[] _appIdentifier; - public CaDADeviceManager(IPreferencesService preferencesService, ICaDAPlatformService cadaPlatformService) + public CaDADeviceManager(IAppIdentifierService appIdentifierService, ICaDAPlatformService cadaPlatformService) { _cadaPlatformService = cadaPlatformService; - _appIdChecksumMaskArray = CaDADeviceManager.GetAppIdentifier(preferencesService); + _appIdentifier = appIdentifierService.GetAppId(AppIdentifierLength).ToArray(); } public AdvertisingInterval AdvertisingIterval => AdvertisingInterval.Min; @@ -31,7 +29,7 @@ public CaDADeviceManager(IPreferencesService preferencesService, ICaDAPlatformSe public ushort ManufacturerId => CaDAProtocol.ManufacturerID; - public ReadOnlyMemory GetAppId() => _appIdChecksumMaskArray; + public ReadOnlyMemory GetAppId() => _appIdentifier; /// /// Create an byte-array to be advertised on device-scan @@ -46,9 +44,9 @@ public byte[] CreateScanData() 0x00, // [2] DeviceAddress 0x00, // [3] DeviceAddress 0x00, // [4] DeviceAddress - _appIdChecksumMaskArray[0], // [5] AppID - _appIdChecksumMaskArray[1], // [6] AppID - _appIdChecksumMaskArray[2], // [7] AppID + _appIdentifier[0], // [5] AppID + _appIdentifier[1], // [6] AppID + _appIdentifier[2], // [7] AppID 0x00, // [8] 0x00, // [9] 0x80, // [10] min 128 @@ -112,9 +110,9 @@ private bool IsCadaRaceCar(ReadOnlySpan manufacturerData) manufacturerData.Length == 18 && manufacturerData[2] == 0x75 && (manufacturerData[3] & 0x40) > 0 && - manufacturerData[7] == _appIdChecksumMaskArray[0] && // response has to have the same appId - manufacturerData[8] == _appIdChecksumMaskArray[1] && - manufacturerData[9] == _appIdChecksumMaskArray[2]; + manufacturerData[7] == _appIdentifier[0] && // response has to have the same appId + manufacturerData[8] == _appIdentifier[1] && + manufacturerData[9] == _appIdentifier[2]; } private static bool IsCadaRaceCarRev2(ReadOnlySpan manufacturerData) => manufacturerData.Length == 16 && @@ -124,50 +122,4 @@ private static bool IsCadaRaceCarRev2(ReadOnlySpan manufacturerData) => ma manufacturerData[4] == 0x00 && // flag not connected yet manufacturerData[7] == 0x85; - - /// - /// Gets or creates an app-persistent AppIdentifier. - /// - /// Reference to preferencesService singleton. - /// byte array containing the AppIdentifier - private static byte[] GetAppIdentifier(IPreferencesService preferencesService) - { - byte[] appIdChecksumMaskArray; - // gets or creates an app-persistent AppIdentifier - try - { - if (preferencesService.ContainsKey(APPIDKEY, SECTION)) - { - // throws exception if converting went wrong - appIdChecksumMaskArray = Convert.FromBase64String(preferencesService.Get(APPIDKEY, string.Empty, SECTION)); - - // check minimum length - if (appIdChecksumMaskArray?.Length >= 3) - { - return appIdChecksumMaskArray; // valid - } - } - } - catch // catch all exceptions - { - } - - // create new byte[] with random values - // * on first run - // * on exception - // * on length to short - appIdChecksumMaskArray = new byte[3]; - - Random.Shared.NextBytes(appIdChecksumMaskArray); - - try - { - preferencesService.Set(APPIDKEY, Convert.ToBase64String(appIdChecksumMaskArray), SECTION); - } - catch // catch all exceptions to keep app alive - { - } - - return appIdChecksumMaskArray; - } } \ No newline at end of file diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/IJieStarDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/IJieStarDeviceManager.cs deleted file mode 100644 index 0654d5fb9..000000000 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/IJieStarDeviceManager.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace BrickController2.DeviceManagement.JieStar; - -/// -/// Manager for JIESTAR devices -/// -public interface IJieStarDeviceManager -{ - ReadOnlyMemory GetAppId(); -} diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStar.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStar.cs index fc35b3713..e17227002 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStar.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStar.cs @@ -15,7 +15,6 @@ protected override void Register(VendorBuilder builder) { // device manager builder.ContainerBuilder.RegisterType() - .As() .SingleInstance(); // manually added devices diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs index 8431c5b62..c99973ed9 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarBase.cs @@ -39,7 +39,7 @@ internal abstract class JieStarBase : BluetoothAdvertisingDevice /// protected readonly float[] _storedValues; - protected JieStarBase(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, IJieStarDeviceManager jieStarDeviceManager, byte[] telegram_Connect, byte[] telegram_Base, byte ctxValue2) + protected JieStarBase(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, JieStarDeviceManager jieStarDeviceManager, byte[] telegram_Connect, byte[] telegram_Base, byte ctxValue2) : base(name, address, deviceData, deviceRepository, bleService) { _telegram_Connect = telegram_Connect; diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarDeviceManager.cs index 105dd6f35..26ce17017 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarDeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarDeviceManager.cs @@ -1,70 +1,21 @@ using System; -using BrickController2.UI.Services.Preferences; +using BrickController2.UI.Services.AppIdentifier; namespace BrickController2.DeviceManagement.JieStar; /// /// Manager for JIESTAR devices /// -public class JieStarDeviceManager : IJieStarDeviceManager +public class JieStarDeviceManager { - private const string SECTION = "JIESTAR"; - private const string APPIDKEY = "AppID"; + private const int AppIdentifierLength = 2; // JIESTAR protocol defines 2 bytes for the app identifier + private readonly ReadOnlyMemory _appIdentifier; - // this identifier is patched into the advertising data to identify the app - private readonly byte[] _appIdChecksumMaskArray; - - public JieStarDeviceManager(IPreferencesService preferencesService) + public JieStarDeviceManager(IAppIdentifierService appIdentifierService) { - _appIdChecksumMaskArray = JieStarDeviceManager.GetAppIdentifier(preferencesService); + _appIdentifier = appIdentifierService.GetAppId(AppIdentifierLength); } - public ReadOnlyMemory GetAppId() => _appIdChecksumMaskArray; - - /// - /// Gets or creates an app-persistent AppIdentifier. - /// - /// Reference to preferencesService singleton. - /// byte array containing the AppIdentifier - private static byte[] GetAppIdentifier(IPreferencesService preferencesService) - { - byte[] appIdChecksumMaskArray; - // gets or creates an app-persistent AppIdentifier - try - { - if (preferencesService.ContainsKey(APPIDKEY, SECTION)) - { - // throws exception if converting went wrong - appIdChecksumMaskArray = Convert.FromBase64String(preferencesService.Get(APPIDKEY, string.Empty, SECTION)); - - // check minimum length - if (appIdChecksumMaskArray?.Length >= 2) - { - return appIdChecksumMaskArray; // valid - } - } - } - catch // catch all exceptions - { - } - - // create new byte[] with random values - // * on first run - // * on exception - // * on length too short - appIdChecksumMaskArray = new byte[2]; - - Random.Shared.NextBytes(appIdChecksumMaskArray); - - try - { - preferencesService.Set(APPIDKEY, Convert.ToBase64String(appIdChecksumMaskArray), SECTION); - } - catch // catch all exceptions to keep app alive - { - } - - return appIdChecksumMaskArray; - } + public ReadOnlyMemory GetAppId() => _appIdentifier; } \ No newline at end of file diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs index 149ee3ec5..7727a37b0 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM4.cs @@ -29,7 +29,7 @@ internal class JieStarSCM4 : JieStarBase, IDeviceType /// private static readonly TimeSpan ReconnectTimeSpan = TimeSpan.FromSeconds(3); - public JieStarSCM4(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, IJieStarDeviceManager jieStarDeviceManager) + public JieStarSCM4(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, JieStarDeviceManager jieStarDeviceManager) : base(name, address, deviceData, deviceRepository, bleService, jieStarPlatformService, jieStarDeviceManager, Telegram_Connect_Device, Telegram_Base_Device, GetCTXValue2(address)) { } diff --git a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM8.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM8.cs index 8a71cbca6..9b0fe6ac3 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM8.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarSCM8.cs @@ -39,7 +39,7 @@ internal class JieStarSCM8 : JieStarBase, IDeviceType /// private static readonly TimeSpan ReconnectTimeSpan = TimeSpan.FromSeconds(3); - public JieStarSCM8(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, IJieStarDeviceManager jieStarDeviceManager) + public JieStarSCM8(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IJieStarPlatformService jieStarPlatformService, JieStarDeviceManager jieStarDeviceManager) : base(name, address, deviceData, deviceRepository, bleService, jieStarPlatformService, jieStarDeviceManager, JieStarSCM8.Telegram_Connect, GetTelegramBase(address), JieStarProtocol.CTXValue2) { } diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK3_8.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK3_8.cs index 477674c21..870c7fc7e 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK3_8.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK3_8.cs @@ -33,8 +33,8 @@ internal class MK3_8 : MKBaseNibble, IDeviceType /// protected override ushort ManufacturerId => MKProtocol.ManufacturerID; - public MK3_8(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService) - : base(name, address, deviceData, deviceRepository, bleService, mkPlatformService, 0, Telegram_Connect, Telegram_Base) + public MK3_8(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService, MouldKingDeviceManager mkDeviceManager) + : base(name, address, deviceData, deviceRepository, bleService, mkPlatformService, mkDeviceManager, 0, Telegram_Connect, Telegram_Base) { } diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs index 2c20fdcd9..5bedcb7b3 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK4.cs @@ -38,8 +38,8 @@ internal class MK4 : MKBaseNibble, IDeviceType /// private static BluetoothAdvertisingDeviceHandler? bluetoothAdvertisingDeviceHandler; - public MK4(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService) - : base(name, address, deviceData, deviceRepository, bleService, mkPlatformService, GetInstanceNo(address), Telegram_Connect, Telegram_Base) + public MK4(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService, MouldKingDeviceManager mkDeviceManager) + : base(name, address, deviceData, deviceRepository, bleService, mkPlatformService, mkDeviceManager, GetInstanceNo(address), Telegram_Connect, Telegram_Base) { } diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK5.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK5.cs index f285f538a..09e78b0c1 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK5.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK5.cs @@ -39,8 +39,8 @@ internal class MK5 : MKBaseNibble, IDeviceType /// private byte _turret_lock_Nibble = TURRET_UNLOCKED_NIBBLE; - public MK5(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService) - : base(name, address, deviceData, deviceRepository, bleService, mkPlatformService, 0, Telegram_Connect, Telegram_Base) + public MK5(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService, MouldKingDeviceManager mkDeviceManager) + : base(name, address, deviceData, deviceRepository, bleService, mkPlatformService, mkDeviceManager, 0, Telegram_Connect, Telegram_Base) { } diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK6.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK6.cs index 452672d19..b162c7e07 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK6.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK6.cs @@ -53,8 +53,8 @@ internal class MK6 : MKBaseByte, IDeviceType /// protected override int BaseTelegram_ChannelStartOffset => 3; - public MK6(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService) - : base(name, address, deviceData, deviceRepository, bleService, 3, MK6.Telegram_Connect, MK6.GetTelegramBase(address), mkPlatformService) + public MK6(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService, MouldKingDeviceManager mkDeviceManager) + : base(name, address, deviceData, deviceRepository, bleService, mkPlatformService, mkDeviceManager, 3, MK6.Telegram_Connect, MK6.GetTelegramBase(address)) { } diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs index 19431c22c..da20feabe 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseByte.cs @@ -30,13 +30,21 @@ internal abstract class MKBaseByte : BluetoothAdvertisingDevice protected readonly int _channelStartOffset; - protected MKBaseByte(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, int channelStartOffset, byte[] telegram_Connect, byte[] telegram_Base, IMKPlatformService mkPlatformService) + protected MKBaseByte(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService, MouldKingDeviceManager mkDeviceManager,int channelStartOffset, byte[] telegram_Connect, byte[] telegram_Base) : base(name, address, deviceData, deviceRepository, bleService) { _channelStartOffset = channelStartOffset; _telegram_Connect = telegram_Connect; _telegram_Base = telegram_Base; _mkPlatformService = mkPlatformService; + + // bytes[1] and [2] of both telegrams can be set to a unique appId + ReadOnlySpan appId = mkDeviceManager.GetAppId().Span[..2]; + _telegram_Connect[1] = appId[0]; + _telegram_Connect[2] = appId[1]; + + _telegram_Base[1] = appId[0]; + _telegram_Base[2] = appId[1]; } /// diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs index 9fadcec20..b90723bb8 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MKBaseNibble.cs @@ -1,4 +1,5 @@ -using BrickController2.PlatformServices.BluetoothLE; +using System; +using BrickController2.PlatformServices.BluetoothLE; namespace BrickController2.DeviceManagement.MouldKing; @@ -43,7 +44,7 @@ internal abstract class MKBaseNibble : BluetoothAdvertisingDevice /// protected readonly int _instanceNo; - protected MKBaseNibble(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService, int instanceNo, byte[] telegram_Connect, byte[] telegram_Base) + protected MKBaseNibble(string name, string address, byte[] deviceData, IDeviceRepository deviceRepository, IBluetoothLEService bleService, IMKPlatformService mkPlatformService, MouldKingDeviceManager mkDeviceManager, int instanceNo, byte[] telegram_Connect, byte[] telegram_Base) : base(name, address, deviceData, deviceRepository, bleService) { _telegram_Connect = telegram_Connect; @@ -52,6 +53,14 @@ protected MKBaseNibble(string name, string address, byte[] deviceData, IDeviceRe _instanceNo = instanceNo; + // bytes[1] and [2] of both telegrams can be set to a unique appId + ReadOnlySpan appId = mkDeviceManager.GetAppId().Span[..2]; + _telegram_Connect[1] = appId[0]; + _telegram_Connect[2] = appId[1]; + + _telegram_Base[1] = appId[0]; + _telegram_Base[2] = appId[1]; + _storedValues = new float[NumberOfChannels]; // initialize output values for all channels } diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MouldKing.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MouldKing.cs index a3219ab8c..e5c78f34e 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MouldKing.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MouldKing.cs @@ -34,6 +34,7 @@ protected override void Register(VendorBuilder builder) .WithDeviceFactory(MK6.Device3, $"{MK6.TypeName} Device 3"); // device manager - builder.RegisterDeviceManager(); + builder.RegisterDeviceManager() + .SingleInstance(); } } diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MouldKingDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MouldKingDeviceManager.cs index 81492c092..6ee506a61 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MouldKingDeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MouldKingDeviceManager.cs @@ -1,5 +1,6 @@ using System; using BrickController2.PlatformServices.BluetoothLE; +using BrickController2.UI.Services.AppIdentifier; namespace BrickController2.DeviceManagement.MouldKing; @@ -8,6 +9,17 @@ namespace BrickController2.DeviceManagement.MouldKing; /// public class MouldKingDeviceManager : BluetoothDeviceManagerBase { + private const int AppIdentifierLength = 2; // MouldKing protocol defines 2 bytes for the app identifier + + private readonly ReadOnlyMemory _appIdentifier; + + public MouldKingDeviceManager(IAppIdentifierService appIdentifierService) + { + _appIdentifier = appIdentifierService.GetAppId(AppIdentifierLength); + } + + public ReadOnlyMemory GetAppId() => _appIdentifier; + protected override bool TryGetDeviceByManufacturerData(ScanResult scanResult, FoundDevice template, ushort manufacturerId, diff --git a/BrickController2/BrickController2/UI/DI/UiModule.cs b/BrickController2/BrickController2/UI/DI/UiModule.cs index bfbf47928..047e7a24d 100644 --- a/BrickController2/BrickController2/UI/DI/UiModule.cs +++ b/BrickController2/BrickController2/UI/DI/UiModule.cs @@ -15,6 +15,7 @@ using BrickController2.UI.Services.Theme; using BrickController2.UI.Services.Translation; using BrickController2.UI.ViewModels; +using BrickController2.UI.Services.AppIdentifier; namespace BrickController2.UI.DI { @@ -31,6 +32,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().AsSelf().As().SingleInstance(); builder.RegisterType().AsSelf().As().SingleInstance(); builder.RegisterType().AsSelf().As().SingleInstance(); + builder.RegisterType().AsSelf().As().SingleInstance(); // Register Dialogs builder.RegisterType().As().As().SingleInstance(); diff --git a/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs b/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs new file mode 100644 index 000000000..449d81732 --- /dev/null +++ b/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs @@ -0,0 +1,101 @@ +using System; +using BrickController2.UI.Services.Preferences; + +namespace BrickController2.UI.Services.AppIdentifier +{ + public class AppIdentifierService : IAppIdentifierService + { + private const int MIN_APPID_LENGTH = 3; // minimum length of the AppIdentifier in bytes, to be valid + private const string SECTION = "App"; + private const string APPIDKEY = "Identifier"; + + private readonly object _lock = new object(); + private readonly IPreferencesService _preferencesService; + + // AppIdentifier is a byte-array that is used to identify the app on the device and to create a unique pairing between app and device. + // It is patched into the advertising data of the device and is used to identify the app on the device-scan. + // It is also used to create a unique pairing between app and device. + // The AppIdentifier is stored in the preferences and is persistent across app restarts and updates. + // It is created on first run and can be reset by clearing the preferences. + private byte[] _appIdentifier; + + public AppIdentifierService(IPreferencesService preferencesService) + { + _preferencesService = preferencesService; + _appIdentifier = GetAppIdentifier(_preferencesService, MIN_APPID_LENGTH); + } + + public ReadOnlyMemory GetAppId(int length) + { + if (length <= 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + lock (_lock) + { + if (length > _appIdentifier.Length) + { + // if the requested length is bigger than the current AppIdentifier, create a new one with the requested length, + // to keep the AppIdentifier as stable as possible + _appIdentifier = GetAppIdentifier(_preferencesService, length); + } + + return _appIdentifier[..length]; + } + } + + /// + /// Gets or creates an app-persistent AppIdentifier. + /// + /// Reference to preferencesService singleton. + /// Minimum length of the AppIdentifier. + /// byte array containing the AppIdentifier + private static byte[] GetAppIdentifier(IPreferencesService preferencesService, int minLength) + { + byte[]? appIdentifier = null; + // gets or creates an app-persistent AppIdentifier + try + { + if (preferencesService.ContainsKey(APPIDKEY, SECTION)) + { + // throws exception if converting went wrong + appIdentifier = Convert.FromBase64String(preferencesService.Get(APPIDKEY, string.Empty, SECTION)); + + // check minimum length + if (appIdentifier?.Length >= minLength) + { + return appIdentifier; // valid + } + } + } + catch // catch all exceptions + { + } + + // create new byte[] with random values + // * on first run + // * on exception + // * on length too short + byte[] newAppIdentifier = new byte[minLength]; + Random.Shared.NextBytes(newAppIdentifier); + + if (appIdentifier != null) + { + // copy old values to new array, if possible, to keep the AppIdentifier as stable as possible + Array.Copy(appIdentifier, newAppIdentifier, Math.Min(appIdentifier.Length, newAppIdentifier.Length)); + } + + try + { + // store new AppIdentifier in preferences, to be persistent across app restarts and updates + preferencesService.Set(APPIDKEY, Convert.ToBase64String(newAppIdentifier), SECTION); + } + catch // catch all exceptions to keep app alive + { + } + + return newAppIdentifier; + } + } +} diff --git a/BrickController2/BrickController2/UI/Services/AppIdentifier/IAppIdentifierService.cs b/BrickController2/BrickController2/UI/Services/AppIdentifier/IAppIdentifierService.cs new file mode 100644 index 000000000..0a7b19c51 --- /dev/null +++ b/BrickController2/BrickController2/UI/Services/AppIdentifier/IAppIdentifierService.cs @@ -0,0 +1,9 @@ +using System; + +namespace BrickController2.UI.Services.AppIdentifier +{ + public interface IAppIdentifierService + { + ReadOnlyMemory GetAppId(int length); + } +}