Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,14 @@ private static async Task Main()

RegisterEvents();

Application.Run();
try
{
Application.Run();
}
catch
{
// ignored
}

await _controlPanel.Shutdown();
}
Expand Down Expand Up @@ -502,8 +509,17 @@ void ParseButtonClicked()
return;
}

var builder = BuildTextFromEntries(PacketDecoding.OSDPCapParser(json, key).Where(entry =>
FilterAddress(entry, address) && IgnorePollsAndAcks(entry)));
StringBuilder builder;
try
{
builder = BuildTextFromEntries(PacketDecoding.OSDPCapParser(json, key).Where(entry =>
FilterAddress(entry, address) && IgnorePollsAndAcks(entry)));
}
catch (Exception exception)
{
MessageBox.ErrorQuery(40, 10, "Error", $"Unable to parse. {exception.Message}", "OK");
return;
}

var saveDialog = new SaveDialog("Save Parsed File",
"Successfully completed parsing of file, select location to save file.", new List<string> { ".txt" });
Expand Down
14 changes: 7 additions & 7 deletions src/OSDP.Net.Tests/ControlPanelTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public async Task DeviceGoesOnlineTest()
{
// Arrange
var mockConnection = new MockConnection();

var panel = new ControlPanel(NullLoggerFactory.Instance);
Guid id = panel.StartConnection(mockConnection.Object);
panel.AddDevice(id, 0, true, false);
Expand All @@ -40,7 +40,7 @@ public async Task ShutdownTest()
{
// Arrange
var mockConnection = new MockConnection();

var panel = new ControlPanel(NullLoggerFactory.Instance);
Guid id = panel.StartConnection(mockConnection.Object);
panel.AddDevice(id, 0, true, false);
Expand Down Expand Up @@ -193,11 +193,11 @@ public async Task ReturnsValidReportTest()
var response = await panel.IdReport(id, 0);

Assert.That(response.ToString(), Is.EqualTo(
" Vendor Code: 5C-26-23\r\n" +
" Model Number: 25\r\n" +
" Version: 2\r\n" +
" Serial Number: 00-00-E9-2A\r\n" +
"Firmware Version: 3.0.0\r\n"
$" Vendor Code: 5C-26-23{Environment.NewLine}" +
$" Model Number: 25{Environment.NewLine}" +
$" Version: 2{Environment.NewLine}" +
$" Serial Number: 00-00-E9-2A{Environment.NewLine}" +
$"Firmware Version: 3.0.0{Environment.NewLine}"
));
}

Expand Down
13 changes: 6 additions & 7 deletions src/OSDP.Net.Tests/IntegrationTests/PeripheryDeviceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
namespace OSDP.Net.Tests.IntegrationTests;

//
// NOTE: Majority of naming/structure in this file is very much a work-in-progress
// NOTE: The Majority of naming/structure in this file is very much a work-in-progress
// and will be updated if we continue to build out a set of integration tests
//
// Presently this is a POC experiment to see how far we can take a test harness that
Expand All @@ -24,7 +24,7 @@ namespace OSDP.Net.Tests.IntegrationTests;
// need to be added to make it easy/clear to write assertions which wait for certain
// events to occur (e.g. device came online).
//
// NOTE: Integration tests by nature are SLOWER than unit tests. Hence why they are
// NOTE: Integration tests by nature are SLOWER than unit tests. Hence, why they are
// tagged with "Integration" category as we might want to exclude them at some point if
// the default PR test checks become too slow. There's only 5 tests here and they already
// take 25 sec to run. Then again, this might also highlight improvement opportunity
Expand Down Expand Up @@ -52,11 +52,11 @@ public class PeripheryDeviceTest : IntegrationTestFixtureBase
//// PD doesn't require Security; ACU doesn't use secure channel; two sides use different keys ==> OK
new (IntegrationConsts.NonDefaultSCBK, IntegrationConsts.DefaultSCBK, false, false, true),

//// PD doesn't requires Security; ACU opens secure channel; two sides use different keys ==> NO
//// PD doesn't require Security; ACU opens secure channel; two sides use different keys ==> NO
new (IntegrationConsts.NonDefaultSCBK, IntegrationConsts.DefaultSCBK, false, true, false),
new (IntegrationConsts.DefaultSCBK, IntegrationConsts.NonDefaultSCBK, false, true, false),

//// PD doesn't requires Security; ACU opens secure channel; both sides use same key ==> OK
//// PD doesn't require Security; ACU opens secure channel; both sides use same key ==> OK
new (IntegrationConsts.NonDefaultSCBK, IntegrationConsts.NonDefaultSCBK, false, true, true),
new (IntegrationConsts.DefaultSCBK, IntegrationConsts.DefaultSCBK, false, true, true),
];
Expand Down Expand Up @@ -212,16 +212,15 @@ protected override PayloadData HandleIdReport()

