Skip to content

Commit 09ae0a3

Browse files
committed
Pair using PIN implemented in test console. Fix stability of rapid include/exclude
1 parent 8931885 commit 09ae0a3

5 files changed

Lines changed: 60 additions & 15 deletions

File tree

TestConsole/TestConsole.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class TestConsole
2828
private static readonly HashSet<ushort> ReadyList = new HashSet<ushort>();
2929
private static RFRegion region = RFRegion.Unknown;
3030
private static readonly LinkedList<string> Reports = new LinkedList<string>();
31-
private enum Mode { Display, Inclusion, Exclusion};
31+
private enum Mode { Display, Inclusion, Exclusion, UserInput};
3232
private static Mode currentMode = Mode.Display;
3333

3434
static async Task Main(string[] args)
@@ -88,8 +88,17 @@ private static async Task InputLoop()
8888
}
8989
else if (key.Key == ConsoleKey.I)
9090
{
91+
currentMode = Mode.UserInput;
92+
Console.Clear();
93+
Console.Write("Enter Inclusion Pin (or press enter to skip): ");
94+
string? pin = Console.ReadLine();
9195
currentMode = Mode.Inclusion;
92-
await controller!.StartInclusion(InclusionStrategy.PreferS2, 12345);
96+
if (pin == null)
97+
return;
98+
if (pin == string.Empty)
99+
await controller!.StartInclusion(InclusionStrategy.PreferS2);
100+
else
101+
await controller!.StartInclusion(InclusionStrategy.PreferS2, ushort.Parse(pin));
93102
PrintMain();
94103
}
95104
else if (key.Key == ConsoleKey.S)
@@ -170,6 +179,8 @@ private static async Task MainLoop()
170179

171180
private static void PrintMain()
172181
{
182+
if (currentMode == Mode.UserInput)
183+
return;
173184
Console.Clear();
174185
Console.Write($"ZWaveDotNet v{Version} - Controller #{controller!.ControllerID} {(controller!.IsConnected ? "Connected" : "Disconnected")}");
175186
Console.Write($" - v{controller.APIVersion.Major} ({region})");
@@ -191,7 +202,7 @@ private static void PrintMain()
191202
}
192203
else if (currentMode == Mode.Inclusion)
193204
{
194-
Console.WriteLine("- Inclusion Mode Active (Default PIN 12345) -");
205+
Console.WriteLine("- Inclusion Mode Active -");
195206
Console.WriteLine("Press the Pairing button on your device");
196207
}
197208
else

ZWaveDotNet/CommandClasses/Security2.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ public async Task Encapsulate(List<byte> payload, SecurityManager.RecordType? ty
266266
{
267267
using (CancellationTokenSource cts = new CancellationTokenSource(3000))
268268
await controller.Nodes[msg.SourceNodeID].GetCommandClass<Security2>()!.KexFail(KexFailType.KEX_FAIL_KEY_VERIFY).ConfigureAwait(false);
269+
if (networkKey.Key != SecurityManager.RecordType.ECDH_TEMP)
270+
controller.SecurityManager.RevokeKey(msg.SourceNodeID, networkKey.Key);
271+
return null;
269272
}
270273
else if (i == 2)
271274
{

ZWaveDotNet/Entities/Controller.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,8 @@ private async Task<bool> BootstrapS2(Node node)
558558
await sec2.KexFail(KexFailType.KEX_FAIL_KEX_SCHEME);
559559
return false;
560560
}
561+
if (this.pin == 0)
562+
requestedKeys.Keys = requestedKeys.Keys & SecurityKey.S2Unauthenticated; //We need a pin for higher levels
561563
SecurityManager!.StoreRequestedKeys(node.ID, requestedKeys);
562564
Log.Information("Sending " + requestedKeys.ToString());
563565
Memory<byte> pub;
@@ -585,6 +587,7 @@ private async Task<bool> BootstrapS2(Node node)
585587
SecurityManager?.RevokeKey(node.ID, SecurityManager.RecordType.S2Access);
586588
SecurityManager?.RevokeKey(node.ID, SecurityManager.RecordType.S2Auth);
587589
SecurityManager?.RevokeKey(node.ID, SecurityManager.RecordType.S2UnAuth);
590+
SecurityManager?.RevokeKey(node.ID, SecurityManager.RecordType.ECDH_TEMP);
588591
return false;
589592
}
590593
await node.Interview(true).ConfigureAwait(false);
@@ -670,24 +673,18 @@ private async Task EventLoop()
670673
{
671674
Log.Information("Added " + node.ToString());
672675
if (SecurityManager != null)
673-
{
674-
if ((currentStrategy == InclusionStrategy.S2Only || currentStrategy == InclusionStrategy.PreferS2) && node.HasCommandClass(CommandClass.Security2))
675-
_ = Task.Run(() => BootstrapS2(node));
676-
else if ((currentStrategy == InclusionStrategy.PreferS2 || currentStrategy == InclusionStrategy.LegacyS0Only) && node.HasCommandClass(CommandClass.Security0))
677-
_ = Task.Run(() => BootstrapS0(node));
678-
else
679-
_ = Task.Run(() => BootstrapUnsecure(node));
680-
}
676+
await Task.Factory.StartNew(() => ExecuteStrategy(node)).ConfigureAwait(false);
681677
}
682678
}
683679
}
684680
else if (inc.Function == Function.RemoveNodeFromNetwork && inc.NodeID > 0)
685681
{
686682
if (Nodes.Remove(inc.NodeID, out Node? node))
687683
{
684+
node.NodeFailed = true;
688685
if (NodeExcluded != null)
689686
NodeExcluded.Invoke(node, EventArgs.Empty);
690-
Log.Information($"Successfully exluded node {inc.NodeID}");
687+
Log.Information($"Successfully excluded node {inc.NodeID}");
691688
}
692689
if (inc.Status == InclusionExclusionStatus.OperationComplete)
693690
await StopExclusion();
@@ -701,6 +698,23 @@ private async Task EventLoop()
701698
}
702699
}
703700

