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
2 changes: 1 addition & 1 deletion BACnet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
<Compile Include="Transport\BacnetPipeTransport.cs" />
<Compile Include="Transport\BacnetPtpProtocolTransport.cs" />
<Compile Include="Transport\BacnetSerialPortTransport.cs" />
<Compile Include="Transport\BACnetTransport.cs" />
<Compile Include="Transport\BacnetIpUdpProtocolTransport.cs" />
<Compile Include="Transport\BacnetTransportEthernet.cs" />
<Compile Include="Transport\BacnetIpV6UdpProtocolTransport.cs" />
<Compile Include="Transport\BVLCV6.cs" />
Expand Down
11 changes: 11 additions & 0 deletions BACnet.csproj.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,15 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=base/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=base_005Cenums/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=transport/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>
103 changes: 51 additions & 52 deletions BACnetClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@ public class BacnetClient : IDisposable
{
private int _retries;
private byte _invokeId;
private byte _lastSequenceNumber;

/// <summary>
/// only used when 'DefaultSegmentationHandling' = true
/// </summary>
private readonly LinkedList<byte[]> _segments = new LinkedList<byte[]>();

private readonly LastSegmentAck _lastSegmentAck = new LastSegmentAck();
private uint _writepriority;

/// <summary>
/// Dictionary of List of Tuples with sequence-number and byte[] per invoke-id
/// TODO: invoke-id should be PER (remote) DEVICE!
/// </summary>
private Dictionary<byte, List<Tuple<byte, byte[]>>> _segmentsPerInvokeId = new Dictionary<byte, List<Tuple<byte, byte[]>>>();
private Dictionary<byte, object> _locksPerInvokeId = new Dictionary<byte, object>();
private Dictionary<byte, byte> _expectedSegmentsPerInvokeId = new Dictionary<byte, byte>();

public const int DEFAULT_UDP_PORT = 0xBAC0;
public static readonly TimeSpan DEFAULT_TIMEOUT = TimeSpan.FromSeconds(1);
public const int DEFAULT_RETRIES = 3;
Expand Down Expand Up @@ -224,7 +226,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy
}
else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY && OnWritePropertyRequest != null)
{
if (Services.DecodeWriteProperty(buffer, offset, length, out var objectId, out var value) >= 0)
if (Services.DecodeWriteProperty(address, buffer, offset, length, out var objectId, out var value) >= 0)
OnWritePropertyRequest(this, address, invokeId, objectId, value, maxSegments);
else
{
Expand All @@ -245,7 +247,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy
}
else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE && OnWritePropertyMultipleRequest != null)
{
if (Services.DecodeWritePropertyMultiple(buffer, offset, length, out var objectId, out var values) >= 0)
if (Services.DecodeWritePropertyMultiple(address, buffer, offset, length, out var objectId, out var values) >= 0)
OnWritePropertyMultipleRequest(this, address, invokeId, objectId, values, maxSegments);
else
{
Expand All @@ -255,7 +257,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy
}
else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION && OnCOVNotification != null)
{
if (Services.DecodeCOVNotifyUnconfirmed(buffer, offset, length, out var subscriberProcessIdentifier, out var initiatingDeviceIdentifier, out var monitoredObjectIdentifier, out var timeRemaining, out var values) >= 0)
if (Services.DecodeCOVNotifyUnconfirmed(address, buffer, offset, length, out var subscriberProcessIdentifier, out var initiatingDeviceIdentifier, out var monitoredObjectIdentifier, out var timeRemaining, out var values) >= 0)
OnCOVNotification(this, address, invokeId, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, true, values, maxSegments);
else
{
Expand Down Expand Up @@ -347,7 +349,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy
}
else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT && OnCreateObjectRequest != null)
{
if (Services.DecodeCreateObject(buffer, offset, length, out var objectId, out var values) >= 0)
if (Services.DecodeCreateObject(address, buffer, offset, length, out var objectId, out var values) >= 0)
OnCreateObjectRequest(this, address, invokeId, objectId, values, maxSegments);
else
{
Expand Down Expand Up @@ -423,7 +425,7 @@ protected void ProcessUnconfirmedServiceRequest(BacnetAddress address, BacnetPdu
}
else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION && OnCOVNotification != null)
{
if (Services.DecodeCOVNotifyUnconfirmed(buffer, offset, length, out var subscriberProcessIdentifier, out var initiatingDeviceIdentifier, out var monitoredObjectIdentifier, out var timeRemaining, out var values) >= 0)
if (Services.DecodeCOVNotifyUnconfirmed(address, buffer, offset, length, out var subscriberProcessIdentifier, out var initiatingDeviceIdentifier, out var monitoredObjectIdentifier, out var timeRemaining, out var values) >= 0)
OnCOVNotification(this, address, 0, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, false, values, BacnetMaxSegments.MAX_SEG0);
else
Log.Warn("Couldn't decode COVNotifyUnconfirmed");
Expand All @@ -442,7 +444,7 @@ protected void ProcessUnconfirmedServiceRequest(BacnetAddress address, BacnetPdu
else
Log.Warn("Couldn't decode TimeSynchronize");
}
else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION && OnEventNotify!=null) // F. Chaxel
else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION && OnEventNotify != null) // F. Chaxel
{
if (Services.DecodeEventNotifyData(buffer, offset, length, out var eventData) >= 0)
OnEventNotify(this, address, 0, eventData, false);
Expand Down Expand Up @@ -560,34 +562,40 @@ protected void ProcessSegmentAck(BacnetAddress address, BacnetPduTypes type, byt
}
}

