29 internal const byte ConfigurationAddress = 0x7f;
31 private readonly
object _lockBusCreation =
new ();
32 private readonly ConcurrentDictionary<Guid, Bus> _buses =
new();
33 private readonly ILogger<ControlPanel> _logger;
34 private readonly BlockingCollection<ReplyTracker> _replies =
new();
35 private TimeSpan _replyResponseTimeout = TimeSpan.FromSeconds(8);
36 private readonly ConcurrentDictionary<int, SemaphoreSlim> _requestLocks =
new();
37 private readonly TimeSpan _timeToWaitToCheckOnData = TimeSpan.FromMilliseconds(10);
38 private readonly IDeviceProxyFactory _deviceProxyFactory;
39 private CancellationTokenSource _shutdownCts =
new();
40 private int _inFlightCommands;
44 [Obsolete(
"Sending a ILogger is deprecated, please send ILoggerFactory instead.")]
45 public ControlPanel(ILogger<ControlPanel> logger) : this(null, null, logger) { }
49 public ControlPanel(ILoggerFactory loggerFactory =
null) : this(null, loggerFactory, null) { }
51 internal ControlPanel(IDeviceProxyFactory deviceProxyFactory, ILoggerFactory loggerFactory =
null)
52 : this(deviceProxyFactory, loggerFactory, null) { }
54 private ControlPanel(IDeviceProxyFactory deviceProxyFactory, ILoggerFactory loggerFactory,
55 ILogger<ControlPanel> logger)
59 ?? NullLoggerFactory.Instance.CreateLogger<
ControlPanel>();
60 _deviceProxyFactory = deviceProxyFactory ??
new DeviceProxyFactory();
62 Task.Factory.StartNew(() =>
64 foreach (var reply
in _replies.GetConsumingEnumerable())
66 OnReplyReceived(reply);
68 }, TaskCreationOptions.LongRunning);
86 StartConnection(connection, pollInterval, isTracing ? OSDPFileCaptureTracer.Trace : _ => { });
110 var newBus = CreateBus(connection, pollInterval, tracer);
111 newBus.ConnectionStatusChanged += BusOnConnectionStatusChanged;
112 newBus.StartPolling();
122 private Bus CreateBus(
IOsdpConnection connection, TimeSpan pollInterval, Action<TraceEntry> tracer)
125 lock (_lockBusCreation)
127 var existingBusWithThisConnection = _buses.Values
128 .FirstOrDefault(bus => bus.Connection == connection);
129 if (existingBusWithThisConnection !=
null)
130 throw new InvalidOperationException(
131 $
"The IOsdpConnection is already active in connection {existingBusWithThisConnection.Id}. " +
132 "That connection must be stopped before starting a new one.");
134 var newBus =
new Bus(
142 _buses[newBus.Id] = newBus;
153 if (!_buses.TryGetValue(connectionId, out Bus bus))
161 bus.ConnectionStatusChanged -= BusOnConnectionStatusChanged;
162 await bus.Close().ConfigureAwait(
false);
164 foreach (
byte address
in bus.ConfigureDeviceAddresses)
167 OnConnectionStatusChanged(bus.Id, address,
false,
false);
168 _requestLocks.TryRemove(
new { connectionId, address }.GetHashCode(), out _);
171 _buses.TryRemove(connectionId, out bus);
174 private void BusOnConnectionStatusChanged(
object sender, Bus.ConnectionStatusEventArgs eventArgs)
176 if (sender is Bus bus)
177 OnConnectionStatusChanged(bus.Id, eventArgs.Address, eventArgs.IsConnected,
178 eventArgs.IsSecureSessionEstablished);
189 await SendCommand(connectionId, address, command).ConfigureAwait(
false);
200 if (!_buses.TryGetValue(connectionId, out var bus))
202 throw new ArgumentException(
"Connection could not be found", nameof(connectionId));
205 bus.SendCommand(address, command);
212 public async Task<DeviceIdentification>
IdReport(Guid connectionId,
byte address) =>
213 await
IdReport(connectionId, address, CancellationToken.None).ConfigureAwait(
false);
220 public async Task<DeviceIdentification>
IdReport(Guid connectionId,
byte address, CancellationToken cancellationToken)
222 var message = await SendCommand(
226 cancellationToken).ConfigureAwait(
false);
238 public async Task<ExtendedDeviceIdentification>
ExtendedIdReport(Guid connectionId,
byte address,
239 TimeSpan timeout, CancellationToken cancellationToken =
default)
241 using var scope = await BeginCommandAsync(connectionId, address, timeout, cancellationToken);
242 return await WaitForExtendedIdData(connectionId, address, timeout, scope.Token);
245 private async Task<T> WaitForMultiPartData<T>(
250 Func<
byte[], T> parser,
251 Action<EventHandler<MultiPartMessageDataReplyEventArgs>> subscribe,
252 Action<EventHandler<MultiPartMessageDataReplyEventArgs>> unsubscribe,
253 CancellationToken cancellationToken)
255 bool complete =
false;
258 void Handler(
object sender, MultiPartMessageDataReplyEventArgs args)
261 if (args.ConnectionId != connectionId || args.Address != address)
return;
263 var fragment = args.DataFragmentResponse;
264 data ??=
new byte[fragment.WholeMessageLength];
266 complete =
Message.BuildMultiPartMessageData(fragment.WholeMessageLength, fragment.Offset,
267 fragment.LengthOfFragment, fragment.Data, data);
271 SetReceivingMultipartMessaging(connectionId, address,
true);
275 await SendCommand(connectionId, address, command, cancellationToken, throwOnNak:
false)
276 .ConfigureAwait(
false);
278 DateTime endTime = DateTime.UtcNow + timeout;
280 while (DateTime.UtcNow <= endTime)
287 await Task.Delay(_timeToWaitToCheckOnData, cancellationToken);
290 throw new TimeoutException(
"Timeout waiting to receive multi-part data.");
294 unsubscribe(Handler);
295 SetReceivingMultipartMessaging(connectionId, address,
false);
299 private async Task<ExtendedDeviceIdentification> WaitForExtendedIdData(Guid connectionId,
byte address,
300 TimeSpan timeout, CancellationToken cancellationToken)
302 return await WaitForMultiPartData(
306 new IdReport(requestExtended:
true),
307 data => ExtendedDeviceIdentification.ParseData(data),
308 handler => ExtendedIdReplyReceived += handler,
309 handler => ExtendedIdReplyReceived -= handler,
310 cancellationToken).ConfigureAwait(
false);
331 public async Task<LocalStatus>
LocalStatus(Guid connectionId,
byte address)
336 new NoPayloadCommandData(
CommandType.LocalStatus)).ConfigureAwait(
false)).Payload);
345 public async Task<InputStatus>
InputStatus(Guid connectionId,
byte address)
350 new NoPayloadCommandData(
CommandType.InputStatus)).ConfigureAwait(
false)).Payload);
359 public async Task<OutputStatus>
OutputStatus(Guid connectionId,
byte address)
364 new NoPayloadCommandData(
CommandType.OutputStatus)).ConfigureAwait(
false)).Payload);
377 CancellationToken cancellationToken =
default)
379 using var scope = await BeginCommandAsync(connectionId, address, timeout, cancellationToken);
380 return await WaitForPIVData(connectionId, address, getPIVData, timeout, scope.Token);
383 private SemaphoreSlim GetRequestLock(Guid connectionId,
byte address)
385 int hash =
new { connectionId, address }.GetHashCode();
387 if (_requestLocks.TryGetValue(hash, out var requestLock))
392 var newRequestLock =
new SemaphoreSlim(1, 1);
393 _requestLocks[hash] = newRequestLock;
394 return newRequestLock;
397 private async Task<CommandScope> BeginCommandAsync(Guid connectionId,
byte address, TimeSpan timeout,
398 CancellationToken cancellationToken)
401 CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _shutdownCts.Token);
402 Interlocked.Increment(ref _inFlightCommands);
405 var requestLock = GetRequestLock(connectionId, address);
406 if (!await requestLock.WaitAsync(timeout, linkedCts.Token).ConfigureAwait(
false))
408 throw new TimeoutException(
"Timeout waiting for another request to complete.");
410 return new CommandScope(
this, requestLock, linkedCts);
415 Interlocked.Decrement(ref _inFlightCommands);
420 private sealed class CommandScope : IDisposable
423 private readonly SemaphoreSlim _requestLock;
424 private readonly CancellationTokenSource _linkedCts;
426 public CommandScope(
ControlPanel owner, SemaphoreSlim requestLock, CancellationTokenSource linkedCts)
429 _requestLock = requestLock;
430 _linkedCts = linkedCts;
433 public CancellationToken Token => _linkedCts.Token;
435 public void Dispose()
437 _requestLock.Release();
438 _linkedCts.Dispose();
439 Interlocked.Decrement(ref _owner._inFlightCommands);
443 private async Task<byte[]> WaitForPIVData(Guid connectionId,
byte address,
GetPIVData getPIVData, TimeSpan timeout,
444 CancellationToken cancellationToken)
446 return await WaitForMultiPartData(
452 handler => PIVDataReplyReceived += handler,
453 handler => PIVDataReplyReceived -= handler,
454 cancellationToken).ConfigureAwait(
false);
463 public async Task<ReaderStatus>
ReaderStatus(Guid connectionId,
byte address)
466 new NoPayloadCommandData(
CommandType.ReaderStatus)).ConfigureAwait(
false)).Payload);
480 var reply = await SendCommand(connectionId, address, manufacturerSpecific).ConfigureAwait(
false);
482 return new ReturnReplyData<ManufacturerSpecific>
499 public async Task<ReturnReplyData<ExtendedRead>>
ExtendedWriteData(Guid connectionId,
byte address,
502 var reply = await SendCommand(connectionId, address, extendedWrite).ConfigureAwait(
false);
504 return new ReturnReplyData<ExtendedRead>
525 CancellationToken cancellationToken =
default)
527 using var scope = await BeginCommandAsync(connectionId, address, timeout, cancellationToken);
528 return await WaitForManufactureResponse(connectionId, address, manufacturerSpecific, maximumFragmentSize,
532 private async Task<ReturnReplyData<ManufacturerSpecific>> WaitForManufactureResponse(Guid connectionId,
byte address,
535 SetReceivingMultipartMessaging(connectionId, address,
true);
537 var manufactureCommandCode = manufacturerSpecific.Data.Skip(0).Take(1).ToArray();
538 var manufactureCommandData = manufacturerSpecific.Data.Skip(1).ToArray();
539 ushort totalSize = (ushort)manufactureCommandData.Length;
541 bool continueTransfer =
true;
546 while (!cancellationToken.IsCancellationRequested && continueTransfer)
548 ushort fragmentSize = (ushort)Math.Min(maximumFragmentSize, totalSize - offset);
550 manufacturerSpecific.VendorCode, manufactureCommandCode.Concat(
552 manufactureCommandData.Skip(offset).Take(fragmentSize).ToArray(),
554 .ToArray()).ToArray()), cancellationToken)
555 .ConfigureAwait(
false);
559 offset += maximumFragmentSize;
562 continueTransfer = offset < totalSize;
567 throw new Exception(
"Command didn't return a valid reply");
570 return new ReturnReplyData<ManufacturerSpecific>
578 SetReceivingMultipartMessaging(connectionId, address,
false);
589 public async Task<ReturnReplyData<OutputStatus>>
OutputControl(Guid connectionId,
byte address,
592 var reply = await SendCommand(connectionId, address, outputControls).ConfigureAwait(
false);
594 return new ReturnReplyData<OutputStatus>
612 var reply = await SendCommand(connectionId, address, readerLedControls).ConfigureAwait(
false);
614 return reply.Type == (byte)
ReplyType.Ack;
627 var reply = await SendCommand(connectionId, address, readerBuzzerControl).ConfigureAwait(
false);
629 return reply.Type == (byte)
ReplyType.Ack;
641 var reply = await SendCommand(connectionId, address, readerTextOutput).ConfigureAwait(
false);
643 return reply.Type == (byte)
ReplyType.Ack;
656 if (_buses.Any(bus =>
657 bus.Key == connectionId &&
658 address != communicationConfiguration.
Address &&
659 bus.Value.ConfigureDeviceAddresses.Any(configuredAddress =>
660 configuredAddress == communicationConfiguration.
Address)))
662 throw new Exception(
"Address is already configured on the bus.");
666 (await SendCommand(connectionId, address, communicationConfiguration).ConfigureAwait(
false))
680 var reply = await SendCommand(connectionId, address, encryptionKeyConfiguration).ConfigureAwait(
false);
682 return reply.Type == (byte)
ReplyType.Ack;
698 Action<FileTransferStatus> callback, CancellationToken cancellationToken =
default)
700 var bus = _buses[connectionId];
701 return Task.Run(async () =>
703 bus.SetSendingMultipartMessage(address,
true);
706 return await SendFileTransferCommands(connectionId, address, fileType, fileData, fragmentSize, callback,
711 bus.SetSendingMultipartMessage(address,
false);
712 bus.SetSendingMultiMessageNoSecureChannel(address,
false);
714 }, cancellationToken);
718 byte address,
byte fileType,
byte[] fileData,
719 ushort fragmentSize, Action<FileTransferStatus> callback, CancellationToken cancellationToken)
721 int totalSize = fileData.Length;
723 bool isFirstMessage =
true;
731 cancellationToken.ThrowIfCancellationRequested();
734 var nextFragmentSize = (ushort)Math.Min(fragmentSize, totalSize - offset);
736 var reply = await SendCommand(connectionId, address,
739 fileData.Skip(offset).Take(nextFragmentSize).ToArray(),
741 cancellationToken, throwOnNak:
false)
742 .ConfigureAwait(
false);
747 isFirstMessage =
false;
752 offset += nextFragmentSize;
755 var fileTransferStatusReply = reply.Type == (byte)
ReplyType.FileTransferStatus
760 if (fileTransferStatusReply !=
null)
763 if ((fileTransferStatusReply.Action &
767 _buses[connectionId].SetSendingMultiMessageNoSecureChannel(address,
true);
771 if (fileTransferStatusReply is { RequestedDelay: > 0 })
773 _buses[connectionId].SetRequestDelay(address,
774 DateTime.UtcNow.AddMilliseconds(fileTransferStatusReply.RequestedDelay));
778 if (fileTransferStatusReply is { UpdateMessageMaximum: > 0 })
780 fragmentSize = Message.CalculateMaximumMessageSize(fileTransferStatusReply.UpdateMessageMaximum,
781 reply.IsSecureMessage);
786 fileTransferStatusReply?.Detail ?? Model.ReplyData.FileTransferStatus.StatusDetail.UnknownError,
788 reply.Type == (
byte)
ReplyType.Nak ?
Nak.ParseData(reply.Payload) :
null);
793 switch (status.Status)
799 case Model.ReplyData.FileTransferStatus.StatusDetail.FinishingFileTransfer:
804 if (offset >= totalSize)
807 return status.Status;
827 CancellationToken cancellationToken =
default)
829 using var scope = await BeginCommandAsync(connectionId, address, timeout, cancellationToken);
830 return await WaitForBiometricRead(connectionId, address, biometricReadData, timeout, scope.Token);
833 private async Task<BiometricReadResult> WaitForBiometricRead(Guid connectionId,
byte address,
835 CancellationToken cancellationToken)
837 bool complete =
false;
840 void Handler(
object sender, BiometricReadResultsReplyEventArgs args)
843 if (args.ConnectionId != connectionId || args.Address != address)
return;
847 result = args.BiometricReadResult;
850 BiometricReadResultsReplyReceived += Handler;
851 SetReceivingMultipartMessaging(connectionId, address,
true);
855 await SendCommand(connectionId, address, biometricReadData, cancellationToken, throwOnNak:
false)
856 .ConfigureAwait(
false);
858 DateTime endTime = DateTime.UtcNow + timeout;
860 while (DateTime.UtcNow <= endTime)
867 await Task.Delay(_timeToWaitToCheckOnData, cancellationToken);
870 throw new TimeoutException(
"Timeout waiting to for biometric read data.");
874 BiometricReadResultsReplyReceived -= Handler;
875 SetReceivingMultipartMessaging(connectionId, address,
false);
891 CancellationToken cancellationToken =
default)
893 using var scope = await BeginCommandAsync(connectionId, address, timeout, cancellationToken);
894 return await WaitForBiometricMatch(connectionId, address, biometricTemplateData, timeout, scope.Token);
897 private async Task<BiometricMatchResult> WaitForBiometricMatch(Guid connectionId,
byte address,
899 CancellationToken cancellationToken)
901 bool complete =
false;
904 void Handler(
object sender, BiometricMatchReplyEventArgs args)
907 if (args.ConnectionId != connectionId || args.Address != address)
return;
911 result = args.BiometricMatchResult;
914 BiometricMatchReplyReceived += Handler;
915 SetReceivingMultipartMessaging(connectionId, address,
true);
919 await SendCommand(connectionId, address, biometricTemplateData, cancellationToken, throwOnNak:
false)
920 .ConfigureAwait(
false);
922 DateTime endTime = DateTime.UtcNow + timeout;
924 while (DateTime.UtcNow <= endTime)
931 await Task.Delay(_timeToWaitToCheckOnData, cancellationToken);
934 throw new TimeoutException(
"Timeout waiting to for biometric match.");
938 BiometricMatchReplyReceived -= Handler;
939 SetReceivingMultipartMessaging(connectionId, address,
false);
956 byte algorithm,
byte key,
byte[] challenge, ushort maximumFragmentSize, TimeSpan timeout,
957 CancellationToken cancellationToken =
default)
959 using var scope = await BeginCommandAsync(connectionId, address, timeout, cancellationToken);
960 return await WaitForChallengeResponse(connectionId, address, algorithm, key, challenge, maximumFragmentSize,
961 timeout, scope.Token);
964 private async Task<byte[]> WaitForChallengeResponse(Guid connectionId,
byte address,
byte algorithm,
byte key,
965 byte[] challenge, ushort maximumFragmentSize, TimeSpan timeout,
966 CancellationToken cancellationToken)
968 bool complete =
false;
969 var requestData =
new List<byte> { algorithm, key };
970 requestData.AddRange(challenge);
971 byte[] responseData =
null;
973 void Handler(
object sender, MultiPartMessageDataReplyEventArgs args)
976 if (args.ConnectionId != connectionId || args.Address != address)
return;
978 var dataFragmentResponse = args.DataFragmentResponse;
979 responseData ??=
new byte[dataFragmentResponse.WholeMessageLength];
981 complete =
Message.BuildMultiPartMessageData(dataFragmentResponse.WholeMessageLength, dataFragmentResponse.Offset,
982 dataFragmentResponse.LengthOfFragment, dataFragmentResponse.Data, responseData);
985 AuthenticationChallengeResponseReceived += Handler;
986 SetReceivingMultipartMessaging(connectionId, address,
true);
988 ushort totalSize = (ushort)requestData.Count;
990 bool continueTransfer =
true;
994 while (!cancellationToken.IsCancellationRequested && continueTransfer)
996 ushort fragmentSize = (ushort)Math.Min(maximumFragmentSize, totalSize - offset);
997 await SendCommand(connectionId, address,
new AuthenticationChallengeFragment(
999 requestData.Skip(offset).Take((ushort)Math.Min(fragmentSize, totalSize - offset))
1002 .ConfigureAwait(
false);
1004 offset += fragmentSize;
1007 continueTransfer = offset < totalSize;
1010 DateTime endTime = DateTime.UtcNow + timeout;
1012 while (DateTime.UtcNow <= endTime)
1016 return responseData;
1019 await Task.Delay(_timeToWaitToCheckOnData, cancellationToken);
1022 throw new TimeoutException(
"Timeout waiting to receive challenge response.");
1026 AuthenticationChallengeResponseReceived -= Handler;
1027 SetReceivingMultipartMessaging(connectionId, address,
false);
1038 public async Task<bool>
ACUReceiveSize(Guid connectionId,
byte address, ushort maximumReceiveSize)
1040 var reply = await SendCommand(connectionId, address,
new ACUReceiveSize(maximumReceiveSize))
1041 .ConfigureAwait(
false);
1043 return reply.Type == (byte)
ReplyType.Ack;
1053 public async Task<bool>
KeepReaderActive(Guid connectionId,
byte address, ushort keepAliveTimeInMilliseconds)
1055 var reply = await SendCommand(connectionId, address,
new KeepReaderActive(keepAliveTimeInMilliseconds))
1056 .ConfigureAwait(
false);
1058 return reply.Type == (byte)
ReplyType.Ack;
1069 var reply = await SendCommand(connectionId, address,
new NoPayloadCommandData(
CommandType.Abort))
1070 .ConfigureAwait(
false);
1072 return reply.Type == (byte)
ReplyType.Ack;
1083 return _buses[connectionId].IsOnline(address);
1086 private void SetReceivingMultipartMessaging(Guid connectionId,
byte address,
bool isReceivingMultipartMessaging)
1088 if (!_buses.TryGetValue(connectionId, out var bus))
1090 throw new ArgumentException(
"Connection could not be found", nameof(connectionId));
1093 bus.SetReceivingMultipartMessage(address, isReceivingMultipartMessaging);
1096 private async Task<IncomingMessage> SendCommand(Guid connectionId,
byte address,
CommandData command,
1097 CancellationToken cancellationToken =
default,
bool throwOnNak =
true)
1099 var source =
new TaskCompletionSource<IncomingMessage>();
1101 void EventHandler(
object sender, ReplyEventArgs replyEventArgs)
1103 var reply = replyEventArgs.Reply;
1104 if (!reply.MatchIssuingCommand(address, command.
Code))
return;
1106 if (throwOnNak && replyEventArgs.Reply.ReplyMessage.Type == (
byte)
ReplyType.Nak)
1108 source.SetException(
new NackReplyException(
Nak.ParseData(reply.ReplyMessage.Payload)));
1112 source.SetResult(reply.ReplyMessage);
1116 if (!_buses.TryGetValue(connectionId, out var bus))
1118 throw new ArgumentException(
"Connection could not be found", nameof(connectionId));
1121 ReplyReceived += EventHandler;
1125 bus.SendCommand(address, command);
1127 var task = await Task.WhenAny(
1128 source.Task, Task.Delay(_replyResponseTimeout, cancellationToken)).ConfigureAwait(
false);
1130 if (source.Task != task)
1132 throw task.IsCanceled
1133 ?
new OperationCanceledException(cancellationToken)
1134 : new TimeoutException();
1137 return await source.Task;
1141 ReplyReceived -= EventHandler;
1150 var shutdownCts = _shutdownCts;
1151 shutdownCts.Cancel();
1153 foreach (var bus
in _buses.Values)
1155 bus.ConnectionStatusChanged -= BusOnConnectionStatusChanged;
1156 await bus.Close().ConfigureAwait(
false);
1158 foreach (
byte address
in bus.ConfigureDeviceAddresses)
1160 OnConnectionStatusChanged(bus.Id, address,
false,
false);
1166 while (Volatile.Read(ref _inFlightCommands) > 0)
1168 await Task.Delay(_timeToWaitToCheckOnData).ConfigureAwait(
false);
1171 foreach (var requestLock
in _requestLocks.Values)
1173 requestLock.Dispose();
1175 _requestLocks.Clear();
1177 _shutdownCts =
new CancellationTokenSource();
1178 shutdownCts.Dispose();
1180 OSDPFileCaptureTracer.CloseAllWriters();
1190 if (_buses.TryGetValue(connectionId, out Bus bus))
1192 bus.ResetDevice(address);
1204 public void AddDevice(Guid connectionId,
byte address,
bool useCrc,
bool useSecureChannel,
byte[] secureChannelKey =
null)
1206 if (!_buses.TryGetValue(connectionId, out Bus foundBus))
1208 throw new ArgumentException(
"Connection could not be found", nameof(connectionId));
1213 throw new ArgumentOutOfRangeException(nameof(address),
"Address is out of range, it must between 0 and 127.");
1216 foundBus.AddDevice(address, useCrc, useSecureChannel, useSecureChannel ? secureChannelKey :
null);
1226 if (_buses.TryGetValue(connectionId, out Bus bus))
1228 bus.RemoveDevice(address);
1250 Guid connectionId = Guid.Empty;
1258 result.Status = status;
1259 options.ProgressCallback?.Invoke(result);
1262 async Task<DeviceIdentification> TryIdReport(
byte address)
1266 return await
IdReport(connectionId, address, options.CancellationToken).ConfigureAwait(
false);
1268 catch (TimeoutException)
1274 async Task<bool> FindConnectionWithActiveDevice()
1276 bool isFirst =
true;
1282 await Task.Delay(options.ReconnectDelay, options.CancellationToken);
1295 connectionId =
StartConnection(connection, TimeSpan.Zero, options.Tracer ?? (_ => {}));
1297 AddDevice(connectionId, ConfigurationAddress,
true,
false);
1299 result.Connection = connection;
1302 var deviceIdentification = await TryIdReport(ConfigurationAddress).ConfigureAwait(
false);
1303 if (deviceIdentification !=
null)
1305 result.Id = deviceIdentification;
1315 result.Connection =
null;
1319 async Task<bool> FindDeviceAddress()
1322 for (
byte address = 0; address < ConfigurationAddress; address++)
1324 AddDevice(connectionId, address,
true,
false);
1326 result.Address = address;
1328 var deviceIdentification = await TryIdReport(address).ConfigureAwait(
false);
1330 if (deviceIdentification !=
null)
1332 result.Id = deviceIdentification;
1343 result.Address = ConfigurationAddress;
1347 async Task CheckForDefaultSecurityKey()
1349 var waitForSecureConnection =
new TaskCompletionSource<bool>();
1355 waitForSecureConnection.SetResult(
true);
1368 options.Tracer ?? (_ => { }));
1371 using var waitForTask = await Task.WhenAny(waitForSecureConnection.Task,
1372 Task.Delay(TimeSpan.FromSeconds(8), options.CancellationToken));
1373 result.UsesDefaultSecurityKey = waitForTask == waitForSecureConnection.Task;
1387 var origResponseTimeout = _replyResponseTimeout;
1388 _replyResponseTimeout = options.ResponseTimeout;
1392 if (!_buses.IsEmpty)
1397 if (!await FindConnectionWithActiveDevice().ConfigureAwait(
false))
1403 if (!await FindDeviceAddress().ConfigureAwait(
false))
1406 $
"Unable to determine address of device that responded to a configuration address on baud rate {result.Connection.BaudRate}");
1415 connectionId = Guid.Empty;
1417 await CheckForDefaultSecurityKey().ConfigureAwait(
false);
1422 catch (Exception exception)
1424 result.Error = exception;
1425 UpdateStatus(exception is OperationCanceledException
1431 if (connectionId != Guid.Empty) await
StopConnection(connectionId).ConfigureAwait(
false);
1432 _replyResponseTimeout = origResponseTimeout;
1438 private void OnConnectionStatusChanged(Guid connectionId,
byte address,
bool isConnected,
1439 bool isSecureChannelEstablished)
1442 new ConnectionStatusEventArgs(connectionId, address, isConnected, isSecureChannelEstablished));
1445 private void OnReplyReceived(ReplyTracker reply)
1450 ReplyReceived?.Invoke(
this,
new ReplyEventArgs {
Reply = reply });
1458 new NakReplyEventArgs(reply.ConnectionId, reply.ReplyMessage.
Address,
1463 new LocalStatusReportReplyEventArgs(reply.ConnectionId,
1469 new InputStatusReportReplyEventArgs(reply.ConnectionId,
1475 new OutputStatusReportReplyEventArgs(reply.ConnectionId,
1481 new ReaderStatusReportReplyEventArgs(reply.ConnectionId,
1487 new RawCardDataReplyEventArgs(reply.ConnectionId,
1493 new FormattedCardDataReplyEventArgs(reply.ConnectionId,
1499 new ManufacturerSpecificReplyEventArgs(reply.ConnectionId,
1505 new ExtendedReadReplyEventArgs(reply.ConnectionId,
1510 PIVDataReplyReceived?.Invoke(
this,
1511 new MultiPartMessageDataReplyEventArgs(reply.ConnectionId,
1516 ExtendedIdReplyReceived?.Invoke(
this,
1517 new MultiPartMessageDataReplyEventArgs(reply.ConnectionId,
1522 AuthenticationChallengeResponseReceived?.Invoke(
this,
1523 new MultiPartMessageDataReplyEventArgs(reply.ConnectionId,
1529 new KeypadReplyEventArgs(reply.ConnectionId, reply.ReplyMessage.
Address,
1533 BiometricReadResultsReplyReceived?.Invoke(
this,
1534 new BiometricReadResultsReplyEventArgs(reply.ConnectionId,
1539 BiometricMatchReplyReceived?.Invoke(
this,
1540 new BiometricMatchReplyEventArgs(reply.ConnectionId,
1546 catch (Exception ex)
1548 _logger.LogError(ex,
1549 "Error in typed reply event handler for reply {ReplyType} from address {Address}",
1557 reply.ReplyMessage.
Type, reply.ReplyMessage.
Payload));
1559 catch (Exception ex)
1561 _logger.LogError(ex,
1562 "Error in raw reply event handler for reply {ReplyType} from address {Address}",
1567 private event EventHandler<ReplyEventArgs> ReplyReceived;
1612 [Obsolete(
"Use Raw Card Data for incoming card reads.")]
1633 private event EventHandler<BiometricReadResultsReplyEventArgs> BiometricReadResultsReplyReceived;
1638 private event EventHandler<BiometricMatchReplyEventArgs> BiometricMatchReplyReceived;
1643 private event EventHandler<MultiPartMessageDataReplyEventArgs> PIVDataReplyReceived;
1648 private event EventHandler<MultiPartMessageDataReplyEventArgs> ExtendedIdReplyReceived;
1653 private event EventHandler<MultiPartMessageDataReplyEventArgs> AuthenticationChallengeResponseReceived;
1702 bool isSecureChannelEstablished)
1732 $
"{ConnectionId}:{Address} - Conn: {IsConnected}; Sec: {IsSecureChannelEstablished}";
2010 private class MultiPartMessageDataReplyEventArgs : EventArgs
2018 public MultiPartMessageDataReplyEventArgs(Guid connectionId,
byte address,
DataFragmentResponse dataFragmentResponse)
2020 ConnectionId = connectionId;
2028 public Guid ConnectionId {
get; }
2033 public byte Address {
get; }
2038 public DataFragmentResponse DataFragmentResponse {
get; }
2078 private class BiometricReadResultsReplyEventArgs : EventArgs
2086 public BiometricReadResultsReplyEventArgs(Guid connectionId,
byte address,
2089 ConnectionId = connectionId;
2097 public Guid ConnectionId {
get; }
2102 public byte Address {
get; }
2107 public BiometricReadResult BiometricReadResult {
get; }
2113 private class BiometricMatchReplyEventArgs : EventArgs
2121 public BiometricMatchReplyEventArgs(Guid connectionId,
byte address,
2122 BiometricMatchResult biometricMatchResult)
2124 ConnectionId = connectionId;
2126 BiometricMatchResult = biometricMatchResult;
2132 public Guid ConnectionId {
get; }
2137 public byte Address {
get; }
2142 public BiometricMatchResult BiometricMatchResult {
get; }
2183 public class FileTransferException : Exception
2208 public RawReplyEventArgs(Guid connectionId,
byte address,
byte replyType, ReadOnlyMemory<byte> payload)
2237 private class ReplyEventArgs : EventArgs
2239 public ReplyTracker Reply {
get;
set; }