diff --git a/TestConsole/TestConsole.cs b/TestConsole/TestConsole.cs index cd5a7ee..944d3d8 100644 --- a/TestConsole/TestConsole.cs +++ b/TestConsole/TestConsole.cs @@ -1,7 +1,10 @@ using Serilog; using ZWaveDotNet.Entities; using System.Reflection; +using ZWaveDotNet.CommandClasses; +using ZWaveDotNet.Enums; using ZWaveDotNet.Entities.Enums; +using System.Xml.Linq; namespace ExampleConsole { @@ -9,11 +12,14 @@ public class TestConsole { private static readonly string? Version = Assembly.GetAssembly(typeof(Controller))!.GetName().Version?.ToString(3); private static Controller? controller; - private static HashSet InterviewList = new HashSet(); - private static HashSet ReadyList = new HashSet(); + private static readonly HashSet InterviewList = new HashSet(); + private static readonly HashSet ReadyList = new HashSet(); private static RFRegion region = RFRegion.Unknown; + private static LinkedList Reports = new LinkedList(); + private enum Mode { Display, Inclusion, Exclusion}; + private static Mode currentMode = Mode.Display; - static async Task Main() + static async Task Main(string[] args) { Log.Logger = new LoggerConfiguration().WriteTo.File("console.log").CreateLogger(); @@ -30,6 +36,8 @@ static async Task Main() //Add event listeners before starting controller.NodeInfoUpdated += Controller_NodeInfoUpdated; controller.NodeReady += Controller_NodeReady; + controller.NodeExcluded += Controller_NodeExcluded; + controller.InclusionStopped += Controller_InclusionStopped; //Start the controller interview Console.WriteLine("Interviewing Controller..."); @@ -42,15 +50,86 @@ static async Task Main() if (File.Exists("nodecache.db")) await controller.ImportNodeDBAsync("nodecache.db"); - await MainLoop(); + _ = Task.Factory.StartNew(MainLoop); + await InputLoop(); + } + + private static void Controller_InclusionStopped(object? sender, EventArgs e) + { + currentMode = Mode.Display; + } + + private static void Controller_NodeExcluded(object? sender, EventArgs e) + { + Node node = (Node)sender!; + InterviewList.Remove(node.ID); + ReadyList.Remove(node.ID); + currentMode = Mode.Display; + } + + private static async Task InputLoop() + { + while (true) + { + ConsoleKeyInfo key = Console.ReadKey(); + if (key.Key == ConsoleKey.E) + { + currentMode = Mode.Exclusion; + await controller!.StartExclusion(); + PrintMain(); + } + else if (key.Key == ConsoleKey.I) + { + currentMode = Mode.Inclusion; + await controller!.StartInclusion(InclusionStrategy.PreferS2, 12345); + PrintMain(); + } + else if (key.Key == ConsoleKey.S) + { + if (currentMode == Mode.Exclusion) + await controller!.StopExclusion(); + else + await controller!.StopInclusion(); + currentMode = Mode.Display; + PrintMain(); + } + } } private static async void Controller_NodeReady(object? sender, EventArgs e) { - ushort id = ((Node)sender!).ID; - ReadyList.Add(id); - InterviewList.Add(id); + Node node = (Node)sender!; + InterviewList.Add(node.ID); + ReadyList.Add(node.ID); await controller!.ExportNodeDBAsync("nodecache.db"); + AttachListeners(node); + } + + private static void AttachListeners(Node node) + { + if (node.HasCommandClass(CommandClass.SensorMultiLevel)) + node.GetCommandClass()!.Updated += Node_Updated; + if (node.HasCommandClass(CommandClass.Meter)) + node.GetCommandClass()!.Updated += Node_Updated; + if (node.HasCommandClass(CommandClass.Notification) && node.GetCommandClass() is Notification not) //ZWave Weirdness + not.Updated += Node_Updated; + if (node.HasCommandClass(CommandClass.Battery)) + node.GetCommandClass()!.Status += Node_Updated; + if (node.HasCommandClass(CommandClass.SensorBinary)) + node.GetCommandClass()!.Updated += Node_Updated; + if (node.HasCommandClass(CommandClass.SensorAlarm)) + node.GetCommandClass()!.Alarm += Node_Updated; + if (node.HasCommandClass(CommandClass.SwitchBinary)) + node.GetCommandClass()!.SwitchReport += Node_Updated; + } + + private static async Task Node_Updated(Node sender, CommandClassEventArgs args) + { + if (args.Report == null) + return; + if (Reports.Count > 10) + Reports.RemoveFirst(); + Reports.AddLast($"{DateTime.Now.ToLongTimeString()} Node {sender.ID}: {args.Report.ToString()!}"); } private static async void Controller_NodeInfoUpdated(object? sender, ApplicationUpdateEventArgs e) @@ -59,20 +138,17 @@ private static async void Controller_NodeInfoUpdated(object? sender, Application if (node != null && !InterviewList.Contains(node.ID)) { InterviewList.Add(node.ID); - CancellationTokenSource cts = new CancellationTokenSource(180000); - await Task.Factory.StartNew(async() => { - try - { - await node.Interview(cts.Token); - ReadyList.Add(node.ID); - await controller!.ExportNodeDBAsync("nodecache.db"); - } - catch(Exception ex) - { - Log.Error(ex, "Uncaught Exception in Node Interview"); - } - }); - } + node.InterviewComplete += Node_InterviewComplete; + _ = Task.Run(() => node.Interview()); + } + } + + private static async void Node_InterviewComplete(object? sender, EventArgs e) + { + Node node = (Node)sender!; + ReadyList.Add(node.ID); + await controller!.ExportNodeDBAsync("nodecache.db"); + AttachListeners(node); } private static async Task MainLoop() @@ -87,7 +163,7 @@ private static async Task MainLoop() private static void PrintMain() { Console.Clear(); - Console.Write($"ZWaveDotNet v{Version} - Controller {controller!.ControllerID} {(controller!.IsConnected ? "Connected" : "Disconnected")}"); + Console.Write($"ZWaveDotNet v{Version} - Controller #{controller!.ControllerID} {(controller!.IsConnected ? "Connected" : "Disconnected")}"); Console.Write($" - v{controller.APIVersion.Major} ({region})"); Console.Write($"{(controller!.SupportsLongRange ? " [LR]" : "")}"); Console.Write($"{(controller!.Primary ? " [Primary]" : "")}"); @@ -97,6 +173,24 @@ private static void PrintMain() Console.WriteLine($"{controller.Nodes.Count} Nodes Found:"); foreach (Node n in controller.Nodes.Values) Console.WriteLine(n.ToString()); + Console.WriteLine(); + if (currentMode == Mode.Display) + { + Console.WriteLine("Press I to enter Inclusion mode, E to enter Exclusion mode or S to Stop"); + Console.WriteLine("Last 10 Node Reports:"); + foreach (string report in Reports) + Console.WriteLine(report); + } + else if (currentMode == Mode.Inclusion) + { + Console.WriteLine("- Inclusion Mode Active (Default PIN 12345) -"); + Console.WriteLine("Press the Pairing button on your device"); + } + else + { + Console.WriteLine("- Exclusion Mode Active -"); + Console.WriteLine("Press the Pairing button on your device"); + } } } } \ No newline at end of file diff --git a/ZWaveDotNet/CommandClasses/CommandClassBase.cs b/ZWaveDotNet/CommandClasses/CommandClassBase.cs index 44cf114..1658221 100644 --- a/ZWaveDotNet/CommandClasses/CommandClassBase.cs +++ b/ZWaveDotNet/CommandClasses/CommandClassBase.cs @@ -180,13 +180,13 @@ public static CommandClassBase Create(CommandClass cc, Node node, byte endpoint, protected async Task SendCommand(Enum command, CancellationToken token, params byte[] payload) { - await SendCommand(command, token, false, payload); + await SendCommand(command, token, false, payload).ConfigureAwait(false); } protected async Task SendCommand(Enum command, CancellationToken token, bool supervised = false, params byte[] payload) { CommandMessage data = new CommandMessage(controller, node.ID, endpoint, commandClass, Convert.ToByte(command), supervised, payload); - await SendCommand(data, token); + await SendCommand(data, token).ConfigureAwait(false); } protected async Task SendCommand(CommandMessage data, CancellationToken token) @@ -199,9 +199,9 @@ protected async Task SendCommand(CommandMessage data, CancellationToken token) if (key == null) throw new InvalidOperationException($"Command classes are secure but no keys exist for node {node.ID}"); if (key.Key == SecurityManager.RecordType.S0) - await node.GetCommandClass()!.Encapsulate(data.Payload, token); + await node.GetCommandClass()!.Encapsulate(data.Payload, token).ConfigureAwait(false); else if (key.Key > SecurityManager.RecordType.S0) - await node.GetCommandClass()!.Encapsulate(data.Payload, key.Key, token); + await node.GetCommandClass()!.Encapsulate(data.Payload, key.Key, token).ConfigureAwait(false); else throw new InvalidOperationException("Security required but no keys are available"); } @@ -209,17 +209,17 @@ protected async Task SendCommand(CommandMessage data, CancellationToken token) DataMessage message = data.ToMessage(); for (int i = 0; i < 3; i++) { - if (await AttemptTransmission(message, token, (i == 2)) == false) + if (await AttemptTransmission(message, token, i == 2).ConfigureAwait(false) == false) { - Log.Error($"Transmission Failure: Retrying [Attempt {i+1}]..."); - await Task.Delay(100 + (1000 * i), token); + Log.Error($"Controller Failed to Send Message: Retrying [Attempt {i+1}]..."); + await Task.Delay(100 + (1000 * i), token).ConfigureAwait(false); } } } private async Task AttemptTransmission(DataMessage message, CancellationToken cancellationToken, bool ex = false) { - DataCallback dc = await controller.Flow.SendAcknowledgedResponseCallback(message, cancellationToken); + DataCallback dc = await controller.Flow.SendAcknowledgedResponseCallback(message, cancellationToken).ConfigureAwait(false); if (dc.Status != TransmissionStatus.CompleteOk && dc.Status != TransmissionStatus.CompleteNoAck && dc.Status != TransmissionStatus.CompleteVerified) { if (!ex) @@ -241,7 +241,7 @@ public virtual Task Interview(CancellationToken cancellationToken) protected async Task SendReceive(Enum command, Enum response, CancellationToken token, params byte[] payload) { - return await SendReceive(command, response, token, false, payload); + return await SendReceive(command, response, token, false, payload).ConfigureAwait(false); } protected async Task SendReceive(Enum command, Enum response, CancellationToken token, bool supervised = false, params byte[] payload) @@ -258,9 +258,7 @@ protected async Task SendReceive(Enum command, Enum response, Can src }; if (!callbacks.TryAdd(cmd, newCallbacks)) - { callbacks[cmd].Add(src); - } } await SendCommand(command, token, supervised, payload); return await src.Task; diff --git a/ZWaveDotNet/CommandClasses/Security0.cs b/ZWaveDotNet/CommandClasses/Security0.cs index ad79315..305807e 100644 --- a/ZWaveDotNet/CommandClasses/Security0.cs +++ b/ZWaveDotNet/CommandClasses/Security0.cs @@ -40,14 +40,14 @@ public async Task CommandsSupportedGet(CancellationToken canc internal async Task SchemeGet(CancellationToken cancellationToken = default) { Log.Debug("Requesting Scheme"); - await SendCommand(Security0Command.SchemeGet, cancellationToken, (byte)0x0); + await SendCommand(Security0Command.SchemeGet, cancellationToken, (byte)0x0).ConfigureAwait(false); } internal async Task KeySet(CancellationToken cancellationToken = default) { Log.Information($"Setting Network Key on {node.ID}"); CommandMessage data = new CommandMessage(controller, node.ID, endpoint, commandClass, (byte)Security0Command.NetworkKeySet, false, controller.NetworkKeyS0); - await TransmitTemp(data.Payload, cancellationToken); + await TransmitTemp(data.Payload, cancellationToken).ConfigureAwait(false); } protected async Task GetNonce(CancellationToken cancellationToken) @@ -67,7 +67,7 @@ public async Task TransmitTemp(List payload, CancellationToken cancellatio using (CancellationTokenSource timeout = new CancellationTokenSource(10000)) { using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken); - report = await GetNonce(cts.Token); + report = await GetNonce(cts.Token).ConfigureAwait(false); if (report.IsMulticastMethod()) return; //This should never happen } @@ -86,12 +86,12 @@ public async Task TransmitTemp(List payload, CancellationToken cancellatio securePayload[8 + encrypted.Length] = receiversNonce[0]; Array.Copy(mac, 0, securePayload, 9 + encrypted.Length, 8); - await SendCommand(Security0Command.MessageEncap, cancellationToken, securePayload); + await SendCommand(Security0Command.MessageEncap, cancellationToken, securePayload).ConfigureAwait(false); } public async Task Encapsulate(List payload, CancellationToken cancellationToken) { - ReportMessage report = await GetNonce(cancellationToken); + ReportMessage report = await GetNonce(cancellationToken).ConfigureAwait(false); if (report.IsMulticastMethod()) return; //This should never happen @@ -149,7 +149,7 @@ public async Task Encapsulate(List payload, CancellationToken cancellation { Log.Information("Providing Next Nonce"); using CancellationTokenSource cts = new CancellationTokenSource(3000); - await controller.Nodes[msg.SourceNodeID].GetCommandClass()!.SendCommand(Security0Command.NonceReport, cts.Token, controller.SecurityManager.CreateS0Nonce(msg.SourceNodeID)); + await controller.Nodes[msg.SourceNodeID].GetCommandClass()!.SendCommand(Security0Command.NonceReport, cts.Token, controller.SecurityManager.CreateS0Nonce(msg.SourceNodeID)).ConfigureAwait(false); } Log.Information("Decrypted: " + msg.ToString()); @@ -179,7 +179,7 @@ protected override async Task Handle(ReportMessage message) if (message.IsMulticastMethod()) return SupervisionStatus.Fail; - await SendCommand(Security0Command.NonceReport, CancellationToken.None, controller.SecurityManager.CreateS0Nonce(node.ID)); + await SendCommand(Security0Command.NonceReport, CancellationToken.None, controller.SecurityManager.CreateS0Nonce(node.ID)).ConfigureAwait(false); return SupervisionStatus.Success; } return SupervisionStatus.NoSupport; diff --git a/ZWaveDotNet/CommandClasses/Security2.cs b/ZWaveDotNet/CommandClasses/Security2.cs index e553371..35e52c8 100644 --- a/ZWaveDotNet/CommandClasses/Security2.cs +++ b/ZWaveDotNet/CommandClasses/Security2.cs @@ -17,9 +17,9 @@ public class Security2 : CommandClassBase { private const byte KEY_VERIFIED = 0x2; private const byte TRANSFER_COMPLETE = 0x1; - public event CommandClassEvent? BootstrapComplete; public event CommandClassEvent? SecurityError; TaskCompletionSource bootstrapComplete = new TaskCompletionSource(); + private static uint sequence = (uint)new Random().Next(); public enum Security2Command { @@ -60,18 +60,30 @@ internal async Task> KexSet(KeyExchangeReport report, CancellationT Log.Information($"Granting Keys {report.Keys}"); ReportMessage msg = await SendReceive(Security2Command.KEXSet, Security2Command.PublicKeyReport, cancellationToken, report.ToBytes()); Log.Information("Received Public Key "+ MemoryUtil.Print(msg.Payload.Slice(1))); + if (msg.Payload.Span[0] == 0x1) //The including node thinks it's us + { + await KexFail(KexFailType.KEX_FAIL_CANCEL, cancellationToken).ConfigureAwait(false); + throw new SecurityException("Including node used controller pubkey"); + } return msg.Payload.Slice(1); } - internal async Task SendPublicKey(CancellationToken cancellationToken = default) + internal async Task SendPublicKey(bool csa, CancellationToken cancellationToken = default) { if (controller.SecurityManager == null) throw new InvalidOperationException("Security Manager does not exist"); Log.Information("Sending Public Key"); byte[] resp = new byte[33]; - resp[0] = 0x1; + resp[0] = 0x1; //We are the including node Array.Copy(controller.SecurityManager.PublicKey, 0, resp, 1, 32); - await SendCommand(Security2Command.PublicKeyReport, cancellationToken, resp); + if (csa) + { + resp[1] = 0x0; + resp[2] = 0x0; + resp[3] = 0x0; + resp[4] = 0x0; + } + await SendCommand(Security2Command.PublicKeyReport, cancellationToken, resp).ConfigureAwait(false); } internal async Task SendNonceReport(bool SOS, bool MOS, bool forceNew, CancellationToken cancellationToken = default) @@ -80,14 +92,14 @@ internal async Task SendNonceReport(bool SOS, bool MOS, bool forceNew, Cancellat return; if (MOS) throw new NotImplementedException("MOS Not Implemented"); //TODO - Multicast - (Memory Bytes, byte Sequence) entropy; + Memory entropy; if (forceNew) - entropy = controller.SecurityManager.CreateEntropy(node.ID, true); + entropy = controller.SecurityManager.CreateEntropy(node.ID); else - entropy = controller.SecurityManager.GetEntropy(node.ID, false) ?? controller.SecurityManager.CreateEntropy(node.ID, true); - NonceReport nonceGetReport = new NonceReport(entropy.Sequence, SOS, MOS, entropy.Bytes); + entropy = controller.SecurityManager.GetEntropy(node.ID, false) ?? controller.SecurityManager.CreateEntropy(node.ID); + NonceReport nonceGetReport = new NonceReport(NextSequence(), SOS, MOS, entropy); Log.Information("Declaring SPAN out of sync"); - await SendCommand(Security2Command.NonceReport, cancellationToken, nonceGetReport.GetBytes()); + await SendCommand(Security2Command.NonceReport, cancellationToken, nonceGetReport.GetBytes()).ConfigureAwait(false); } internal async Task KexFail(KexFailType type, CancellationToken cancellationToken = default) @@ -97,10 +109,10 @@ internal async Task KexFail(KexFailType type, CancellationToken cancellationToke if (type == KexFailType.KEX_FAIL_AUTH || type == KexFailType.KEX_FAIL_DECRYPT || type == KexFailType.KEX_FAIL_KEY_VERIFY || type == KexFailType.KEX_FAIL_KEY_GET) { CommandMessage reportKex = new CommandMessage(controller, node.ID, endpoint, commandClass, (byte)Security2Command.KEXFail, false, (byte)type); - await Transmit(reportKex.Payload, SecurityManager.RecordType.ECDH_TEMP, cancellationToken); + await Transmit(reportKex.Payload, SecurityManager.RecordType.ECDH_TEMP, cancellationToken).ConfigureAwait(false); } else - await SendCommand(Security2Command.KEXFail, cancellationToken, (byte)type); + await SendCommand(Security2Command.KEXFail, cancellationToken, (byte)type).ConfigureAwait(false); bootstrapComplete.TrySetException(new SecurityException(type.ToString())); } @@ -114,7 +126,7 @@ public async Task Transmit(List payload, SecurityManager.RecordType? type, await Encapsulate(payload, type, cancellationToken); if (payload.Count > 2) payload.RemoveRange(0, 2); - await SendCommand(Security2Command.MessageEncap, cancellationToken, payload.ToArray()); + await SendCommand(Security2Command.MessageEncap, cancellationToken, payload.ToArray()).ConfigureAwait(false); Log.Debug("Transmit Complete"); } @@ -138,29 +150,30 @@ public async Task Encapsulate(List payload, SecurityManager.RecordType? ty else Log.Information("Using Key " + networkKey.Key.ToString()); - (Memory output, byte sequence)? nonce = controller.SecurityManager.NextSpanNonce(node.ID, networkKey.Key); - if (nonce == null) + Memory? nonce = controller.SecurityManager.NextSpanNonce(node.ID, networkKey.Key); + if (!nonce.HasValue) { //We need a new Nonce Memory MEI; - (Memory Bytes, byte Sequence)? sendersEntropy = controller.SecurityManager.CreateEntropy(node.ID, false); - (Memory Bytes, byte Sequence)? receiversEntropy = controller.SecurityManager.GetEntropy(node.ID, true); - if (receiversEntropy == null) + Memory? sendersEntropy = controller.SecurityManager.CreateEntropy(node.ID); + Memory? receiversEntropy = controller.SecurityManager.GetEntropy(node.ID, true); + if (!receiversEntropy.HasValue) { Log.Debug("Requesting new entropy"); - ReportMessage msg = await SendReceive(Security2Command.NonceGet, Security2Command.NonceReport, cancellationToken, (byte)new Random().Next()); + ReportMessage msg = await SendReceive(Security2Command.NonceGet, Security2Command.NonceReport, cancellationToken, NextSequence()).ConfigureAwait(false); NonceReport nr = new NonceReport(msg.Payload); - MEI = AES.CKDFMEIExpand(AES.CKDFMEIExtract(sendersEntropy.Value.Bytes, nr.Entropy)); + MEI = AES.CKDFMEIExpand(AES.CKDFMEIExtract(sendersEntropy.Value, nr.Entropy)); } else { Log.Debug("Using receivers entropy"); //TODO - Investigate further. Are sender/receiver inverted in this case? - MEI = AES.CKDFMEIExpand(AES.CKDFMEIExtract(sendersEntropy.Value.Bytes, receiversEntropy.Value.Bytes)); - controller.SecurityManager.DeleteEntropy(node.ID); + MEI = AES.CKDFMEIExpand(AES.CKDFMEIExtract(sendersEntropy.Value, receiversEntropy.Value)); } - controller.SecurityManager.CreateSpan(node.ID, sendersEntropy.Value.Sequence, MEI, networkKey.PString, networkKey.Key); + controller.SecurityManager.DeleteEntropy(node.ID); //Delete Senders Entropy + controller.SecurityManager.CreateSpan(node.ID, MEI, networkKey.PString, networkKey.Key); nonce = controller.SecurityManager.NextSpanNonce(node.ID, networkKey.Key); + Log.Information("New Span Created"); if (nonce == null) { @@ -168,21 +181,21 @@ public async Task Encapsulate(List payload, SecurityManager.RecordType? ty return; } - extensionData.Add(nonce.Value.sequence); + extensionData.Add(NextSequence()); extensionData.Add(0x1); extensionData.Add(18); extensionData.Add((byte)(Security2Ext.Critical | Security2Ext.SPAN)); - extensionData.AddRange(sendersEntropy!.Value.Bytes.ToArray()); + extensionData.AddRange(sendersEntropy!.Value.ToArray()); } else { - extensionData.Add(nonce.Value.sequence); + extensionData.Add(NextSequence()); extensionData.Add(0x0); } // 8(tag) + 1 (command class) + 1 (command) + extension len AdditionalAuthData ad = new AdditionalAuthData(node, controller, true, payload.Count + 10 + extensionData.Count, extensionData.ToArray()); //TODO - Include encrypted extension - Memory encoded = EncryptCCM(payload.ToArray(), nonce.Value.output, networkKey!.KeyCCM, ad); + Memory encoded = EncryptCCM(payload.ToArray(), nonce.Value, networkKey!.KeyCCM, ad); byte[] securePayload = new byte[extensionData.Count + encoded.Length]; extensionData.CopyTo(securePayload); @@ -192,6 +205,7 @@ public async Task Encapsulate(List payload, SecurityManager.RecordType? ty payload.Add((byte)commandClass); payload.Add((byte)Security2Command.MessageEncap); payload.AddRange(securePayload); + Log.Debug("Encapsulation Complete"); } internal static async Task Free(ReportMessage msg, Controller controller) @@ -240,9 +254,10 @@ public async Task Encapsulate(List payload, SecurityManager.RecordType? ty { try { + Log.Information("Declaring SPAN failed and sending SOS"); controller.SecurityManager.PurgeRecords(msg.SourceNodeID, networkKey.Key); using (CancellationTokenSource cts = new CancellationTokenSource(3000)) - await controller.Nodes[msg.SourceNodeID].GetCommandClass()!.SendNonceReport(true, false, false, cts.Token); + await controller.Nodes[msg.SourceNodeID].GetCommandClass()!.SendNonceReport(true, false, false, cts.Token).ConfigureAwait(false); } catch (Exception e) { @@ -271,15 +286,15 @@ public async Task Encapsulate(List payload, SecurityManager.RecordType? ty { try { - (Memory output, byte sequence)? nonce = controller.SecurityManager!.NextSpanNonce(msg.SourceNodeID, networkKey.Key); - if (nonce == null) + Memory? nonce = controller.SecurityManager!.NextSpanNonce(msg.SourceNodeID, networkKey.Key); + if (!nonce.HasValue) { Log.Error("No Nonce for Received Message"); attempt = 2; return null; } return DecryptCCM(msg.Payload, - nonce!.Value.output, + nonce!.Value, networkKey!.KeyCCM, ad); } @@ -300,11 +315,17 @@ private static bool ProcessExtension(Memory payload, ushort nodeId, Securi case Security2Ext.SPAN: Memory sendersEntropy = payload.Slice(2, 16); var result = sm.GetEntropy(nodeId, false); - Memory MEI = AES.CKDFMEIExpand(AES.CKDFMEIExtract(sendersEntropy, result!.Value.bytes)); - sm.CreateSpan(nodeId, result!.Value.sequence, MEI, netKey.PString, netKey.Key); + if (result == null) + { + Log.Error("Received SPAN extension without providing our entropy"); + break; + } + sm.DeleteEntropy(nodeId); //Delete Senders Entropy + Memory MEI = AES.CKDFMEIExpand(AES.CKDFMEIExtract(sendersEntropy, result!.Value)); + sm.CreateSpan(nodeId, MEI, netKey.PString, netKey.Key); Log.Warning("Created new SPAN"); Log.Warning("Senders Entropy: " + MemoryUtil.Print(sendersEntropy)); - Log.Warning("Receivers Entropy: " + MemoryUtil.Print(result!.Value.bytes)); + Log.Warning("Receivers Entropy: " + MemoryUtil.Print(result!.Value)); Log.Warning("Mixed Entropy: " + MemoryUtil.Print(MEI)); break; case Security2Ext.MGRP: @@ -340,11 +361,11 @@ protected override async Task Handle(ReportMessage message) requestedKeys.Echo = true; Log.Information("Responding: " + requestedKeys.ToString()); CommandMessage reportKex = new CommandMessage(controller, node.ID, endpoint, commandClass, (byte)Security2Command.KEXReport, false, requestedKeys.ToBytes()); - await Transmit(reportKex.Payload, SecurityManager.RecordType.ECDH_TEMP); + await Transmit(reportKex.Payload, SecurityManager.RecordType.ECDH_TEMP).ConfigureAwait(false); } } else - await SendCommand(Security2Command.KEXReport, CancellationToken.None, kexReport.ToBytes()); + await SendCommand(Security2Command.KEXReport, CancellationToken.None, kexReport.ToBytes()).ConfigureAwait(false); return SupervisionStatus.Success; case Security2Command.NetworkKeyGet: if (controller.SecurityManager == null) @@ -383,7 +404,8 @@ protected override async Task Handle(ReportMessage message) return SupervisionStatus.Fail; //Invalid Key Type - Ignore this } CommandMessage data = new CommandMessage(controller, node.ID, endpoint, commandClass, (byte)Security2Command.NetworkKeyReport, false, resp); - await Transmit(data.Payload, SecurityManager.RecordType.ECDH_TEMP); + await Transmit(data.Payload, SecurityManager.RecordType.ECDH_TEMP).ConfigureAwait(false); + Log.Information($"Provided Network Key {key}"); return SupervisionStatus.Success; case Security2Command.NetworkKeyVerify: if (controller.SecurityManager == null) @@ -394,6 +416,7 @@ protected override async Task Handle(ReportMessage message) Log.Information("Network Key Verify Received without proper security"); return SupervisionStatus.Fail; //Verify must be secured by the ECDH Temp Key } + Log.Information($"Revoking {message.SecurityLevel}"); controller.SecurityManager.RevokeKey(node.ID, SecurityManager.KeyToType(message.SecurityLevel)); CommandMessage transferEnd = new CommandMessage(controller, node.ID, endpoint, commandClass, (byte)Security2Command.TransferEnd, false, KEY_VERIFIED); await Transmit(transferEnd.Payload, SecurityManager.RecordType.ECDH_TEMP); @@ -408,12 +431,12 @@ protected override async Task Handle(ReportMessage message) Log.Error("Duplicate S2 Nonce Get Skipped"); return SupervisionStatus.Fail; //Duplicate Message } - Log.Warning("Creating new Nonce"); + Log.Warning("Creating new Nonce for GET"); SecurityManager.NetworkKey? nonceKey = controller.SecurityManager.GetHighestKey(message.SourceNodeID); if (nonceKey == null) return SupervisionStatus.Fail; controller.SecurityManager.PurgeRecords(node.ID, nonceKey.Key); - await SendNonceReport(true, false, true, CancellationToken.None); + await SendNonceReport(true, false, true, CancellationToken.None).ConfigureAwait(false); return SupervisionStatus.Success; case Security2Command.NonceReport: if (controller.SecurityManager == null) @@ -424,10 +447,15 @@ protected override async Task Handle(ReportMessage message) if (networkKey == null) return SupervisionStatus.Fail; NonceReport nr = new NonceReport(message.Payload); + if (!controller.SecurityManager.IsSequenceNew(message.SourceNodeID, nr.Sequence)) + { + Log.Error("Duplicate S2 Nonce Report Skipped"); + return SupervisionStatus.Fail; //Duplicate Message + } if (nr.SPAN_OS) { Log.Information("Received Unsolicited SOS"); - controller.SecurityManager.StoreRemoteEntropy(node.ID, nr.Entropy, nr.Sequence); + controller.SecurityManager.StoreRemoteEntropy(node.ID, nr.Entropy); } return SupervisionStatus.Success; case Security2Command.TransferEnd: @@ -461,7 +489,6 @@ protected override async Task Handle(ReportMessage message) controller.SecurityManager.GrantKey(node.ID, SecurityManager.RecordType.S2Access, controller.NetworkKeyS2Access); Log.Information("Transfer Complete"); - await FireEvent(BootstrapComplete, null); bootstrapComplete.TrySetResult(); return SupervisionStatus.Success; case Security2Command.KEXFail: @@ -481,10 +508,7 @@ protected override bool IsSecure(byte command) { case Security2Command.CommandsSupportedGet: case Security2Command.CommandsSupportedReport: - case Security2Command.NetworkKeyGet: - case Security2Command.NetworkKeyReport: case Security2Command.NetworkKeyVerify: - case Security2Command.TransferEnd: return true; } return false; @@ -512,5 +536,11 @@ public static Memory DecryptCCM(Memory cipherText, Memory nonc aes.Decrypt(nonce.Span, cipherText.Slice(0, cipherText.Length - 8).Span, cipherText.Slice(cipherText.Length - 8, 8).Span, ret.Span, ad.GetBytes().Span); return ret; } + + private static byte NextSequence() + { + Interlocked.Increment(ref sequence); + return (byte)sequence; + } } } diff --git a/ZWaveDotNet/Entities/Controller.cs b/ZWaveDotNet/Entities/Controller.cs index f09970b..4c48d3a 100644 --- a/ZWaveDotNet/Entities/Controller.cs +++ b/ZWaveDotNet/Entities/Controller.cs @@ -27,6 +27,8 @@ public class Controller public event EventHandler? NodeInfoUpdated; public event EventHandler? SecurityBootstrapComplete; public event EventHandler? NodeReady; + public event EventHandler? NodeExcluded; + public event EventHandler? InclusionStopped; private readonly Flow flow; internal byte[] tempA; @@ -96,7 +98,7 @@ public async Task Reset() public async ValueTask Start(CancellationToken cancellationToken = default) { SecurityManager = new SecurityManager(await GetRandom(32, cancellationToken)); - await Task.Factory.StartNew(EventLoop, TaskCreationOptions.LongRunning); + _ = await Task.Factory.StartNew(EventLoop, TaskCreationOptions.LongRunning); //See what the controller supports await GetSupportedFunctions(cancellationToken); @@ -240,7 +242,7 @@ public async Task> GetRandom(byte length, CancellationToken cancell random = await flow.SendAcknowledgedResponse(Function.GetRandom, cancellationToken, length) as PayloadMessage; } catch (Exception) { }; - if (random == null || random.Data.Span[0] != 0x1) //TODO - Status Enums + if (random == null || random.Data.Span[0] == 0x0) //TODO - Status Enums { Memory planB = new byte[length]; new Random().NextBytes(planB.Span); @@ -480,53 +482,59 @@ public async Task StopExclusion(CancellationToken cancellationToken = default) private async Task BootstrapUnsecure(Node node) { + await Task.Delay(1000); //Give including node a chance to get ready Log.Information("Included without Security. Moving to interview"); - await node.Interview(true); + await node.Interview(true).ConfigureAwait(false); NodeReady?.Invoke(node, new EventArgs()); return true; } private async Task BootstrapS0(Node node) { + await Task.Delay(1000); //Give including node a chance to get ready Log.Information("Starting Secure(0-Legacy) Inclusion"); using (CancellationTokenSource cts = new CancellationTokenSource(30000)) { try { Security0 sec0 = node.GetCommandClass()!; - await sec0.SchemeGet(cts.Token); - await sec0.KeySet(cts.Token); - await sec0.WaitForKeyVerified(cts.Token); + await sec0.SchemeGet(cts.Token).ConfigureAwait(false); + _ = Task.Run(() => sec0.KeySet(cts.Token)); + await sec0.WaitForKeyVerified(cts.Token).ConfigureAwait(false); SecurityBootstrapComplete?.Invoke(node, new EventArgs()); } catch (Exception e) { + SecurityManager?.RevokeKey(node.ID, SecurityManager.RecordType.S0); Log.Error(e, "Error in S0 Bootstrapping"); return false; } } - await node.Interview(true); + await node.Interview(true).ConfigureAwait(false); NodeReady?.Invoke(node, new EventArgs()); return true; } private async Task BootstrapS2(Node node) { + await Task.Delay(1000); //Give including node a chance to get ready Security2 sec2 = node.GetCommandClass()!; Log.Information("Starting Secure S2 Inclusion"); try { KeyExchangeReport requestedKeys; using (CancellationTokenSource TA1 = new CancellationTokenSource(10000)) - requestedKeys = await sec2.KexGet(TA1.Token); + requestedKeys = await sec2.KexGet(TA1.Token).ConfigureAwait(false); if (!requestedKeys.Curve25519) { + Log.Error("Invalid S2 Curve"); await sec2.KexFail(KexFailType.KEX_FAIL_KEX_CURVES); return false; } if (!requestedKeys.Scheme1) { + Log.Error("Invalid S2 Scheme"); await sec2.KexFail(KexFailType.KEX_FAIL_KEX_SCHEME); return false; } @@ -534,7 +542,7 @@ private async Task BootstrapS2(Node node) Log.Information("Sending " + requestedKeys.ToString()); Memory pub; using (CancellationTokenSource TA2 = new CancellationTokenSource(10000)) - pub = await sec2.KexSet(requestedKeys, TA2.Token); + pub = await sec2.KexSet(requestedKeys, TA2.Token).ConfigureAwait(false); if ((requestedKeys.Keys & SecurityKey.S2Access) == SecurityKey.S2Access || (requestedKeys.Keys & SecurityKey.S2Authenticated) == SecurityKey.S2Authenticated) BinaryPrimitives.WriteUInt16BigEndian(pub.Slice(0, 2).Span, pin); @@ -542,19 +550,24 @@ private async Task BootstrapS2(Node node) var prk = AES.CKDFTempExtract(sharedSecret, SecurityManager.PublicKey, pub); Log.Information("Temp Key: " + MemoryUtil.Print(prk)); SecurityManager.GrantKey(node.ID, SecurityManager.RecordType.ECDH_TEMP, prk, true); - await sec2.SendPublicKey(); using (CancellationTokenSource cts = new CancellationTokenSource(30000)) - await sec2.WaitForBootstrap(cts.Token); + { + _ = Task.Run(() => sec2.SendPublicKey(requestedKeys.ClientSideAuth, cts.Token)); + await sec2.WaitForBootstrap(cts.Token).ConfigureAwait(false); + } SecurityBootstrapComplete?.Invoke(node, new EventArgs()); } catch (Exception e) { Log.Error(e, "Error in S2 Bootstrapping"); using (CancellationTokenSource cts = new CancellationTokenSource(5000)) - await sec2.KexFail(KexFailType.KEX_FAIL_CANCEL, cts.Token); + await sec2.KexFail(KexFailType.KEX_FAIL_CANCEL, cts.Token).ConfigureAwait(false); + SecurityManager?.RevokeKey(node.ID, SecurityManager.RecordType.S2Access); + SecurityManager?.RevokeKey(node.ID, SecurityManager.RecordType.S2Auth); + SecurityManager?.RevokeKey(node.ID, SecurityManager.RecordType.S2UnAuth); return false; } - await node.Interview(true); + await node.Interview(true).ConfigureAwait(false); NodeReady?.Invoke(node, new EventArgs()); return true; } @@ -612,7 +625,7 @@ private async Task EventLoop() else if (msg is ApplicationCommand cmd) { if (Nodes.TryGetValue(cmd.SourceNodeID, out Node? node)) - _ = Task.Run(async() => { try { await node.HandleApplicationCommand(cmd); } catch (Exception e) { Log.Error(e, "Unhandled"); } }); + _ = Task.Factory.StartNew(async() => { try { await node.HandleApplicationCommand(cmd); } catch (Exception e) { Log.Error(e, "Unhandled"); } }, CancellationToken.None, TaskCreationOptions.RunContinuationsAsynchronously, TaskScheduler.Default); else Log.Warning("Node " + cmd.SourceNodeID + " not found"); Log.Information(cmd.ToString()); @@ -632,25 +645,30 @@ private async Task EventLoop() await StopInclusion(); else if (inc.Status == InclusionExclusionStatus.OperationComplete) { + InclusionStopped?.Invoke(this, EventArgs.Empty); if (inc.NodeID > 0 && Nodes.TryGetValue(inc.NodeID, out Node? node)) { Log.Information("Added " + node.ToString()); if (SecurityManager != null) { if ((currentStrategy == InclusionStrategy.S2Only || currentStrategy == InclusionStrategy.PreferS2) && node.HasCommandClass(CommandClass.Security2)) - await Task.Run(() => BootstrapS2(node)); + _ = Task.Run(() => BootstrapS2(node)); else if ((currentStrategy == InclusionStrategy.PreferS2 || currentStrategy == InclusionStrategy.LegacyS0Only) && node.HasCommandClass(CommandClass.Security0)) - await Task.Run(() => BootstrapS0(node)); + _ = Task.Run(() => BootstrapS0(node)); else - await Task.Run(() => BootstrapUnsecure(node)); + _ = Task.Run(() => BootstrapUnsecure(node)); } } } } else if (inc.Function == Function.RemoveNodeFromNetwork && inc.NodeID > 0) { - if (Nodes.Remove(inc.NodeID)) - Log.Information($"Successfully exluded node {inc.NodeID}"); //TODO - Event This + if (Nodes.Remove(inc.NodeID, out Node? node)) + { + if (NodeExcluded != null) + NodeExcluded.Invoke(node, EventArgs.Empty); + Log.Information($"Successfully exluded node {inc.NodeID}"); + } if (inc.Status == InclusionExclusionStatus.OperationComplete) await StopExclusion(); } diff --git a/ZWaveDotNet/Entities/Node.cs b/ZWaveDotNet/Entities/Node.cs index 683530c..364d1a3 100644 --- a/ZWaveDotNet/Entities/Node.cs +++ b/ZWaveDotNet/Entities/Node.cs @@ -105,19 +105,19 @@ internal async Task HandleApplicationCommand(ApplicationCommand cmd) { if (TransportService.IsEncapsulated(msg)) { - msg = await TransportService.Process(msg, controller); + msg = await TransportService.Process(msg, controller).ConfigureAwait(false); if (msg == null) return; //Not Complete Yet } if (Security0.IsEncapsulated(msg)) { - msg = await Security0.Free(msg, controller); + msg = await Security0.Free(msg, controller).ConfigureAwait(false); if (msg == null) return; } if (Security2.IsEncapsulated(msg)) { - msg = await Security2.Free(msg, controller); + msg = await Security2.Free(msg, controller).ConfigureAwait(false); if (msg == null) return; } @@ -133,7 +133,7 @@ internal async Task HandleApplicationCommand(ApplicationCommand cmd) SupervisionStatus status = SupervisionStatus.Success; foreach (ReportMessage r in msgs) { - SupervisionStatus cmdStatus = await HandleReport(r); + SupervisionStatus cmdStatus = await HandleReport(r).ConfigureAwait(false); if (cmdStatus == SupervisionStatus.Fail) status = cmdStatus; else if (cmdStatus == SupervisionStatus.NoSupport && status != SupervisionStatus.Fail) @@ -146,7 +146,7 @@ internal async Task HandleApplicationCommand(ApplicationCommand cmd) } else { - SupervisionStatus status = await HandleReport(msg); + SupervisionStatus status = await HandleReport(msg).ConfigureAwait(false); if (status == SupervisionStatus.NoSupport) Log.Warning("No Support for " + msg.ToString()); if ((msg.Flags & ReportFlags.SupervisedOnce) == ReportFlags.SupervisedOnce && commandClasses.TryGetValue(CommandClass.Supervision, out CommandClassBase? supervision)) @@ -166,7 +166,7 @@ private async Task HandleReport(ReportMessage msg) { EndPoint? ep = GetEndPoint(msg.SourceEndpoint); if (ep != null) - return await ep.HandleReport(msg); + return await ep.HandleReport(msg).ConfigureAwait(false); } return SupervisionStatus.NoSupport; } @@ -267,7 +267,7 @@ internal async Task Interview(bool newlyIncluded, CancellationToken cancellation key = controller.SecurityManager.GetHighestKey(ID); if (Listening || newlyIncluded) { - await Interview(newlyIncluded, key, cancellationToken); + await Interview(newlyIncluded, key, cancellationToken).ConfigureAwait(false); } else await Task.Run(async () => @@ -276,11 +276,11 @@ await Task.Run(async () => { //TODO - Make sure we abort this if interview is already in progress while (!commandClasses.ContainsKey(CommandClass.WakeUp)) - await Task.Delay(3000); //TODO - Improve this - await ((WakeUp)commandClasses[CommandClass.WakeUp]).WaitForAwake(); + await Task.Delay(3000).ConfigureAwait(false); //TODO - Improve this + await ((WakeUp)commandClasses[CommandClass.WakeUp]).WaitForAwake().ConfigureAwait(false); using (CancellationTokenSource cts = new CancellationTokenSource(90000)) - await Interview(newlyIncluded, key, cts.Token); - await ((WakeUp)commandClasses[CommandClass.WakeUp]).NoMoreInformation(); + await Interview(newlyIncluded, key, cts.Token).ConfigureAwait(false); + await ((WakeUp)commandClasses[CommandClass.WakeUp]).NoMoreInformation().ConfigureAwait(false); } catch(Exception ex) { @@ -309,7 +309,7 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key using (CancellationTokenSource timeout = new CancellationTokenSource(5000)) { using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken)) - await RequestS0(cts.Token); + await RequestS0(cts.Token).ConfigureAwait(false); } break; } @@ -339,7 +339,7 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key using (CancellationTokenSource timeout = new CancellationTokenSource(5000)) { using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken)) - await RequestS2(cts.Token); + await RequestS2(cts.Token).ConfigureAwait(false); } break; } @@ -366,7 +366,7 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key using (CancellationTokenSource timeout = new CancellationTokenSource(5000)) { using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken)) - await RequestS2(cts.Token); + await RequestS2(cts.Token).ConfigureAwait(false); } break; } @@ -393,7 +393,7 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key using (CancellationTokenSource timeout = new CancellationTokenSource(5000)) { using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken)) - await RequestS2(cts.Token); + await RequestS2(cts.Token).ConfigureAwait(false); } break; } @@ -414,10 +414,11 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key else { //Whatever keys we have is what the device has + Log.Information("Requesting secure classes"); if (key != null && key.Key == SecurityManager.RecordType.S0 && commandClasses.ContainsKey(CommandClass.Security0)) - await RequestS0(cancellationToken); + await RequestS0(cancellationToken).ConfigureAwait(false); else if (key != null && commandClasses.ContainsKey(CommandClass.Security2)) - await RequestS2(cancellationToken); + await RequestS2(cancellationToken).ConfigureAwait(false); } } if (this.commandClasses.ContainsKey(CommandClass.MultiChannel)) @@ -466,7 +467,7 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key Log.Information("Interviewing Command Classes"); foreach (CommandClassBase cc in commandClasses.Values) - await cc.Interview(cancellationToken); + await cc.Interview(cancellationToken).ConfigureAwait(false); Log.Information($"Interview Complete [{ID}]"); InterviewComplete?.Invoke(this, new EventArgs()); } @@ -474,7 +475,7 @@ private async Task Interview(bool newlyIncluded, SecurityManager.NetworkKey? key private async Task RequestS0(CancellationToken cancellationToken) { Log.Information("Requesting S0 classes for " + ID); - SupportedCommands supportedCmds = await((Security0)commandClasses[CommandClass.Security0]).CommandsSupportedGet(cancellationToken); + SupportedCommands supportedCmds = await((Security0)commandClasses[CommandClass.Security0]).CommandsSupportedGet(cancellationToken).ConfigureAwait(false); Log.Information($"Received {string.Join(',', supportedCmds.CommandClasses)}"); foreach (CommandClass cls in supportedCmds.CommandClasses) { @@ -486,7 +487,7 @@ private async Task RequestS0(CancellationToken cancellationToken) private async Task RequestS2(CancellationToken cancellationToken) { Log.Information("Requesting S2 classes for " + ID); - List supportedCmds = await ((Security2)commandClasses[CommandClass.Security2]).GetSupportedCommands(cancellationToken); + List supportedCmds = await ((Security2)commandClasses[CommandClass.Security2]).GetSupportedCommands(cancellationToken).ConfigureAwait(false); Log.Information($"Received {string.Join(',', supportedCmds)}"); foreach (CommandClass cls in supportedCmds) { diff --git a/ZWaveDotNet/Security/SecurityManager.cs b/ZWaveDotNet/Security/SecurityManager.cs index 7a7e8a2..0314ac5 100644 --- a/ZWaveDotNet/Security/SecurityManager.cs +++ b/ZWaveDotNet/Security/SecurityManager.cs @@ -26,7 +26,6 @@ private class SpanRecord public Memory Bytes; public DateTime Expires; public RecordType Type; - public byte SequenceNumber; } private class MpanRecord { @@ -157,14 +156,13 @@ public void StoreRequestedKeys(ushort nodeId, KeyExchangeReport request) return null; } - public void CreateSpan(ushort nodeId, byte sequence, Memory mixedEntropy, Memory personalization, RecordType type) + public void CreateSpan(ushort nodeId, Memory mixedEntropy, Memory personalization, RecordType type) { Log.Information($"Created SPAN ({MemoryUtil.Print(mixedEntropy)}, {MemoryUtil.Print(personalization)})"); Memory working_state = CTR_DRBG.Instantiate(mixedEntropy, personalization); SpanRecord nr = new SpanRecord() { Bytes = working_state, - SequenceNumber = ++sequence, Type = type }; List stack = GetStack(nodeId); @@ -174,20 +172,23 @@ public void CreateSpan(ushort nodeId, byte sequence, Memory mixedEntropy, public bool IsSequenceNew(ushort nodeId, byte sequence) { - if (sequenceCache.TryGetValue(nodeId, out List? sequences)) + lock (sequenceCache) { - if (sequences.Contains(sequence)) - return false; - sequences.Add(sequence); - if (sequences.Count > 10) - sequences.RemoveAt(0); - return true; + if (sequenceCache.TryGetValue(nodeId, out List? sequences)) + { + if (sequences.Contains(sequence)) + return false; + sequences.Add(sequence); + if (sequences.Count > 10) + sequences.RemoveAt(0); + return true; + } + sequenceCache.Add(nodeId, new List(new byte[] { sequence })); } - sequenceCache.Add(nodeId, new List(new byte[] { sequence })); return true; } - public (Memory output, byte sequence)? NextSpanNonce(ushort nodeId, RecordType type) + public Memory? NextSpanNonce(ushort nodeId, RecordType type) { if (spanRecords.TryGetValue(nodeId, out List? stack)) { @@ -197,9 +198,8 @@ public bool IsSequenceNew(ushort nodeId, byte sequence) { Log.Warning("Generating Next Nonce"); var result = CTR_DRBG.Generate(record.Bytes, 13); - record.SequenceNumber++; record.Bytes = result.working_state; - return (result.output, record.SequenceNumber); + return result.output; } } } @@ -235,14 +235,16 @@ public bool SpanExists(ushort nodeId, RecordType type) return false; } - public (Memory bytes, byte sequence)? GetEntropy(ushort nodeId, bool remote) + public Memory? GetEntropy(ushort nodeId, bool remote) { if (spanRecords.TryGetValue(nodeId, out List? stack)) { foreach (SpanRecord record in stack) { if (record.Type == (remote ? RecordType.RemoteEntropy : RecordType.LocalEntropy)) - return (record.Bytes, record.SequenceNumber++); + { + return record.Bytes; + } } } return null; @@ -253,37 +255,34 @@ public int DeleteEntropy(ushort nodeId, RecordType? type = null) if (spanRecords.TryGetValue(nodeId, out List? stack)) { if (type != RecordType.LocalEntropy) - return stack.RemoveAll(n => n.Type == RecordType.RemoteEntropy); - if (type != RecordType.RemoteEntropy) return stack.RemoveAll(n => n.Type == RecordType.LocalEntropy); + if (type != RecordType.RemoteEntropy) + return stack.RemoveAll(n => n.Type == RecordType.RemoteEntropy); } return 0; } - public (Memory Bytes, byte Sequence) CreateEntropy(ushort nodeId, bool store) + public Memory CreateEntropy(ushort nodeId) { var result = CTR_DRBG.Generate(prngWorking, 16); prngWorking = result.working_state; SpanRecord nr = new SpanRecord() { Bytes = result.output, - Type = RecordType.LocalEntropy, - SequenceNumber = (byte)new Random().Next() + Type = RecordType.LocalEntropy }; DeleteEntropy(nodeId, RecordType.LocalEntropy); - if (store) - GetStack(nodeId, true).Add(nr); + GetStack(nodeId, true).Add(nr); - return (nr.Bytes, nr.SequenceNumber); + return nr.Bytes; } - public void StoreRemoteEntropy(ushort nodeId, Memory bytes, byte sequence) + public void StoreRemoteEntropy(ushort nodeId, Memory bytes) { SpanRecord nr = new SpanRecord() { Bytes = bytes, - Type = RecordType.RemoteEntropy, - SequenceNumber = sequence + Type = RecordType.RemoteEntropy }; DeleteEntropy(nodeId, RecordType.RemoteEntropy); List stack = GetStack(nodeId, true); diff --git a/ZWaveDotNet/SerialAPI/Flow.cs b/ZWaveDotNet/SerialAPI/Flow.cs index 887475a..dae7ad4 100644 --- a/ZWaveDotNet/SerialAPI/Flow.cs +++ b/ZWaveDotNet/SerialAPI/Flow.cs @@ -1,4 +1,5 @@ -using System.Threading.Channels; +using Serilog; +using System.Threading.Channels; using ZWaveDotNet.SerialAPI.Enums; using ZWaveDotNet.SerialAPI.Messages; @@ -8,6 +9,7 @@ public class Flow { private readonly Port port; private readonly Channel unsolicited; + private readonly SemaphoreSlim portLock = new SemaphoreSlim(1, 1); public bool WideID { get; set; } internal bool IsConnected { get { return port.IsConnected(); } } @@ -19,20 +21,28 @@ public Flow(string portName) public async Task SendUnacknowledged(Function function, params byte[] payload) { - Frame frame = new Frame(FrameType.SOF, DataFrameType.Request, function, payload); - await port.QueueTX(frame); + try + { + await portLock.WaitAsync(); + Frame frame = new Frame(FrameType.SOF, DataFrameType.Request, function, payload); + await port.QueueTX(frame); + } + finally + { + portLock.Release(); + } } - public Task SendAcknowledged(Function function, CancellationToken cancellationToken = default, params byte[] payload) + public async Task SendAcknowledged(Function function, CancellationToken cancellationToken = default, params byte[] payload) { Frame frame = new Frame(FrameType.SOF, DataFrameType.Request, function, payload); - return SendAcknowledged(frame, cancellationToken); + await SendAcknowledged(frame, cancellationToken).ConfigureAwait(false); } - public Task SendAcknowledged(Message message, CancellationToken cancellationToken = default) + public async Task SendAcknowledged(Message message, CancellationToken cancellationToken = default) { Frame frame = new Frame(FrameType.SOF, DataFrameType.Request, message.Function, message.GetPayload()); - return SendAcknowledged(frame, cancellationToken); + await SendAcknowledged(frame, cancellationToken).ConfigureAwait(false); } public async Task SendAcknowledged(Frame frame, CancellationToken cancellationToken = default) @@ -49,13 +59,14 @@ public async Task SendAcknowledged(Frame frame, CancellationToken cancellationTo public async Task SendAcknowledgedResponse(Function function, CancellationToken cancellationToken = default, params byte[] payload) { Frame frame = new Frame(FrameType.SOF, DataFrameType.Request, function, payload); - return GetMessage(await SendAcknowledgedResponse(frame, cancellationToken)); + Frame response = await SendAcknowledgedResponse(frame, cancellationToken).ConfigureAwait(false); + return GetMessage(response); } public async Task SendAcknowledgedResponse(Message message, CancellationToken cancellationToken = default) { Frame frame = new Frame(FrameType.SOF, DataFrameType.Request, message.Function, message.GetPayload()); - Frame response = await SendAcknowledgedResponse(frame, cancellationToken); + Frame response = await SendAcknowledgedResponse(frame, cancellationToken).ConfigureAwait(false); return GetMessage(response)!; } @@ -65,7 +76,7 @@ public async Task SendAcknowledgedResponse(Frame frame, CancellationToken try { await SendAcknowledgedIntl(reader, frame, cancellationToken); - return await GetAcknowledgedResponseIntl(reader, cancellationToken); + return await GetAcknowledgedResponseIntl(reader, cancellationToken).ConfigureAwait(false); } finally { port.DisposeReader(reader); @@ -77,7 +88,7 @@ public async Task SendAcknowledgedResponseCallback(DataMessage mes Frame frame = new Frame(FrameType.SOF, DataFrameType.Request, message.Function, message.GetPayload()); var reader = port.CreateReader(); try { - return await SendAcknowledgedResponseCallbackIntl(reader, frame, message.SessionID, token); + return await SendAcknowledgedResponseCallbackIntl(reader, frame, message.SessionID, token).ConfigureAwait(false); } finally { port.DisposeReader(reader); @@ -86,18 +97,18 @@ public async Task SendAcknowledgedResponseCallback(DataMessage mes public async Task GetUnsolicited() { - return GetMessage(await unsolicited.Reader.ReadAsync()); + return GetMessage(await unsolicited.Reader.ReadAsync().ConfigureAwait(false)); } private async Task SendAcknowledgedResponseCallbackIntl(Channel reader, Frame frame, byte sessionId, CancellationToken token = default) { await SendAcknowledgedIntl(reader, frame, token); - Frame status = await GetAcknowledgedResponseIntl(reader, token); + Frame status = await GetAcknowledgedResponseIntl(reader, token).ConfigureAwait(false); if (!new Response(status.Payload, status.CommandID).Success) throw new Exception("Failed to transmit command"); while (!token.IsCancellationRequested) { - Frame response = await reader.Reader.ReadAsync(token); + Frame response = await reader.Reader.ReadAsync(token).ConfigureAwait(false); if (response.DataType == DataFrameType.Request) { Message msg = GetMessage(response)!; @@ -110,16 +121,26 @@ private async Task SendAcknowledgedResponseCallbackIntl(Channel reader, Frame frame, CancellationToken cancellationToken) { - using (CancellationTokenSource timeout = new CancellationTokenSource(1600)) + try { - using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken)) + await portLock.WaitAsync(cancellationToken); + for (int attempt = 0; attempt < 3; attempt++) { - do + using (CancellationTokenSource timeout = new CancellationTokenSource(1600)) + using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken)) { await port.QueueTX(frame); - } while (!await SuccessfulAck(reader, cts.Token)); + if (await SuccessfulAck(reader, cts.Token)) + break; + } + Log.Warning($"Retransmit Attempt {attempt + 1}"); + await Task.Delay(100 + (1000 * attempt), cancellationToken); } } + finally + { + portLock.Release(); + } } private static async Task GetAcknowledgedResponseIntl(Channel reader, CancellationToken token) @@ -138,9 +159,7 @@ private static async Task SuccessfulAck(Channel reader, Cancellatio Frame f; do { - f = await reader.Reader.ReadAsync(token); - if (f.Type == FrameType.CAN) - await Task.Delay(100, token); + f = await reader.Reader.ReadAsync(token).ConfigureAwait(false); } while (f.Type == FrameType.SOF); return f.Type == FrameType.ACK; } diff --git a/ZWaveDotNet/SerialAPI/Port.cs b/ZWaveDotNet/SerialAPI/Port.cs index 2eb8d78..1797cca 100644 --- a/ZWaveDotNet/SerialAPI/Port.cs +++ b/ZWaveDotNet/SerialAPI/Port.cs @@ -18,8 +18,8 @@ public Port(string path) port.Open(); Reset(); - Task.Factory.StartNew(WriteTask, TaskCreationOptions.LongRunning); - Task.Factory.StartNew(ReadTask, TaskCreationOptions.LongRunning); + _ = Task.Factory.StartNew(WriteTask, TaskCreationOptions.LongRunning); + _ = Task.Factory.StartNew(ReadTask, TaskCreationOptions.LongRunning); } public bool IsConnected()