Skip to content

Commit

Permalink
Merge pull request #916 from dorssel/rules
Browse files Browse the repository at this point in the history
Add policy rules
  • Loading branch information
dorssel committed Apr 17, 2024
2 parents 46c7f5c + 42415ad commit fc51ba5
Show file tree
Hide file tree
Showing 19 changed files with 866 additions and 33 deletions.
4 changes: 4 additions & 0 deletions Installer/Server.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ SPDX-License-Identifier: GPL-3.0-only
Key="Devices"
ForceCreateOnInstall="yes"
/>
<RegistryKey
Key="Policy"
ForceCreateOnInstall="yes"
/>
</RegistryKey>
<Environment
Id="PATH"
Expand Down
28 changes: 14 additions & 14 deletions UnitTests/BusId_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ sealed class BusIdData
"0-0",
"0-1",
"1-0",
"1-65536",
"65536-1",
"1-100",
"100-1",
"01-1",
"1-01",
];
Expand All @@ -89,20 +89,20 @@ public static IEnumerable<string[]> Invalid
"IncompatibleHub",
"1-1",
"1-2",
"1-65534",
"1-65535",
"1-98",
"1-99",
"2-1",
"2-2",
"2-65534",
"2-65535",
"65534-1",
"65534-2",
"65534-65534",
"65534-65535",
"65535-1",
"65535-2",
"65535-65534",
"65535-65535",
"2-98",
"2-99",
"98-1",
"98-2",
"98-98",
"98-99",
"99-1",
"99-2",
"99-98",
"99-99",
];

public static IEnumerable<string[]> Valid => from value in _Valid select new string[] { value };
Expand Down
136 changes: 136 additions & 0 deletions UnitTests/Parse_policy_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
//
// SPDX-License-Identifier: GPL-3.0-only

using System.CommandLine;
using Usbipd.Automation;

namespace UnitTests;

