Skip to content

Commit ede1e4a

Browse files
committed
Improve secure inclusion stability
1 parent 61026ae commit ede1e4a

File tree

9 files changed

+331
-172
lines changed

9 files changed

+331
-172
lines changed

TestConsole/TestConsole.cs

Lines changed: 116 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
using Serilog;
22
using ZWaveDotNet.Entities;
33
using System.Reflection;
4+
using ZWaveDotNet.CommandClasses;
5+
using ZWaveDotNet.Enums;
46
using ZWaveDotNet.Entities.Enums;
7+
using System.Xml.Linq;
58

69
namespace ExampleConsole
710
{
811
public class TestConsole
912
{
1013
private static readonly string? Version = Assembly.GetAssembly(typeof(Controller))!.GetName().Version?.ToString(3);
1114
private static Controller? controller;
12-
private static HashSet<ushort> InterviewList = new HashSet<ushort>();
13-
private static HashSet<ushort> ReadyList = new HashSet<ushort>();
15+
private static readonly HashSet<ushort> InterviewList = new HashSet<ushort>();
16+
private static readonly HashSet<ushort> ReadyList = new HashSet<ushort>();
1417
private static RFRegion region = RFRegion.Unknown;
18+
private static LinkedList<string> Reports = new LinkedList<string>();
19+
private enum Mode { Display, Inclusion, Exclusion};
20+
private static Mode currentMode = Mode.Display;
1521

16-
static async Task Main()
22+
static async Task Main(string[] args)
1723
{
1824
Log.Logger = new LoggerConfiguration().WriteTo.File("console.log").CreateLogger();
1925

@@ -30,6 +36,8 @@ static async Task Main()
3036
//Add event listeners before starting
3137
controller.NodeInfoUpdated += Controller_NodeInfoUpdated;
3238
controller.NodeReady += Controller_NodeReady;
39+
controller.NodeExcluded += Controller_NodeExcluded;
40+
controller.InclusionStopped += Controller_InclusionStopped;
3341

3442
//Start the controller interview
3543
Console.WriteLine("Interviewing Controller...");
@@ -42,15 +50,86 @@ static async Task Main()
4250
if (File.Exists("nodecache.db"))
4351
await controller.ImportNodeDBAsync("nodecache.db");
4452

45-
await MainLoop();
53+
_ = Task.Factory.StartNew(MainLoop);
54+
await InputLoop();
55+
}
56+
57+
private static void Controller_InclusionStopped(object? sender, EventArgs e)
58+
{
59+
currentMode = Mode.Display;
60+
}
61+
62+
private static void Controller_NodeExcluded(object? sender, EventArgs e)
63+
{
64+
Node node = (Node)sender!;
65+
InterviewList.Remove(node.ID);
66+
ReadyList.Remove(node.ID);
67+
currentMode = Mode.Display;
68+
}
69+
70+
private static async Task InputLoop()
71+
{
72+
while (true)
73+
{
74+
ConsoleKeyInfo key = Console.ReadKey();
75+
if (key.Key == ConsoleKey.E)
76+
{
77+
currentMode = Mode.Exclusion;
78+
await controller!.StartExclusion();
79+
PrintMain();
80+
}
81+
else if (key.Key == ConsoleKey.I)
82+
{
83+
currentMode = Mode.Inclusion;
84+
await controller!.StartInclusion(InclusionStrategy.PreferS2, 12345);
85+
PrintMain();
86+
}
87+
else if (key.Key == ConsoleKey.S)
88+
{
89+
if (currentMode == Mode.Exclusion)
90+
await controller!.StopExclusion();
91+
else
92+
await controller!.StopInclusion();
93+
currentMode = Mode.Display;
94+
PrintMain();
95+
}
96+
}
4697
}
4798

4899
private static async void Controller_NodeReady(object? sender, EventArgs e)
49100
{
50-
ushort id = ((Node)sender!).ID;
51-
ReadyList.Add(id);
52-
InterviewList.Add(id);
101+
Node node = (Node)sender!;
102+
InterviewList.Add(node.ID);
103+
ReadyList.Add(node.ID);
53104
await controller!.ExportNodeDBAsync("nodecache.db");
105+
AttachListeners(node);
106+
}
107+
108+
private static void AttachListeners(Node node)
109+
{
110+
if (node.HasCommandClass(CommandClass.SensorMultiLevel))
111+
node.GetCommandClass<SensorMultiLevel>()!.Updated += Node_Updated;
112+
if (node.HasCommandClass(CommandClass.Meter))
113+
node.GetCommandClass<Meter>()!.Updated += Node_Updated;
114+
if (node.HasCommandClass(CommandClass.Notification) && node.GetCommandClass<Notification>() is Notification not) //ZWave Weirdness
115+
not.Updated += Node_Updated;
116+
if (node.HasCommandClass(CommandClass.Battery))
117+
node.GetCommandClass<Battery>()!.Status += Node_Updated;
118+
if (node.HasCommandClass(CommandClass.SensorBinary))
119+
node.GetCommandClass<SensorBinary>()!.Updated += Node_Updated;
120+
if (node.HasCommandClass(CommandClass.SensorAlarm))
121+
node.GetCommandClass<SensorAlarm>()!.Alarm += Node_Updated;
122+
if (node.HasCommandClass(CommandClass.SwitchBinary))
123+
node.GetCommandClass<SwitchBinary>()!.SwitchReport += Node_Updated;
124+
}
125+
126+
private static async Task Node_Updated(Node sender, CommandClassEventArgs args)
127+
{
128+
if (args.Report == null)
129+
return;
130+
if (Reports.Count > 10)
131+
Reports.RemoveFirst();
132+
Reports.AddLast($"{DateTime.Now.ToLongTimeString()} Node {sender.ID}: {args.Report.ToString()!}");
54133
}
55134

56135
private static async void Controller_NodeInfoUpdated(object? sender, ApplicationUpdateEventArgs e)
@@ -59,20 +138,17 @@ private static async void Controller_NodeInfoUpdated(object? sender, Application
59138
if (node != null && !InterviewList.Contains(node.ID))
60139
{
61140
InterviewList.Add(node.ID);
62-
CancellationTokenSource cts = new CancellationTokenSource(180000);
63-
await Task.Factory.StartNew(async() => {
64-
try
65-
{
66-
await node.Interview(cts.Token);
67-
ReadyList.Add(node.ID);
68-
await controller!.ExportNodeDBAsync("nodecache.db");
69-
}
70-
catch(Exception ex)
71-
{
72-
Log.Error(ex, "Uncaught Exception in Node Interview");
73-
}
74-
});
75-
}
141+
node.InterviewComplete += Node_InterviewComplete;
142+
_ = Task.Run(() => node.Interview());
143+
}
144+
}
145+
146+
private static async void Node_InterviewComplete(object? sender, EventArgs e)
147+
{
148+
Node node = (Node)sender!;
149+
ReadyList.Add(node.ID);
150+
await controller!.ExportNodeDBAsync("nodecache.db");
151+
AttachListeners(node);
76152
}
77153

78154
private static async Task MainLoop()
@@ -87,7 +163,7 @@ private static async Task MainLoop()
87163
private static void PrintMain()
88164
{
89165
Console.Clear();
90-
Console.Write($"ZWaveDotNet v{Version} - Controller {controller!.ControllerID} {(controller!.IsConnected ? "Connected" : "Disconnected")}");
166+
Console.Write($"ZWaveDotNet v{Version} - Controller #{controller!.ControllerID} {(controller!.IsConnected ? "Connected" : "Disconnected")}");
91167
Console.Write($" - v{controller.APIVersion.Major} ({region})");
92168
Console.Write($"{(controller!.SupportsLongRange ? " [LR]" : "")}");
93169
Console.Write($"{(controller!.Primary ? " [Primary]" : "")}");
@@ -97,6 +173,24 @@ private static void PrintMain()
97173
Console.WriteLine($"{controller.Nodes.Count} Nodes Found:");
98174
foreach (Node n in controller.Nodes.Values)
99175
Console.WriteLine(n.ToString());
176+
Console.WriteLine();
177+
if (currentMode == Mode.Display)
178+
{
179+
Console.WriteLine("Press I to enter Inclusion mode, E to enter Exclusion mode or S to Stop");
180+
Console.WriteLine("Last 10 Node Reports:");
181+
foreach (string report in Reports)
182+
Console.WriteLine(report);
183+
}
184+
else if (currentMode == Mode.Inclusion)
185+
{
186+
Console.WriteLine("- Inclusion Mode Active (Default PIN 12345) -");
187+
Console.WriteLine("Press the Pairing button on your device");
188+
}
189+
else
190+
{
191+
Console.WriteLine("- Exclusion Mode Active -");
192+
Console.WriteLine("Press the Pairing button on your device");
193+
}
100194
}
101195
}
102196
}