public delegate void SegmentHandler(BacnetClient sender, BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, byte sequenceNumber, bool first, bool moreFollows, byte[] buffer, int offset, int length);
public delegate void SegmentHandler(BacnetClient sender, BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, byte sequenceNumber, byte[] buffer, int offset, int length);
public event SegmentHandler OnSegment;

private void ProcessSegment(BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, bool server, byte sequenceNumber, byte proposedWindowNumber, byte[] buffer, int offset, int length)
{
var first = false;

if (sequenceNumber == 0 && _lastSequenceNumber == 0)
if (!_locksPerInvokeId.TryGetValue(invokeId, out var lockObj))
{
first = true;
lockObj = new object();
_locksPerInvokeId[invokeId] = lockObj;
}
else

lock (lockObj)
{
//send negative ack
if (sequenceNumber != _lastSequenceNumber + 1)
{
SegmentAckResponse(address, true, server, invokeId, _lastSequenceNumber, proposedWindowNumber);
Log.Debug("Segment sequence out of order");
return;
}
ProcessSegmentLocked(address, type, service, invokeId, maxSegments, maxAdpu, server, sequenceNumber,
proposedWindowNumber, buffer, offset, length);
}
}

private void ProcessSegmentLocked(BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service,
byte invokeId, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, bool server, byte sequenceNumber,
byte proposedWindowNumber, byte[] buffer, int offset, int length)
{
Log.Trace($@"Processing Segment #{sequenceNumber} of invoke-id #{invokeId}");

_lastSequenceNumber = sequenceNumber;
if (!_segmentsPerInvokeId.ContainsKey(invokeId))
_segmentsPerInvokeId[invokeId] = new List<Tuple<byte, byte[]>>();

if (!_expectedSegmentsPerInvokeId.ContainsKey(invokeId))
_expectedSegmentsPerInvokeId[invokeId] = byte.MaxValue;

var moreFollows = (type & BacnetPduTypes.MORE_FOLLOWS) == BacnetPduTypes.MORE_FOLLOWS;

if (!moreFollows)
_lastSequenceNumber = 0; //reset last sequenceNumber
_expectedSegmentsPerInvokeId[invokeId] = (byte)(sequenceNumber + 1);

//send ACK
if (sequenceNumber % proposedWindowNumber == 0 || !moreFollows)
Expand All @@ -599,34 +607,23 @@ private void ProcessSegment(BacnetAddress address, BacnetPduTypes type, BacnetCo
}

//Send on
OnSegment?.Invoke(this, address, type, service, invokeId, maxSegments, maxAdpu, sequenceNumber, first, moreFollows, buffer, offset, length);
OnSegment?.Invoke(this, address, type, service, invokeId, maxSegments, maxAdpu, sequenceNumber, buffer, offset, length);

//default segment assembly. We run this seperately from the above handler, to make sure that it comes after!
if (DefaultSegmentationHandling)
PerformDefaultSegmentHandling(address, type, service, invokeId, maxSegments, maxAdpu, first, moreFollows, buffer, offset, length);
}

private byte[] AssembleSegments()
{
return _segments.Aggregate(new byte[0], (result, next) =>
{
var offset = result.Length;
Array.Resize(ref result, result.Length + next.Length);
Array.Copy(next, 0, result, offset, next.Length);
return result;
});
PerformDefaultSegmentHandling(address, type, service, invokeId, maxSegments, maxAdpu, sequenceNumber, buffer, offset, length);
}