[TestClass]
sealed class Parse_policy_Tests
: ParseTestBase
{
static readonly BusId TestBusId = BusId.Parse("3-42");
static readonly Guid TestGuid = Guid.Parse("{E863A2AF-AE47-440B-A32B-FAB1C03017AB}");
static readonly VidPid TestHardwareId = VidPid.Parse("0123:cdef");

[TestMethod]
public void Help()
{
Test(ExitCode.Success, "policy", "--help");
}

[TestMethod]
public void CommandMissing()
{
Test(ExitCode.ParseError, "policy");
}

[TestMethod]
public void CommandInvalid()
{
Test(ExitCode.ParseError, "policy", "invalid-command");
}

[TestMethod]
public void OptionInvalid()
{
Test(ExitCode.ParseError, "policy", "--invalid-option");
}

[TestMethod]
public void list_Success()
{
var mock = CreateMock();
mock.Setup(m => m.PolicyList(It.IsNotNull<IConsole>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(ExitCode.Success));

Test(ExitCode.Success, mock, "policy", "list");
}

[TestMethod]
public void list_Help()
{
Test(ExitCode.Success, "policy", "list", "--help");
}

[TestMethod]
public void list_OptionInvalid()
{
Test(ExitCode.ParseError, "policy", "list", "--invalid-option");
}

[TestMethod]
public void list_StrayArgument()
{
Test(ExitCode.ParseError, "policy", "list", "stray-argument");
}

[TestMethod]
public void add_BusIdSuccess()
{
var mock = CreateMock();
mock.Setup(m => m.PolicyAdd(It.IsAny<PolicyRule>(),
It.IsNotNull<IConsole>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(ExitCode.Success));

Test(ExitCode.Success, mock, "policy", "add", "--effect", "Allow", "--operation", "AutoBind", "--busid", TestBusId.ToString());
}

[TestMethod]
public void add_HardwareIdSuccess()
{
var mock = CreateMock();
mock.Setup(m => m.PolicyAdd(It.IsAny<PolicyRule>(),
It.IsNotNull<IConsole>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(ExitCode.Success));

Test(ExitCode.Success, mock, "policy", "add", "--effect", "Allow", "--operation", "AutoBind", "--hardware-id", TestHardwareId.ToString());
}

[TestMethod]
public void add_BothSuccess()
{
var mock = CreateMock();
mock.Setup(m => m.PolicyAdd(It.IsAny<PolicyRule>(),
It.IsNotNull<IConsole>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(ExitCode.Success));

Test(ExitCode.Success, mock, "policy", "add", "--effect", "Allow", "--operation", "AutoBind",
"--busid", TestBusId.ToString(), "--hardware-id", TestHardwareId.ToString());
}

[TestMethod]
public void add_MissingCondition()
{
Test(ExitCode.ParseError, "policy", "add", "--effect", "Allow", "--operation", "AutoBind", TestHardwareId.ToString());
}

[TestMethod]
public void remove_GuidSuccess()
{
var mock = CreateMock();
mock.Setup(m => m.PolicyRemove(It.IsAny<Guid>(),
It.IsNotNull<IConsole>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(ExitCode.Success));

Test(ExitCode.Success, mock, "policy", "remove", "--guid", TestGuid.ToString());
}

[TestMethod]
public void remove_AllSuccess()
{
var mock = CreateMock();
mock.Setup(m => m.PolicyRemoveAll(It.IsNotNull<IConsole>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(ExitCode.Success));

Test(ExitCode.Success, mock, "policy", "remove", "--all");
}

[TestMethod]
public void remove_MissingOption()
{
Test(ExitCode.ParseError, "policy", "remove");
}

[TestMethod]
public void remove_TooManyOptions()
{
Test(ExitCode.ParseError, "policy", "remove", "--all", "--guid", TestGuid.ToString());
}
}
4 changes: 2 additions & 2 deletions UnitTests/Parse_usbipd_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ sealed class Parse_usbipd_Tests
: ParseTestBase
{
[TestMethod]
public void Success()
public void NoCommand()
{
Test(ExitCode.Success);
Test(ExitCode.ParseError);
}

[TestMethod]
Expand Down
128 changes: 128 additions & 0 deletions UnitTests/Policy_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
//
// SPDX-License-Identifier: GPL-3.0-only

using Microsoft.Win32;
using Usbipd.Automation;

namespace UnitTests;

[TestClass]
sealed class Policy_Tests
{
static readonly BusId TestBusId = BusId.Parse("3-42");
static readonly VidPid TestHardwareId = VidPid.Parse("0123:cdef");
static readonly BusId OtherBusId = BusId.Parse("1-1");
static readonly VidPid OtherHardwareId = VidPid.Parse("4567:89ab");

static TempRegistry BaseTempRegistryKey = null!;

[ClassInitialize]
public static void ClassInitialize(TestContext testContext)
{
_ = testContext;

BaseTempRegistryKey = new TempRegistry(Registry.CurrentUser);
}

[ClassCleanup]
public static void ClassCleanup()
{
BaseTempRegistryKey.Dispose();
}

static TempRegistry CreateTempRegistry() => new(BaseTempRegistryKey.Key);

[TestMethod]
public void Constructor_Success()
{
_ = new PolicyRuleAutoBind(PolicyRuleEffect.Allow, TestBusId, TestHardwareId);
}

sealed class AutoBindData
{
static readonly PolicyRuleAutoBind[] _Valid = [
new(PolicyRuleEffect.Allow, TestBusId, null),
new(PolicyRuleEffect.Allow, null, TestHardwareId),
new(PolicyRuleEffect.Allow, TestBusId, TestHardwareId),
];

static readonly PolicyRuleAutoBind[] _Invalid = [
new(PolicyRuleEffect.Allow, null, null),
new(PolicyRuleEffect.Allow, BusId.IncompatibleHub, null),
new(PolicyRuleEffect.Allow, BusId.IncompatibleHub, TestHardwareId),
];

public static IEnumerable<PolicyRuleAutoBind[]> Valid
=> from value in _Valid select new PolicyRuleAutoBind[] { value };

public static IEnumerable<PolicyRuleAutoBind[]> Invalid
=> from value in _Invalid select new PolicyRuleAutoBind[] { value };
}

[TestMethod]
[DynamicData(nameof(AutoBindData.Valid), typeof(AutoBindData))]
public void Persist_AutoBind(PolicyRuleAutoBind rule)
{
using var tempRegistry = CreateTempRegistry();
rule.Save(tempRegistry.Key);

var verify = PolicyRuleAutoBind.Load(rule.Effect, tempRegistry.Key);

Assert.AreEqual(rule.BusId, verify.BusId);
Assert.AreEqual(rule.HardwareId, verify.HardwareId);
}

[TestMethod]
[DynamicData(nameof(AutoBindData.Valid), typeof(AutoBindData))]
public void Valid_AutoBind(PolicyRuleAutoBind rule)
{
Assert.IsTrue(rule.IsValid());
}

[TestMethod]
[DynamicData(nameof(AutoBindData.Invalid), typeof(AutoBindData))]
public void Invalid_AutoBind(PolicyRuleAutoBind rule)
{
Assert.IsFalse(rule.IsValid());
}

static UsbDevice CreateTestUsbDevice(BusId busId, VidPid hardwareId)
{
return new UsbDevice($"VID_{hardwareId.Vid:X04}&PID_{hardwareId.Pid:X04}", "Description", false, busId, null, null, null);
}

[TestMethod]
[DynamicData(nameof(AutoBindData.Invalid), typeof(AutoBindData))]
public void Match_Invalid_AutoBind(PolicyRuleAutoBind rule)
{
Assert.ThrowsException<InvalidOperationException>(() =>
{
rule.Matches(CreateTestUsbDevice(TestBusId, TestHardwareId));
});
}

[TestMethod]
[DynamicData(nameof(AutoBindData.Valid), typeof(AutoBindData))]
public void Match_Valid_AutoBind(PolicyRuleAutoBind rule)
{
Assert.IsTrue(rule.Matches(CreateTestUsbDevice(TestBusId, TestHardwareId)));
Assert.IsFalse(rule.Matches(CreateTestUsbDevice(OtherBusId, OtherHardwareId)));
if (rule.BusId is null)
{
Assert.IsTrue(rule.Matches(CreateTestUsbDevice(OtherBusId, TestHardwareId)));
}
else
{
Assert.IsFalse(rule.Matches(CreateTestUsbDevice(OtherBusId, TestHardwareId)));
}
if (rule.HardwareId is null)
{
Assert.IsTrue(rule.Matches(CreateTestUsbDevice(TestBusId, OtherHardwareId)));
}
else
{
Assert.IsFalse(rule.Matches(CreateTestUsbDevice(TestBusId, OtherHardwareId)));
}
}
}
26 changes: 26 additions & 0 deletions UnitTests/TempRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
//
// SPDX-License-Identifier: GPL-3.0-only

using Microsoft.Win32;

namespace UnitTests;

sealed class TempRegistry : IDisposable
{
public string Name { get; } = Guid.NewGuid().ToString("B");
public RegistryKey Key { get; }
public RegistryKey Parent { get; }

public TempRegistry(RegistryKey parent)
{
Parent = parent;
Key = Parent.CreateSubKey(Name, true, RegistryOptions.Volatile);
}

public void Dispose()
{
Key.Close();
Parent.DeleteSubKeyTree(Name, false);
}
}
10 changes: 6 additions & 4 deletions Usbipd.Automation/BusId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ public BusId(ushort bus, ushort port)
{
// Do not allow the explicit creation of the special IncompatibleHub value.
// Instead, use the static IncompatibleHub field (preferrable) or "default".
if (bus == 0)
// USB supports up to 127 devices, but that would require multiple hubs; the "per hub" port will never be >99.
// And if you have more than 99 hubs on one system, then you win a prize! (but we're not going to support it...)
if (bus == 0 || bus > 99)
{
throw new ArgumentOutOfRangeException(nameof(bus));
}
if (port == 0)
if (port == 0 || port > 99)
{
throw new ArgumentOutOfRangeException(nameof(port));
}
Expand Down Expand Up @@ -63,8 +65,8 @@ public static bool TryParse(string input, out BusId busId)
}
var match = Regex.Match(input, "^([1-9][0-9]*)-([1-9][0-9]*)$");
if (match.Success
&& ushort.TryParse(match.Groups[1].Value, out var bus)
&& ushort.TryParse(match.Groups[2].Value, out var port))
&& ushort.TryParse(match.Groups[1].Value, out var bus) && bus <= 99
&& ushort.TryParse(match.Groups[2].Value, out var port) && port <= 99)
{
busId = new(bus, port);
return true;
Expand Down
Loading

0 comments on commit fc51ba5

Please sign in to comment.