ZWaveDotNet/CommandClasses/CommandClassBase.cs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,13 @@ public static CommandClassBase Create(CommandClass cc, Node node, byte endpoint,
180180

181181
protected async Task SendCommand(Enum command, CancellationToken token, params byte[] payload)
182182
{
183-
await SendCommand(command, token, false, payload);
183+
await SendCommand(command, token, false, payload).ConfigureAwait(false);
184184
}
185185

186186
protected async Task SendCommand(Enum command, CancellationToken token, bool supervised = false, params byte[] payload)
187187
{
188188
CommandMessage data = new CommandMessage(controller, node.ID, endpoint, commandClass, Convert.ToByte(command), supervised, payload);
189-
await SendCommand(data, token);
189+
await SendCommand(data, token).ConfigureAwait(false);
190190
}
191191

192192
protected async Task SendCommand(CommandMessage data, CancellationToken token)
@@ -199,27 +199,27 @@ protected async Task SendCommand(CommandMessage data, CancellationToken token)
199199
if (key == null)
200200
throw new InvalidOperationException($"Command classes are secure but no keys exist for node {node.ID}");
201201
if (key.Key == SecurityManager.RecordType.S0)
202-
await node.GetCommandClass<Security0>()!.Encapsulate(data.Payload, token);
202+
await node.GetCommandClass<Security0>()!.Encapsulate(data.Payload, token).ConfigureAwait(false);
203203
else if (key.Key > SecurityManager.RecordType.S0)
204-
await node.GetCommandClass<Security2>()!.Encapsulate(data.Payload, key.Key, token);
204+
await node.GetCommandClass<Security2>()!.Encapsulate(data.Payload, key.Key, token).ConfigureAwait(false);
205205
else
206206
throw new InvalidOperationException("Security required but no keys are available");
207207
}
208208