701+
private async Task ExecuteStrategy(Node node)
702+
{
703+
if ((currentStrategy == InclusionStrategy.S2Only || currentStrategy == InclusionStrategy.AnySecure || currentStrategy == InclusionStrategy.PreferS2) && node.HasCommandClass(CommandClass.Security2))
704+
{
705+
if (await BootstrapS2(node) || currentStrategy == InclusionStrategy.S2Only)
706+
return; //Successful S2 or abort if failed with S2 only strategy
707+
if ((node.HasCommandClass(CommandClass.Security0) && await BootstrapS0(node)) || currentStrategy == InclusionStrategy.AnySecure)
708+
return; //Successful S0 or abort if secure required
709+
}
710+
else if ((currentStrategy == InclusionStrategy.PreferS2 || currentStrategy == InclusionStrategy.AnySecure || currentStrategy == InclusionStrategy.LegacyS0Only) && node.HasCommandClass(CommandClass.Security0))
711+
{
712+
if (await BootstrapS0(node) || currentStrategy == InclusionStrategy.LegacyS0Only || currentStrategy == InclusionStrategy.AnySecure)
713+
return; //Successful S0 or abort if failed with S0 only or any secure strategy
714+
}
715+
await BootstrapUnsecure(node);
716+
}
717+
704718
private byte[] NodeIDToBytes(ushort nodeId)
705719
{
706720
if (WideID)

ZWaveDotNet/Entities/Enums/InclusionStrategy.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,9 @@ public enum InclusionStrategy
3030
/// Only S2 security will be attempted
3131
/// </summary>
3232
S2Only = 0x3,
33+
/// <summary>
34+
/// Prefer S2 first, fallback to S0. Do not attempt insecure inclusion
35+
/// </summary>
36+
AnySecure = 0x4
3337
}
3438
}

ZWaveDotNet/Entities/Node.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,11 @@ await Task.Run(async () =>
307307
while (!commandClasses.ContainsKey(CommandClass.WakeUp))
308308
await Task.Delay(3000).ConfigureAwait(false); //TODO - Improve this
309309
await ((WakeUp)commandClasses[CommandClass.WakeUp]).WaitForAwake().ConfigureAwait(false);
310-
using (CancellationTokenSource cts = new CancellationTokenSource(90000))
311-
await Interview(newlyIncluded, key, cts.Token).ConfigureAwait(false);
310+
using (CancellationTokenSource timeout = new CancellationTokenSource(90000))
311+
{
312+
using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeout.Token))
313+
await Interview(newlyIncluded, key, cts.Token).ConfigureAwait(false);
314+
}
312315
await ((WakeUp)commandClasses[CommandClass.WakeUp]).NoMoreInformation().ConfigureAwait(false);
313316
}
314317
catch(Exception ex)
@@ -320,7 +323,7 @@ await Task.Run(async () =>
320323

321324
private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key, CancellationToken cancellationToken)
322325
{
323-
if (controller.SecurityManager != null)
326+
if (controller.SecurityManager != null && !failed)
324327
{
325328
if (!newlyIncluded && key == null)
326329
{
@@ -333,6 +336,8 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key
333336
{
334337
for (int i = 0; i < 3; i++)
335338
{
339+
if (failed)
340+
return;
336341
try
337342
{
338343
using (CancellationTokenSource timeout = new CancellationTokenSource(5000))
@@ -363,6 +368,8 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key
363368
{
364369
for (int i = 0; i < 3; i++)
365370
{
371+
if (failed)
372+
return;
366373
try
367374
{
368375
using (CancellationTokenSource timeout = new CancellationTokenSource(5000))
@@ -390,6 +397,8 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key
390397
{
391398
for (int i = 0; i < 3; i++)
392399
{
400+
if (failed)
401+
return;
393402
try
394403
{
395404
using (CancellationTokenSource timeout = new CancellationTokenSource(5000))
@@ -417,6 +426,8 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key
417426
{
418427
for (int i = 0; i < 3; i++)
419428
{
429+
if (failed)
430+
return;
420431
try
421432
{
422433
using (CancellationTokenSource timeout = new CancellationTokenSource(5000))
@@ -464,6 +475,8 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key
464475
CommandClasses.Version version = (CommandClasses.Version)commandClasses[CommandClass.Version];
465476
foreach (CommandClassBase cc in commandClasses.Values)
466477
{
478+
if (failed)
479+
return;
467480
CCVersion? ccVersion = (CCVersion?)cc.GetType().GetCustomAttribute(typeof(CCVersion));
468481
if ((ccVersion == null || ccVersion.maxVersion > 1) && (cc.CommandClass >= CommandClass.Basic))
469482
{

0 commit comments

Comments
 (0)