From 19884e9ba29b8ff08aebf32675ba65fd9603746a Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 11 Jun 2026 09:46:30 +0200 Subject: [PATCH 1/9] Add a global AppIdService to the application architecture --- .../CaDA/CaDADeviceManagerTests.cs | 8 +- .../DeviceManagement/DI/VendorBuilderTests.cs | 5 +- .../MouldKing/MouldKingDeviceManagerTests.cs | 30 +++++- .../CaDA/CaDADeviceManager.cs | 72 +++------------ .../JieStar/JieStarDeviceManager.cs | 61 ++---------- .../MouldKing/IMouldKingDeviceManager.cs | 11 +++ .../DeviceManagement/MouldKing/MK3_8.cs | 4 +- .../DeviceManagement/MouldKing/MK4.cs | 4 +- .../DeviceManagement/MouldKing/MK5.cs | 4 +- .../DeviceManagement/MouldKing/MK6.cs | 4 +- .../DeviceManagement/MouldKing/MKBaseByte.cs | 10 +- .../MouldKing/MKBaseNibble.cs | 13 ++- .../DeviceManagement/MouldKing/MouldKing.cs | 3 +- .../MouldKing/MouldKingDeviceManager.cs | 15 ++- .../BrickController2/UI/DI/UiModule.cs | 2 + .../AppIdentifier/AppIdentifierService.cs | 92 +++++++++++++++++++ .../AppIdentifier/IAppIdentifierService.cs | 9 ++ 17 files changed, 212 insertions(+), 135 deletions(-) create mode 100644 BrickController2/BrickController2/DeviceManagement/MouldKing/IMouldKingDeviceManager.cs create mode 100644 BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs create mode 100644 BrickController2/BrickController2/UI/Services/AppIdentifier/IAppIdentifierService.cs 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..9b04f0fcc 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs @@ -3,7 +3,6 @@ using BrickController2.DeviceManagement.DI; using BrickController2.DeviceManagement.MouldKing; using BrickController2.PlatformServices.BluetoothLE; -using BrickController2.Settings; using FluentAssertions; using Moq; using Xunit; @@ -23,6 +22,10 @@ public void RegisterDevice_MK6_ReturnedDevice() builder.RegisterInstance(Mock.Of()); builder.RegisterInstance(Mock.Of()); + Mock mouldKingDeviceManager = new(); + mouldKingDeviceManager.Setup(x => x.GetAppId()).Returns(new byte[] { 0x01, 0x02 }); + builder.RegisterInstance(mouldKingDeviceManager.Object); + var vendorBuilder = new VendorBuilder(builder, new MouldKingVendor()); // Act diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDeviceManagerTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDeviceManagerTests.cs index 4183220dc..029e493fb 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,7 +49,10 @@ 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); 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/JieStarDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarDeviceManager.cs index 105dd6f35..95d115590 100644 --- a/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarDeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/JieStar/JieStarDeviceManager.cs @@ -1,5 +1,5 @@ using System; -using BrickController2.UI.Services.Preferences; +using BrickController2.UI.Services.AppIdentifier; namespace BrickController2.DeviceManagement.JieStar; @@ -8,63 +8,14 @@ namespace BrickController2.DeviceManagement.JieStar; /// public class JieStarDeviceManager : IJieStarDeviceManager { - 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/MouldKing/IMouldKingDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/IMouldKingDeviceManager.cs new file mode 100644 index 000000000..a1a2bf47f --- /dev/null +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/IMouldKingDeviceManager.cs @@ -0,0 +1,11 @@ +using System; + +namespace BrickController2.DeviceManagement.MouldKing; + +/// +/// Interface definition for MouldKing specific PlatformService +/// +public interface IMouldKingDeviceManager +{ + ReadOnlyMemory GetAppId(); +} diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MK3_8.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MK3_8.cs index 477674c21..cdd5dc722 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, IMouldKingDeviceManager 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..fd062dbd1 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, IMouldKingDeviceManager 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..e2aaf8600 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, IMouldKingDeviceManager 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..b3eb8e31a 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, IMouldKingDeviceManager 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..ea9c470e7 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, IMouldKingDeviceManager 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..ed3f91f3c 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, IMouldKingDeviceManager 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..0715ad624 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() + .As(); } } diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/MouldKingDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/MouldKingDeviceManager.cs index 81492c092..73e563235 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/MouldKingDeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/MouldKingDeviceManager.cs @@ -1,13 +1,26 @@ using System; using BrickController2.PlatformServices.BluetoothLE; +using BrickController2.UI.Services.AppIdentifier; namespace BrickController2.DeviceManagement.MouldKing; /// /// Manager for MouldKing devices /// -public class MouldKingDeviceManager : BluetoothDeviceManagerBase +public class MouldKingDeviceManager : BluetoothDeviceManagerBase, + IMouldKingDeviceManager { + 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..fd5ac3545 --- /dev/null +++ b/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs @@ -0,0 +1,92 @@ +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 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 < _appIdentifier.Length) + { + // if the requested length is smaller than the current AppIdentifier, create a new one with the requested length, + // to keep the AppIdentifier as stable as possible + _appIdentifier = GetAppIdentifier(_preferencesService, MIN_APPID_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); + } +} From 2d562db3cd17d2c68130ef2cff78ad3fe0026a40 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 11 Jun 2026 12:20:39 +0200 Subject: [PATCH 2/9] added VendorBuilderTests --- .../DeviceManagement/DI/VendorBuilderTests.cs | 191 +++++++++++++++++- 1 file changed, 188 insertions(+), 3 deletions(-) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs index 9b04f0fcc..ab1e2235c 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs @@ -5,6 +5,7 @@ using BrickController2.PlatformServices.BluetoothLE; using FluentAssertions; using Moq; +using System; using Xunit; using MouldKingVendor = BrickController2.DeviceManagement.MouldKing.MouldKing; @@ -13,8 +14,155 @@ namespace BrickController2.Tests.DeviceManagement.DI; public class VendorBuilderTests { - [Fact] - public void RegisterDevice_MK6_ReturnedDevice() + [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 mouldKingDeviceManager = new(); + mouldKingDeviceManager.Setup(x => x.GetAppId()).Returns(new byte[] { 0x01, 0x02 }); + builder.RegisterInstance(mouldKingDeviceManager.Object); + + 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 mouldKingDeviceManager = new(); + mouldKingDeviceManager.Setup(x => x.GetAppId()).Returns(new byte[] { 0x01, 0x02 }); + builder.RegisterInstance(mouldKingDeviceManager.Object); + + 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(); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + builder.RegisterInstance(Mock.Of()); + + Mock mouldKingDeviceManager = new(); + mouldKingDeviceManager.Setup(x => x.GetAppId()).Returns(new byte[] { 0x01, 0x02 }); + builder.RegisterInstance(mouldKingDeviceManager.Object); + + 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 mouldKingDeviceManager = new(); + mouldKingDeviceManager.Setup(x => x.GetAppId()).Returns(new byte[] { 0x01, 0x02 }); + builder.RegisterInstance(mouldKingDeviceManager.Object); + + 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(); @@ -35,7 +183,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, @@ -46,4 +193,42 @@ 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 mouldKingDeviceManager = new(); + mouldKingDeviceManager.Setup(x => x.GetAppId()).Returns(new byte[] { 0x01, 0x02 }); + builder.RegisterInstance(mouldKingDeviceManager.Object); + + 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)); + } } From f4a127db6a78b359fe00e9380520783eeb855949 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 11 Jun 2026 13:28:57 +0200 Subject: [PATCH 3/9] added JieStar SCM4 and JieStar SCM8 to VendorBuilderTests --- .../DeviceManagement/DI/VendorBuilderTests.cs | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs index ab1e2235c..bf585c7a9 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/DI/VendorBuilderTests.cs @@ -9,11 +9,161 @@ using Xunit; using MouldKingVendor = BrickController2.DeviceManagement.MouldKing.MouldKing; +using JieStarVendor = BrickController2.DeviceManagement.JieStar.JieStar; +using BrickController2.DeviceManagement.JieStar; namespace BrickController2.Tests.DeviceManagement.DI; public class VendorBuilderTests { + [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 jieStarDeviceManager = new(); + jieStarDeviceManager.Setup(x => x.GetAppId()).Returns(new byte[] { 0x01, 0x02 }); + builder.RegisterInstance(jieStarDeviceManager.Object); + + 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 jieStarDeviceManager = new(); + jieStarDeviceManager.Setup(x => x.GetAppId()).Returns(new byte[] { 0x01, 0x02 }); + builder.RegisterInstance(jieStarDeviceManager.Object); + + 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 jieStarDeviceManager = new(); + jieStarDeviceManager.Setup(x => x.GetAppId()).Returns(new byte[] { 0x01, 0x02 }); + builder.RegisterInstance(jieStarDeviceManager.Object); + + 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 jieStarDeviceManager = new(); + jieStarDeviceManager.Setup(x => x.GetAppId()).Returns(new byte[] { 0x01, 0x02 }); + builder.RegisterInstance(jieStarDeviceManager.Object); + + 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 From 512b31e6669f55b7ddbee404d0a42c7c11e7bc6c Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 11 Jun 2026 13:40:28 +0200 Subject: [PATCH 4/9] Bugfix: create new AppIdentifier if the requested length is *bigger * - not smaller --- .../UI/Services/AppIdentifier/AppIdentifierService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs b/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs index fd5ac3545..41e2956e7 100644 --- a/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs +++ b/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs @@ -26,11 +26,11 @@ public AppIdentifierService(IPreferencesService preferencesService) public ReadOnlyMemory GetAppId(int length) { - if (length < _appIdentifier.Length) + if (length > _appIdentifier.Length) { - // if the requested length is smaller than the current AppIdentifier, create a new one with the requested 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, MIN_APPID_LENGTH); + _appIdentifier = GetAppIdentifier(_preferencesService, length); } return _appIdentifier[..length]; From ce69d656eb9ff27a66dcd96744ea26efbdcd2166 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 11 Jun 2026 13:44:31 +0200 Subject: [PATCH 5/9] lock access to _appIdentifier to be threadsafe --- .../AppIdentifier/AppIdentifierService.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs b/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs index 41e2956e7..007d057a0 100644 --- a/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs +++ b/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs @@ -9,6 +9,7 @@ public class AppIdentifierService : IAppIdentifierService 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. @@ -26,14 +27,17 @@ public AppIdentifierService(IPreferencesService preferencesService) public ReadOnlyMemory GetAppId(int length) { - if (length > _appIdentifier.Length) + lock (_lock) { - // 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); - } + 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]; + return _appIdentifier[..length]; + } } /// From 502fdfe3125550b12124e490834b9358f7b7c356 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 11 Jun 2026 13:49:34 +0200 Subject: [PATCH 6/9] new UnitTest MouldKingDeviceManagerTests.AppId_TwoBytesInPreferences_AllBytes --- .../MouldKing/MouldKingDeviceManagerTests.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDeviceManagerTests.cs b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDeviceManagerTests.cs index 029e493fb..c590aeef2 100644 --- a/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDeviceManagerTests.cs +++ b/BrickController2/BrickController2.Tests/DeviceManagement/MouldKing/MouldKingDeviceManagerTests.cs @@ -59,4 +59,13 @@ public void TryGetDevice_WrongManufacturerId_ReturnsFalse() 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 + } } From 5a9e67a3e5a4984872a185ac89bd137bc03d1788 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Thu, 11 Jun 2026 13:50:01 +0200 Subject: [PATCH 7/9] new UnitTest JieStarDeviceManagerTests.AppId_TwoBytesInPreferences_AllBytes --- .../JieStar/JieStarDeviceManagerTests.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 BrickController2/BrickController2.Tests/DeviceManagement/JieStar/JieStarDeviceManagerTests.cs 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 + } +} From 3bb8d6655c8c07bf59a9839b57ea0688b839a886 Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Sat, 13 Jun 2026 05:39:53 +0200 Subject: [PATCH 8/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../UI/Services/AppIdentifier/AppIdentifierService.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs b/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs index 007d057a0..449d81732 100644 --- a/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs +++ b/BrickController2/BrickController2/UI/Services/AppIdentifier/AppIdentifierService.cs @@ -27,6 +27,11 @@ public AppIdentifierService(IPreferencesService preferencesService) public ReadOnlyMemory GetAppId(int length) { + if (length <= 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + lock (_lock) { if (length > _appIdentifier.Length) From afe721f4f410b95bc6e200d1b059b914db84931d Mon Sep 17 00:00:00 2001 From: J0EK3R Date: Sat, 13 Jun 2026 05:52:38 +0200 Subject: [PATCH 9/9] Clarify interface summary for MouldKing device manager --- .../DeviceManagement/MouldKing/IMouldKingDeviceManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BrickController2/BrickController2/DeviceManagement/MouldKing/IMouldKingDeviceManager.cs b/BrickController2/BrickController2/DeviceManagement/MouldKing/IMouldKingDeviceManager.cs index a1a2bf47f..584e7bd1c 100644 --- a/BrickController2/BrickController2/DeviceManagement/MouldKing/IMouldKingDeviceManager.cs +++ b/BrickController2/BrickController2/DeviceManagement/MouldKing/IMouldKingDeviceManager.cs @@ -3,7 +3,7 @@ namespace BrickController2.DeviceManagement.MouldKing; /// -/// Interface definition for MouldKing specific PlatformService +/// Interface definition for MouldKing-specific device manager functionality. /// public interface IMouldKingDeviceManager {