/// <summary>
/// This is a simple handling that stores all segments in memory and assembles them when done
/// </summary>
private void PerformDefaultSegmentHandling(BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, bool first, bool moreFollows, byte[] buffer, int offset, int length)
private void PerformDefaultSegmentHandling(BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, byte sequenceNumber, byte[] buffer, int offset, int length)
{
if (first)
{
//clear any leftover segments
_segments.Clear();
var segments = _segmentsPerInvokeId[invokeId];
var moreFollows = segments.Count < _expectedSegmentsPerInvokeId[invokeId];

if (sequenceNumber == 0)
{
//copy buffer + encode new adpu header
type &= ~BacnetPduTypes.SEGMENTED_MESSAGE;
var confirmedServiceRequest = (type & BacnetPduTypes.PDU_TYPE_MASK) == BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
Expand All @@ -641,21 +638,22 @@ private void PerformDefaultSegmentHandling(BacnetAddress address, BacnetPduTypes
else
APDU.EncodeComplexAck(encodedBuffer, type, service, invokeId);

_segments.AddLast(copy); // doesn't include BVLC or NPDU
segments.Add(Tuple.Create(sequenceNumber, copy)); // doesn't include BVLC or NPDU
}
else
{
//copy only content part
_segments.AddLast(buffer.Skip(offset).Take(length).ToArray());
segments.Add(Tuple.Create(sequenceNumber, buffer.Skip(offset).Take(length).ToArray()));
}

//process when finished
if (moreFollows)
return;

//assemble whole part
var apduBuffer = AssembleSegments();
_segments.Clear();
var apduBuffer = segments.OrderBy(s => s.Item1).SelectMany(s => s.Item2).ToArray();
segments.Clear();
_expectedSegmentsPerInvokeId[invokeId] = byte.MaxValue;

//process
ProcessApdu(address, type, apduBuffer, 0, apduBuffer.Length);
Expand Down Expand Up @@ -1175,7 +1173,7 @@ public IList<BacnetValue> EndReadPropertyRequest(BacnetAsyncResult request)
{
return request.GetResult(Timeout, Retries, r =>
{
var byteCount = Services.DecodeReadPropertyAcknowledge(r.Result, 0, r.Result.Length,
var byteCount = Services.DecodeReadPropertyAcknowledge(r.Address, r.Result, 0, r.Result.Length,
out _, out _, out var valueList);

if (byteCount < 0)
Expand Down Expand Up @@ -1281,8 +1279,9 @@ public IList<BacnetReadAccessResult> EndReadPropertyMultipleRequest(BacnetAsyncR
{
return request.GetResult(Timeout, Retries, r =>
{
var byteCount = Services.DecodeReadPropertyMultipleAcknowledge(
r.Result, 0, r.Result.Length, out var values);
var byteCount =
Services.DecodeReadPropertyMultipleAcknowledge(
r.Address, r.Result, 0, r.Result.Length, out var values);

if (byteCount < 0)
throw new Exception("Failed to decode ReadPropertyMultipleAcknowledge");
Expand Down
8 changes: 4 additions & 4 deletions BacnetAsyncResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ namespace System.IO.BACnet
public class BacnetAsyncResult : IAsyncResult, IDisposable
{
private BacnetClient _comm;
private readonly BacnetAddress _addr;
private readonly byte _waitInvokeId;
private Exception _error;
private readonly byte[] _transmitBuffer;
Expand All @@ -20,6 +19,7 @@ public class BacnetAsyncResult : IAsyncResult, IDisposable
public bool CompletedSynchronously { get; private set; }
public WaitHandle AsyncWaitHandle => _waitHandle;
public bool IsCompleted => _waitHandle.WaitOne(0);
public BacnetAddress Address { get; }

public Exception Error
{
Expand All @@ -36,7 +36,7 @@ public BacnetAsyncResult(BacnetClient comm, BacnetAddress adr, byte invokeId,
byte[] transmitBuffer, int transmitLength, bool waitForTransmit, TimeSpan transmitTimeout)
{
_transmitTimeout = transmitTimeout;
_addr = adr;
Address = adr;
_waitForTransmit = waitForTransmit;
_transmitBuffer = transmitBuffer;
_transmitLength = transmitLength;
Expand All @@ -56,7 +56,7 @@ public BacnetAsyncResult Send()
try
{
var bytesSent = _comm.Transport.Send(_transmitBuffer, _comm.Transport.HeaderLength,
_transmitLength, _addr, _waitForTransmit, (int)_transmitTimeout.TotalMilliseconds);
_transmitLength, Address, _waitForTransmit, (int)_transmitTimeout.TotalMilliseconds);

if (_waitForTransmit && bytesSent < 0)
Error = new IOException("Write Timeout");
Expand All @@ -74,7 +74,7 @@ public void Resend()
Send();
}

private void OnSegment(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, byte sequenceNumber, bool first, bool moreFollows, byte[] buffer, int offset, int length)
private void OnSegment(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, byte sequenceNumber, byte[] buffer, int offset, int length)
{
if (invokeId != _waitInvokeId)
return;
Expand Down
Loading