209209
DataMessage message = data.ToMessage();
210210
for (int i = 0; i < 3; i++)
211211
{
212-
if (await AttemptTransmission(message, token, (i == 2)) == false)
212+
if (await AttemptTransmission(message, token, i == 2).ConfigureAwait(false) == false)
213213
{
214-
Log.Error($"Transmission Failure: Retrying [Attempt {i+1}]...");
215-
await Task.Delay(100 + (1000 * i), token);
214+
Log.Error($"Controller Failed to Send Message: Retrying [Attempt {i+1}]...");
215+
await Task.Delay(100 + (1000 * i), token).ConfigureAwait(false);
216216
}
217217
}
218218
}
219219

220220
private async Task<bool> AttemptTransmission(DataMessage message, CancellationToken cancellationToken, bool ex = false)
221221
{
222-
DataCallback dc = await controller.Flow.SendAcknowledgedResponseCallback(message, cancellationToken);
222+
DataCallback dc = await controller.Flow.SendAcknowledgedResponseCallback(message, cancellationToken).ConfigureAwait(false);
223223
if (dc.Status != TransmissionStatus.CompleteOk && dc.Status != TransmissionStatus.CompleteNoAck && dc.Status != TransmissionStatus.CompleteVerified)
224224
{
225225
if (!ex)
@@ -241,7 +241,7 @@ public virtual Task Interview(CancellationToken cancellationToken)
241241

242242
protected async Task<ReportMessage> SendReceive(Enum command, Enum response, CancellationToken token, params byte[] payload)
243243
{
244-
return await SendReceive(command, response, token, false, payload);
244+
return await SendReceive(command, response, token, false, payload).ConfigureAwait(false);
245245
}
246246

247247
protected async Task<ReportMessage> SendReceive(Enum command, Enum response, CancellationToken token, bool supervised = false, params byte[] payload)
@@ -258,9 +258,7 @@ protected async Task<ReportMessage> SendReceive(Enum command, Enum response, Can
258258
src
259259
};
260260
if (!callbacks.TryAdd(cmd, newCallbacks))
261-
{
262261
callbacks[cmd].Add(src);
263-
}
264262
}
265263
await SendCommand(command, token, supervised, payload);
266264
return await src.Task;

ZWaveDotNet/CommandClasses/Security0.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ public async Task<SupportedCommands> CommandsSupportedGet(CancellationToken canc
4040
internal async Task SchemeGet(CancellationToken cancellationToken = default)
4141
{
4242
Log.Debug("Requesting Scheme");
43-
await SendCommand(Security0Command.SchemeGet, cancellationToken, (byte)0x0);
43+
await SendCommand(Security0Command.SchemeGet, cancellationToken, (byte)0x0).ConfigureAwait(false);
4444
}
4545

4646
internal async Task KeySet(CancellationToken cancellationToken = default)
4747
{
4848
Log.Information($"Setting Network Key on {node.ID}");
4949
CommandMessage data = new CommandMessage(controller, node.ID, endpoint, commandClass, (byte)Security0Command.NetworkKeySet, false, controller.NetworkKeyS0);
50-
await TransmitTemp(data.Payload, cancellationToken);
50+
await TransmitTemp(data.Payload, cancellationToken).ConfigureAwait(false);
5151
}
5252

5353
protected async Task<ReportMessage> GetNonce(CancellationToken cancellationToken)
@@ -67,7 +67,7 @@ public async Task TransmitTemp(List<byte> payload, CancellationToken cancellatio
6767
using (CancellationTokenSource timeout = new CancellationTokenSource(10000))
6868
{
6969
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken);
70-
report = await GetNonce(cts.Token);
70+
report = await GetNonce(cts.Token).ConfigureAwait(false);
7171
if (report.IsMulticastMethod())
7272
return; //This should never happen
7373
}
@@ -86,12 +86,12 @@ public async Task TransmitTemp(List<byte> payload, CancellationToken cancellatio
8686
securePayload[8 + encrypted.Length] = receiversNonce[0];
8787
Array.Copy(mac, 0, securePayload, 9 + encrypted.Length, 8);
8888

89-
await SendCommand(Security0Command.MessageEncap, cancellationToken, securePayload);
89+
await SendCommand(Security0Command.MessageEncap, cancellationToken, securePayload).ConfigureAwait(false);
9090
}
9191

9292
public async Task Encapsulate(List<byte> payload, CancellationToken cancellationToken)
9393
{
94-
ReportMessage report = await GetNonce(cancellationToken);
94+
ReportMessage report = await GetNonce(cancellationToken).ConfigureAwait(false);
9595
if (report.IsMulticastMethod())
9696
return; //This should never happen
9797

@@ -149,7 +149,7 @@ public async Task Encapsulate(List<byte> payload, CancellationToken cancellation
149149
{
150150
Log.Information("Providing Next Nonce");
151151
using CancellationTokenSource cts = new CancellationTokenSource(3000);
152-
await controller.Nodes[msg.SourceNodeID].GetCommandClass<Security0>()!.SendCommand(Security0Command.NonceReport, cts.Token, controller.SecurityManager.CreateS0Nonce(msg.SourceNodeID));
152+
await controller.Nodes[msg.SourceNodeID].GetCommandClass<Security0>()!.SendCommand(Security0Command.NonceReport, cts.Token, controller.SecurityManager.CreateS0Nonce(msg.SourceNodeID)).ConfigureAwait(false);
153153
}
154154

155155
Log.Information("Decrypted: " + msg.ToString());
@@ -179,7 +179,7 @@ protected override async Task<SupervisionStatus> Handle(ReportMessage message)
179179
if (message.IsMulticastMethod())
180180
return SupervisionStatus.Fail;
181181

182-
await SendCommand(Security0Command.NonceReport, CancellationToken.None, controller.SecurityManager.CreateS0Nonce(node.ID));
182+
await SendCommand(Security0Command.NonceReport, CancellationToken.None, controller.SecurityManager.CreateS0Nonce(node.ID)).ConfigureAwait(false);
183183
return SupervisionStatus.Success;
184184
}
185185
return SupervisionStatus.NoSupport;

0 commit comments

Comments
 (0)