protected override PayloadData HandleDeviceCapabilities()
{
var deviceCapabilities = new DeviceCapabilities(new[]
{
var deviceCapabilities = new DeviceCapabilities([
new DeviceCapability(CapabilityFunction.CardDataFormat, 1, 0),
new DeviceCapability(CapabilityFunction.ReaderLEDControl, 1, 0),
new DeviceCapability(CapabilityFunction.ReaderTextOutput, 0, 0),
new DeviceCapability(CapabilityFunction.CheckCharacterSupport, 1, 0),
new DeviceCapability(CapabilityFunction.CommunicationSecurity, 1, 1),
new DeviceCapability(CapabilityFunction.ReceiveBufferSize, 0, 1),
new DeviceCapability(CapabilityFunction.OSDPVersion, 2, 0)
});
]);

return deviceCapabilities;
}
Expand Down
30 changes: 15 additions & 15 deletions src/OSDP.Net.Tests/Model/ReplyData/DeviceCapabilitiesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public void ThrowsWhenDataWrongLength()
[Test]
public void ParsesOutFunctionCodes()
{
#pragma warning disable CS0618 // Type or member is obsolete
var expectedFuncCodes = new []
#pragma warning disable CS0618 // Type or member is obsolete
var expectedFuncCodes = new[]
{
CapabilityFunction.CardDataFormat,
CapabilityFunction.ReaderLEDControl,
Expand All @@ -38,10 +38,10 @@ public void ParsesOutFunctionCodes()
CapabilityFunction.CommunicationSecurity,
CapabilityFunction.ReceiveBufferSize,
CapabilityFunction.Biometrics,
CapabilityFunction.SecurePINEntry,
CapabilityFunction.SecurePINEntry,
CapabilityFunction.OSDPVersion
};
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning restore CS0618 // Type or member is obsolete

var actual = DeviceCapabilities.ParseData(_rawCapsFromDennisBrivoKeypad);

Expand All @@ -64,17 +64,17 @@ public void ToStringTest()
{
var actual = DeviceCapabilities.ParseData(_rawCapsFromDennisBrivoKeypad.AsSpan().Slice(18, 9));
var expectedText =
" Function: Communication Security\r\n" +
"Supports AES-128: True\r\n" +
"Uses Default Key: True\r\n" +
"\r\n" +
" Function: Receive Buffer Size\r\n" +
" Size: 450\r\n" +
"\r\n" +
" Function: Biometrics\r\n" +
"Compliance: 0\r\n" +
" Number Of: 0\r\n" +
"\r\n";
$" Function: Communication Security{Environment.NewLine}" +
$"Supports AES-128: True{Environment.NewLine}" +
$"Uses Default Key: True{Environment.NewLine}" +
Environment.NewLine +
$" Function: Receive Buffer Size{Environment.NewLine}" +
$" Size: 450{Environment.NewLine}" +
Environment.NewLine +
$" Function: Biometrics{Environment.NewLine}" +
$"Compliance: 0{Environment.NewLine}" +
$" Number Of: 0{Environment.NewLine}" +
Environment.NewLine;

Assert.That(actual.ToString(), Is.EqualTo(expectedText).NoClip);
}
Expand Down
29 changes: 29 additions & 0 deletions src/OSDP.Net.Tests/Model/ReplyData/FormattedCardDataTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using NUnit.Framework;
using OSDP.Net.Model.ReplyData;

namespace OSDP.Net.Tests.Model.ReplyData;

public class FormattedCardDataTest
{
[Test]
public void ParseData()
{
var data = new byte[] { 0x05, 0x00, 0x09, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x70, 0x75, 0x74 };

var formattedCardData = FormattedCardData.ParseData(data);

Assert.That(formattedCardData.ReaderNumber, Is.EqualTo(5));
Assert.That(formattedCardData.ReadDirection, Is.EqualTo(ReadDirection.Forward));
Assert.That(formattedCardData.Lenght, Is.EqualTo(9));
Assert.That(formattedCardData.Data, Is.EqualTo("testinput"));
}

[Test]
public void BuildData()
{
var formattedCardData = new FormattedCardData(5, ReadDirection.Forward, "testinput");
var buffer = formattedCardData.BuildData();
Assert.That(BitConverter.ToString(buffer), Is.EqualTo("05-00-09-74-65-73-74-69-6E-70-75-74"));
}
}
48 changes: 25 additions & 23 deletions src/OSDP.Net/Bus.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -30,8 +31,6 @@ internal class Bus : IDisposable

public static readonly TimeSpan DefaultPollInterval = TimeSpan.FromMilliseconds(200);

private readonly SortedSet<DeviceProxy> _configuredDevices = new ();
private readonly object _configuredDevicesLock = new ();
private readonly Dictionary<byte, bool> _lastOnlineConnectionStatus = new ();
private readonly Dictionary<byte, bool> _lastSecureConnectionStatus = new ();

Expand All @@ -44,6 +43,7 @@ internal class Bus : IDisposable
private readonly IDeviceProxyFactory _deviceProxyFactory;

private CancellationTokenSource _cancellationTokenSource;
private ImmutableSortedSet<DeviceProxy> _configuredDevices = ImmutableSortedSet<DeviceProxy>.Empty;

public Bus(IOsdpConnection connection, BlockingCollection<ReplyTracker> replies, TimeSpan pollInterval,
Action<TraceEntry> tracer, IDeviceProxyFactory deviceProxyFactory,
Expand Down Expand Up @@ -122,19 +122,20 @@ public void SendCommand(byte address, CommandData command)
/// <param name="secureChannelKey">Set the secure channel key, default is used if not specified</param>
public void AddDevice(byte address, bool useCrc, bool useSecureChannel, byte[] secureChannelKey = null)
{
lock (_configuredDevicesLock)
var configuredDevices = _configuredDevices.ToImmutableSortedSet();

var foundDevice = configuredDevices.FirstOrDefault(device => device.Address == address);

if (foundDevice != null)
{
var foundDevice = _configuredDevices.FirstOrDefault(device => device.Address == address);

if (foundDevice != null)
{
_configuredDevices.Remove(foundDevice);
}
configuredDevices = configuredDevices.Remove(foundDevice);
}

var addedDevice = _deviceProxyFactory.Create(address, useCrc, useSecureChannel, secureChannelKey);
var addedDevice = _deviceProxyFactory.Create(address, useCrc, useSecureChannel, secureChannelKey);

_configuredDevices.Add(addedDevice);
}
configuredDevices = configuredDevices.Add(addedDevice);

_configuredDevices = configuredDevices;
}

/// <summary>
Expand All @@ -143,13 +144,14 @@ public void AddDevice(byte address, bool useCrc, bool useSecureChannel, byte[] s
/// <param name="address">Address of the device</param>
public void RemoveDevice(byte address)
{
lock (_configuredDevicesLock)
{
var foundDevice = _configuredDevices.FirstOrDefault(device => device.Address == address);
if (foundDevice == null) return;

_configuredDevices.Remove(foundDevice);
}
var configuredDevices = _configuredDevices.ToImmutableSortedSet();

var foundDevice = configuredDevices.FirstOrDefault(device => device.Address == address);
if (foundDevice == null) return;

configuredDevices = configuredDevices.Remove(foundDevice);

_configuredDevices = configuredDevices;
}

/// <summary>
Expand Down Expand Up @@ -192,7 +194,7 @@ public void StartPolling()
}

/// <summary>
/// Poll the the devices on the bus
/// Poll the devices on the bus
/// </summary>
/// <returns></returns>
private async Task PollingLoop(CancellationToken cancellationToken)
Expand All @@ -211,7 +213,7 @@ private async Task PollingLoop(CancellationToken cancellationToken)
catch (Exception exception)
{
_logger?.LogError(exception, $"[{Connection}] Error while opening connection");
foreach (var device in _configuredDevices.ToArray())
foreach (var device in _configuredDevices)
{
ResetDevice(device);
UpdateConnectionStatus(device);
Expand Down Expand Up @@ -246,7 +248,7 @@ private async Task PollingLoop(CancellationToken cancellationToken)
_commandAvailableEvent.WaitOne(TimeSpan.FromMilliseconds(10));
}

foreach (var device in _configuredDevices.ToArray())
foreach (var device in _configuredDevices)
{
// Right now it always sends sequence 0
if (!IsPolling)
Expand Down Expand Up @@ -291,7 +293,7 @@ private async Task PollingLoop(CancellationToken cancellationToken)

// Prevent plain text message replies when secure channel has been established
// The busy and Nak reply types are a special case which is allowed to be sent as insecure message on a secure channel
// Workaround for KeySet command sending back an clear text Ack
// Workaround for KeySet command sending back a clear text Ack
if (reply.ReplyMessage.Type != (byte)ReplyType.Busy && reply.ReplyMessage.Type != (byte)ReplyType.Nak && device.UseSecureChannel &&
device.IsSecurityEstablished && !reply.ReplyMessage.IsSecureMessage && commandMessage.Code != (byte)CommandType.KeySet)
{
Expand Down
47 changes: 46 additions & 1 deletion src/OSDP.Net/ControlPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,7 @@ private async Task<IncomingMessage> SendCommand(Guid connectionId, byte address,
void EventHandler(object sender, ReplyEventArgs replyEventArgs)
{
var reply = replyEventArgs.Reply;
if (!reply.MatchIssuingCommand(command.Code)) return;
if (!reply.MatchIssuingCommand(address, command.Code)) return;

if (throwOnNak && replyEventArgs.Reply.ReplyMessage.Type == (byte)ReplyType.Nak)
{
Expand Down Expand Up @@ -1393,6 +1393,11 @@ private void OnReplyReceived(ReplyTracker reply)
new RawCardDataReplyEventArgs(reply.ConnectionId, reply.ReplyMessage.Address,
RawCardData.ParseData(reply.ReplyMessage.Payload)));
break;
case ReplyType.FormattedReaderData:
FormattedCardDataReplyReceived?.Invoke(this,
new FormattedCardDataReplyEventArgs(reply.ConnectionId, reply.ReplyMessage.Address,
FormattedCardData.ParseData(reply.ReplyMessage.Payload)));
break;
case ReplyType.ManufactureSpecific:
ManufacturerSpecificReplyReceived?.Invoke(this,
new ManufacturerSpecificReplyEventArgs(reply.ConnectionId, reply.ReplyMessage.Address,
Expand Down Expand Up @@ -1463,6 +1468,12 @@ private void OnReplyReceived(ReplyTracker reply)
/// </summary>
public event EventHandler<RawCardDataReplyEventArgs> RawCardDataReplyReceived;

/// <summary>
/// Occurs when formatted card data reply is received.
/// </summary>
[Obsolete("Use Raw Card Data for incoming card reads.")]
public event EventHandler<FormattedCardDataReplyEventArgs> FormattedCardDataReplyReceived;

/// <summary>
/// Occurs when manufacturer specific reply is received.
/// </summary>
Expand Down Expand Up @@ -1743,6 +1754,40 @@ public RawCardDataReplyEventArgs(Guid connectionId, byte address, RawCardData ra
public RawCardData RawCardData { get; }
}

/// <summary>
/// The formatted card data reply has been received.
/// </summary>
public class FormattedCardDataReplyEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="FormattedCardDataReplyEventArgs"/> class.
/// </summary>
/// <param name="connectionId">Identify the connection for communicating to the device.</param>
/// <param name="address">Address assigned to the device.</param>
/// <param name="formattedCardData">A formatted card data reply.</param>
public FormattedCardDataReplyEventArgs(Guid connectionId, byte address, FormattedCardData formattedCardData)
{
ConnectionId = connectionId;
Address = address;
FormattedCardData = formattedCardData;
}

/// <summary>
/// Identify the connection for communicating to the device.
/// </summary>
public Guid ConnectionId { get; }

/// <summary>
/// Address assigned to the device.
/// </summary>
public byte Address { get; }

/// <summary>
/// A formatted card data reply.
/// </summary>
public FormattedCardData FormattedCardData { get; }
}

/// <summary>
/// The manufacture specific reply has been received.
/// </summary>
Expand Down
Loading