2using System.Collections.Concurrent;
4using System.Runtime.CompilerServices;
9[assembly: InternalsVisibleTo(
"OSDP.Net.Tests")]
12internal class DeviceProxy : IComparable<DeviceProxy>
14 private const int RetryAmount = 2;
15 private const int BusyRetryAmount = 20;
17 private readonly ConcurrentQueue<CommandData> _commands =
new();
18 private readonly
bool _useSecureChannel;
20 private int _counter = RetryAmount;
21 private bool _counterIsBusyRetry;
22 private DateTime _lastValidReply = DateTime.MinValue;
23 private CommandData _retryCommand;
32 public DeviceProxy(
byte address,
bool useCrc,
bool useSecureChannel,
byte[] secureChannelKey =
null)
34 _useSecureChannel = useSecureChannel;
37 MessageControl =
new Control(0, useCrc, useSecureChannel);
41 SecureChannelKey = secureChannelKey ?? SecurityContext.DefaultKey;
43 IsDefaultKey = SecurityContext.DefaultKey.SequenceEqual(SecureChannelKey);
45 MessageSecureChannel =
new ACUMessageSecureChannel(
new SecurityContext(SecureChannelKey));
49 MessageSecureChannel =
new ACUMessageSecureChannel(
new SecurityContext());
53 internal byte[] SecureChannelKey {
get; }
55 private bool IsDefaultKey {
get; }
57 public byte Address {
get; }
59 internal Control MessageControl {
get; }
61 public bool UseSecureChannel => !IsSendingMultiMessageNoSecureChannel && _useSecureChannel;
63 public bool IsSecurityEstablished => !IsSendingMultiMessageNoSecureChannel && MessageControl.HasSecurityControlBlock && MessageSecureChannel.IsSecurityEstablished;
65 public bool IsConnected => _lastValidReply + TimeSpan.FromSeconds(8) >= DateTime.UtcNow &&
66 (IsSendingMultiMessageNoSecureChannel || !MessageControl.HasSecurityControlBlock || IsSecurityEstablished);
68 public IMessageSecureChannel MessageSecureChannel {
get; }
70 internal bool IsSendingMultipartMessage {
get;
set; }
72 internal DateTime RequestDelay {
get;
set; }
74 internal bool IsSendingMultiMessageNoSecureChannel {
get;
set; }
76 internal bool IsReceivingMultipartMessage {
get;
set; }
81 internal bool HasQueuedCommand => _commands.Any();
84 public int CompareTo(DeviceProxy other)
86 if (ReferenceEquals(
this, other))
return 0;
87 if (ReferenceEquals(
null, other))
return 1;
88 return Address.CompareTo(other.Address);
96 internal virtual OutgoingMessage GetNextCommandData(
bool isPolling)
98 if (_retryCommand !=
null)
100 var saveCommand = _retryCommand;
101 _retryCommand =
null;
102 return new OutgoingMessage(Address, MessageControl, saveCommand);
108 if (MessageControl.Sequence == 0 && !UseSecureChannel)
110 return new OutgoingMessage(Address, MessageControl,
new NoPayloadCommandData(
CommandType.Poll));
113 if (UseSecureChannel && !MessageSecureChannel.IsInitialized)
115 return new OutgoingMessage(Address, MessageControl,
116 new SecurityInitialization(MessageSecureChannel.ServerRandomNumber, IsDefaultKey));
119 if (UseSecureChannel && !MessageSecureChannel.IsSecurityEstablished)
121 return new OutgoingMessage(Address, MessageControl,
122 new ServerCryptogramData(MessageSecureChannel.ServerCryptogram, IsDefaultKey));
126 if (!_commands.TryDequeue(out var command) && isPolling)
128 return new OutgoingMessage(Address, MessageControl,
new NoPayloadCommandData(
CommandType.Poll));
131 return command !=
null
132 ?
new OutgoingMessage(Address, MessageControl, command)
133 : new OutgoingMessage(Address, MessageControl, new NoPayloadCommandData(
CommandType.
Poll));
136 internal void SendCommand(CommandData command)
138 _commands.Enqueue(command);
146 internal void RetryCommand(CommandData command,
bool isBusyRetry =
false)
148 if (_counterIsBusyRetry != isBusyRetry)
150 _counter = isBusyRetry ? BusyRetryAmount : RetryAmount;
151 _counterIsBusyRetry = isBusyRetry;
156 _retryCommand = command;
160 _retryCommand =
null;
161 _counter = isBusyRetry ? BusyRetryAmount : RetryAmount;
165 internal void ValidReplyHasBeenReceived(
byte sequence)
167 MessageControl.IncrementSequence(sequence);
170 if (sequence > 0) _lastValidReply = DateTime.UtcNow;
173 _counter = RetryAmount;
174 _counterIsBusyRetry =
false;
177 internal void InitializeSecureChannel(
byte[] payload,
byte[] secureBlockData)
180 if (payload ==
null || payload.Length < 32)
182 throw new InvalidPayloadException(
183 $
"osdp_CCRYPT payload must be 32 bytes, received {payload?.Length ?? 0}");
188 if (secureBlockData ==
null || secureBlockData.Length == 0)
190 throw new InvalidPayloadException(
191 "osdp_CCRYPT response missing security control block data for key type validation");
194 bool pdClaimsDefaultKey = secureBlockData[0] == 0x00;
195 if (IsDefaultKey != pdClaimsDefaultKey)
197 throw new SecureChannelKeyTypeMismatchException(IsDefaultKey, pdClaimsDefaultKey);
200 MessageSecureChannel.InitializeACU(payload.Skip(8).Take(8).ToArray(),
201 payload.Skip(16).Take(16).ToArray());
204 internal void ResetSecureChannelSession()
206 MessageSecureChannel.ResetSecureChannelSession();
209 internal ReadOnlySpan<byte> GenerateMac(ReadOnlySpan<byte> message,
bool isIncoming)
211 return MessageSecureChannel.GenerateMac(message, isIncoming);
215internal interface IDeviceProxyFactory
217 DeviceProxy Create(
byte address,
bool useCrc,
bool useSecureChannel,
byte[] secureChannelKey =
null);
220internal class DeviceProxyFactory : IDeviceProxyFactory
222 public DeviceProxy Create(
223 byte address,
bool useCrc,
bool useSecureChannel,
byte[] secureChannelKey =
null)
224 =>
new DeviceProxy(address, useCrc, useSecureChannel, secureChannelKey);
CommandType
Identifies the type of OSDP protocol command (ACU -> PD) message.
@ Poll
osdp_POLL - poll request