From 46b9818ccdbad02935b3e963e2b1a0f4dd539207 Mon Sep 17 00:00:00 2001 From: Jakub Bartkowiak Date: Thu, 30 Nov 2017 00:56:56 +0100 Subject: [PATCH 01/31] change: rewrite/update BACnetClient API Breaking changes. Trying to eliminate duplicates and make the API more friendly. --- BACnetClient.cs | 438 +++++++++++++++---------------------------- BacnetAsyncResult.cs | 55 +++++- 2 files changed, 199 insertions(+), 294 deletions(-) diff --git a/BACnetClient.cs b/BACnetClient.cs index 1709c761..72c63c23 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -54,13 +54,13 @@ public class BacnetClient : IDisposable private uint _writepriority; public const int DEFAULT_UDP_PORT = 0xBAC0; - public const int DEFAULT_TIMEOUT = 1000; + public static readonly TimeSpan DEFAULT_TIMEOUT = TimeSpan.FromSeconds(1); public const int DEFAULT_RETRIES = 3; public IBacnetTransport Transport { get; } public ushort VendorId { get; set; } = 260; - public int Timeout { get; set; } - public int TransmitTimeout { get; set; } = 30000; + public TimeSpan Timeout { get; set; } + public TimeSpan TransmitTimeout { get; set; } = TimeSpan.FromSeconds(30); public BacnetMaxSegments MaxSegments { get; set; } = BacnetMaxSegments.MAX_SEG0; public byte ProposedWindowSize { get; set; } = 10; public bool ForceWindowSize { get; set; } @@ -121,10 +121,10 @@ public void Set(BacnetAddress adr, byte invokeId, byte sequenceNumber, byte wind } } - public bool Wait(BacnetAddress adr, byte invokeId, int timeout) + public bool Wait(BacnetAddress adr, byte invokeId, TimeSpan timeout) { Monitor.Enter(_lockObject); - while (!adr.Equals(this._address) || this._invokeId != invokeId) + while (!adr.Equals(_address) || _invokeId != invokeId) { _wait.Reset(); Monitor.Exit(_lockObject); @@ -137,15 +137,15 @@ public bool Wait(BacnetAddress adr, byte invokeId, int timeout) } } - public BacnetClient(int port = DEFAULT_UDP_PORT, int timeout = DEFAULT_TIMEOUT, int retries = DEFAULT_RETRIES) + public BacnetClient(int port = DEFAULT_UDP_PORT, TimeSpan? timeout = null, int retries = DEFAULT_RETRIES) : this(new BacnetIpUdpProtocolTransport(port), timeout, retries) { } - public BacnetClient(IBacnetTransport transport, int timeout = DEFAULT_TIMEOUT, int retries = DEFAULT_RETRIES) + public BacnetClient(IBacnetTransport transport, TimeSpan? timeout = null, int retries = DEFAULT_RETRIES) { Transport = transport; - Timeout = timeout; + Timeout = timeout ?? DEFAULT_TIMEOUT; Retries = retries; } @@ -634,8 +634,13 @@ private void ProcessSegment(BacnetAddress adr, BacnetPduTypes type, BacnetConfir private byte[] AssembleSegments() { - return _segments.Aggregate(new byte[0], (current, next) => - current.Concat(next).ToArray()); + 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; + }); } /// @@ -1182,7 +1187,7 @@ public void EndReadRangeRequest(IAsyncResult result, out byte[] trendbuffer, out trendbuffer = null; ex = res.Error; - if (ex == null && !res.WaitForDone(40*1000)) + if (ex == null && !res.WaitForDone(TimeSpan.FromSeconds(40))) ex = new Exception("Wait Timeout"); if (ex == null) @@ -1313,346 +1318,164 @@ public void EndSubscribePropertyRequest(IAsyncResult result, out Exception ex) res.Dispose(); } - public bool ReadPropertyRequest(BacnetAddress adr, BacnetObjectId objectId, BacnetPropertyIds propertyId, out IList valueList, byte invokeId = 0, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) + public BacnetValue ReadPropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, uint index) { - using (var result = (BacnetAsyncResult)BeginReadPropertyRequest(adr, objectId, propertyId, true, invokeId, arrayIndex)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndReadPropertyRequest(result, out valueList, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - valueList = null; - return false; + if (index == ASN1.BACNET_ARRAY_ALL) throw new ArgumentOutOfRangeException(nameof(index)); + using (var asyncResult = BeginReadPropertyRequest(address, objectId, propertyId, index)) + return EndReadPropertyRequest(asyncResult).Single(); } - public Task> ReadPropertyAsync(BacnetAddress address, BacnetObjectTypes objType, uint objInstance, - BacnetPropertyIds propertyId, byte invokeId = 0, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) + public IList ReadPropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId) { - var objectId = new BacnetObjectId(objType, objInstance); - return ReadPropertyAsync(address, objectId, propertyId, invokeId, arrayIndex); + using (var asyncResult = BeginReadPropertyRequest(address, objectId, propertyId)) + return EndReadPropertyRequest(asyncResult); } - public Task> ReadPropertyAsync(BacnetAddress address, BacnetObjectId objectId, - BacnetPropertyIds propertyId, byte invokeId = 0, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) + public Task ReadPropertyAsync(BacnetAddress address, BacnetObjectTypes objType, uint objInstance, BacnetPropertyIds propertyId, uint index) + => ReadPropertyAsync(address, new BacnetObjectId(objType, objInstance), propertyId, index); + + public Task ReadPropertyAsync(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, uint index) { - IList result; + if (index == ASN1.BACNET_ARRAY_ALL) throw new ArgumentOutOfRangeException(nameof(index)); + var exceptionMessage = $"Failed to read property {propertyId}{(index == ASN1.BACNET_ARRAY_ALL? "" : $"[{index}]")} of {objectId} from {address}"; + return CallAsync(() => ReadPropertyRequest(address, objectId, propertyId, index), exceptionMessage); + } - return Task>.Factory.StartNew(() => - { - if (!ReadPropertyRequest(address, objectId, propertyId, out result, invokeId, arrayIndex)) - throw new Exception($"Failed to read property {propertyId} of {objectId} from {address}"); + public Task> ReadPropertyAsync(BacnetAddress address, BacnetObjectTypes objType, uint objInstance, BacnetPropertyIds propertyId) + => ReadPropertyAsync(address, new BacnetObjectId(objType, objInstance), propertyId); - return result; - }); + public Task> ReadPropertyAsync(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId) + { + var exceptionMessage = $"Failed to read property {propertyId} of {objectId} from {address}"; + return CallAsync(() => ReadPropertyRequest(address, objectId, propertyId), exceptionMessage); } - public IAsyncResult BeginReadPropertyRequest(BacnetAddress adr, BacnetObjectId objectId, BacnetPropertyIds propertyId, bool waitForTransmit, byte invokeId = 0, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) + public BacnetAsyncResult BeginReadPropertyRequest(BacnetAddress address, BacnetObjectId objectId, + BacnetPropertyIds propertyId, uint index = ASN1.BACNET_ARRAY_ALL, bool waitForTransmit = true) { - Log.Debug($"Sending ReadPropertyRequest {objectId} {propertyId}"); - if(invokeId == 0) - invokeId = unchecked (_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeReadProperty(buffer, objectId, (uint)propertyId, arrayIndex); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + var propertyIndex = index == ASN1.BACNET_ARRAY_ALL ? "" : $"[{index}]"; + Log.Debug($"Sending ReadPropertyRequest {objectId} {propertyId}{propertyIndex}"); + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, + buffer => Services.EncodeReadProperty(buffer, objectId, (uint)propertyId, index), waitForTransmit); } - public void EndReadPropertyRequest(IAsyncResult result, out IList valueList, out Exception ex) + public IList EndReadPropertyRequest(BacnetAsyncResult request) { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - - if (ex == null) + using (request) { - //decode - if (Services.DecodeReadPropertyAcknowledge(res.Result, 0, res.Result.Length, out _, out _, out valueList) < 0) - ex = new Exception("Decode"); - } - else - { - valueList = null; + return request.GetResult(Timeout, Retries, r => + { + var byteCount = Services.DecodeReadPropertyAcknowledge(r.Result, 0, r.Result.Length, + out _, out _, out var valueList); + + if (byteCount < 0) + throw new Exception("Failed to decode ReadPropertyAcknowledge"); + + return valueList; + }); } + } - res.Dispose(); + public void WritePropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, BacnetValue value) + { + WritePropertyRequest(address, objectId, propertyId, new[] { value }); } - public bool WritePropertyRequest(BacnetAddress adr, BacnetObjectId objectId, BacnetPropertyIds propertyId, IEnumerable valueList, byte invokeId = 0) + public void WritePropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, IEnumerable valueList) { - using (var result = (BacnetAsyncResult)BeginWritePropertyRequest(adr, objectId, propertyId, valueList, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndWritePropertyRequest(result, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - return false; + using (var asyncResult = BeginWritePropertyRequest(address, objectId, propertyId, valueList)) + asyncResult.GetResult(Timeout, Retries); } - public bool WritePropertyMultipleRequest(BacnetAddress adr, BacnetObjectId objectId, ICollection valueList, byte invokeId = 0) + public void WritePropertyMultipleRequest(BacnetAddress address, BacnetObjectId objectId, ICollection valueList) { - using (var result = (BacnetAsyncResult)BeginWritePropertyMultipleRequest(adr, objectId, valueList, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndWritePropertyRequest(result, out var ex); // Share the same with single write - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - return false; + using (var asyncResult = BeginWritePropertyMultipleRequest(address, objectId, valueList)) + asyncResult.GetResult(Timeout, Retries); } - public IAsyncResult BeginWritePropertyRequest(BacnetAddress adr, BacnetObjectId objectId, BacnetPropertyIds propertyId, IEnumerable valueList, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginWritePropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, IEnumerable valueList, bool waitForTransmit = true) { Log.Debug($"Sending WritePropertyRequest {objectId} {propertyId}"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST , BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeWriteProperty(buffer, objectId, (uint)propertyId, ASN1.BACNET_ARRAY_ALL, _writepriority, valueList); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, + buffer => Services.EncodeWriteProperty(buffer, objectId, (uint)propertyId, ASN1.BACNET_ARRAY_ALL, _writepriority, valueList), waitForTransmit); } - public IAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress adr, BacnetObjectId objectId, ICollection valueList, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress address, BacnetObjectId objectId, + ICollection valueList, bool waitForTransmit = true) { Log.Debug($"Sending WritePropertyMultipleRequest {objectId}"); - if (invokeId == 0) invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - //BacnetNpduControls.PriorityNormalMessage - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeWritePropertyMultiple(buffer, objectId, valueList); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, + buffer => Services.EncodeWritePropertyMultiple(buffer, objectId, valueList), waitForTransmit); } - public void EndWritePropertyRequest(IAsyncResult result, out Exception ex) + public void WritePropertyMultipleRequest(BacnetAddress address, ICollection valueList) { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - - res.Dispose(); + using (var asyncResult = BeginWritePropertyMultipleRequest(address, valueList)) + asyncResult.GetResult(Timeout, Retries); } - // By Chritopher Günter : Write multiple properties on multiple objects - public bool WritePropertyMultipleRequest(BacnetAddress adr, ICollection valueList, byte invokeId = 0) - { - using (var result = (BacnetAsyncResult)BeginWritePropertyMultipleRequest(adr, valueList, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndWritePropertyRequest(result, out var ex); // Share the same with single write - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - return false; - } - - public IAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress adr, ICollection valueList, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress adr, ICollection valueList, bool waitForTransmit = true) { var objectIds = string.Join(", ", valueList.Select(v => v.objectIdentifier)); Log.Debug($"Sending WritePropertyMultipleRequest {objectIds}"); - - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - //BacnetNpduControls.PriorityNormalMessage - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeWriteObjectMultiple(buffer, valueList); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, + buffer => Services.EncodeWriteObjectMultiple(buffer, valueList), waitForTransmit); } - public bool ReadPropertyMultipleRequest(BacnetAddress address, BacnetObjectId objectId, IList propertyIdAndArrayIndex, out IList values, byte invokeId = 0) + public IList ReadPropertyMultipleRequest(BacnetAddress address, BacnetObjectId objectId, IList propertyIdAndIndex) { - using (var result = (BacnetAsyncResult)BeginReadPropertyMultipleRequest(address, objectId, propertyIdAndArrayIndex, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndReadPropertyMultipleRequest(result, out values, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - values = null; - return false; + using (var asyncResult = BeginReadPropertyMultipleRequest(address, objectId, propertyIdAndIndex)) + return EndReadPropertyMultipleRequest(asyncResult); } - public Task> ReadPropertyMultipleAsync(BacnetAddress address, - BacnetObjectTypes objType, uint objInstance, params BacnetPropertyIds[] propertyIds) + public Task> ReadPropertyMultipleAsync(BacnetAddress address, BacnetObjectTypes objType, uint objInstance, params BacnetPropertyIds[] propertyIds) { var objectId = new BacnetObjectId(objType, objInstance); return ReadPropertyMultipleAsync(address, objectId, propertyIds); } - public Task> ReadPropertyMultipleAsync(BacnetAddress address, - BacnetObjectId objectId, params BacnetPropertyIds[] propertyIds) + public Task> ReadPropertyMultipleAsync(BacnetAddress address, BacnetObjectId objectId, params BacnetPropertyIds[] propertyIds) { - var propertyReferences = propertyIds.Select(p => - new BacnetPropertyReference((uint)p, ASN1.BACNET_ARRAY_ALL)); - - return Task>.Factory.StartNew(() => - { - if (!ReadPropertyMultipleRequest(address, objectId, propertyReferences.ToList(), out var result)) - throw new Exception($"Failed to read multiple properties of {objectId} from {address}"); - - return result.Single().values; - }); + var propertyReferences = propertyIds.Select(p => new BacnetPropertyReference((uint)p, ASN1.BACNET_ARRAY_ALL)).ToArray(); + return CallAsync(() => ReadPropertyMultipleRequest(address, objectId, propertyReferences).Single().values, + $"Failed to read multiple properties of {objectId} from {address}"); } - public IAsyncResult BeginReadPropertyMultipleRequest(BacnetAddress adr, BacnetObjectId objectId, IList propertyIdAndArrayIndex, bool waitForTransmit, byte invokeId = 0) + public IList ReadPropertyMultipleRequest(BacnetAddress address, IList properties) { - var propertyIds = string.Join(", ", propertyIdAndArrayIndex.Select(v => (BacnetPropertyIds)v.propertyIdentifier)); - Log.Debug($"Sending ReadPropertyMultipleRequest {objectId} {propertyIds}"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeReadPropertyMultiple(buffer, objectId, propertyIdAndArrayIndex); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + using (var asyncResult = BeginReadPropertyMultipleRequest(address, properties)) + return EndReadPropertyMultipleRequest(asyncResult); } - // Another way to read multiple properties on multiples objects, if supported by devices - public bool ReadPropertyMultipleRequest(BacnetAddress address, IList properties, out IList values, byte invokeId = 0) + public BacnetAsyncResult BeginReadPropertyMultipleRequest(BacnetAddress adr, BacnetObjectId objectId, IList propertyIdAndIndex, bool waitForTransmit = true) { - using (var result = (BacnetAsyncResult)BeginReadPropertyMultipleRequest(address, properties, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndReadPropertyMultipleRequest(result, out values, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - values = null; - return false; + var properties = new[] { new BacnetReadAccessSpecification(objectId, propertyIdAndIndex) }; + return BeginReadPropertyMultipleRequest(adr, properties); } - public IAsyncResult BeginReadPropertyMultipleRequest(BacnetAddress adr, IList properties, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginReadPropertyMultipleRequest(BacnetAddress address, IList properties, bool waitForTransmit = true) { var objectIds = string.Join(", ", properties.Select(v => v.objectIdentifier)); Log.Debug($"Sending ReadPropertyMultipleRequest {objectIds}"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeReadPropertyMultiple(buffer, properties); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, + buffer => Services.EncodeReadPropertyMultiple(buffer, properties), waitForTransmit); } - public void EndReadPropertyMultipleRequest(IAsyncResult result, out IList values, out Exception ex) + public IList EndReadPropertyMultipleRequest(BacnetAsyncResult request) { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - - if (ex == null) - { - //decode - if (Services.DecodeReadPropertyMultipleAcknowledge(res.Result, 0, res.Result.Length, out values) < 0) - ex = new Exception("Decode"); - } - else + using (request) { - values = null; - } + return request.GetResult(Timeout, Retries, r => + { + var byteCount = Services.DecodeReadPropertyMultipleAcknowledge( + r.Result, 0, r.Result.Length, out var values); - res.Dispose(); - } + if (byteCount < 0) + throw new Exception("Failed to decode ReadPropertyMultipleAcknowledge"); - private BacnetPduTypes PduConfirmedServiceRequest() - { - return MaxSegments != BacnetMaxSegments.MAX_SEG0 - ? BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST | BacnetPduTypes.SEGMENTED_RESPONSE_ACCEPTED - : BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + return values; + }); + } } // By Christopher Günter @@ -2410,7 +2233,7 @@ private void HandleSegmentationResponse(BacnetAddress adr, byte invokeId, Segmen { // a negative segmentACK perhaps var currentNumber = segmentation.sequence_number; - WaitForSegmentAck(adr, invokeId, segmentation, 0); // don't wait + WaitForSegmentAck(adr, invokeId, segmentation, TimeSpan.Zero); // don't wait if (segmentation.sequence_number != currentNumber) Log.Debug("Oh, a retransmit"); @@ -2552,12 +2375,12 @@ public void SegmentAckResponse(BacnetAddress adr, bool negative, bool server, by Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, adr, false, 0); } - public bool WaitForAllTransmits(int timeout) + public bool WaitForAllTransmits(TimeSpan timeout) { - return Transport.WaitForAllTransmits(timeout); + return Transport.WaitForAllTransmits((int)timeout.TotalMilliseconds); } - public bool WaitForSegmentAck(BacnetAddress adr, byte invokeId, Segmentation segmentation, int timeout) + public bool WaitForSegmentAck(BacnetAddress adr, byte invokeId, Segmentation segmentation, TimeSpan timeout) { if (!_lastSegmentAck.Wait(adr, invokeId, timeout)) return false; @@ -2573,6 +2396,47 @@ private static string ToTitleCase(object obj) return cultureTextInfo.ToTitleCase($"{obj}".ToLower()); } + public BacnetAsyncResult BeginConfirmedServiceRequest(BacnetAddress address, BacnetConfirmedServices service, Action encode, bool waitForTransmit = true) + { + var invokeId = unchecked(_invokeId++); + var buffer = GetEncodeBuffer(Transport.HeaderLength); + var function = BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply; + + NPDU.Encode(buffer, function, address.RoutedSource); + APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), service, MaxSegments, Transport.MaxAdpuLength, invokeId); + encode(buffer); + + var transmitLength = buffer.offset - Transport.HeaderLength; + var asyncResult = new BacnetAsyncResult(this, address, invokeId, buffer.buffer, transmitLength, waitForTransmit, TransmitTimeout); + return asyncResult.Send(); + } + + private BacnetPduTypes PduConfirmedServiceRequest() + { + return MaxSegments != BacnetMaxSegments.MAX_SEG0 + ? BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST | BacnetPduTypes.SEGMENTED_RESPONSE_ACCEPTED + : BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + } + + private static Task CallAsync(Func func, string exceptionMessage) + { + return Task.Factory.StartNew(() => + { + try + { + return func(); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new Exception(exceptionMessage, e); + } + }); + } + public void Dispose() { Transport.Dispose(); diff --git a/BacnetAsyncResult.cs b/BacnetAsyncResult.cs index 294427cd..eed7fa5f 100644 --- a/BacnetAsyncResult.cs +++ b/BacnetAsyncResult.cs @@ -11,7 +11,7 @@ public class BacnetAsyncResult : IAsyncResult, IDisposable private readonly byte[] _transmitBuffer; private readonly int _transmitLength; private readonly bool _waitForTransmit; - private readonly int _transmitTimeout; + private readonly TimeSpan _transmitTimeout; private ManualResetEvent _waitHandle; public bool Segmented { get; private set; } @@ -32,7 +32,8 @@ public Exception Error } } - public BacnetAsyncResult(BacnetClient comm, BacnetAddress adr, byte invokeId, byte[] transmitBuffer, int transmitLength, bool waitForTransmit, int transmitTimeout) + public BacnetAsyncResult(BacnetClient comm, BacnetAddress adr, byte invokeId, + byte[] transmitBuffer, int transmitLength, bool waitForTransmit, TimeSpan transmitTimeout) { _transmitTimeout = transmitTimeout; _addr = adr; @@ -50,19 +51,27 @@ public BacnetAsyncResult(BacnetClient comm, BacnetAddress adr, byte invokeId, by _waitHandle = new ManualResetEvent(false); } - public void Resend() + public BacnetAsyncResult Send() { try { - if (_comm.Transport.Send(_transmitBuffer, _comm.Transport.HeaderLength, _transmitLength, _addr, _waitForTransmit, _transmitTimeout) < 0) - { + var bytesSent = _comm.Transport.Send(_transmitBuffer, _comm.Transport.HeaderLength, + _transmitLength, _addr, _waitForTransmit, (int)_transmitTimeout.TotalMilliseconds); + + if (_waitForTransmit && bytesSent < 0) Error = new IOException("Write Timeout"); - } } catch (Exception ex) { Error = new Exception($"Write Exception: {ex.Message}"); } + + return this; + } + + 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) @@ -124,7 +133,7 @@ private void OnComplexAck(BacnetClient sender, BacnetAddress adr, BacnetPduTypes /// /// Will continue waiting until all segments are recieved /// - public bool WaitForDone(int timeout) + public bool WaitForDone(TimeSpan timeout) { while (true) { @@ -137,6 +146,38 @@ public bool WaitForDone(int timeout) } } + public void GetResult(TimeSpan? timeout = null, int retryCount = 0) + { + GetResult(timeout, retryCount); + } + + /// + /// Exception is thrown if given was exceeded before full response was received. + /// + public TResult GetResult(TimeSpan? timeout = null, int retryCount = 0, Func decodeFunc = null) + { + if (retryCount < 0) + throw new ArgumentOutOfRangeException(nameof(retryCount), "Retry count should be a non-negative value"); + + while (true) + { + if (WaitForDone(timeout ?? TimeSpan.FromSeconds(1)) && Error == null) + return decodeFunc != null + ? decodeFunc.Invoke(this) + : default(TResult); + + if (retryCount == 0) + break; + + retryCount--; + Resend(); + } + + throw Error ?? new Exception(Result?.Length > 0 + ? $"Failed to receive complete response within {timeout}" + : $"No response within {timeout}"); + } + public void Dispose() { if (_comm != null) From 8a4e64c01354d6b8115da7aa2965c3d4c4dc1ccb Mon Sep 17 00:00:00 2001 From: Jakub Bartkowiak Date: Fri, 1 Dec 2017 01:23:45 +0100 Subject: [PATCH 02/31] chane: rewrite ReadRange, SubscribeCOV and SubscribeProperty requests --- BACnetClient.cs | 178 ++++++++++++------------------------------------ 1 file changed, 42 insertions(+), 136 deletions(-) diff --git a/BACnetClient.cs b/BACnetClient.cs index 72c63c23..d8fbd001 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -1159,163 +1159,61 @@ public bool ReadFileRequest(BacnetAddress adr, BacnetObjectId objectId, ref int return false; } - // Fc - public IAsyncResult BeginReadRangeRequest(BacnetAddress adr, BacnetObjectId objectId, uint idxBegin, uint quantity, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginReadRangeRequest(BacnetAddress address, BacnetObjectId objectId, uint idxBegin, uint quantity, bool waitForTransmit = false) { Log.Debug("Sending ReadRangeRequest"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - //encode - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeReadRange(buffer, objectId, (uint)BacnetPropertyIds.PROP_LOG_BUFFER, ASN1.BACNET_ARRAY_ALL, BacnetReadRangeRequestTypes.RR_BY_POSITION, idxBegin, DateTime.Now, (int)quantity); - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; - + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, + buffer => Services.EncodeReadRange(buffer, objectId, (uint)BacnetPropertyIds.PROP_LOG_BUFFER, ASN1.BACNET_ARRAY_ALL, + BacnetReadRangeRequestTypes.RR_BY_POSITION, idxBegin, DateTime.Now, (int)quantity), waitForTransmit); } - // Fc - public void EndReadRangeRequest(IAsyncResult result, out byte[] trendbuffer, out uint itemCount, out Exception ex) - { - var res = (BacnetAsyncResult)result; - itemCount = 0; - trendbuffer = null; - - ex = res.Error; - if (ex == null && !res.WaitForDone(TimeSpan.FromSeconds(40))) - ex = new Exception("Wait Timeout"); - - if (ex == null) - { - itemCount = Services.DecodeReadRangeAcknowledge(res.Result, 0, res.Result.Length, out trendbuffer); - if (itemCount == 0) - ex = new Exception("Decode"); - } - - res.Dispose(); - } - - // Fc - public bool ReadRangeRequest(BacnetAddress adr, BacnetObjectId objectId, uint idxBegin, ref uint quantity, out byte[] range, byte invokeId = 0) + public void EndReadRangeRequest(BacnetAsyncResult request, out byte[] trendBuffer, out uint itemCount) { - range = null; - using (var result = (BacnetAsyncResult)BeginReadRangeRequest(adr, objectId, idxBegin, quantity, true, invokeId)) + using (request) { - for (var r = 0; r < _retries; r++) + var result = request.GetResult(TimeSpan.FromSeconds(40), Retries, r => { - if (result.WaitForDone(Timeout)) - { - EndReadRangeRequest(result, out range, out quantity, out var ex); // quantity read could be less than demanded - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - return false; - } + var itemCountValue = Services.DecodeReadRangeAcknowledge(r.Result, 0, r.Result.Length, out var trendBufferValue); + if (itemCountValue == 0) + throw new Exception("Failed to decode ReadRangeAcknowledge"); + return Tuple.Create(itemCountValue, trendBufferValue); + }); - public bool SubscribeCOVRequest(BacnetAddress adr, BacnetObjectId objectId, uint subscribeId, bool cancel, bool issueConfirmedNotifications, uint lifetime, byte invokeId = 0) - { - using (var result = (BacnetAsyncResult)BeginSubscribeCOVRequest(adr, objectId, subscribeId, cancel, issueConfirmedNotifications, lifetime, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndSubscribeCOVRequest(result, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } + itemCount = result.Item1; + trendBuffer = result.Item2; } - return false; } - public IAsyncResult BeginSubscribeCOVRequest(BacnetAddress adr, BacnetObjectId objectId, uint subscribeId, bool cancel, bool issueConfirmedNotifications, uint lifetime, bool waitForTransmit, byte invokeId = 0) + public void ReadRangeRequest(BacnetAddress adr, BacnetObjectId objectId, uint idxBegin, ref uint quantity, out byte[] range) { - Log.Debug($"Sending SubscribeCOVRequest {objectId}"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeSubscribeCOV(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, lifetime); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + using (var asyncResult = BeginReadRangeRequest(adr, objectId, idxBegin, quantity, true)) + EndReadRangeRequest(asyncResult, out range, out quantity); // quantity read could be less than demanded } - public void EndSubscribeCOVRequest(IAsyncResult result, out Exception ex) + public void SubscribeCOVRequest(BacnetAddress address, BacnetObjectId objectId, uint subscribeId, bool cancel, bool issueConfirmedNotifications, uint lifetime) { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - - res.Dispose(); + using (var asyncResult = BeginSubscribeCOVRequest(address, objectId, subscribeId, cancel, issueConfirmedNotifications, lifetime, true)) + EndConfirmedServiceRequest(asyncResult); } - public bool SubscribePropertyRequest(BacnetAddress adr, BacnetObjectId objectId, BacnetPropertyReference monitoredProperty, uint subscribeId, bool cancel, bool issueConfirmedNotifications, byte invokeId = 0) + public BacnetAsyncResult BeginSubscribeCOVRequest(BacnetAddress address, BacnetObjectId objectId, uint subscribeId, bool cancel, bool issueConfirmedNotifications, uint lifetime, bool waitForTransmit = false) { - using (var result = (BacnetAsyncResult)BeginSubscribePropertyRequest(adr, objectId, monitoredProperty, subscribeId, cancel, issueConfirmedNotifications, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndSubscribePropertyRequest(result, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - return false; + Log.Debug($"Sending SubscribeCOVRequest {objectId}"); + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, + buffer => Services.EncodeSubscribeCOV(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, lifetime), waitForTransmit); } - public IAsyncResult BeginSubscribePropertyRequest(BacnetAddress adr, BacnetObjectId objectId, BacnetPropertyReference monitoredProperty, uint subscribeId, bool cancel, bool issueConfirmedNotifications, bool waitForTransmit, byte invokeId = 0) + public void SubscribePropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyReference monitoredProperty, uint subscribeId, bool cancel, bool issueConfirmedNotifications) { - Log.Debug($"Sending SubscribePropertyRequest {objectId}.{monitoredProperty}"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeSubscribeProperty(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, 0, monitoredProperty, false, 0f); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + using (var asyncResult = BeginSubscribePropertyRequest(address, objectId, monitoredProperty, subscribeId, cancel, issueConfirmedNotifications, true)) + EndConfirmedServiceRequest(asyncResult); } - public void EndSubscribePropertyRequest(IAsyncResult result, out Exception ex) + public BacnetAsyncResult BeginSubscribePropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyReference monitoredProperty, uint subscribeId, bool cancel, bool issueConfirmedNotifications, bool waitForTransmit) { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - - res.Dispose(); + Log.Debug($"Sending SubscribePropertyRequest {objectId}.{monitoredProperty}"); + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, + buffer => Services.EncodeSubscribeProperty(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, 0, monitoredProperty, false, 0f), waitForTransmit); } public BacnetValue ReadPropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, uint index) @@ -1384,13 +1282,13 @@ public void WritePropertyRequest(BacnetAddress address, BacnetObjectId objectId, public void WritePropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, IEnumerable valueList) { using (var asyncResult = BeginWritePropertyRequest(address, objectId, propertyId, valueList)) - asyncResult.GetResult(Timeout, Retries); + EndConfirmedServiceRequest(asyncResult); } public void WritePropertyMultipleRequest(BacnetAddress address, BacnetObjectId objectId, ICollection valueList) { using (var asyncResult = BeginWritePropertyMultipleRequest(address, objectId, valueList)) - asyncResult.GetResult(Timeout, Retries); + EndConfirmedServiceRequest(asyncResult); } public BacnetAsyncResult BeginWritePropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, IEnumerable valueList, bool waitForTransmit = true) @@ -1411,7 +1309,7 @@ public BacnetAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress address public void WritePropertyMultipleRequest(BacnetAddress address, ICollection valueList) { using (var asyncResult = BeginWritePropertyMultipleRequest(address, valueList)) - asyncResult.GetResult(Timeout, Retries); + EndConfirmedServiceRequest(asyncResult); } public BacnetAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress adr, ICollection valueList, bool waitForTransmit = true) @@ -2418,6 +2316,14 @@ private BacnetPduTypes PduConfirmedServiceRequest() : BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST; } + public void EndConfirmedServiceRequest(BacnetAsyncResult request) + { + using (request) + { + request.GetResult(Timeout, Retries); + } + } + private static Task CallAsync(Func func, string exceptionMessage) { return Task.Factory.StartNew(() => From 93ce70c295bbefe7f8ad0b2729397d7f0cdc4fab Mon Sep 17 00:00:00 2001 From: Jakub Bartkowiak Date: Sun, 3 Dec 2017 22:08:21 +0100 Subject: [PATCH 03/31] chore: rewrite remaining confirmed service requests --- BACnet.csproj | 1 + BACnetClient.cs | 1111 +++++++++++--------------------- Base/BacnetAlarmSummaryData.cs | 9 + Serialize/Services.cs | 103 +-- 4 files changed, 433 insertions(+), 791 deletions(-) create mode 100644 Base/BacnetAlarmSummaryData.cs diff --git a/BACnet.csproj b/BACnet.csproj index b794e3ab..76f878ba 100644 --- a/BACnet.csproj +++ b/BACnet.csproj @@ -56,6 +56,7 @@ + diff --git a/BACnetClient.cs b/BACnetClient.cs index d8fbd001..c6902b52 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -109,11 +109,11 @@ private class LastSegmentAck public byte SequenceNumber; public byte WindowSize; - public void Set(BacnetAddress adr, byte invokeId, byte sequenceNumber, byte windowSize) + public void Set(BacnetAddress address, byte invokeId, byte sequenceNumber, byte windowSize) { lock (_lockObject) { - _address = adr; + _address = address; _invokeId = invokeId; SequenceNumber = sequenceNumber; WindowSize = windowSize; @@ -121,10 +121,10 @@ public void Set(BacnetAddress adr, byte invokeId, byte sequenceNumber, byte wind } } - public bool Wait(BacnetAddress adr, byte invokeId, TimeSpan timeout) + public bool Wait(BacnetAddress address, byte invokeId, TimeSpan timeout) { Monitor.Enter(_lockObject); - while (!adr.Equals(_address) || _invokeId != invokeId) + while (!address.Equals(_address) || _invokeId != invokeId) { _wait.Reset(); Monitor.Exit(_lockObject); @@ -176,38 +176,38 @@ public void Start() Log.Info("Started communication"); } - public delegate void ConfirmedServiceRequestHandler(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, byte invokeId, byte[] buffer, int offset, int length); + public delegate void ConfirmedServiceRequestHandler(BacnetClient sender, BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, byte invokeId, byte[] buffer, int offset, int length); public event ConfirmedServiceRequestHandler OnConfirmedServiceRequest; - public delegate void ReadPropertyRequestHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, BacnetObjectId objectId, BacnetPropertyReference property, BacnetMaxSegments maxSegments); + public delegate void ReadPropertyRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetObjectId objectId, BacnetPropertyReference property, BacnetMaxSegments maxSegments); public event ReadPropertyRequestHandler OnReadPropertyRequest; - public delegate void ReadPropertyMultipleRequestHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, IList properties, BacnetMaxSegments maxSegments); + public delegate void ReadPropertyMultipleRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, IList properties, BacnetMaxSegments maxSegments); public event ReadPropertyMultipleRequestHandler OnReadPropertyMultipleRequest; - public delegate void WritePropertyRequestHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, BacnetObjectId objectId, BacnetPropertyValue value, BacnetMaxSegments maxSegments); + public delegate void WritePropertyRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetObjectId objectId, BacnetPropertyValue value, BacnetMaxSegments maxSegments); public event WritePropertyRequestHandler OnWritePropertyRequest; - public delegate void WritePropertyMultipleRequestHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, BacnetObjectId objectId, ICollection values, BacnetMaxSegments maxSegments); + public delegate void WritePropertyMultipleRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetObjectId objectId, ICollection values, BacnetMaxSegments maxSegments); public event WritePropertyMultipleRequestHandler OnWritePropertyMultipleRequest; - public delegate void AtomicWriteFileRequestHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, bool isStream, BacnetObjectId objectId, int position, uint blockCount, byte[][] blocks, int[] counts, BacnetMaxSegments maxSegments); + public delegate void AtomicWriteFileRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, bool isStream, BacnetObjectId objectId, int position, uint blockCount, byte[][] blocks, int[] counts, BacnetMaxSegments maxSegments); public event AtomicWriteFileRequestHandler OnAtomicWriteFileRequest; - public delegate void AtomicReadFileRequestHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, bool isStream, BacnetObjectId objectId, int position, uint count, BacnetMaxSegments maxSegments); + public delegate void AtomicReadFileRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, bool isStream, BacnetObjectId objectId, int position, uint count, BacnetMaxSegments maxSegments); public event AtomicReadFileRequestHandler OnAtomicReadFileRequest; - public delegate void SubscribeCOVRequestHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, BacnetMaxSegments maxSegments); + public delegate void SubscribeCOVRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, BacnetMaxSegments maxSegments); public event SubscribeCOVRequestHandler OnSubscribeCOV; - public delegate void EventNotificationCallbackHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, BacnetEventNotificationData eventData, bool needConfirm); + public delegate void EventNotificationCallbackHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetEventNotificationData eventData, bool needConfirm); public event EventNotificationCallbackHandler OnEventNotify; - public delegate void SubscribeCOVPropertyRequestHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, BacnetPropertyReference monitoredProperty, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, float covIncrement, BacnetMaxSegments maxSegments); + public delegate void SubscribeCOVPropertyRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, BacnetPropertyReference monitoredProperty, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, float covIncrement, BacnetMaxSegments maxSegments); public event SubscribeCOVPropertyRequestHandler OnSubscribeCOVProperty; - public delegate void DeviceCommunicationControlRequestHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, uint timeDuration, uint enableDisable, string password, BacnetMaxSegments maxSegments); + public delegate void DeviceCommunicationControlRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, uint timeDuration, uint enableDisable, string password, BacnetMaxSegments maxSegments); public event DeviceCommunicationControlRequestHandler OnDeviceCommunicationControl; - public delegate void ReinitializedRequestHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, BacnetReinitializedStates state, string password, BacnetMaxSegments maxSegments); + public delegate void ReinitializedRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetReinitializedStates state, string password, BacnetMaxSegments maxSegments); public event ReinitializedRequestHandler OnReinitializedDevice; - public delegate void ReadRangeHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, BacnetObjectId objectId, BacnetPropertyReference property, BacnetReadRangeRequestTypes requestType, uint position, DateTime time, int count, BacnetMaxSegments maxSegments); + public delegate void ReadRangeHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetObjectId objectId, BacnetPropertyReference property, BacnetReadRangeRequestTypes requestType, uint position, DateTime time, int count, BacnetMaxSegments maxSegments); public event ReadRangeHandler OnReadRange; - public delegate void CreateObjectRequestHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, BacnetObjectId objectId, ICollection values, BacnetMaxSegments maxSegments); + public delegate void CreateObjectRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetObjectId objectId, ICollection values, BacnetMaxSegments maxSegments); public event CreateObjectRequestHandler OnCreateObjectRequest; - public delegate void DeleteObjectRequestHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, BacnetObjectId objectId, BacnetMaxSegments maxSegments); + public delegate void DeleteObjectRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetObjectId objectId, BacnetMaxSegments maxSegments); public event DeleteObjectRequestHandler OnDeleteObjectRequest; - protected void ProcessConfirmedServiceRequest(BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, byte invokeId, byte[] buffer, int offset, int length) + protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, byte invokeId, byte[] buffer, int offset, int length) { try { @@ -217,7 +217,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress adr, BacnetPduTypes raw_length = length; raw_offset = offset; - OnConfirmedServiceRequest?.Invoke(this, adr, type, service, maxSegments, maxAdpu, invokeId, buffer, offset, length); + OnConfirmedServiceRequest?.Invoke(this, address, type, service, maxSegments, maxAdpu, invokeId, buffer, offset, length); //don't send segmented messages, if client don't want it if ((type & BacnetPduTypes.SEGMENTED_RESPONSE_ACCEPTED) == 0) @@ -229,20 +229,20 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress adr, BacnetPduTypes if ((thsRejectReason = Services.DecodeReadProperty(buffer, offset, length, out var objectId, out var property)) >= 0) { - OnReadPropertyRequest(this, adr, invokeId, objectId, property, maxSegments); + OnReadPropertyRequest(this, address, invokeId, objectId, property, maxSegments); } else { switch (thsRejectReason) { case -1: - SendConfirmedServiceReject(adr, invokeId, BacnetRejectReason.MISSING_REQUIRED_PARAMETER); + SendConfirmedServiceReject(address, invokeId, BacnetRejectReason.MISSING_REQUIRED_PARAMETER); break; case -2: - SendConfirmedServiceReject(adr, invokeId, BacnetRejectReason.INVALID_TAG); + SendConfirmedServiceReject(address, invokeId, BacnetRejectReason.INVALID_TAG); break; case -3: - SendConfirmedServiceReject(adr, invokeId, BacnetRejectReason.TOO_MANY_ARGUMENTS); + SendConfirmedServiceReject(address, invokeId, BacnetRejectReason.TOO_MANY_ARGUMENTS); break; } Log.Warn("Couldn't decode DecodeReadProperty"); @@ -251,101 +251,101 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress adr, BacnetPduTypes else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY && OnWritePropertyRequest != null) { if (Services.DecodeWriteProperty(buffer, offset, length, out var objectId, out var value) >= 0) - OnWritePropertyRequest(this, adr, invokeId, objectId, value, maxSegments); + OnWritePropertyRequest(this, address, invokeId, objectId, value, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); - //SendConfirmedServiceReject(adr, invokeId, BacnetRejectReason.OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + //SendConfirmedServiceReject(address, invokeId, BacnetRejectReason.OTHER); Log.Warn("Couldn't decode DecodeWriteProperty"); } } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE && OnReadPropertyMultipleRequest != null) { if (Services.DecodeReadPropertyMultiple(buffer, offset, length, out var properties) >= 0) - OnReadPropertyMultipleRequest(this, adr, invokeId, properties, maxSegments); + OnReadPropertyMultipleRequest(this, address, invokeId, properties, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode DecodeReadPropertyMultiple"); } } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE && OnWritePropertyMultipleRequest != null) { if (Services.DecodeWritePropertyMultiple(buffer, offset, length, out var objectId, out var values) >= 0) - OnWritePropertyMultipleRequest(this, adr, invokeId, objectId, values, maxSegments); + OnWritePropertyMultipleRequest(this, address, invokeId, objectId, values, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode DecodeWritePropertyMultiple"); } } 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) - OnCOVNotification(this, adr, invokeId, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, true, values, maxSegments); + OnCOVNotification(this, address, invokeId, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, true, values, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode COVNotify"); } } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE && OnAtomicWriteFileRequest != null) { if (Services.DecodeAtomicWriteFile(buffer, offset, length, out var isStream, out var objectId, out var position, out var blockCount, out var blocks, out var counts) >= 0) - OnAtomicWriteFileRequest(this, adr, invokeId, isStream, objectId, position, blockCount, blocks, counts, maxSegments); + OnAtomicWriteFileRequest(this, address, invokeId, isStream, objectId, position, blockCount, blocks, counts, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode AtomicWriteFile"); } } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE && OnAtomicReadFileRequest != null) { if (Services.DecodeAtomicReadFile(buffer, offset, length, out var isStream, out var objectId, out var position, out var count) >= 0) - OnAtomicReadFileRequest(this, adr, invokeId, isStream, objectId, position, count, maxSegments); + OnAtomicReadFileRequest(this, address, invokeId, isStream, objectId, position, count, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode AtomicReadFile"); } } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV && OnSubscribeCOV != null) { if (Services.DecodeSubscribeCOV(buffer, offset, length, out var subscriberProcessIdentifier, out var monitoredObjectIdentifier, out var cancellationRequest, out var issueConfirmedNotifications, out var lifetime) >= 0) - OnSubscribeCOV(this, adr, invokeId, subscriberProcessIdentifier, monitoredObjectIdentifier, cancellationRequest, issueConfirmedNotifications, lifetime, maxSegments); + OnSubscribeCOV(this, address, invokeId, subscriberProcessIdentifier, monitoredObjectIdentifier, cancellationRequest, issueConfirmedNotifications, lifetime, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode SubscribeCOV"); } } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY && OnSubscribeCOVProperty != null) { if (Services.DecodeSubscribeProperty(buffer, offset, length, out var subscriberProcessIdentifier, out var monitoredObjectIdentifier, out var monitoredProperty, out var cancellationRequest, out var issueConfirmedNotifications, out var lifetime, out var covIncrement) >= 0) - OnSubscribeCOVProperty(this, adr, invokeId, subscriberProcessIdentifier, monitoredObjectIdentifier, monitoredProperty, cancellationRequest, issueConfirmedNotifications, lifetime, covIncrement, maxSegments); + OnSubscribeCOVProperty(this, address, invokeId, subscriberProcessIdentifier, monitoredObjectIdentifier, monitoredProperty, cancellationRequest, issueConfirmedNotifications, lifetime, covIncrement, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode SubscribeCOVProperty"); } } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL && OnDeviceCommunicationControl != null) { if (Services.DecodeDeviceCommunicationControl(buffer, offset, length, out var timeDuration, out var enableDisable, out var password) >= 0) - OnDeviceCommunicationControl(this, adr, invokeId, timeDuration, enableDisable, password, maxSegments); + OnDeviceCommunicationControl(this, address, invokeId, timeDuration, enableDisable, password, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode DeviceCommunicationControl"); } } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_REINITIALIZE_DEVICE && OnReinitializedDevice != null) { if (Services.DecodeReinitializeDevice(buffer, offset, length, out var state, out var password) >= 0) - OnReinitializedDevice(this, adr, invokeId, state, password, maxSegments); + OnReinitializedDevice(this, address, invokeId, state, password, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode ReinitializeDevice"); } } @@ -353,89 +353,89 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress adr, BacnetPduTypes { if (Services.DecodeEventNotifyData(buffer, offset, length, out var eventData) >= 0) { - OnEventNotify(this, adr, invokeId, eventData, true); + OnEventNotify(this, address, invokeId, eventData, true); } else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode Event/Alarm Notification"); } } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE && OnReadRange != null) { if (Services.DecodeReadRange(buffer, offset, length, out var objectId, out var property, out var requestType, out var position, out var time, out var count) >= 0) - OnReadRange(this, adr, invokeId, objectId, property, requestType, position, time, count, maxSegments); + OnReadRange(this, address, invokeId, objectId, property, requestType, position, time, count, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode ReadRange"); } } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT && OnCreateObjectRequest != null) { if (Services.DecodeCreateObject(buffer, offset, length, out var objectId, out var values) >= 0) - OnCreateObjectRequest(this, adr, invokeId, objectId, values, maxSegments); + OnCreateObjectRequest(this, address, invokeId, objectId, values, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode CreateObject"); } } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT && OnDeleteObjectRequest != null) { if (Services.DecodeDeleteObject(buffer, offset, length, out var objectId) >= 0) - OnDeleteObjectRequest(this, adr, invokeId, objectId, maxSegments); + OnDeleteObjectRequest(this, address, invokeId, objectId, maxSegments); else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Warn("Couldn't decode DecodeDeleteObject"); } } else { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_REJECT_UNRECOGNIZED_SERVICE); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_REJECT_UNRECOGNIZED_SERVICE); Log.Warn($"Confirmed service not handled: {service}"); } } catch (Exception ex) { - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); Log.Error("Error in ProcessConfirmedServiceRequest", ex); } } - public delegate void UnconfirmedServiceRequestHandler(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetUnconfirmedServices service, byte[] buffer, int offset, int length); + public delegate void UnconfirmedServiceRequestHandler(BacnetClient sender, BacnetAddress address, BacnetPduTypes type, BacnetUnconfirmedServices service, byte[] buffer, int offset, int length); public event UnconfirmedServiceRequestHandler OnUnconfirmedServiceRequest; - public delegate void WhoHasHandler(BacnetClient sender, BacnetAddress adr, int lowLimit, int highLimit, BacnetObjectId objId, string objName); + public delegate void WhoHasHandler(BacnetClient sender, BacnetAddress address, int lowLimit, int highLimit, BacnetObjectId objId, string objName); public event WhoHasHandler OnWhoHas; - public delegate void IamHandler(BacnetClient sender, BacnetAddress adr, uint deviceId, uint maxAPDU, BacnetSegmentations segmentation, ushort vendorId); + public delegate void IamHandler(BacnetClient sender, BacnetAddress address, uint deviceId, uint maxAPDU, BacnetSegmentations segmentation, ushort vendorId); public event IamHandler OnIam; - public delegate void WhoIsHandler(BacnetClient sender, BacnetAddress adr, int lowLimit, int highLimit); + public delegate void WhoIsHandler(BacnetClient sender, BacnetAddress address, int lowLimit, int highLimit); public event WhoIsHandler OnWhoIs; - public delegate void TimeSynchronizeHandler(BacnetClient sender, BacnetAddress adr, DateTime dateTime, bool utc); + public delegate void TimeSynchronizeHandler(BacnetClient sender, BacnetAddress address, DateTime dateTime, bool utc); public event TimeSynchronizeHandler OnTimeSynchronize; //used by both 'confirmed' and 'unconfirmed' notify - public delegate void COVNotificationHandler(BacnetClient sender, BacnetAddress adr, byte invokeId, uint subscriberProcessIdentifier, BacnetObjectId initiatingDeviceIdentifier, BacnetObjectId monitoredObjectIdentifier, uint timeRemaining, bool needConfirm, ICollection values, BacnetMaxSegments maxSegments); + public delegate void COVNotificationHandler(BacnetClient sender, BacnetAddress address, byte invokeId, uint subscriberProcessIdentifier, BacnetObjectId initiatingDeviceIdentifier, BacnetObjectId monitoredObjectIdentifier, uint timeRemaining, bool needConfirm, ICollection values, BacnetMaxSegments maxSegments); public event COVNotificationHandler OnCOVNotification; - protected void ProcessUnconfirmedServiceRequest(BacnetAddress adr, BacnetPduTypes type, BacnetUnconfirmedServices service, byte[] buffer, int offset, int length) + protected void ProcessUnconfirmedServiceRequest(BacnetAddress address, BacnetPduTypes type, BacnetUnconfirmedServices service, byte[] buffer, int offset, int length) { try { Log.Debug("UnconfirmedServiceRequest"); - OnUnconfirmedServiceRequest?.Invoke(this, adr, type, service, buffer, offset, length); + OnUnconfirmedServiceRequest?.Invoke(this, address, type, service, buffer, offset, length); if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_I_AM && OnIam != null) { if (Services.DecodeIamBroadcast(buffer, offset, out var deviceId, out var maxAdpu, out var segmentation, out var vendorId) >= 0) - OnIam(this, adr, deviceId, maxAdpu, segmentation, vendorId); + OnIam(this, address, deviceId, maxAdpu, segmentation, vendorId); else Log.Warn("Couldn't decode IamBroadcast"); } else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS && OnWhoIs != null) { if (Services.DecodeWhoIsBroadcast(buffer, offset, length, out var lowLimit, out var highLimit) >= 0) - OnWhoIs(this, adr, lowLimit, highLimit); + OnWhoIs(this, address, lowLimit, highLimit); else Log.Warn("Couldn't decode WhoIsBroadcast"); } @@ -443,42 +443,42 @@ protected void ProcessUnconfirmedServiceRequest(BacnetAddress adr, BacnetPduType else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_HAS && OnWhoHas != null) { if (Services.DecodeWhoHasBroadcast(buffer, offset, length, out var lowLimit, out var highLimit, out var objId, out var objName) >= 0) - OnWhoHas(this, adr, lowLimit, highLimit, objId, objName); + OnWhoHas(this, address, lowLimit, highLimit, objId, objName); else Log.Warn("Couldn't decode WhoHasBroadcast"); } 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) - OnCOVNotification(this, adr, 0, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, false, values, BacnetMaxSegments.MAX_SEG0); + OnCOVNotification(this, address, 0, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, false, values, BacnetMaxSegments.MAX_SEG0); else Log.Warn("Couldn't decode COVNotifyUnconfirmed"); } else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION && OnTimeSynchronize != null) { if (Services.DecodeTimeSync(buffer, offset, length, out var dateTime) >= 0) - OnTimeSynchronize(this, adr, dateTime, false); + OnTimeSynchronize(this, address, dateTime, false); else Log.Warn("Couldn't decode TimeSynchronize"); } else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION && OnTimeSynchronize != null) { if (Services.DecodeTimeSync(buffer, offset, length, out var dateTime) >= 0) - OnTimeSynchronize(this, adr, dateTime, true); + OnTimeSynchronize(this, address, dateTime, true); else Log.Warn("Couldn't decode TimeSynchronize"); } else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION && OnEventNotify!=null) // F. Chaxel { if (Services.DecodeEventNotifyData(buffer, offset, length, out var eventData) >= 0) - OnEventNotify(this, adr, 0, eventData, false); + OnEventNotify(this, address, 0, eventData, false); else Log.Warn("Couldn't decode Event/Alarm Notification"); } else { Log.Warn($"Unconfirmed service not handled: {service}"); - // SendUnConfirmedServiceReject(adr); ? exists ? + // SendUnConfirmedServiceReject(address); ? exists ? } } catch (Exception ex) @@ -487,15 +487,15 @@ protected void ProcessUnconfirmedServiceRequest(BacnetAddress adr, BacnetPduType } } - public delegate void SimpleAckHandler(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] data, int dataOffset, int dataLength); + public delegate void SimpleAckHandler(BacnetClient sender, BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] data, int dataOffset, int dataLength); public event SimpleAckHandler OnSimpleAck; - protected void ProcessSimpleAck(BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] buffer, int offset, int length) + protected void ProcessSimpleAck(BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] buffer, int offset, int length) { try { Log.Debug($"Received SimpleAck for {service}"); - OnSimpleAck?.Invoke(this, adr, type, service, invokeId, buffer, offset, length); + OnSimpleAck?.Invoke(this, address, type, service, invokeId, buffer, offset, length); } catch (Exception ex) { @@ -503,15 +503,15 @@ protected void ProcessSimpleAck(BacnetAddress adr, BacnetPduTypes type, BacnetCo } } - public delegate void ComplexAckHandler(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] buffer, int offset, int length); + public delegate void ComplexAckHandler(BacnetClient sender, BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] buffer, int offset, int length); public event ComplexAckHandler OnComplexAck; - protected void ProcessComplexAck(BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] buffer, int offset, int length) + protected void ProcessComplexAck(BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] buffer, int offset, int length) { try { Log.Debug($"Received ComplexAck for {service}"); - OnComplexAck?.Invoke(this, adr, type, service, invokeId, buffer, offset, length); + OnComplexAck?.Invoke(this, address, type, service, invokeId, buffer, offset, length); } catch (Exception ex) { @@ -519,10 +519,10 @@ protected void ProcessComplexAck(BacnetAddress adr, BacnetPduTypes type, BacnetC } } - public delegate void ErrorHandler(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetErrorClasses errorClass, BacnetErrorCodes errorCode, byte[] buffer, int offset, int length); + public delegate void ErrorHandler(BacnetClient sender, BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetErrorClasses errorClass, BacnetErrorCodes errorCode, byte[] buffer, int offset, int length); public event ErrorHandler OnError; - protected void ProcessError(BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] buffer, int offset, int length) + protected void ProcessError(BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] buffer, int offset, int length) { try { @@ -530,7 +530,7 @@ protected void ProcessError(BacnetAddress adr, BacnetPduTypes type, BacnetConfir Log.Warn("Couldn't decode received Error"); Log.Debug($"Received Error {errorClass} {errorCode}"); - OnError?.Invoke(this, adr, type, service, invokeId, errorClass, errorCode, buffer, offset, length); + OnError?.Invoke(this, address, type, service, invokeId, errorClass, errorCode, buffer, offset, length); } catch (Exception ex) { @@ -538,15 +538,15 @@ protected void ProcessError(BacnetAddress adr, BacnetPduTypes type, BacnetConfir } } - public delegate void AbortHandler(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, byte invokeId, BacnetAbortReason reason, byte[] buffer, int offset, int length); + public delegate void AbortHandler(BacnetClient sender, BacnetAddress address, BacnetPduTypes type, byte invokeId, BacnetAbortReason reason, byte[] buffer, int offset, int length); public event AbortHandler OnAbort; - protected void ProcessAbort(BacnetAddress adr, BacnetPduTypes type, byte invokeId, BacnetAbortReason reason, byte[] buffer, int offset, int length) + protected void ProcessAbort(BacnetAddress address, BacnetPduTypes type, byte invokeId, BacnetAbortReason reason, byte[] buffer, int offset, int length) { try { Log.Debug($"Received Abort, reason: {reason}"); - OnAbort?.Invoke(this, adr, type, invokeId, reason, buffer, offset, length); + OnAbort?.Invoke(this, address, type, invokeId, reason, buffer, offset, length); } catch (Exception ex) { @@ -554,15 +554,15 @@ protected void ProcessAbort(BacnetAddress adr, BacnetPduTypes type, byte invokeI } } - public delegate void RejectHandler(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, byte invokeId, BacnetRejectReason reason, byte[] buffer, int offset, int length); + public delegate void RejectHandler(BacnetClient sender, BacnetAddress address, BacnetPduTypes type, byte invokeId, BacnetRejectReason reason, byte[] buffer, int offset, int length); public event RejectHandler OnReject; - protected void ProcessReject(BacnetAddress adr, BacnetPduTypes type, byte invokeId, BacnetRejectReason reason, byte[] buffer, int offset, int length) + protected void ProcessReject(BacnetAddress address, BacnetPduTypes type, byte invokeId, BacnetRejectReason reason, byte[] buffer, int offset, int length) { try { Log.Debug($"Received Reject, reason: {reason}"); - OnReject?.Invoke(this, adr, type, invokeId, reason, buffer, offset, length); + OnReject?.Invoke(this, address, type, invokeId, reason, buffer, offset, length); } catch (Exception ex) { @@ -570,15 +570,15 @@ protected void ProcessReject(BacnetAddress adr, BacnetPduTypes type, byte invoke } } - public delegate void SegmentAckHandler(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, byte originalInvokeId, byte sequenceNumber, byte actualWindowSize, byte[] buffer, int offset, int length); + public delegate void SegmentAckHandler(BacnetClient sender, BacnetAddress address, BacnetPduTypes type, byte originalInvokeId, byte sequenceNumber, byte actualWindowSize, byte[] buffer, int offset, int length); public event SegmentAckHandler OnSegmentAck; - protected void ProcessSegmentAck(BacnetAddress adr, BacnetPduTypes type, byte originalInvokeId, byte sequenceNumber, byte actualWindowSize, byte[] buffer, int offset, int length) + protected void ProcessSegmentAck(BacnetAddress address, BacnetPduTypes type, byte originalInvokeId, byte sequenceNumber, byte actualWindowSize, byte[] buffer, int offset, int length) { try { Log.Debug("Received SegmentAck"); - OnSegmentAck?.Invoke(this, adr, type, originalInvokeId, sequenceNumber, actualWindowSize, buffer, offset, length); + OnSegmentAck?.Invoke(this, address, type, originalInvokeId, sequenceNumber, actualWindowSize, buffer, offset, length); } catch (Exception ex) { @@ -586,10 +586,10 @@ protected void ProcessSegmentAck(BacnetAddress adr, BacnetPduTypes type, byte or } } - public delegate void SegmentHandler(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); + 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 event SegmentHandler OnSegment; - private void ProcessSegment(BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, bool server, byte sequenceNumber, byte proposedWindowNumber, byte[] buffer, int offset, int length) + 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; @@ -602,7 +602,7 @@ private void ProcessSegment(BacnetAddress adr, BacnetPduTypes type, BacnetConfir //send negative ack if (sequenceNumber != _lastSequenceNumber + 1) { - SegmentAckResponse(adr, true, server, invokeId, _lastSequenceNumber, proposedWindowNumber); + SegmentAckResponse(address, true, server, invokeId, _lastSequenceNumber, proposedWindowNumber); Log.Debug("Segment sequence out of order"); return; } @@ -621,15 +621,15 @@ private void ProcessSegment(BacnetAddress adr, BacnetPduTypes type, BacnetConfir if (ForceWindowSize) proposedWindowNumber = ProposedWindowSize; - SegmentAckResponse(adr, false, server, invokeId, sequenceNumber, proposedWindowNumber); + SegmentAckResponse(address, false, server, invokeId, sequenceNumber, proposedWindowNumber); } //Send on - OnSegment?.Invoke(this, adr, type, service, invokeId, maxSegments, maxAdpu, sequenceNumber, first, moreFollows, buffer, offset, length); + OnSegment?.Invoke(this, address, type, service, invokeId, maxSegments, maxAdpu, sequenceNumber, first, moreFollows, buffer, offset, length); //default segment assembly. We run this seperately from the above handler, to make sure that it comes after! if (DefaultSegmentationHandling) - PerformDefaultSegmentHandling(adr, type, service, invokeId, maxSegments, maxAdpu, first, moreFollows, buffer, offset, length); + PerformDefaultSegmentHandling(address, type, service, invokeId, maxSegments, maxAdpu, first, moreFollows, buffer, offset, length); } private byte[] AssembleSegments() @@ -646,7 +646,7 @@ private byte[] AssembleSegments() /// /// This is a simple handling that stores all segments in memory and assembles them when done /// - private void PerformDefaultSegmentHandling(BacnetAddress adr, 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, bool first, bool moreFollows, byte[] buffer, int offset, int length) { if (first) { @@ -684,10 +684,10 @@ private void PerformDefaultSegmentHandling(BacnetAddress adr, BacnetPduTypes typ _segments.Clear(); //process - ProcessApdu(adr, type, apduBuffer, 0, apduBuffer.Length); + ProcessApdu(address, type, apduBuffer, 0, apduBuffer.Length); } - private void ProcessApdu(BacnetAddress adr, BacnetPduTypes type, byte[] buffer, int offset, int length) + private void ProcessApdu(BacnetAddress address, BacnetPduTypes type, byte[] buffer, int offset, int length) { switch (type & BacnetPduTypes.PDU_TYPE_MASK) { @@ -696,7 +696,7 @@ private void ProcessApdu(BacnetAddress adr, BacnetPduTypes type, byte[] buffer, var apduHeaderLen = APDU.DecodeUnconfirmedServiceRequest(buffer, offset, out type, out var service); offset += apduHeaderLen; length -= apduHeaderLen; - ProcessUnconfirmedServiceRequest(adr, type, service, buffer, offset, length); + ProcessUnconfirmedServiceRequest(address, type, service, buffer, offset, length); } break; @@ -705,7 +705,7 @@ private void ProcessApdu(BacnetAddress adr, BacnetPduTypes type, byte[] buffer, var apduHeaderLen = APDU.DecodeSimpleAck(buffer, offset, out type, out var service, out var invokeId); offset += apduHeaderLen; length -= apduHeaderLen; - ProcessSimpleAck(adr, type, service, invokeId, buffer, offset, length); + ProcessSimpleAck(address, type, service, invokeId, buffer, offset, length); } break; @@ -718,11 +718,11 @@ private void ProcessApdu(BacnetAddress adr, BacnetPduTypes type, byte[] buffer, length -= apduHeaderLen; if ((type & BacnetPduTypes.SEGMENTED_MESSAGE) == 0) //don't process segmented messages here { - ProcessComplexAck(adr, type, service, invokeId, buffer, offset, length); + ProcessComplexAck(address, type, service, invokeId, buffer, offset, length); } else { - ProcessSegment(adr, type, service, invokeId, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU50, false, + ProcessSegment(address, type, service, invokeId, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU50, false, sequenceNumber, proposedWindowNumber, buffer, offset, length); } } @@ -735,8 +735,8 @@ private void ProcessApdu(BacnetAddress adr, BacnetPduTypes type, byte[] buffer, offset += apduHeaderLen; length -= apduHeaderLen; - _lastSegmentAck.Set(adr, originalInvokeId, sequenceNumber, actualWindowSize); - ProcessSegmentAck(adr, type, originalInvokeId, sequenceNumber, actualWindowSize, buffer, offset, length); + _lastSegmentAck.Set(address, originalInvokeId, sequenceNumber, actualWindowSize); + ProcessSegmentAck(address, type, originalInvokeId, sequenceNumber, actualWindowSize, buffer, offset, length); } break; @@ -745,7 +745,7 @@ private void ProcessApdu(BacnetAddress adr, BacnetPduTypes type, byte[] buffer, var apduHeaderLen = APDU.DecodeError(buffer, offset, out type, out var service, out var invokeId); offset += apduHeaderLen; length -= apduHeaderLen; - ProcessError(adr, type, service, invokeId, buffer, offset, length); + ProcessError(address, type, service, invokeId, buffer, offset, length); } break; @@ -754,7 +754,7 @@ private void ProcessApdu(BacnetAddress adr, BacnetPduTypes type, byte[] buffer, var apduHeaderLen = APDU.DecodeAbort(buffer, offset, out type, out var invokeId, out var reason); offset += apduHeaderLen; length -= apduHeaderLen; - ProcessAbort(adr, type, invokeId, reason, buffer, offset, length); + ProcessAbort(address, type, invokeId, reason, buffer, offset, length); } break; @@ -763,7 +763,7 @@ private void ProcessApdu(BacnetAddress adr, BacnetPduTypes type, byte[] buffer, var apduHeaderLen = APDU.DecodeReject(buffer, offset, out type, out var invokeId, out var reason); offset += apduHeaderLen; length -= apduHeaderLen; - ProcessReject(adr, type, invokeId, reason, buffer, offset, length); + ProcessReject(address, type, invokeId, reason, buffer, offset, length); } break; @@ -777,11 +777,11 @@ private void ProcessApdu(BacnetAddress adr, BacnetPduTypes type, byte[] buffer, if ((type & BacnetPduTypes.SEGMENTED_MESSAGE) == 0) //don't process segmented messages here { - ProcessConfirmedServiceRequest(adr, type, service, maxSegments, maxAdpu, invokeId, buffer, offset, length); + ProcessConfirmedServiceRequest(address, type, service, maxSegments, maxAdpu, invokeId, buffer, offset, length); } else { - ProcessSegment(adr, type, service, invokeId, maxSegments, maxAdpu, true, sequenceNumber, proposedWindowNumber, buffer, offset, length); + ProcessSegment(address, type, service, invokeId, maxSegments, maxAdpu, true, sequenceNumber, proposedWindowNumber, buffer, offset, length); } } break; @@ -956,39 +956,39 @@ public void IHave(BacnetObjectId deviceId, BacnetObjectId objId, string objName) } - public void SendUnconfirmedEventNotification(BacnetAddress adr, BacnetEventNotificationData eventData) + public void SendUnconfirmedEventNotification(BacnetAddress address, BacnetEventNotificationData eventData) { Log.Debug($"Sending Event Notification {eventData.eventType} {eventData.eventObjectIdentifier}"); var b = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, adr); + NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, address); APDU.EncodeUnconfirmedServiceRequest(b, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION); Services.EncodeEventNotifyUnconfirmed(b, eventData); - Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, adr, false, 0); + Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, address, false, 0); } - public void SendConfirmedServiceReject(BacnetAddress adr, byte invokeId, BacnetRejectReason reason) + public void SendConfirmedServiceReject(BacnetAddress address, byte invokeId, BacnetRejectReason reason) { Log.Debug($"Sending Service reject: {reason}"); var b = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, adr); + NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, address); APDU.EncodeError(b, BacnetPduTypes.PDU_TYPE_REJECT, (BacnetConfirmedServices)reason, invokeId); - Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, adr, false, 0); + Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, address, false, 0); } - public void SynchronizeTime(BacnetAddress adr, DateTime dateTime) + public void SynchronizeTime(BacnetAddress address, DateTime dateTime) { Log.Debug($"Sending Time Synchronize: {dateTime} {dateTime.Kind.ToString().ToUpper()}"); var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, adr); + NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, address); APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, dateTime.Kind == DateTimeKind.Utc ? BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION : BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION); Services.EncodeTimeSync(buffer, dateTime); - Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, adr, false, 0); + Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, address, false, 0); } public int GetMaxApdu() @@ -1031,132 +1031,66 @@ public int GetFileBufferMaxSize() return GetMaxApdu() - 18; } - public bool WriteFileRequest(BacnetAddress adr, BacnetObjectId objectId, ref int position, int count, byte[] fileBuffer, byte invokeId = 0) + public void WriteFileRequest(BacnetAddress address, BacnetObjectId objectId, ref int position, int count, byte[] fileBuffer) { - using (var result = (BacnetAsyncResult)BeginWriteFileRequest(adr, objectId, position, count, fileBuffer, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndWriteFileRequest(result, out position, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - return false; + using (var request = BeginWriteFileRequest(address, objectId, position, count, fileBuffer, true)) + EndWriteFileRequest(request, out position); } - public IAsyncResult BeginWriteFileRequest(BacnetAddress adr, BacnetObjectId objectId, int position, int count, byte[] fileBuffer, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginWriteFileRequest(BacnetAddress address, BacnetObjectId objectId, int position, int count, byte[] fileBuffer, bool waitForTransmit = false) { Log.Debug("Sending AtomicWriteFileRequest"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeAtomicWriteFile(buffer, true, objectId, position, 1, new[] { fileBuffer }, new[] { count }); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, + buffer => Services.EncodeAtomicWriteFile(buffer, true, objectId, position, 1, new[] { fileBuffer }, new[] { count }), waitForTransmit); } - public void EndWriteFileRequest(IAsyncResult result, out int position, out Exception ex) + public void EndWriteFileRequest(BacnetAsyncResult request, out int position) { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - - if (ex == null) - { - //decode - if (Services.DecodeAtomicWriteFileAcknowledge(res.Result, 0, res.Result.Length, out _, out position) < 0) - ex = new Exception("Decode"); - } - else + using (request) { - position = -1; + position = request.GetResult(Timeout, Retries, r => + { + if (Services.DecodeAtomicWriteFileAcknowledge(r.Result, 0, r.Result.Length, out _, out var positionValue) < 0) + throw new Exception("Failed to decode AtomicWriteFileAcknowledge"); + return positionValue; + }); } - - res.Dispose(); } - public IAsyncResult BeginReadFileRequest(BacnetAddress adr, BacnetObjectId objectId, int position, uint count, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginReadFileRequest(BacnetAddress address, BacnetObjectId objectId, int position, uint count, bool waitForTransmit = false) { Log.Debug("Sending AtomicReadFileRequest"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - //encode - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeAtomicReadFile(buffer, true, objectId, position, count); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, + buffer => Services.EncodeAtomicReadFile(buffer, true, objectId, position, count), waitForTransmit); } - public void EndReadFileRequest(IAsyncResult result, out uint count, out int position, out bool endOfFile, out byte[] fileBuffer, out int fileBufferOffset, out Exception ex) + public void EndReadFileRequest(BacnetAsyncResult result, out uint count, out int position, out bool endOfFile, out byte[] fileBuffer, out int fileBufferOffset) { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - - if (ex == null) - { - //decode - if (Services.DecodeAtomicReadFileAcknowledge(res.Result, 0, res.Result.Length, out endOfFile, out _, out position, out count, out fileBuffer, out fileBufferOffset) < 0) - ex = new Exception("Decode"); - } - else + using (result) { - count = 0; - endOfFile = true; - position = -1; - fileBufferOffset = -1; - fileBuffer = new byte[0]; - } + var values = result.GetResult(Timeout, Retries, res => + { + var decodedBytesCount = Services.DecodeAtomicReadFileAcknowledge(res.Result, 0, res.Result.Length, out var endOfFileValue, out _, + out var positionValue, out var countValue, out var fileBufferValue, out var fileBufferOffsetValue); + + if (decodedBytesCount < 0) + throw new Exception("Failed to decode AtomicReadFileAcknowledge"); + + return Tuple.Create(countValue, positionValue, endOfFileValue, fileBufferValue, fileBufferOffsetValue); + }); - res.Dispose(); + count = values.Item1; + position = values.Item2; + endOfFile = values.Item3; + fileBuffer = values.Item4; + fileBufferOffset = values.Item5; + } } - public bool ReadFileRequest(BacnetAddress adr, BacnetObjectId objectId, ref int position, ref uint count, out bool endOfFile, out byte[] fileBuffer, out int fileBufferOffset, byte invokeId = 0) + public void ReadFileRequest(BacnetAddress address, BacnetObjectId objectId, ref int position, ref uint count, out bool endOfFile, out byte[] fileBuffer, out int fileBufferOffset) { - using (var result = (BacnetAsyncResult)BeginReadFileRequest(adr, objectId, position, count, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndReadFileRequest(result, out count, out position, out endOfFile, out fileBuffer, out fileBufferOffset, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - position = -1; - count = 0; - fileBuffer = null; - endOfFile = true; - fileBufferOffset = -1; - return false; + using (var request = BeginReadFileRequest(address, objectId, position, count, true)) + EndReadFileRequest(request, out count, out position, out endOfFile, out fileBuffer, out fileBufferOffset); } public BacnetAsyncResult BeginReadRangeRequest(BacnetAddress address, BacnetObjectId objectId, uint idxBegin, uint quantity, bool waitForTransmit = false) @@ -1184,16 +1118,16 @@ public void EndReadRangeRequest(BacnetAsyncResult request, out byte[] trendBuffe } } - public void ReadRangeRequest(BacnetAddress adr, BacnetObjectId objectId, uint idxBegin, ref uint quantity, out byte[] range) + public void ReadRangeRequest(BacnetAddress address, BacnetObjectId objectId, uint idxBegin, ref uint quantity, out byte[] range) { - using (var asyncResult = BeginReadRangeRequest(adr, objectId, idxBegin, quantity, true)) + using (var asyncResult = BeginReadRangeRequest(address, objectId, idxBegin, quantity, true)) EndReadRangeRequest(asyncResult, out range, out quantity); // quantity read could be less than demanded } public void SubscribeCOVRequest(BacnetAddress address, BacnetObjectId objectId, uint subscribeId, bool cancel, bool issueConfirmedNotifications, uint lifetime) { using (var asyncResult = BeginSubscribeCOVRequest(address, objectId, subscribeId, cancel, issueConfirmedNotifications, lifetime, true)) - EndConfirmedServiceRequest(asyncResult); + EndSubscribeCOVRequest(asyncResult); } public BacnetAsyncResult BeginSubscribeCOVRequest(BacnetAddress address, BacnetObjectId objectId, uint subscribeId, bool cancel, bool issueConfirmedNotifications, uint lifetime, bool waitForTransmit = false) @@ -1203,10 +1137,12 @@ public BacnetAsyncResult BeginSubscribeCOVRequest(BacnetAddress address, BacnetO buffer => Services.EncodeSubscribeCOV(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, lifetime), waitForTransmit); } + public void EndSubscribeCOVRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); + public void SubscribePropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyReference monitoredProperty, uint subscribeId, bool cancel, bool issueConfirmedNotifications) { using (var asyncResult = BeginSubscribePropertyRequest(address, objectId, monitoredProperty, subscribeId, cancel, issueConfirmedNotifications, true)) - EndConfirmedServiceRequest(asyncResult); + EndSubscribePropertyRequest(asyncResult); } public BacnetAsyncResult BeginSubscribePropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyReference monitoredProperty, uint subscribeId, bool cancel, bool issueConfirmedNotifications, bool waitForTransmit) @@ -1216,6 +1152,8 @@ public BacnetAsyncResult BeginSubscribePropertyRequest(BacnetAddress address, Ba buffer => Services.EncodeSubscribeProperty(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, 0, monitoredProperty, false, 0f), waitForTransmit); } + public void EndSubscribePropertyRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); + public BacnetValue ReadPropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, uint index) { if (index == ASN1.BACNET_ARRAY_ALL) throw new ArgumentOutOfRangeException(nameof(index)); @@ -1282,13 +1220,13 @@ public void WritePropertyRequest(BacnetAddress address, BacnetObjectId objectId, public void WritePropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, IEnumerable valueList) { using (var asyncResult = BeginWritePropertyRequest(address, objectId, propertyId, valueList)) - EndConfirmedServiceRequest(asyncResult); + EndWritePropertyRequest(asyncResult); } public void WritePropertyMultipleRequest(BacnetAddress address, BacnetObjectId objectId, ICollection valueList) { using (var asyncResult = BeginWritePropertyMultipleRequest(address, objectId, valueList)) - EndConfirmedServiceRequest(asyncResult); + EndWritePropertyMultipleRequest(asyncResult); } public BacnetAsyncResult BeginWritePropertyRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, IEnumerable valueList, bool waitForTransmit = true) @@ -1298,6 +1236,8 @@ public BacnetAsyncResult BeginWritePropertyRequest(BacnetAddress address, Bacnet buffer => Services.EncodeWriteProperty(buffer, objectId, (uint)propertyId, ASN1.BACNET_ARRAY_ALL, _writepriority, valueList), waitForTransmit); } + public void EndWritePropertyRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); + public BacnetAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress address, BacnetObjectId objectId, ICollection valueList, bool waitForTransmit = true) { @@ -1306,17 +1246,19 @@ public BacnetAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress address buffer => Services.EncodeWritePropertyMultiple(buffer, objectId, valueList), waitForTransmit); } + public void EndWritePropertyMultipleRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); + public void WritePropertyMultipleRequest(BacnetAddress address, ICollection valueList) { using (var asyncResult = BeginWritePropertyMultipleRequest(address, valueList)) - EndConfirmedServiceRequest(asyncResult); + EndWritePropertyMultipleRequest(asyncResult); } - public BacnetAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress adr, ICollection valueList, bool waitForTransmit = true) + public BacnetAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress address, ICollection valueList, bool waitForTransmit = true) { var objectIds = string.Join(", ", valueList.Select(v => v.objectIdentifier)); Log.Debug($"Sending WritePropertyMultipleRequest {objectIds}"); - return BeginConfirmedServiceRequest(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, buffer => Services.EncodeWriteObjectMultiple(buffer, valueList), waitForTransmit); } @@ -1345,10 +1287,10 @@ public IList ReadPropertyMultipleRequest(BacnetAddress a return EndReadPropertyMultipleRequest(asyncResult); } - public BacnetAsyncResult BeginReadPropertyMultipleRequest(BacnetAddress adr, BacnetObjectId objectId, IList propertyIdAndIndex, bool waitForTransmit = true) + public BacnetAsyncResult BeginReadPropertyMultipleRequest(BacnetAddress address, BacnetObjectId objectId, IList propertyIdAndIndex, bool waitForTransmit = true) { var properties = new[] { new BacnetReadAccessSpecification(objectId, propertyIdAndIndex) }; - return BeginReadPropertyMultipleRequest(adr, properties); + return BeginReadPropertyMultipleRequest(address, properties, waitForTransmit); } public BacnetAsyncResult BeginReadPropertyMultipleRequest(BacnetAddress address, IList properties, bool waitForTransmit = true) @@ -1376,608 +1318,287 @@ public IList EndReadPropertyMultipleRequest(BacnetAsyncR } } - // By Christopher Günter - public bool CreateObjectRequest(BacnetAddress adr, BacnetObjectId objectId, ICollection valueList = null, byte invokeId = 0) + public void CreateObjectRequest(BacnetAddress address, BacnetObjectId objectId, ICollection valueList = null) { - using (var result = (BacnetAsyncResult)BeginCreateObjectRequest(adr, objectId, valueList, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndCreateObjectRequest(result, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - return false; + using (var request = BeginCreateObjectRequest(address, objectId, valueList, true)) + EndCreateObjectRequest(request); } - public IAsyncResult BeginCreateObjectRequest(BacnetAddress adr, BacnetObjectId objectId, ICollection valueList, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginCreateObjectRequest(BacnetAddress address, BacnetObjectId objectId, ICollection valueList, bool waitForTransmit = false) { Log.Debug("Sending CreateObjectRequest"); - if (invokeId == 0) invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeCreateProperty(buffer, objectId, valueList); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT, + buffer => Services.EncodeCreateProperty(buffer, objectId, valueList), waitForTransmit); } - public void EndCreateObjectRequest(IAsyncResult result, out Exception ex) - { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - - res.Dispose(); - } + public void EndCreateObjectRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); - public bool DeleteObjectRequest(BacnetAddress adr, BacnetObjectId objectId, byte invokeId = 0) + public void DeleteObjectRequest(BacnetAddress address, BacnetObjectId objectId) { - using (var result = (BacnetAsyncResult)BeginDeleteObjectRequest(adr, objectId, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndDeleteObjectRequest(result, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - - return false; + using (var result = BeginDeleteObjectRequest(address, objectId, true)) + EndDeleteObjectRequest(result); } - public IAsyncResult BeginDeleteObjectRequest(BacnetAddress adr, BacnetObjectId objectId, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginDeleteObjectRequest(BacnetAddress address, BacnetObjectId objectId, bool waitForTransmit = false) { Log.Debug("Sending DeleteObjectRequest"); - if (invokeId == 0) invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - //NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply , adr.RoutedSource); - - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT, MaxSegments, Transport.MaxAdpuLength, invokeId); - ASN1.encode_application_object_id(buffer, objectId.type, objectId.instance); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT, + buffer => ASN1.encode_application_object_id(buffer, objectId.type, objectId.instance), waitForTransmit); } - public void EndDeleteObjectRequest(IAsyncResult result, out Exception ex) - { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - - res.Dispose(); - } + public void EndDeleteObjectRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); - public bool AddListElementRequest(BacnetAddress adr, BacnetObjectId objectId,BacnetPropertyReference reference, IList valueList, byte invokeId = 0) + public void AddListElementRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyReference reference, IList valueList) { - using (var result = (BacnetAsyncResult)BeginAddListElementRequest(adr, objectId,reference,valueList, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - - if (result.WaitForDone(Timeout)) - { - EndAddListElementRequest(result, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - //values = null; - return false; + using (var request = BeginAddListElementRequest(address, objectId,reference,valueList, true)) + EndAddListElementRequest(request); } - public bool RemoveListElementRequest(BacnetAddress adr, BacnetObjectId objectId,BacnetPropertyReference reference, IList valueList, byte invokeId = 0) + public void RemoveListElementRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyReference reference, IList valueList) { - using (var result = (BacnetAsyncResult)BeginRemoveListElementRequest(adr, objectId,reference,valueList, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndAddListElementRequest(result, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - //values = null; - return false; + using (var request = BeginRemoveListElementRequest(address, objectId, reference, valueList, true)) + EndRemoveListElementRequest(request); } - public IAsyncResult BeginRemoveListElementRequest(BacnetAddress adr, BacnetObjectId objectId,BacnetPropertyReference reference, IList valueList, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginRemoveListElementRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyReference reference, IList valueList, bool waitForTransmit = false) { Log.Debug("Sending RemoveListElementRequest"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, BacnetConfirmedServices.SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeAddListElement(buffer, objectId, reference.propertyIdentifier, reference.propertyArrayIndex,valueList); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT, + buffer => Services.EncodeAddListElement(buffer, objectId, reference.propertyIdentifier, reference.propertyArrayIndex, valueList), waitForTransmit); } - public IAsyncResult BeginAddListElementRequest(BacnetAddress adr, BacnetObjectId objectId, BacnetPropertyReference reference, IList valueList, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginAddListElementRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyReference reference, IList valueList, bool waitForTransmit = false) { Log.Debug($"Sending AddListElementRequest {objectId} {(BacnetPropertyIds)reference.propertyIdentifier}"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, BacnetConfirmedServices.SERVICE_CONFIRMED_ADD_LIST_ELEMENT, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeAddListElement(buffer, objectId, reference.propertyIdentifier, reference.propertyArrayIndex, valueList); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_ADD_LIST_ELEMENT, + buffer => Services.EncodeAddListElement(buffer, objectId, reference.propertyIdentifier, reference.propertyArrayIndex, valueList), waitForTransmit); } - public void EndAddListElementRequest(IAsyncResult result, out Exception ex) - { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); + public void EndAddListElementRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); - res.Dispose(); - } + public void EndRemoveListElementRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); - // Fc - // Read or Write without APDU Data encoding nor Decoding (just Request type, Object id and Property id) - // Data is given by the caller starting with the Tag 3 (or maybe another one), and ending with it - // return buffer start also with the Tag 3 - public bool RawEncodedDecodedPropertyConfirmedRequest(BacnetAddress adr, BacnetObjectId objectId, BacnetPropertyIds propertyId, BacnetConfirmedServices serviceId, ref byte[] inOutBuffer, byte invokeId = 0) + /// + /// Read or Write without APDU Data encoding nor Decoding (just Request type, Object id and Property id) + /// + /// + /// Data is given by the caller starting with the Tag 3 (or maybe another one), and ending with it + /// + /// Return buffer start also with the Tag 3 + public byte[] RawEncodedDecodedPropertyConfirmedRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, BacnetConfirmedServices serviceId, byte[] inBuffer) { - using (var result = (BacnetAsyncResult)BeginRawEncodedDecodedPropertyConfirmedRequest(adr, objectId, propertyId, serviceId, inOutBuffer, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndRawEncodedDecodedPropertyConfirmedRequest(result, serviceId, out inOutBuffer, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } - inOutBuffer = null; - return false; + using (var result = BeginRawEncodedDecodedPropertyConfirmedRequest(address, objectId, propertyId, serviceId, inBuffer, true)) + return EndRawEncodedDecodedPropertyConfirmedRequest(result, serviceId); } - // Fc - public IAsyncResult BeginRawEncodedDecodedPropertyConfirmedRequest(BacnetAddress adr, BacnetObjectId objectId, BacnetPropertyIds propertyId, BacnetConfirmedServices serviceId, byte[] inOutBuffer, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginRawEncodedDecodedPropertyConfirmedRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyIds propertyId, BacnetConfirmedServices serviceId, byte[] inBuffer, bool waitForTransmit = false) { - Log.Debug("Sending RawEncodedRequest"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), serviceId , MaxSegments, Transport.MaxAdpuLength, invokeId); - - ASN1.encode_context_object_id(buffer, 0, objectId.type, objectId.instance); - ASN1.encode_context_enumerated(buffer, 1, (byte)propertyId); - - // No content encoding to do - if (inOutBuffer!=null) - buffer.Add(inOutBuffer, inOutBuffer.Length); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + Log.Debug("Sending RawEncodedDecodedProperty"); + return BeginConfirmedServiceRequest(address, serviceId, buffer => + { + ASN1.encode_context_object_id(buffer, 0, objectId.type, objectId.instance); + ASN1.encode_context_enumerated(buffer, 1, (byte) propertyId); + if (inBuffer?.Length > 0) + buffer.Add(inBuffer, inBuffer.Length); // No content encoding + }, waitForTransmit); } - // Fc - public void EndRawEncodedDecodedPropertyConfirmedRequest(IAsyncResult result, BacnetConfirmedServices serviceId, out byte[] inOutBuffer, out Exception ex) + public byte[] EndRawEncodedDecodedPropertyConfirmedRequest(BacnetAsyncResult request, BacnetConfirmedServices serviceId) { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - - inOutBuffer = null; - - if (ex == null) + using (request) { - if (serviceId == BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY) + return request.GetResult(Timeout, Retries, r => { + if (serviceId != BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY) + return null; + //decode const int offset = 0; - var buffer = res.Result; - - ex = new Exception("Decode"); + var buffer = r.Result; if (!ASN1.decode_is_context_tag(buffer, offset, 0)) - return; + throw new Exception("Failed to decode"); var len = 1; len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes _, out _); /* Tag 1: Property ID */ len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 1) - return; + throw new Exception("Failed to decode"); len += ASN1.decode_enumerated(buffer, offset + len, lenValueType, out _); - inOutBuffer = new byte[buffer.Length - len]; - Array.Copy(buffer, len, inOutBuffer, 0, inOutBuffer.Length); - - ex = null; - } + var outBuffer = new byte[buffer.Length - len]; + Array.Copy(buffer, len, outBuffer, 0, outBuffer.Length); + return outBuffer; + }); } - - res.Dispose(); } - public bool DeviceCommunicationControlRequest(BacnetAddress adr, uint timeDuration, uint enableDisable, string password, byte invokeId = 0) + public void DeviceCommunicationControlRequest(BacnetAddress address, uint timeDuration, uint enableDisable, string password) { - using (var result = (BacnetAsyncResult)BeginDeviceCommunicationControlRequest(adr, timeDuration, enableDisable, password, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndDeviceCommunicationControlRequest(result, out var ex); - return ex == null; - } - if (r < Retries - 1) - result.Resend(); - } - } - return false; + using (var request = BeginDeviceCommunicationControlRequest(address, timeDuration, enableDisable, password, true)) + EndDeviceCommunicationControlRequest(request); } - public IAsyncResult BeginDeviceCommunicationControlRequest(BacnetAddress adr, uint timeDuration, uint enableDisable, string password, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginDeviceCommunicationControlRequest(BacnetAddress address, uint timeDuration, uint enableDisable, string password, bool waitForTransmit = false) { Log.Debug("Sending DeviceCommunicationControlRequest"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeDeviceCommunicationControl(buffer, timeDuration, enableDisable, password); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, + buffer => Services.EncodeDeviceCommunicationControl(buffer, timeDuration, enableDisable, password), waitForTransmit); } - public void EndDeviceCommunicationControlRequest(IAsyncResult result, out Exception ex) - { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); + public void EndDeviceCommunicationControlRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); - res.Dispose(); + public IList GetAlarmSummaryRequest(BacnetAddress address) + { + using (var request = BeginGetAlarmSummaryRequest(address, true)) + return EndGetAlarmSummaryRequest(request); } - // FChaxel - public bool GetAlarmSummaryOrEventRequest(BacnetAddress adr, bool getEvent, ref IList alarms, byte invokeId = 0) + public IList GetEventsRequest(BacnetAddress address) { - using (var result = (BacnetAsyncResult)BeginGetAlarmSummaryOrEventRequest(adr, getEvent, alarms, true, invokeId)) + var events = new List(); + + while (true) { - for (var r = 0; r < _retries; r++) + var lastEventObjectId = events.Count > 0 ? events.Last().objectIdentifier : default(BacnetObjectId?); + using (var request = BeginGetEventsRequest(address, lastEventObjectId, true)) { - if (result.WaitForDone(Timeout)) - { - EndGetAlarmSummaryOrEventRequest(result, getEvent, ref alarms, out var moreEvent, out var ex); - if (ex != null) - return false; - return !moreEvent || GetAlarmSummaryOrEventRequest(adr, getEvent, ref alarms); - } - - if (r < Retries - 1) - result.Resend(); + events.AddRange(EndGetEventsRequest(request, out var moreEvents)); + if (!moreEvents) break; } } - return false; + + return events; } - public Task> GetEventsAsync(BacnetAddress address, byte invokeId = 0) + public Task> GetEventsAsync(BacnetAddress address) { - IList result = new List(); - - return Task>.Factory.StartNew(() => - { - if (!GetAlarmSummaryOrEventRequest(address, true, ref result, invokeId)) - throw new Exception($"Failed to get events from {address}"); - - return result; - }); + return CallAsync(() => GetEventsRequest(address), $"Failed to get events from {address}"); } - public IAsyncResult BeginGetAlarmSummaryOrEventRequest(BacnetAddress adr, bool getEvent, IList alarms, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginGetAlarmSummaryRequest(BacnetAddress address, bool waitForTransmit = false) { - Log.Debug("Sending Alarm summary request"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - - var service = getEvent - ? BacnetConfirmedServices.SERVICE_CONFIRMED_GET_EVENT_INFORMATION - : BacnetConfirmedServices.SERVICE_CONFIRMED_GET_ALARM_SUMMARY; - - APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), service, MaxSegments, Transport.MaxAdpuLength, invokeId); - - // Get Next, never true if GetAlarmSummary is usee - if (alarms.Count != 0) - ASN1.encode_context_object_id(buffer, 0, alarms[alarms.Count - 1].objectIdentifier.type, alarms[alarms.Count - 1].objectIdentifier.instance); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + Log.Debug("Sending AlarmSummary request"); + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_ALARM_SUMMARY, null, waitForTransmit); } - public void EndGetAlarmSummaryOrEventRequest(IAsyncResult result, bool getEvent, ref IList alarms, out bool moreEvent, out Exception ex) + public BacnetAsyncResult BeginGetEventsRequest(BacnetAddress address, BacnetObjectId? lastEventObjectId = null, bool waitForTransmit = false) { - moreEvent = false; - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); + Log.Debug("Sending Events request"); + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_EVENT_INFORMATION, + buffer => Services.EncodeGetEventInformation(buffer, lastEventObjectId), waitForTransmit); + } - if (ex == null) - { - if (Services.DecodeAlarmSummaryOrEvent(res.Result, 0, res.Result.Length, getEvent, ref alarms, out moreEvent) < 0) - ex = new Exception("Decode"); - } - else + public IList EndGetAlarmSummaryRequest(BacnetAsyncResult request) + { + using (request) { - ex = new Exception("Service not available"); + return request.GetResult(Timeout, Retries, r => + { + IList alarms = new List(); + if (Services.DecodeAlarmSummary(r.Result, 0, r.Result.Length, ref alarms) < 0) + throw new Exception("Failed to decode AlarmSummary"); + return alarms; + }); } - - res.Dispose(); } - // FChaxel - public bool AlarmAcknowledgement(BacnetAddress adr, BacnetObjectId objId, BacnetEventStates eventState, string ackText, BacnetGenericTime evTimeStamp, BacnetGenericTime ackTimeStamp, byte invokeId = 0) + public IList EndGetEventsRequest(BacnetAsyncResult request, out bool moreEvents) { - using (var result = (BacnetAsyncResult)BeginAlarmAcknowledgement(adr, objId, eventState, ackText, evTimeStamp, ackTimeStamp, true, invokeId)) + using (request) { - for (var r = 0; r < _retries; r++) + var result = request.GetResult(Timeout, Retries, r => { - if (result.WaitForDone(Timeout)) - { - EndAlarmAcknowledgement(result, out var ex); - return ex == null; - } - if (r < Retries - 1) - result.Resend(); - } + IList events = new List(); + if (Services.DecodeEventInformation(r.Result, 0, r.Result.Length, ref events, out var moreEventsValue) < 0) + throw new Exception("Failed to decode Events"); + return Tuple.Create(events, moreEventsValue); + }); + + moreEvents = result.Item2; + return result.Item1; } - return false; } - public IAsyncResult BeginAlarmAcknowledgement(BacnetAddress adr, BacnetObjectId objId, BacnetEventStates eventState, string ackText, BacnetGenericTime evTimeStamp, BacnetGenericTime ackTimeStamp, bool waitForTransmit, byte invokeId = 0) + public void AlarmAcknowledgement(BacnetAddress address, BacnetObjectId objId, BacnetEventStates eventState, string ackText, BacnetGenericTime evTimeStamp, BacnetGenericTime ackTimeStamp) { - Log.Debug("Sending AlarmAcknowledgement"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, BacnetConfirmedServices.SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeAlarmAcknowledge(buffer, 57, objId, (uint)eventState, ackText, evTimeStamp, ackTimeStamp); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + using (var request = BeginAlarmAcknowledgement(address, objId, eventState, ackText, evTimeStamp, ackTimeStamp, true)) + EndAlarmAcknowledgement(request); } - public void EndAlarmAcknowledgement(IAsyncResult result, out Exception ex) + public BacnetAsyncResult BeginAlarmAcknowledgement(BacnetAddress address, BacnetObjectId objId, BacnetEventStates eventState, string ackText, + BacnetGenericTime evTimeStamp, BacnetGenericTime ackTimeStamp, bool waitForTransmit = false) { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (!res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); + Log.Debug("Sending AlarmAcknowledgement"); + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, + buffer => Services.EncodeAlarmAcknowledge(buffer, 57, objId, (uint)eventState, ackText, evTimeStamp, ackTimeStamp), waitForTransmit); } - public bool ReinitializeRequest(BacnetAddress adr, BacnetReinitializedStates state, string password, byte invokeId = 0) + public void EndAlarmAcknowledgement(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); + + public void ReinitializeRequest(BacnetAddress address, BacnetReinitializedStates state, string password) { - using (var result = (BacnetAsyncResult)BeginReinitializeRequest(adr, state, password, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndReinitializeRequest(result, out var ex); - return ex == null; - } - if (r < Retries - 1) - result.Resend(); - } - } - return false; + using (var request = BeginReinitializeRequest(address, state, password, true)) + EndReinitializeRequest(request); } - public IAsyncResult BeginReinitializeRequest(BacnetAddress adr, BacnetReinitializedStates state, string password, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginReinitializeRequest(BacnetAddress address, BacnetReinitializedStates state, string password, bool waitForTransmit = false) { Log.Debug("Sending ReinitializeRequest"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, BacnetConfirmedServices.SERVICE_CONFIRMED_REINITIALIZE_DEVICE, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeReinitializeDevice(buffer, state, password); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_REINITIALIZE_DEVICE, + buffer => Services.EncodeReinitializeDevice(buffer, state, password), waitForTransmit); } - public void EndReinitializeRequest(IAsyncResult result, out Exception ex) - { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - - res.Dispose(); - } + public void EndReinitializeRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); - public IAsyncResult BeginConfirmedNotify(BacnetAddress adr, uint subscriberProcessIdentifier, uint initiatingDeviceIdentifier, BacnetObjectId monitoredObjectIdentifier, uint timeRemaining, IList values, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginConfirmedNotify(BacnetAddress address, uint subscriberProcessIdentifier, uint initiatingDeviceIdentifier, + BacnetObjectId monitoredObjectIdentifier, uint timeRemaining, IList values, bool waitForTransmit = false) { Log.Debug("Sending Notify (confirmed)"); - if (invokeId == 0) invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, adr.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeCOVNotifyConfirmed(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values); - - //send - var ret = new BacnetAsyncResult(this, adr, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION, + buffer => Services.EncodeCOVNotifyConfirmed(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, + monitoredObjectIdentifier, timeRemaining, values), waitForTransmit); } - public void EndConfirmedNotify(IAsyncResult result, out Exception ex) - { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (!res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - } + public void EndConfirmedNotify(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); - public bool Notify(BacnetAddress adr, uint subscriberProcessIdentifier, uint initiatingDeviceIdentifier, BacnetObjectId monitoredObjectIdentifier, uint timeRemaining, bool issueConfirmedNotifications, IList values) + public bool Notify(BacnetAddress address, uint subscriberProcessIdentifier, uint initiatingDeviceIdentifier, BacnetObjectId monitoredObjectIdentifier, + uint timeRemaining, bool issueConfirmedNotifications, IList values) { if (!issueConfirmedNotifications) { Log.Debug("Sending Notify (unconfirmed)"); var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, adr.RoutedSource); + NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, address.RoutedSource); APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION); Services.EncodeCOVNotifyUnconfirmed(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values); // Modif F. Chaxel - var sendbytes=Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, adr, false, 0); + var sendbytes=Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, address, false, 0); return sendbytes == buffer.offset; } - using (var result = (BacnetAsyncResult)BeginConfirmedNotify(adr, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values, true)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndConfirmedNotify(result, out var ex); - if (ex != null) - throw ex; - return true; - } - if (r < Retries - 1) - result.Resend(); - } - } + using (var result = BeginConfirmedNotify(address, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values, true)) + EndConfirmedNotify(result); return false; } - public bool LifeSafetyOperationRequest(BacnetAddress address, BacnetObjectId objectId, string requestingSrc, BacnetLifeSafetyOperations operation, byte invokeId = 0) + public void LifeSafetyOperationRequest(BacnetAddress address, BacnetObjectId objectId, string requestingSrc, BacnetLifeSafetyOperations operation) { - using (var result = (BacnetAsyncResult)BeginLifeSafetyOperationRequest(address, objectId, 0, requestingSrc, operation, true, invokeId)) - { - for (var r = 0; r < _retries; r++) - { - if (result.WaitForDone(Timeout)) - { - EndLifeSafetyOperationRequest(result, out var ex); - return ex == null; - } - if (r < Retries - 1) - result.Resend(); - } - } - return false; + using (var request = BeginLifeSafetyOperationRequest(address, objectId, 0, requestingSrc, operation, true)) + EndLifeSafetyOperationRequest(request); } - public IAsyncResult BeginLifeSafetyOperationRequest(BacnetAddress address, BacnetObjectId objectId, uint processId, string requestingSrc, BacnetLifeSafetyOperations operation, bool waitForTransmit, byte invokeId = 0) + public BacnetAsyncResult BeginLifeSafetyOperationRequest(BacnetAddress address, BacnetObjectId objectId, uint processId, string requestingSrc, + BacnetLifeSafetyOperations operation, bool waitForTransmit = false) { Log.Debug($"Sending {ToTitleCase(operation)} {objectId}"); - if (invokeId == 0) - invokeId = unchecked(_invokeId++); - - var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply, address.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, BacnetConfirmedServices.SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION, MaxSegments, Transport.MaxAdpuLength, invokeId); - Services.EncodeLifeSafetyOperation(buffer, processId, requestingSrc, (uint)operation, objectId); - - //send - var ret = new BacnetAsyncResult(this, address, invokeId, buffer.buffer, buffer.offset - Transport.HeaderLength, waitForTransmit, TransmitTimeout); - ret.Resend(); - - return ret; + return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION, + buffer => Services.EncodeLifeSafetyOperation(buffer, processId, requestingSrc, (uint)operation, objectId), waitForTransmit); } - public void EndLifeSafetyOperationRequest(IAsyncResult result, out Exception ex) - { - var res = (BacnetAsyncResult)result; - ex = res.Error; - if (ex == null && !res.WaitForDone(Timeout)) - ex = new Exception("Wait Timeout"); - - res.Dispose(); - } + public void EndLifeSafetyOperationRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); public static byte GetSegmentsCount(BacnetMaxSegments maxSegments) { @@ -2037,7 +1658,7 @@ public Segmentation GetSegmentBuffer(BacnetMaxSegments maxSegments) }; } - private EncodeBuffer EncodeSegmentHeader(BacnetAddress adr, byte invokeId, Segmentation segmentation, BacnetConfirmedServices service, bool moreFollows) + private EncodeBuffer EncodeSegmentHeader(BacnetAddress address, byte invokeId, Segmentation segmentation, BacnetConfirmedServices service, bool moreFollows) { EncodeBuffer buffer; var isSegmented = false; @@ -2051,7 +1672,7 @@ private EncodeBuffer EncodeSegmentHeader(BacnetAddress adr, byte invokeId, Segme buffer.Reset(Transport.HeaderLength); //encode - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, adr.RoutedSource); + NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, address.RoutedSource); //set segments limits buffer.max_offset = buffer.offset + GetMaxApdu(); @@ -2061,17 +1682,17 @@ private EncodeBuffer EncodeSegmentHeader(BacnetAddress adr, byte invokeId, Segme return buffer; } - private bool EncodeSegment(BacnetAddress adr, byte invokeId, Segmentation segmentation, BacnetConfirmedServices service, out EncodeBuffer buffer, Action apduContentEncode) + private bool EncodeSegment(BacnetAddress address, byte invokeId, Segmentation segmentation, BacnetConfirmedServices service, out EncodeBuffer buffer, Action apduContentEncode) { //encode (regular) - buffer = EncodeSegmentHeader(adr, invokeId, segmentation, service, false); + buffer = EncodeSegmentHeader(address, invokeId, segmentation, service, false); apduContentEncode(buffer); var moreFollows = (buffer.result & EncodeResult.NotEnoughBuffer) > 0; if (segmentation != null && moreFollows) { //reencode in segmented - EncodeSegmentHeader(adr, invokeId, segmentation, service, true); + EncodeSegmentHeader(address, invokeId, segmentation, service, true); apduContentEncode(buffer); return true; } @@ -2086,7 +1707,7 @@ private bool EncodeSegment(BacnetAddress adr, byte invokeId, Segmentation segmen /// Handle the segmentation of several too hugh response (if it's accepted by the client) /// used by ReadRange, ReadProperty, ReadPropertyMultiple & ReadFile responses /// - private void HandleSegmentationResponse(BacnetAddress adr, byte invokeId, Segmentation segmentation, Action transmit) + private void HandleSegmentationResponse(BacnetAddress address, byte invokeId, Segmentation segmentation, Action transmit) { // send first transmit(segmentation); @@ -2115,7 +1736,7 @@ private void HandleSegmentationResponse(BacnetAddress adr, byte invokeId, Segmen var currentNumber = segmentation.sequence_number; - if (!WaitForSegmentAck(adr, invokeId, segmentation, Timeout)) + if (!WaitForSegmentAck(address, invokeId, segmentation, Timeout)) { Log.Warn("Didn't get segmentACK"); break; @@ -2131,7 +1752,7 @@ private void HandleSegmentationResponse(BacnetAddress adr, byte invokeId, Segmen { // a negative segmentACK perhaps var currentNumber = segmentation.sequence_number; - WaitForSegmentAck(adr, invokeId, segmentation, TimeSpan.Zero); // don't wait + WaitForSegmentAck(address, invokeId, segmentation, TimeSpan.Zero); // don't wait if (segmentation.sequence_number != currentNumber) Log.Debug("Oh, a retransmit"); @@ -2148,18 +1769,18 @@ private void HandleSegmentationResponse(BacnetAddress adr, byte invokeId, Segmen }); } - private void SendComplexAck(BacnetAddress adr, byte invokeId, Segmentation segmentation, BacnetConfirmedServices service, Action apduContentEncode) + private void SendComplexAck(BacnetAddress address, byte invokeId, Segmentation segmentation, BacnetConfirmedServices service, Action apduContentEncode) { Log.Debug($"Sending {ToTitleCase(service)}"); //encode - if (EncodeSegment(adr, invokeId, segmentation, service, out var buffer, apduContentEncode)) + if (EncodeSegment(address, invokeId, segmentation, service, out var buffer, apduContentEncode)) { //client doesn't support segments if (segmentation == null) { Log.Info("Segmenation denied"); - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_APDU_TOO_LONG); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_APDU_TOO_LONG); buffer.result = EncodeResult.Good; //don't continue the segmentation return; } @@ -2170,7 +1791,7 @@ private void SendComplexAck(BacnetAddress adr, byte invokeId, Segmentation segme if (segmentation.max_segments != 0xFF && segmentation.buffer.offset > segmentation.max_segments * (GetMaxApdu() - 5)) //5 is adpu header { Log.Info("Too much segmenation"); - ErrorResponse(adr, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_APDU_TOO_LONG); + ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_APDU_TOO_LONG); buffer.result = EncodeResult.Good; //don't continue the segmentation return; } @@ -2182,95 +1803,95 @@ private void SendComplexAck(BacnetAddress adr, byte invokeId, Segmentation segme } //send - Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.GetLength() - Transport.HeaderLength, adr, false, 0); + Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.GetLength() - Transport.HeaderLength, address, false, 0); } - public void ReadPropertyResponse(BacnetAddress adr, byte invokeId, Segmentation segmentation, BacnetObjectId objectId, BacnetPropertyReference property, IEnumerable value) + public void ReadPropertyResponse(BacnetAddress address, byte invokeId, Segmentation segmentation, BacnetObjectId objectId, BacnetPropertyReference property, IEnumerable value) { - HandleSegmentationResponse(adr, invokeId, segmentation, o => + HandleSegmentationResponse(address, invokeId, segmentation, o => { - SendComplexAck(adr, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, b => + SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, b => { Services.EncodeReadPropertyAcknowledge(b, objectId, property.propertyIdentifier, property.propertyArrayIndex, value); }); }); } - public void CreateObjectResponse(BacnetAddress adr, byte invokeId, Segmentation segmentation, BacnetObjectId objectId) + public void CreateObjectResponse(BacnetAddress address, byte invokeId, Segmentation segmentation, BacnetObjectId objectId) { - SendComplexAck(adr, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT, b => + SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT, b => { Services.EncodeCreateObjectAcknowledge(b, objectId); }); } - public void ReadPropertyMultipleResponse(BacnetAddress adr, byte invokeId, Segmentation segmentation, IList values) + public void ReadPropertyMultipleResponse(BacnetAddress address, byte invokeId, Segmentation segmentation, IList values) { - HandleSegmentationResponse(adr, invokeId, segmentation, o => + HandleSegmentationResponse(address, invokeId, segmentation, o => { - SendComplexAck(adr, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, b => + SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, b => { Services.EncodeReadPropertyMultipleAcknowledge(b, values); }); }); } - public void ReadRangeResponse(BacnetAddress adr, byte invokeId, Segmentation segmentation, BacnetObjectId objectId, BacnetPropertyReference property, BacnetResultFlags status, uint itemCount, byte[] applicationData, BacnetReadRangeRequestTypes requestType, uint firstSequenceNo) + public void ReadRangeResponse(BacnetAddress address, byte invokeId, Segmentation segmentation, BacnetObjectId objectId, BacnetPropertyReference property, BacnetResultFlags status, uint itemCount, byte[] applicationData, BacnetReadRangeRequestTypes requestType, uint firstSequenceNo) { - HandleSegmentationResponse(adr, invokeId, segmentation, o => + HandleSegmentationResponse(address, invokeId, segmentation, o => { - SendComplexAck(adr, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, b => + SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, b => { Services.EncodeReadRangeAcknowledge(b, objectId, property.propertyIdentifier, property.propertyArrayIndex, BacnetBitString.ConvertFromInt((uint)status), itemCount, applicationData, requestType, firstSequenceNo); }); }); } - public void ReadFileResponse(BacnetAddress adr, byte invokeId, Segmentation segmentation, int position, uint count, bool endOfFile, byte[] fileBuffer) + public void ReadFileResponse(BacnetAddress address, byte invokeId, Segmentation segmentation, int position, uint count, bool endOfFile, byte[] fileBuffer) { - HandleSegmentationResponse(adr, invokeId, segmentation, o => + HandleSegmentationResponse(address, invokeId, segmentation, o => { - SendComplexAck(adr, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, b => + SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, b => { Services.EncodeAtomicReadFileAcknowledge(b, true, endOfFile, position, 1, new[] { fileBuffer }, new[] { (int)count }); }); }); } - public void WriteFileResponse(BacnetAddress adr, byte invokeId, Segmentation segmentation, int position) + public void WriteFileResponse(BacnetAddress address, byte invokeId, Segmentation segmentation, int position) { - SendComplexAck(adr, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, b => + SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, b => { Services.EncodeAtomicWriteFileAcknowledge(b, true, position); }); } - public void ErrorResponse(BacnetAddress adr, BacnetConfirmedServices service, byte invokeId, BacnetErrorClasses errorClass, BacnetErrorCodes errorCode) + public void ErrorResponse(BacnetAddress address, BacnetConfirmedServices service, byte invokeId, BacnetErrorClasses errorClass, BacnetErrorCodes errorCode) { Log.Debug($"Sending ErrorResponse for {service}: {errorCode}"); var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, adr.RoutedSource); + NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, address.RoutedSource); APDU.EncodeError(buffer, BacnetPduTypes.PDU_TYPE_ERROR, service, invokeId); Services.EncodeError(buffer, errorClass, errorCode); - Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, adr, false, 0); + Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, address, false, 0); } - public void SimpleAckResponse(BacnetAddress adr, BacnetConfirmedServices service, byte invokeId) + public void SimpleAckResponse(BacnetAddress address, BacnetConfirmedServices service, byte invokeId) { Log.Debug($"Sending SimpleAckResponse for {service}"); var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, adr.RoutedSource); + NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, address.RoutedSource); APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, service, invokeId); - Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, adr, false, 0); + Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, address, false, 0); } - public void SegmentAckResponse(BacnetAddress adr, bool negative, bool server, byte originalInvokeId, byte sequenceNumber, byte actualWindowSize) + public void SegmentAckResponse(BacnetAddress address, bool negative, bool server, byte originalInvokeId, byte sequenceNumber, byte actualWindowSize) { Log.Debug("Sending SegmentAckResponse"); var buffer = GetEncodeBuffer(Transport.HeaderLength); - NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, adr.RoutedSource); + NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, address.RoutedSource); APDU.EncodeSegmentAck(buffer, BacnetPduTypes.PDU_TYPE_SEGMENT_ACK | (negative ? BacnetPduTypes.NEGATIVE_ACK : 0) | (server ? BacnetPduTypes.SERVER : 0), originalInvokeId, sequenceNumber, actualWindowSize); - Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, adr, false, 0); + Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, address, false, 0); } public bool WaitForAllTransmits(TimeSpan timeout) @@ -2278,9 +1899,9 @@ public bool WaitForAllTransmits(TimeSpan timeout) return Transport.WaitForAllTransmits((int)timeout.TotalMilliseconds); } - public bool WaitForSegmentAck(BacnetAddress adr, byte invokeId, Segmentation segmentation, TimeSpan timeout) + public bool WaitForSegmentAck(BacnetAddress address, byte invokeId, Segmentation segmentation, TimeSpan timeout) { - if (!_lastSegmentAck.Wait(adr, invokeId, timeout)) + if (!_lastSegmentAck.Wait(address, invokeId, timeout)) return false; segmentation.sequence_number = (byte)((_lastSegmentAck.SequenceNumber + 1) % 256); @@ -2302,7 +1923,7 @@ public BacnetAsyncResult BeginConfirmedServiceRequest(BacnetAddress address, Bac NPDU.Encode(buffer, function, address.RoutedSource); APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), service, MaxSegments, Transport.MaxAdpuLength, invokeId); - encode(buffer); + encode?.Invoke(buffer); var transmitLength = buffer.offset - Transport.HeaderLength; var asyncResult = new BacnetAsyncResult(this, address, invokeId, buffer.buffer, transmitLength, waitForTransmit, TransmitTimeout); diff --git a/Base/BacnetAlarmSummaryData.cs b/Base/BacnetAlarmSummaryData.cs new file mode 100644 index 00000000..87611bd0 --- /dev/null +++ b/Base/BacnetAlarmSummaryData.cs @@ -0,0 +1,9 @@ +namespace System.IO.BACnet +{ + public struct BacnetAlarmSummaryData + { + public BacnetObjectId objectIdentifier; + public BacnetEventStates alarmState; + public BacnetBitString acknowledgedTransitions; + } +} \ No newline at end of file diff --git a/Serialize/Services.cs b/Serialize/Services.cs index 43d98b85..9c084589 100644 --- a/Serialize/Services.cs +++ b/Serialize/Services.cs @@ -1029,23 +1029,19 @@ public static void EncodeEventNotifyUnconfirmed(EncodeBuffer buffer, BacnetEvent EncodeEventNotifyData(buffer, data); } - public static void EncodeAlarmSummary(EncodeBuffer buffer, BacnetObjectId objectIdentifier, uint alarmState, BacnetBitString acknowledgedTransitions) + public static void EncodeAlarmSummary(EncodeBuffer buffer, BacnetObjectId objectIdentifier, BacnetEventStates alarmState, BacnetBitString acknowledgedTransitions) { /* tag 0 - Object Identifier */ ASN1.encode_application_object_id(buffer, objectIdentifier.type, objectIdentifier.instance); /* tag 1 - Alarm State */ - ASN1.encode_application_enumerated(buffer, alarmState); + ASN1.encode_application_enumerated(buffer, (uint)alarmState); /* tag 2 - Acknowledged Transitions */ ASN1.encode_application_bitstring(buffer, acknowledgedTransitions); } - // FChaxel - public static int DecodeAlarmSummaryOrEvent(byte[] buffer, int offset, int apduLen, bool getEvent, ref IList alarms, out bool moreEvent) + public static int DecodeEventInformation(byte[] buffer, int offset, int apduLen, ref IList events, out bool moreEvent) { - var len = 0; ; - - if (getEvent) - len++; // peut être tag 0 + var len = 1; // tag 0 while (apduLen - 3 - len > 0) { @@ -1059,62 +1055,77 @@ public static int DecodeAlarmSummaryOrEvent(byte[] buffer, int offset, int apduL len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.acknowledgedTransitions); - if (getEvent) + len++; // opening Tag 3 + value.eventTimeStamps = new BacnetGenericTime[3]; + + for (var i = 0; i < 3; i++) { - len++; // opening Tag 3 - value.eventTimeStamps = new BacnetGenericTime[3]; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); // opening tag - for (var i = 0; i < 3; i++) + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_NULL) { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); // opening tag - - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_NULL) - { - len += ASN1.decode_application_date(buffer, offset + len, out var date); - len += ASN1.decode_application_time(buffer, offset + len, out var time); - var timestamp = date.Date + time.TimeOfDay; - value.eventTimeStamps[i] = new BacnetGenericTime(timestamp, BacnetTimestampTags.TIME_STAMP_DATETIME); - len++; // closing tag - } - else - len += (int)lenValue; + len += ASN1.decode_application_date(buffer, offset + len, out var date); + len += ASN1.decode_application_time(buffer, offset + len, out var time); + var timestamp = date.Date + time.TimeOfDay; + value.eventTimeStamps[i] = new BacnetGenericTime(timestamp, BacnetTimestampTags.TIME_STAMP_DATETIME); + len++; // closing tag } + else + len += (int)lenValue; + } - len++; // closing Tag 3 + len++; // closing Tag 3 - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out tmp); - value.notifyType = (BacnetNotifyTypes)tmp; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out tmp); + value.notifyType = (BacnetNotifyTypes)tmp; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.eventEnable); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.eventEnable); - len++; // opening tag 6; - value.eventPriorities = new uint[3]; - for (var i = 0; i < 3; i++) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out value.eventPriorities[i]); - } - len++; // closing Tag 6 + len++; // opening tag 6; + value.eventPriorities = new uint[3]; + for (var i = 0; i < 3; i++) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out value.eventPriorities[i]); } + len++; // closing Tag 6 - alarms.Add(value); + events.Add(value); } - if (getEvent) - moreEvent = buffer[apduLen - 1] == 1; - else - moreEvent = false; + moreEvent = buffer[apduLen - 1] == 1; + return len; + } + + public static int DecodeAlarmSummary(byte[] buffer, int offset, int apduLen, ref IList alarms) + { + var len = 0; + + while (apduLen - 3 - len > 0) + { + var value = new BacnetAlarmSummaryData(); + + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); + len += ASN1.decode_object_id(buffer, offset + len, out value.objectIdentifier.type, out value.objectIdentifier.instance); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out var tmp); + value.alarmState = (BacnetEventStates)tmp; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.acknowledgedTransitions); + + alarms.Add(value); + } return len; } - public static void EncodeGetEventInformation(EncodeBuffer buffer, bool sendLast, BacnetObjectId lastReceivedObjectIdentifier) + public static void EncodeGetEventInformation(EncodeBuffer buffer, BacnetObjectId? lastReceivedObjectIdentifier) { /* encode optional parameter */ - if (sendLast) - ASN1.encode_context_object_id(buffer, 0, lastReceivedObjectIdentifier.type, lastReceivedObjectIdentifier.instance); + if (lastReceivedObjectIdentifier != null) + ASN1.encode_context_object_id(buffer, 0, lastReceivedObjectIdentifier.Value.type, lastReceivedObjectIdentifier.Value.instance); } public static void EncodeGetEventInformationAcknowledge(EncodeBuffer buffer, BacnetGetEventInformationData[] events, bool moreEvents) From dd25d3526b634bb2850c8fdfdc9b4da52d32d880 Mon Sep 17 00:00:00 2001 From: Jakub Bartkowiak Date: Sun, 3 Dec 2017 22:57:50 +0100 Subject: [PATCH 04/31] chore: sync with YABE repo (svn@267) --- Base/BacnetCOVSubscription.cs | 7 +++++++ Serialize/ASN1.cs | 2 +- Transport/BACnetTransport.cs | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Base/BacnetCOVSubscription.cs b/Base/BacnetCOVSubscription.cs index 007827b4..61c40c86 100644 --- a/Base/BacnetCOVSubscription.cs +++ b/Base/BacnetCOVSubscription.cs @@ -12,5 +12,12 @@ public struct BacnetCOVSubscription public bool IssueConfirmedNotifications; public uint TimeRemaining; public float COVIncrement; + + public override string ToString() + { + var objType = $"{monitoredObjectIdentifier.Type}".Replace("OBJECT_", ""); + var objInstance = monitoredObjectIdentifier.Instance; + return $"{objType}:{objInstance} by {Recipient}, remain {TimeRemaining}s"; + } } } \ No newline at end of file diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index feef522e..02dd8035 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -2224,7 +2224,7 @@ public static int decode_cov_subscription(byte[] buffer, int offset, int apduLen return -1; len += decode_unsigned(buffer, offset + len, lenValueType, out value.TimeRemaining); - if (len < apduLen && IS_OPENING_TAG(buffer[offset + len])) + if (len < apduLen && !IS_CLOSING_TAG(buffer[offset + len])) { decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); if (tagNumber != 4) diff --git a/Transport/BACnetTransport.cs b/Transport/BACnetTransport.cs index 6cd8d134..acb55bb0 100644 --- a/Transport/BACnetTransport.cs +++ b/Transport/BACnetTransport.cs @@ -357,7 +357,7 @@ private static UnicastIPAddressInformation GetAddressDefaultInterface() var unicastAddresses = NetworkInterface.GetAllNetworkInterfaces() .Where(i => i.OperationalStatus == OperationalStatus.Up) .Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback) - .Where(i => !(i.Name.Contains("VirtualBox") || i.Name.Contains("VMware"))) + .Where(i => !(i.Description.Contains("VirtualBox") || i.Description.Contains("VMware"))) .SelectMany(i => i.GetIPProperties().UnicastAddresses) .Where(a => a.Address.AddressFamily == AddressFamily.InterNetwork) .ToArray(); From 6edd0eeac9dd91fcedfe543d284b205e27503fec Mon Sep 17 00:00:00 2001 From: Jakub Bartkowiak Date: Mon, 4 Dec 2017 00:59:36 +0100 Subject: [PATCH 05/31] chore: update copyright --- BACnet.nuspec | 5 ++-- BACnetClient.cs | 28 +----------------- MIT_license.txt | 5 +++- Properties/AssemblyInfo.cs | 10 ++----- README.md | 24 ++++++++++++++-- Storage/DeviceStorage.cs | 28 +----------------- Transport/BACnetTransport.cs | 28 +----------------- Transport/BacnetIpV6UdpProtocolTransport.cs | 28 +----------------- Transport/BacnetTransportEthernet.cs | 32 +-------------------- 9 files changed, 37 insertions(+), 151 deletions(-) diff --git a/BACnet.nuspec b/BACnet.nuspec index 70f7bcf9..3e1ba31f 100644 --- a/BACnet.nuspec +++ b/BACnet.nuspec @@ -4,10 +4,11 @@ BACnet $version$ BACnet - Morten Kvistgaard, Ela-compil + Jakub Brtkowiak, Ela-compil https://github.com/ela-compil/BACnet https://raw.githubusercontent.com/ela-compil/BACnet/master/logo.png BACnet protocol library for .NET - Copyright © Morten Kvistgaard 2016, MIT License + Copyright (c) 2017 Jakub Bartkowiak, MIT License + bacnet scada bvlc mstp \ No newline at end of file diff --git a/BACnetClient.cs b/BACnetClient.cs index c6902b52..abf20c43 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -1,30 +1,4 @@ -/************************************************************************** -* MIT License -* -* Copyright (C) 2014 Morten Kvistgaard -* -* Permission is hereby granted, free of charge, to any person obtaining -* a copy of this software and associated documentation files (the -* "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to -* permit persons to whom the Software is furnished to do so, subject to -* the following conditions: -* -* The above copyright notice and this permission notice shall be included -* in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -* -*********************************************************************/ - -using System.Collections.Generic; +using System.Collections.Generic; using System.IO.BACnet.Serialize; using System.Linq; using System.Net; diff --git a/MIT_license.txt b/MIT_license.txt index 618a383d..f6acef05 100644 --- a/MIT_license.txt +++ b/MIT_license.txt @@ -1,6 +1,9 @@ The MIT License (MIT) -Copyright (c) 2014 Yabe project +Copyright (C) 2014 Loren Van Spronsen +Copyright (C) 2014-2016 Morten Kvistgaard +Copyright (C) 2015-2016 Frederic Chaxel, YABE Project +Copyright (c) 2017 Jakub Bartkowiak, Ela-compil Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index f08a4306..8f69c57a 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -1,19 +1,15 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyTitle("BACnet")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("BACnet protocol library for .NET")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Yabe")] +[assembly: AssemblyCompany("Ela-compil sp. z o. o.")] [assembly: AssemblyProduct("BACnet")] -[assembly: AssemblyCopyright("Copyright © Morten Kvistgaard 2015, MIT License")] +[assembly: AssemblyCopyright("Copyright (c) 2017 Jakub Bartkowiak, MIT License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] - [assembly: ComVisible(false)] - [assembly: Guid("fce4c8e3-0d3a-4d90-9c26-349177129433")] - [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/README.md b/README.md index 66ab4d3e..737fd720 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,31 @@ [![NuGet version](https://badge.fury.io/nu/bacnet.svg)](https://www.nuget.org/packages/BACnet) [![Donate](https://img.shields.io/badge/%24-donate-ff00ff.svg)](https://www.paypal.me/JakubBartkowiak) -This library was originaly developed by Morten Kvistgaard with a lot of contribution from F. Chaxel. It is used as core library in [YABE (Yet Another BACnet Explore)](https://sourceforge.net/projects/yetanotherbacnetexplorer/). It has been forked here from SourceForge SVN source, splitted into separate repositories and made available to download from Nuget. +This library allows C# developers to communicate with BACnet devices. It has a full C# BACnet stack implementation. Application that uses this library can read/write/control remote devices as well as share it's own data with the BACnet network. + +## Feature highlight: + +* Client and Server +* Serializing +* IP over UDP +* IPv6 +* MSTP over local pipe or serial port +* Ethernet +* Segmentation support +* Service functions such as read/write/subscribeCOV etc. +* Exotic functions such as TimeSync, DeviceCommunicationControl, LifeSafetyOperations +* Remote BBMD Tables edition (Read & Write) +* CreateObject & DeleteObject services +* BACnet PTP (not tested yet) +* TrendLog, Calendar, Notification class, Schedule, Alarms summary + +## History + +The foundation of this library has been made by Morten Kvistgaard. Later F. Chaxel made a lot of contribution to it and based his [YABE (Yet Another BACnet Explore)](https://sourceforge.net/projects/yetanotherbacnetexplorer/) project on it. F.Chaxel was not interested in separating YABE from it's core library and making it available thru NuGet. Because of that a fork has been made here from SourceForge SVN source. Original history of commits has been preserved. YABE application source code has been removed leaving only the core library part that has been splitted into separate repositories and made available to download from Nuget. ## How to use -You can get BACnet library by grabbing the latest [NuGet package](https://www.nuget.org/packages/BACnet). +Easiest way is to use BACnet library is grabbing the latest [NuGet package](https://www.nuget.org/packages/BACnet). ## Getting Started diff --git a/Storage/DeviceStorage.cs b/Storage/DeviceStorage.cs index c0e673a3..c94219ea 100644 --- a/Storage/DeviceStorage.cs +++ b/Storage/DeviceStorage.cs @@ -1,30 +1,4 @@ -/************************************************************************** -* MIT License -* -* Copyright (C) 2014 Morten Kvistgaard -* -* Permission is hereby granted, free of charge, to any person obtaining -* a copy of this software and associated documentation files (the -* "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to -* permit persons to whom the Software is furnished to do so, subject to -* the following conditions: -* -* The above copyright notice and this permission notice shall be included -* in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -* -*********************************************************************/ - -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Xml.Serialization; diff --git a/Transport/BACnetTransport.cs b/Transport/BACnetTransport.cs index acb55bb0..30f12d81 100644 --- a/Transport/BACnetTransport.cs +++ b/Transport/BACnetTransport.cs @@ -1,30 +1,4 @@ -/************************************************************************** -* MIT License -* -* Copyright (C) 2014 Morten Kvistgaard -* -* Permission is hereby granted, free of charge, to any person obtaining -* a copy of this software and associated documentation files (the -* "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to -* permit persons to whom the Software is furnished to do so, subject to -* the following conditions: -* -* The above copyright notice and this permission notice shall be included -* in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -* -*********************************************************************/ - -using System.Text; +using System.Text; using System.IO.BACnet.Serialize; using System.Linq; using System.Net; diff --git a/Transport/BacnetIpV6UdpProtocolTransport.cs b/Transport/BacnetIpV6UdpProtocolTransport.cs index cd86cbc5..2664cf0a 100644 --- a/Transport/BacnetIpV6UdpProtocolTransport.cs +++ b/Transport/BacnetIpV6UdpProtocolTransport.cs @@ -1,30 +1,4 @@ -/************************************************************************** -* MIT License -* -* Copyright (C) 2015 Frederic Chaxel -* Morten Kvistgaard -* Permission is hereby granted, free of charge, to any person obtaining -* a copy of this software and associated documentation files (the -* "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to -* permit persons to whom the Software is furnished to do so, subject to -* the following conditions: -* -* The above copyright notice and this permission notice shall be included -* in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -* -*********************************************************************/ - -using System.Net; +using System.Net; using System.Net.Sockets; using System.Threading; diff --git a/Transport/BacnetTransportEthernet.cs b/Transport/BacnetTransportEthernet.cs index 1132b9e1..df7ef896 100644 --- a/Transport/BacnetTransportEthernet.cs +++ b/Transport/BacnetTransportEthernet.cs @@ -1,34 +1,4 @@ -/************************************************************************** -* MIT License -* -* Copyright (C) 2015 Frederic Chaxel -* -* lot of code ported from https://github.com/LorenVS/bacstack -* Copyright (C) 2014 Loren Van Spronsen, thank to him. -* Thank to Christopher Gunther for the idea, and the starting code -* -* Permission is hereby granted, free of charge, to any person obtaining -* a copy of this software and associated documentation files (the -* "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to -* permit persons to whom the Software is furnished to do so, subject to -* the following conditions: -* -* The above copyright notice and this permission notice shall be included -* in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -* -*********************************************************************/ - -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading; using PacketDotNet; From 04a96d765d34a859e1891f02228e77be541bd05e Mon Sep 17 00:00:00 2001 From: Jakub Bartkowiak Date: Thu, 14 Dec 2017 03:52:53 +0100 Subject: [PATCH 06/31] fix: BacnetStatusFlags instead of BacnetBitString in BacnetLogRecord (#8) --- Base/BacnetLogRecord.cs | 8 ++++---- Serialize/Services.cs | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Base/BacnetLogRecord.cs b/Base/BacnetLogRecord.cs index 5d33810a..88cf5d12 100644 --- a/Base/BacnetLogRecord.cs +++ b/Base/BacnetLogRecord.cs @@ -19,13 +19,13 @@ public struct BacnetLogRecord private object any_value; /* } */ - public BacnetBitString statusFlags; + public BacnetStatusFlags statusFlags; - public BacnetLogRecord(BacnetTrendLogValueType type, object value, DateTime stamp, uint status) + public BacnetLogRecord(BacnetTrendLogValueType type, object value, DateTime timestamp, BacnetStatusFlags status) { this.type = type; - timestamp = stamp; - statusFlags = BacnetBitString.ConvertFromInt(status); + this.timestamp = timestamp; + statusFlags = status; any_value = null; Value = value; } diff --git a/Serialize/Services.cs b/Serialize/Services.cs index 43d98b85..b41ca49e 100644 --- a/Serialize/Services.cs +++ b/Serialize/Services.cs @@ -2316,10 +2316,11 @@ public static void EncodeLogRecord(EncodeBuffer buffer, BacnetLogRecord record) } /* Tag 2: status */ - if (record.statusFlags.bits_used > 0) + var recordStatusFlags = BacnetBitString.ConvertFromInt((uint)record.statusFlags); + if (recordStatusFlags.bits_used > 0) { ASN1.encode_opening_tag(buffer, 2); - ASN1.encode_application_bitstring(buffer, record.statusFlags); + ASN1.encode_application_bitstring(buffer, recordStatusFlags); ASN1.encode_closing_tag(buffer, 2); } } @@ -2363,7 +2364,7 @@ public static int DecodeLogRecord(byte[] buffer, int offset, int length, int nCu records[curveNumber].Value = sval; break; case BacnetTrendLogValueType.TL_TYPE_BOOL: - records[curveNumber].Value = buffer[offset + len] > 0 ? true : false; + records[curveNumber].Value = buffer[offset + len] > 0; len++; break; case BacnetTrendLogValueType.TL_TYPE_REAL: @@ -2423,9 +2424,10 @@ public static int DecodeLogRecord(byte[] buffer, int offset, int length, int nCu return len; len += l; - len += ASN1.decode_bitstring(buffer, offset + len, 2, out var statusFlags); + len += ASN1.decode_bitstring(buffer, offset + len, 2, out var statusFlagsBits); //set status to all returns + var statusFlags = (BacnetStatusFlags)statusFlagsBits.ConvertToInt(); for (var curveNumber = 0; curveNumber < nCurves; curveNumber++) records[curveNumber].statusFlags = statusFlags; From 64da2d2ebaa5d2165cd0f87d844c57d0edfc930a Mon Sep 17 00:00:00 2001 From: Jakub Bartkowiak Date: Thu, 14 Dec 2017 11:56:14 +0100 Subject: [PATCH 07/31] fix: calculating bits used in BacnetBitString (#8) --- BACnet.sln | 11 +++- BACnet.sln.DotSettings | 1 + Base/BacnetBitString.cs | 4 +- Serialize/Services.cs | 2 +- Tests/BACnet.Tests.csproj | 55 ++++++++++++++++++ Tests/Base/BacnetBitStringTests.cs | 92 ++++++++++++++++++++++++++++++ Tests/Properties/AssemblyInfo.cs | 36 ++++++++++++ Tests/packages.config | 4 ++ 8 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 Tests/BACnet.Tests.csproj create mode 100644 Tests/Base/BacnetBitStringTests.cs create mode 100644 Tests/Properties/AssemblyInfo.cs create mode 100644 Tests/packages.config diff --git a/BACnet.sln b/BACnet.sln index af80bfd5..43767d00 100644 --- a/BACnet.sln +++ b/BACnet.sln @@ -1,10 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27004.2008 +VisualStudioVersion = 15.0.27130.2003 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BACnet", "BACnet.csproj", "{66832876-01FC-4B7C-8D92-54195773FABF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BACnet.Tests", "Tests\BACnet.Tests.csproj", "{5DC7DC72-E57B-418C-ABE7-8A26B28AD953}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,8 +17,15 @@ Global {66832876-01FC-4B7C-8D92-54195773FABF}.Debug|Any CPU.Build.0 = Debug|Any CPU {66832876-01FC-4B7C-8D92-54195773FABF}.Release|Any CPU.ActiveCfg = Release|Any CPU {66832876-01FC-4B7C-8D92-54195773FABF}.Release|Any CPU.Build.0 = Release|Any CPU + {5DC7DC72-E57B-418C-ABE7-8A26B28AD953}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DC7DC72-E57B-418C-ABE7-8A26B28AD953}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DC7DC72-E57B-418C-ABE7-8A26B28AD953}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DC7DC72-E57B-418C-ABE7-8A26B28AD953}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {32CA8FD5-D852-4706-BDBF-1A8DBD5D9A4A} + EndGlobalSection EndGlobal diff --git a/BACnet.sln.DotSettings b/BACnet.sln.DotSettings index 904b042b..76153a73 100644 --- a/BACnet.sln.DotSettings +++ b/BACnet.sln.DotSettings @@ -13,4 +13,5 @@ VMAC <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="_test" Style="aa_bb" /></Policy> \ No newline at end of file diff --git a/Base/BacnetBitString.cs b/Base/BacnetBitString.cs index 3d0eb924..1e49ccc9 100644 --- a/Base/BacnetBitString.cs +++ b/Base/BacnetBitString.cs @@ -82,12 +82,12 @@ public uint ConvertToInt() : 0; } - public static BacnetBitString ConvertFromInt(uint value) + public static BacnetBitString ConvertFromInt(uint value, byte? bitsUsed = null) { return new BacnetBitString { value = BitConverter.GetBytes(value), - bits_used = (byte)Math.Ceiling(Math.Log(value, 2)) + bits_used = bitsUsed ?? (byte)(Math.Log(value, 2) + 1) }; } }; diff --git a/Serialize/Services.cs b/Serialize/Services.cs index b41ca49e..6418bdf9 100644 --- a/Serialize/Services.cs +++ b/Serialize/Services.cs @@ -2316,7 +2316,7 @@ public static void EncodeLogRecord(EncodeBuffer buffer, BacnetLogRecord record) } /* Tag 2: status */ - var recordStatusFlags = BacnetBitString.ConvertFromInt((uint)record.statusFlags); + var recordStatusFlags = BacnetBitString.ConvertFromInt((uint)record.statusFlags, 4); if (recordStatusFlags.bits_used > 0) { ASN1.encode_opening_tag(buffer, 2); diff --git a/Tests/BACnet.Tests.csproj b/Tests/BACnet.Tests.csproj new file mode 100644 index 00000000..63514956 --- /dev/null +++ b/Tests/BACnet.Tests.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {5DC7DC72-E57B-418C-ABE7-8A26B28AD953} + Library + Properties + System.IO.BACnet + BACnet.Tests + v4.0 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\BACnet\packages\NUnit.3.9.0\lib\net40\nunit.framework.dll + + + + + + + + + + + + + + + {66832876-01FC-4B7C-8D92-54195773FABF} + BACnet + + + + \ No newline at end of file diff --git a/Tests/Base/BacnetBitStringTests.cs b/Tests/Base/BacnetBitStringTests.cs new file mode 100644 index 00000000..577b8175 --- /dev/null +++ b/Tests/Base/BacnetBitStringTests.cs @@ -0,0 +1,92 @@ +using NUnit.Framework; + +namespace System.IO.BACnet.Base +{ + public class BacnetBitStringTests + { + [Test] + public void should_set_bit_test() + { + for (byte i = 0; i < 32; i++) + { + var bitString = new BacnetBitString(); + bitString.SetBit(i, true); + Assert.AreEqual(true, bitString.GetBit(i)); + } + } + + [Test] + public void should_calculate_bits_used_when_setting_bit_to_false_test() + { + var bitString = new BacnetBitString(); + bitString.SetBit(2, false); + Assert.AreEqual(3, bitString.bits_used); + } + + [Test] + public void should_calculate_bits_used_test() + { + for (byte i = 0; i < 32; i++) + { + var bitString = new BacnetBitString(); + bitString.SetBit(i, true); + Assert.AreEqual(i + 1, bitString.bits_used); + } + } + + [Test] + public void should_convert_to_uint_test() + { + for (byte i = 0; i < 32; i++) + { + var bitString = new BacnetBitString(); + bitString.SetBit(i, true); + Assert.AreEqual((uint)(1 << i), bitString.ConvertToInt()); + } + } + + [Test] + public void should_set_bit_when_converting_from_uint_test() + { + for (byte i = 0; i < 32; i++) + { + var bitString = BacnetBitString.ConvertFromInt((uint)(1 << i)); + Assert.AreEqual(true, bitString.GetBit(i)); + } + } + + [Test] + public void should_respect_provided_bits_used_value_whe_converting_from_uint_test() + { + const byte bitsUsed = 3; + var bitString = BacnetBitString.ConvertFromInt(0, bitsUsed); + Assert.AreEqual(bitsUsed, bitString.bits_used); + } + + [TestCase((uint)0b00000000000000000000000000000000, 0)] + [TestCase((uint)0b00000000000000000000000000000001, 1)] + [TestCase((uint)0b01000000000000000000000000000000, 31)] + [TestCase((uint)0b01111111111111111111111111111111, 31)] + [TestCase(0b10101010101010101010101010101010, 32)] + [TestCase(0b10000000000000000000000000000000, 32)] + [TestCase(0b11111111111111111111111111111111, 32)] + public void should_calculate_bits_used_when_converting_from_uint_test(uint value, byte bitsUsed) + { + var bitString = BacnetBitString.ConvertFromInt(value); + Assert.AreEqual(bitsUsed, bitString.bits_used); + } + + [TestCase((uint)0b00000000000000000000000000000000)] + [TestCase((uint)0b00000000000000000000000000000001)] + [TestCase((uint)0b01000000000000000000000000000000)] + [TestCase((uint)0b01111111111111111111111111111111)] + [TestCase(0b10101010101010101010101010101010)] + [TestCase(0b10000000000000000000000000000000)] + [TestCase(0b11111111111111111111111111111111)] + public void should_convert_back_to_uint_when_converting_from_uint_test(uint value) + { + var bitString = BacnetBitString.ConvertFromInt(value); + Assert.AreEqual(value, bitString.ConvertToInt()); + } + } +} \ No newline at end of file diff --git a/Tests/Properties/AssemblyInfo.cs b/Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..aa8c723b --- /dev/null +++ b/Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("BACnet.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BACnet.Tests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5dc7dc72-e57b-418c-abe7-8a26b28ad953")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/packages.config b/Tests/packages.config new file mode 100644 index 00000000..82758b0e --- /dev/null +++ b/Tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From a96722c81347aa30ad4032e9d891a9ece5250215 Mon Sep 17 00:00:00 2001 From: iqbal-hassan Date: Thu, 15 Feb 2018 03:09:47 +0800 Subject: [PATCH 08/31] new: BACnet Prescale and Scale for Accumulator objects (#16) * Add BACnet Prescale and Scale for Accumulator objects * Made modifications as per review --- BACnet.csproj | 2 ++ Base/BacnetApplicationTags.cs | 4 ++- Base/BacnetPrescale.cs | 56 ++++++++++++++++++++++++++++++ Base/BacnetScale.cs | 64 +++++++++++++++++++++++++++++++++++ Serialize/ASN1.cs | 18 ++++++++++ 5 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 Base/BacnetPrescale.cs create mode 100644 Base/BacnetScale.cs diff --git a/BACnet.csproj b/BACnet.csproj index 76f878ba..6d62f407 100644 --- a/BACnet.csproj +++ b/BACnet.csproj @@ -58,6 +58,7 @@ + @@ -95,6 +96,7 @@ + diff --git a/Base/BacnetApplicationTags.cs b/Base/BacnetApplicationTags.cs index 2835a9c5..ac59c920 100644 --- a/Base/BacnetApplicationTags.cs +++ b/Base/BacnetApplicationTags.cs @@ -61,6 +61,8 @@ public enum BacnetApplicationTags BACNET_APPLICATION_TAG_CONTEXT_SPECIFIC_DECODED, BACNET_APPLICATION_TAG_CONTEXT_SPECIFIC_ENCODED, /* BACnetLogRecord */ - BACNET_APPLICATION_TAG_LOG_RECORD + BACNET_APPLICATION_TAG_LOG_RECORD, + BACNET_APPLICATION_TAG_PRESCALE, + BACNET_APPLICATION_TAG_SCALE } } \ No newline at end of file diff --git a/Base/BacnetPrescale.cs b/Base/BacnetPrescale.cs new file mode 100644 index 00000000..f8b05d3c --- /dev/null +++ b/Base/BacnetPrescale.cs @@ -0,0 +1,56 @@ +using System.IO.BACnet.Serialize; + +namespace System.IO.BACnet +{ + public class BacnetPrescale : ASN1.IEncode, ASN1.IDecode + { + public uint Multiplier + { + get { return multiplier; } + set { multiplier = value; } + } + + public uint Modulo + { + get { return modulo; } + set { modulo = value; } + } + + public uint multiplier; + public uint modulo; + + public BacnetPrescale(uint multiplier, uint modulo) + { + this.multiplier = multiplier; + this.modulo = modulo; + } + + public void Encode(EncodeBuffer buffer) + { + ASN1.encode_context_unsigned(buffer, 0, multiplier); + ASN1.encode_context_unsigned(buffer, 1, modulo); + } + + public int Decode(byte[] buffer, int offset, uint count) + { + var len = 0; + + var tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); + if (tagNumber != 0) + { + return -1; + } + len += tagLen; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out multiplier); + + tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + if (tagNumber != 1) + { + return -1; + } + len += tagLen; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out modulo); + return len; + } + } +} \ No newline at end of file diff --git a/Base/BacnetScale.cs b/Base/BacnetScale.cs new file mode 100644 index 00000000..54335d63 --- /dev/null +++ b/Base/BacnetScale.cs @@ -0,0 +1,64 @@ +using System.IO.BACnet.Serialize; + +namespace System.IO.BACnet +{ + public class BacnetScale : ASN1.IEncode, ASN1.IDecode + { + public enum ValueType : byte + { + FLOAT = 0, + INTEGER = 1, + } + + public object Value + { + get { return value; } + set { this.value = value; } + } + + public object value; + + public BacnetScale(object value) + { + this.value = value; + } + + public void Encode(EncodeBuffer buffer) + { + if (value is float floatVal) + { + ASN1.encode_context_real(buffer, (byte)ValueType.FLOAT, floatVal); + } + else if (value is uint uintVal) + { + ASN1.encode_context_unsigned(buffer, (byte)ValueType.INTEGER, uintVal); + } + } + + public int Decode(byte[] buffer, int offset, uint count) + { + var len = 0; + + var tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); + if (tagNumber == (byte)ValueType.FLOAT) + { + float oval; + len += tagLen; + len += ASN1.decode_real(buffer, offset + len, out oval); + + value = oval; + } + else if (tagNumber == (byte)ValueType.INTEGER) + { + len += tagLen; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out var oval); + + value = oval; + } + else + return -1; + + return len; + } + } +} \ No newline at end of file diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index 02dd8035..faf99356 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -1931,6 +1931,24 @@ public static int bacapp_decode_context_application_data(byte[] buffer, int offs return len; } + if (propertyId == BacnetPropertyIds.PROP_PRESCALE) + { + BacnetPrescale v = new BacnetPrescale(0, 0); + tagLen = v.Decode(buffer, offset, (uint)maxOffset); + if (tagLen < 0) return -1; + value.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_PRESCALE; + value.Value = v; + return tagLen; + } + if (propertyId == BacnetPropertyIds.PROP_SCALE) + { + BacnetScale v = new BacnetScale(0.0f); + tagLen = v.Decode(buffer, offset, (uint)maxOffset); + if (tagLen < 0) return -1; + value.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_SCALE; + value.Value = v; + return tagLen; + } value.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_CONTEXT_SPECIFIC_DECODED; var list = new List(); From 50a55dc3ef61b284d5ddee3361ccf7ab2b48ab4f Mon Sep 17 00:00:00 2001 From: DarkStarDS9 Date: Mon, 19 Feb 2018 10:38:19 +0100 Subject: [PATCH 09/31] chore: enhancements to improve compatibility (#18) * Support for re-ordering of segment-packets (out of order packets are NOT an error by default, e.g. with UDP) * fix: don't throw exception when setting MaxInfoFrames (#10) * you can now provide a specific exclusive port * added custom tag resolver * Revert "you can now provide a specific exclusive port" This reverts commit a1be941b05b91ffe794ba1e8ba454e5b1db8cfd3. * changes requested by @gralin --- BACnet.csproj | 2 +- BACnet.csproj.DotSettings | 11 ++ BACnetClient.cs | 103 +++++++++--------- BacnetAsyncResult.cs | 8 +- Serialize/ASN1.cs | 55 ++++++++-- Serialize/BVLC.cs | 4 +- Serialize/Services.cs | 24 ++-- ...ort.cs => BacnetIpUdpProtocolTransport.cs} | 62 +++++++---- Transport/BacnetIpV6UdpProtocolTransport.cs | 10 +- Transport/BacnetMstpProtocolTransport.cs | 9 +- Transport/BacnetTransportBase.cs | 9 +- 11 files changed, 173 insertions(+), 124 deletions(-) rename Transport/{BACnetTransport.cs => BacnetIpUdpProtocolTransport.cs} (90%) diff --git a/BACnet.csproj b/BACnet.csproj index 6d62f407..04f00c9d 100644 --- a/BACnet.csproj +++ b/BACnet.csproj @@ -131,7 +131,7 @@ - + diff --git a/BACnet.csproj.DotSettings b/BACnet.csproj.DotSettings index 8935b676..39a24956 100644 --- a/BACnet.csproj.DotSettings +++ b/BACnet.csproj.DotSettings @@ -2,4 +2,15 @@ True True True + NEVER + True + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> + True + True + True + True + True + True + True + True \ No newline at end of file diff --git a/BACnetClient.cs b/BACnetClient.cs index abf20c43..d455b51b 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -17,16 +17,18 @@ public class BacnetClient : IDisposable { private int _retries; private byte _invokeId; - private byte _lastSequenceNumber; - - /// - /// only used when 'DefaultSegmentationHandling' = true - /// - private readonly LinkedList _segments = new LinkedList(); private readonly LastSegmentAck _lastSegmentAck = new LastSegmentAck(); private uint _writepriority; + /// + /// Dictionary of List of Tuples with sequence-number and byte[] per invoke-id + /// TODO: invoke-id should be PER (remote) DEVICE! + /// + private Dictionary>> _segmentsPerInvokeId = new Dictionary>>(); + private Dictionary _locksPerInvokeId = new Dictionary(); + private Dictionary _expectedSegmentsPerInvokeId = new Dictionary(); + public const int DEFAULT_UDP_PORT = 0xBAC0; public static readonly TimeSpan DEFAULT_TIMEOUT = TimeSpan.FromSeconds(1); public const int DEFAULT_RETRIES = 3; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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"); @@ -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); @@ -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>(); + + 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) @@ -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); } /// /// This is a simple handling that stores all segments in memory and assembles them when done /// - 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; @@ -641,12 +638,12 @@ 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 @@ -654,8 +651,9 @@ private void PerformDefaultSegmentHandling(BacnetAddress address, BacnetPduTypes 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); @@ -1175,7 +1173,7 @@ public IList 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) @@ -1281,8 +1279,9 @@ public IList 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"); diff --git a/BacnetAsyncResult.cs b/BacnetAsyncResult.cs index eed7fa5f..62a893cb 100644 --- a/BacnetAsyncResult.cs +++ b/BacnetAsyncResult.cs @@ -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; @@ -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 { @@ -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; @@ -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"); @@ -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; diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index faf99356..08d2160e 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -14,6 +14,13 @@ public class ASN1 public const uint BACNET_MIN_PRIORITY = 1; public const uint BACNET_MAX_PRIORITY = 16; + /// + /// You can provide a function to resolve a given tag within a property to a different tag (e.g. for custom properties) + /// The address of the remote device is provided so that you can support devices from multiple vendors + /// (the same custom property# has different meaning for each vendor) + /// + public static Func CustomTagResolver; + public interface IEncode { void Encode(EncodeBuffer buffer); @@ -364,6 +371,19 @@ public static void encode_application_bitstring(EncodeBuffer buffer, BacnetBitSt encode_bitstring(buffer, bitString); } + public static void EncodeApplicationDestination(EncodeBuffer buffer, BacnetObjectTypes objectType, uint instance) + { + var tempBuffer = new EncodeBuffer(); + encode_bacnet_object_id(tempBuffer, objectType, instance); + buffer.Add(0x0C); + buffer.Add(tempBuffer.buffer, tempBuffer.offset); + } + + public static void EncodeApplicationDestination(EncodeBuffer buffer, BacnetAddress address) + { + address.Encode(buffer); + } + public static void bacapp_encode_application_data(EncodeBuffer buffer, BacnetValue value) { if (value.Value == null) @@ -434,6 +454,19 @@ public static void bacapp_encode_application_data(EncodeBuffer buffer, BacnetVal encode_read_access_specification(buffer, (BacnetReadAccessSpecification)value.Value); //is this the right way to do it, I wonder? break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_DESTINATION: + switch (value.Value) + { + case BacnetObjectId oid: + EncodeApplicationDestination(buffer, oid.type, oid.instance); + break; + case BacnetAddress adr: + EncodeApplicationDestination(buffer, adr); + break; + default: + throw new ArgumentException($"Unsupported destination value '{value.Value}' (type {value.GetType()})"); + } + break; default: //context specific if (value.Value is byte[] arr) @@ -838,7 +871,7 @@ public static void encode_read_access_result(EncodeBuffer buffer, BacnetReadAcce encode_closing_tag(buffer, 1); } - public static int decode_read_access_result(byte[] buffer, int offset, int apdu_len, out BacnetReadAccessResult value) + public static int decode_read_access_result(BacnetAddress address, byte[] buffer, int offset, int apdu_len, out BacnetReadAccessResult value) { var len = 0; @@ -893,7 +926,7 @@ public static int decode_read_access_result(byte[] buffer, int offset, int apdu_ var localValueList = new List(); while (!decode_is_closing_tag_number(buffer, offset + len, 4)) { - tagLen = bacapp_decode_application_data(buffer, offset + len, apdu_len + offset - 1, + tagLen = bacapp_decode_application_data(address, buffer, offset + len, apdu_len + offset - 1, value.objectIdentifier.type, (BacnetPropertyIds)new_entry.property.propertyIdentifier, out var v); if (tagLen < 0) return -1; len += tagLen; @@ -1655,7 +1688,7 @@ public static int bacapp_decode_data(byte[] buffer, int offset, int maxLength, B /* returns the fixed tag type for certain context tagged properties */ - private static BacnetApplicationTags bacapp_context_tag_type(BacnetPropertyIds property, byte tagNumber) + private static BacnetApplicationTags bacapp_context_tag_type(BacnetAddress address, BacnetPropertyIds property, byte tagNumber) { var tag = BacnetApplicationTags.MAX_BACNET_APPLICATION_TAG; @@ -1737,6 +1770,7 @@ private static BacnetApplicationTags bacapp_context_tag_type(BacnetPropertyIds p } break; case BacnetPropertyIds.PROP_SUBORDINATE_LIST: + case BacnetPropertyIds.PROP_ZONE_MEMBERS: /* BACnetARRAY[N] of BACnetDeviceObjectReference */ switch (tagNumber) { @@ -1774,6 +1808,9 @@ private static BacnetApplicationTags bacapp_context_tag_type(BacnetPropertyIds p break; } break; + default: + tag = CustomTagResolver?.Invoke(address, property, tagNumber) ?? tag; + break; } return tag; @@ -1817,7 +1854,7 @@ public static int bacapp_decode_context_data(byte[] buffer, int offset, uint max return apduLen; } - public static int bacapp_decode_application_data(byte[] buffer, int offset, int maxOffset, BacnetObjectTypes objectType, BacnetPropertyIds propertyId, out BacnetValue value) + public static int bacapp_decode_application_data(BacnetAddress address, byte[] buffer, int offset, int maxOffset, BacnetObjectTypes objectType, BacnetPropertyIds propertyId, out BacnetValue value) { var len = 0; @@ -1838,14 +1875,14 @@ public static int bacapp_decode_application_data(byte[] buffer, int offset, int } else { - return bacapp_decode_context_application_data(buffer, offset, maxOffset, objectType, propertyId, + return bacapp_decode_context_application_data(address, buffer, offset, maxOffset, objectType, propertyId, out value); } return len; } - public static int bacapp_decode_context_application_data(byte[] buffer, int offset, int maxOffset, BacnetObjectTypes objectType, BacnetPropertyIds propertyId, out BacnetValue value) + public static int bacapp_decode_context_application_data(BacnetAddress address, byte[] buffer, int offset, int maxOffset, BacnetObjectTypes objectType, BacnetPropertyIds propertyId, out BacnetValue value) { var len = 0; @@ -1877,7 +1914,7 @@ public static int bacapp_decode_context_application_data(byte[] buffer, int offs if (objectType == BacnetObjectTypes.OBJECT_GROUP && propertyId == BacnetPropertyIds.PROP_PRESENT_VALUE) { BacnetReadAccessResult v; - tagLen = decode_read_access_result(buffer, offset, maxOffset, out v); + tagLen = decode_read_access_result(address, buffer, offset, maxOffset, out v); if (tagLen < 0) return -1; value.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_READ_ACCESS_RESULT; value.Value = v; @@ -1965,7 +2002,7 @@ public static int bacapp_decode_context_application_data(byte[] buffer, int offs if (lenValueType == 0) { len += tagLen; - tagLen = bacapp_decode_application_data(buffer, offset + len, maxOffset, + tagLen = bacapp_decode_application_data(address, buffer, offset + len, maxOffset, BacnetObjectTypes.MAX_BACNET_OBJECT_TYPE, BacnetPropertyIds.MAX_BACNET_PROPERTY_ID, out var subValue); if (tagLen < 0) return -1; @@ -1975,7 +2012,7 @@ public static int bacapp_decode_context_application_data(byte[] buffer, int offs else { //override tagNumber - var overrideTagNumber = bacapp_context_tag_type(propertyId, subTagNumber); + var overrideTagNumber = bacapp_context_tag_type(address, propertyId, subTagNumber); if (overrideTagNumber != BacnetApplicationTags.MAX_BACNET_APPLICATION_TAG) subTagNumber = (byte)overrideTagNumber; diff --git a/Serialize/BVLC.cs b/Serialize/BVLC.cs index 1929e508..54a0ec9c 100644 --- a/Serialize/BVLC.cs +++ b/Serialize/BVLC.cs @@ -375,7 +375,7 @@ public int Decode(byte[] buffer, int offset, out BacnetBvlcFunctions function, o var mask = new byte[4]; Array.Copy(buffer, 10 + i * 10, mask, 0, 4); - var entry = new Tuple(new IPEndPoint(new IPAddress(add), port), new IPAddress(mask)); + var entry = Tuple.Create(new IPEndPoint(new IPAddress(add), port), new IPAddress(mask)); entries.Add(entry); } @@ -406,7 +406,7 @@ public int Decode(byte[] buffer, int offset, out BacnetBvlcFunctions function, o Array.Reverse(buffer, 12 + i * 10, 2); var remainTtl = BitConverter.ToUInt16(buffer, 12 + i * 10); - var entry = new Tuple(new IPEndPoint(new IPAddress(add), port), ttl, remainTtl); + var entry = Tuple.Create(new IPEndPoint(new IPAddress(add), port), ttl, remainTtl); entries.Add(entry); } diff --git a/Serialize/Services.cs b/Serialize/Services.cs index 9c084589..ad92c36c 100644 --- a/Serialize/Services.cs +++ b/Serialize/Services.cs @@ -1667,7 +1667,7 @@ public static void EncodeReadPropertyAcknowledge(EncodeBuffer buffer, BacnetObje ASN1.encode_closing_tag(buffer, 3); } - public static int DecodeReadPropertyAcknowledge(byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyReference property, out IList valueList) + public static int DecodeReadPropertyAcknowledge(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyReference property, out IList valueList) { objectId = new BacnetObjectId(); property = new BacnetPropertyReference(); @@ -1702,7 +1702,7 @@ public static int DecodeReadPropertyAcknowledge(byte[] buffer, int offset, int a while (apduLen - len > 1) { - tagLen = ASN1.bacapp_decode_application_data(buffer, offset + len, apduLen + offset, objectId.type, (BacnetPropertyIds)property.propertyIdentifier, out var value); + tagLen = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, objectId.type, (BacnetPropertyIds)property.propertyIdentifier, out var value); if (tagLen < 0) return -1; len += tagLen; valueList.Add(value); @@ -1754,7 +1754,7 @@ public static void EncodeReadPropertyMultipleAcknowledge(EncodeBuffer buffer, IL ASN1.encode_read_access_result(buffer, value); } - public static int DecodeReadPropertyMultipleAcknowledge(byte[] buffer, int offset, int apduLen, out IList values) + public static int DecodeReadPropertyMultipleAcknowledge(BacnetAddress address, byte[] buffer, int offset, int apduLen, out IList values) { var len = 0; @@ -1762,7 +1762,7 @@ public static int DecodeReadPropertyMultipleAcknowledge(byte[] buffer, int offse while (apduLen - len > 0) { - var tmp = ASN1.decode_read_access_result(buffer, offset + len, apduLen - len, out var value); + var tmp = ASN1.decode_read_access_result(address, buffer, offset + len, apduLen - len, out var value); if (tmp < 0) { values = null; @@ -1802,7 +1802,7 @@ public static void EncodeWriteProperty(EncodeBuffer buffer, BacnetObjectId objec } } - public static int DecodeCOVNotifyUnconfirmed(byte[] buffer, int offset, int apduLen, out uint subscriberProcessIdentifier, out BacnetObjectId initiatingDeviceIdentifier, out BacnetObjectId monitoredObjectIdentifier, out uint timeRemaining, out ICollection values) + public static int DecodeCOVNotifyUnconfirmed(BacnetAddress address, byte[] buffer, int offset, int apduLen, out uint subscriberProcessIdentifier, out BacnetObjectId initiatingDeviceIdentifier, out BacnetObjectId monitoredObjectIdentifier, out uint timeRemaining, out ICollection values) { var len = 0; uint lenValue; @@ -1887,7 +1887,7 @@ public static int DecodeCOVNotifyUnconfirmed(byte[] buffer, int offset, int apdu var bValues = new List(); while (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 2)) { - var tmp = ASN1.bacapp_decode_application_data(buffer, offset + len, apduLen + offset, monitoredObjectIdentifier.type, (BacnetPropertyIds)newEntry.property.propertyIdentifier, out var bValue); + var tmp = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, monitoredObjectIdentifier.type, (BacnetPropertyIds)newEntry.property.propertyIdentifier, out var bValue); if (tmp < 0) return -1; len += tmp; bValues.Add(bValue); @@ -1913,7 +1913,7 @@ public static int DecodeCOVNotifyUnconfirmed(byte[] buffer, int offset, int apdu return len; } - public static int DecodeWriteProperty(byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyValue value) + public static int DecodeWriteProperty(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyValue value) { var len = 0; @@ -1949,7 +1949,7 @@ public static int DecodeWriteProperty(byte[] buffer, int offset, int apduLen, ou var valueList = new List(); while (apduLen - len > 1 && !ASN1.decode_is_closing_tag_number(buffer, offset + len, 3)) { - var l = ASN1.bacapp_decode_application_data(buffer, offset + len, apduLen + offset, objectId.type, (BacnetPropertyIds)value.property.propertyIdentifier, out var bValue); + var l = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, objectId.type, (BacnetPropertyIds)value.property.propertyIdentifier, out var bValue); if (l <= 0) return -1; len += l; valueList.Add(bValue); @@ -2018,7 +2018,7 @@ public static void EncodeWriteObjectMultiple(EncodeBuffer buffer, ICollection valuesRefs) + public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out ICollection valuesRefs) { var len = 0; @@ -2083,7 +2083,7 @@ public static int DecodeCreateObject(byte[] buffer, int offset, int apduLen, out var values = new List(); while (!ASN1.decode_is_closing_tag(buffer, offset + len)) { - var l = ASN1.bacapp_decode_application_data(buffer, offset + len, apduLen + offset, objectId.type, (BacnetPropertyIds)propertyId, out var value); + var l = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, objectId.type, (BacnetPropertyIds)propertyId, out var value); if (l <= 0) return -1; len += l; values.Add(value); @@ -2129,7 +2129,7 @@ public static void EncodeCreateObjectAcknowledge(EncodeBuffer buffer, BacnetObje ASN1.encode_application_object_id(buffer, objectId.type, objectId.instance); } - public static int DecodeWritePropertyMultiple(byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out ICollection valuesRefs) + public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out ICollection valuesRefs) { var len = 0; objectId = new BacnetObjectId(); @@ -2184,7 +2184,7 @@ public static int DecodeWritePropertyMultiple(byte[] buffer, int offset, int apd var values = new List(); while (!ASN1.decode_is_closing_tag(buffer, offset + len)) { - var l = ASN1.bacapp_decode_application_data(buffer, offset + len, apduLen + offset, objectId.type, (BacnetPropertyIds)propertyId, out var value); + var l = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, objectId.type, (BacnetPropertyIds)propertyId, out var value); if (l <= 0) return -1; len += l; values.Add(value); diff --git a/Transport/BACnetTransport.cs b/Transport/BacnetIpUdpProtocolTransport.cs similarity index 90% rename from Transport/BACnetTransport.cs rename to Transport/BacnetIpUdpProtocolTransport.cs index 30f12d81..5fccca1c 100644 --- a/Transport/BACnetTransport.cs +++ b/Transport/BacnetIpUdpProtocolTransport.cs @@ -23,7 +23,7 @@ public class BacnetIpUdpProtocolTransport : BacnetTransportBase public BVLC Bvlc { get; private set; } public int SharedPort { get; } - public int ExclusivePort => ((IPEndPoint)_exclusiveConn.Client.LocalEndPoint).Port; + public int ExclusivePort { get; } // Give 0.0.0.0:xxxx if the socket is open with System.Net.IPAddress.Any // Today only used by _GetBroadcastAddress method & the bvlc layer class in BBMD mode @@ -44,6 +44,13 @@ public BacnetIpUdpProtocolTransport(int port, bool useExclusivePort = false, boo _localEndpoint = localEndpointIp; } + public BacnetIpUdpProtocolTransport(int sharedPort, int exclusivePort, bool dontFragment = false, + int maxPayload = 1472, string localEndpointIp = "") + : this(sharedPort, false, dontFragment, maxPayload, localEndpointIp) + { + ExclusivePort = exclusivePort; + } + public override bool Equals(object obj) { var a = obj as BacnetIpUdpProtocolTransport; @@ -82,8 +89,8 @@ private void Open() /* So this is how we'll present our selves to the world */ if (_exclusiveConn == null) { - var ep = new IPEndPoint(IPAddress.Any, 0); - if (!string.IsNullOrEmpty(_localEndpoint)) ep = new IPEndPoint(IPAddress.Parse(_localEndpoint), 0); + var ep = new IPEndPoint(IPAddress.Any, ExclusivePort); + if (!string.IsNullOrEmpty(_localEndpoint)) ep = new IPEndPoint(IPAddress.Parse(_localEndpoint), ExclusivePort); _exclusiveConn = new UdpClient(ep); // Gets the Endpoint : the assigned Udp port number in fact @@ -154,8 +161,29 @@ private void OnReceiveData(IAsyncResult asyncResult) try { - var ep = new IPEndPoint(IPAddress.Any, 0); - var receiveBuffer = connection.EndReceive(asyncResult, ref ep); + IPEndPoint ep = null; + byte[] receiveBuffer; + + try + { + receiveBuffer = connection.EndReceive(asyncResult, ref ep); + } + finally + { + if (connection.Client != null && !_disposing) + { + try + { + // BeginReceive ASAP to enable parallel processing (e.g. query additional data while processing a notification) + connection.BeginReceive(OnReceiveData, connection); + } + catch (Exception ex) + { + Log.Error($"Failed to restart data receive. {ex}"); + } + } + } + var receiveBufferHex = ConvertToHex(receiveBuffer); var receivedLength = receiveBuffer.Length; @@ -226,20 +254,6 @@ private void OnReceiveData(IAsyncResult asyncResult) Log.Error("Exception in OnRecieveData", e); } - finally - { - if (connection.Client != null && !_disposing) - { - try - { - connection.BeginReceive(OnReceiveData, connection); - } - catch (Exception ex) - { - Log.Error("Failed to restart data receive", ex); - } - } - } } public bool SendRegisterAsForeignDevice(IPEndPoint bbmd, short ttl) @@ -306,20 +320,20 @@ public override int Send(byte[] buffer, int offset, int dataLength, BacnetAddres } } - public static void Convert(IPEndPoint ep, out BacnetAddress addr) + public static void Convert(IPEndPoint ep, out BacnetAddress address) { var tmp1 = ep.Address.GetAddressBytes(); var tmp2 = BitConverter.GetBytes((ushort)ep.Port); Array.Reverse(tmp2); Array.Resize(ref tmp1, tmp1.Length + tmp2.Length); Array.Copy(tmp2, 0, tmp1, tmp1.Length - tmp2.Length, tmp2.Length); - addr = new BacnetAddress(BacnetAddressTypes.IP, 0, tmp1); + address = new BacnetAddress(BacnetAddressTypes.IP, 0, tmp1); } - public static void Convert(BacnetAddress addr, out IPEndPoint ep) + public static void Convert(BacnetAddress address, out IPEndPoint ep) { - long ipAddress = BitConverter.ToUInt32(addr.adr, 0); - var port = (ushort)((addr.adr[4] << 8) | (addr.adr[5] << 0)); + long ipAddress = BitConverter.ToUInt32(address.adr, 0); + var port = (ushort)((address.adr[4] << 8) | (address.adr[5] << 0)); ep = new IPEndPoint(ipAddress, port); } diff --git a/Transport/BacnetIpV6UdpProtocolTransport.cs b/Transport/BacnetIpV6UdpProtocolTransport.cs index 2664cf0a..55c90afb 100644 --- a/Transport/BacnetIpV6UdpProtocolTransport.cs +++ b/Transport/BacnetIpV6UdpProtocolTransport.cs @@ -320,21 +320,21 @@ public bool SendRemoteWhois(byte[] buffer, IPEndPoint bbmd, int msgLength) return true; } - public static void Convert(IPEndPoint ep, out BacnetAddress addr) + public static void Convert(IPEndPoint ep, out BacnetAddress address) { var tmp1 = ep.Address.GetAddressBytes(); var tmp2 = BitConverter.GetBytes((ushort)ep.Port); Array.Reverse(tmp2); Array.Resize(ref tmp1, tmp1.Length + tmp2.Length); Array.Copy(tmp2, 0, tmp1, tmp1.Length - tmp2.Length, tmp2.Length); - addr = new BacnetAddress(BacnetAddressTypes.IPV6, 0, tmp1); + address = new BacnetAddress(BacnetAddressTypes.IPV6, 0, tmp1); } - public static void Convert(BacnetAddress addr, out IPEndPoint ep) + public static void Convert(BacnetAddress address, out IPEndPoint ep) { - var port = (ushort)((addr.adr[16] << 8) | (addr.adr[17] << 0)); + var port = (ushort)((address.adr[16] << 8) | (address.adr[17] << 0)); var ipv6 = new byte[16]; - Array.Copy(addr.adr, ipv6, 16); + Array.Copy(address.adr, ipv6, 16); ep = new IPEndPoint(new IPAddress(ipv6), port); } } diff --git a/Transport/BacnetMstpProtocolTransport.cs b/Transport/BacnetMstpProtocolTransport.cs index aaaadd09..48fbf659 100644 --- a/Transport/BacnetMstpProtocolTransport.cs +++ b/Transport/BacnetMstpProtocolTransport.cs @@ -77,18 +77,11 @@ public enum GetMessageStatus private bool _soleMaster; private byte _tokenCount; private Thread _transmitThread; - private byte _maxInfoFrames; public short SourceAddress { get; set; } public byte MaxMaster { get; set; } public bool IsRunning { get; private set; } = true; - public override byte MaxInfoFrames - { - get => _maxInfoFrames; - set => _maxInfoFrames = value; - } - public BacnetMstpProtocolTransport(IBacnetSerialTransport transport, short sourceAddress = -1, byte maxMaster = 127, byte maxInfoFrames = 1) { @@ -98,8 +91,8 @@ public BacnetMstpProtocolTransport(IBacnetSerialTransport transport, short sourc HeaderLength = MSTP.MSTP_HEADER_LENGTH; MaxBufferLength = 502; MaxAdpuLength = MSTP.MSTP_MAX_APDU; + MaxInfoFrames = maxInfoFrames; - _maxInfoFrames = maxInfoFrames; _localBuffer = new byte[MaxBufferLength]; _port = transport; } diff --git a/Transport/BacnetTransportBase.cs b/Transport/BacnetTransportBase.cs index e572f438..37883e26 100644 --- a/Transport/BacnetTransportBase.cs +++ b/Transport/BacnetTransportBase.cs @@ -4,17 +4,12 @@ namespace System.IO.BACnet { public abstract class BacnetTransportBase : IBacnetTransport { - public ILog Log { get; set; } + public ILog Log { get; set; } public int HeaderLength { get; protected set; } public int MaxBufferLength { get; protected set; } public BacnetAddressTypes Type { get; protected set; } public BacnetMaxAdpu MaxAdpuLength { get; protected set; } - - public virtual byte MaxInfoFrames - { - get => 0xFF; - set => throw new NotSupportedException(); - } + public byte MaxInfoFrames { get; set; } = 0xFF; protected BacnetTransportBase() { From 7b18fdbfe03833a7a2f51626bde386383f51597c Mon Sep 17 00:00:00 2001 From: DarkStarDS9 Date: Tue, 20 Feb 2018 14:55:18 +0100 Subject: [PATCH 10/31] fix: issue with segments that slipped in during PR (#20) --- BACnetClient.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BACnetClient.cs b/BACnetClient.cs index d455b51b..3cb69b7b 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -620,7 +620,6 @@ private void ProcessSegmentLocked(BacnetAddress address, BacnetPduTypes type, Ba private void PerformDefaultSegmentHandling(BacnetAddress address, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, byte sequenceNumber, byte[] buffer, int offset, int length) { var segments = _segmentsPerInvokeId[invokeId]; - var moreFollows = segments.Count < _expectedSegmentsPerInvokeId[invokeId]; if (sequenceNumber == 0) { @@ -647,7 +646,7 @@ private void PerformDefaultSegmentHandling(BacnetAddress address, BacnetPduTypes } //process when finished - if (moreFollows) + if (segments.Count < _expectedSegmentsPerInvokeId[invokeId]) return; //assemble whole part From 4241fb0fc1a7506672c8b0cbb7be0c840a8fed76 Mon Sep 17 00:00:00 2001 From: iqbal-hassan Date: Sat, 24 Feb 2018 03:04:09 +0800 Subject: [PATCH 11/31] enhancement: perform APDU retries only on timeout. (#22) --- BACnet.csproj | 1 + BacnetAsyncResult.cs | 16 +++++++--- BacnetException.cs | 69 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 BacnetException.cs diff --git a/BACnet.csproj b/BACnet.csproj index 04f00c9d..0a9a5180 100644 --- a/BACnet.csproj +++ b/BACnet.csproj @@ -53,6 +53,7 @@ + diff --git a/BacnetAsyncResult.cs b/BacnetAsyncResult.cs index 62a893cb..10c8cb3c 100644 --- a/BacnetAsyncResult.cs +++ b/BacnetAsyncResult.cs @@ -96,7 +96,7 @@ private void OnAbort(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type if (invokeId != _waitInvokeId) return; - Error = new Exception($"Abort from device, reason: {reason}"); + Error = new BacnetAbortException(reason); } private void OnReject(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, byte invokeId, BacnetRejectReason reason, byte[] buffer, int offset, int length) @@ -104,7 +104,7 @@ private void OnReject(BacnetClient sender, BacnetAddress adr, BacnetPduTypes typ if (invokeId != _waitInvokeId) return; - Error = new Exception($"Reject from device, reason: {reason}"); + Error = new BacnetRejectException(reason); } private void OnError(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, BacnetErrorClasses errorClass, BacnetErrorCodes errorCode, byte[] buffer, int offset, int length) @@ -112,7 +112,7 @@ private void OnError(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type if (invokeId != _waitInvokeId) return; - Error = new Exception($"Error from device: {errorClass} - {errorCode}"); + Error = new BacnetErrorException(errorClass, errorCode); } private void OnComplexAck(BacnetClient sender, BacnetAddress adr, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte[] buffer, int offset, int length) @@ -168,12 +168,20 @@ public TResult GetResult(TimeSpan? timeout = null, int retryCount = 0, if (retryCount == 0) break; + + switch (Error) + { + case BacnetAbortException abortException: + case BacnetRejectException rejectException: + case BacnetErrorException errorException: + throw Error; + } retryCount--; Resend(); } - throw Error ?? new Exception(Result?.Length > 0 + throw Error ?? new BacnetApduTimeoutException(Result?.Length > 0 ? $"Failed to receive complete response within {timeout}" : $"No response within {timeout}"); } diff --git a/BacnetException.cs b/BacnetException.cs new file mode 100644 index 00000000..767f18db --- /dev/null +++ b/BacnetException.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace System.IO.BACnet +{ + public class BacnetException : Exception + { + public BacnetException() + { + } + + public BacnetException(string message) + : base(message) + { + } + + public BacnetException(string message, Exception inner) + : base(message, inner) + { + } + } + + public class BacnetApduTimeoutException : BacnetException + { + public BacnetApduTimeoutException(string message) + : base(message) + { + } + } + + public class BacnetErrorException : BacnetException + { + public BacnetErrorClasses ErrorClass { get; } + public BacnetErrorCodes ErrorCode { get; } + + public BacnetErrorException(BacnetErrorClasses errorClass, BacnetErrorCodes errorCode) + : base($"Error from device: {errorClass} - {errorCode}") + { + ErrorClass = errorClass; + ErrorCode = errorCode; + } + } + + public class BacnetAbortException : BacnetException + { + public BacnetAbortReason Reason { get; } + public BacnetErrorCodes ErrorCode { get; } + + public BacnetAbortException(BacnetAbortReason reason) + : base($"Abort from device, reason: {reason}") + { + Reason = reason; + } + } + + public class BacnetRejectException : BacnetException + { + public BacnetRejectReason Reason { get; } + public BacnetErrorCodes ErrorCode { get; } + + public BacnetRejectException(BacnetRejectReason reason) + : base($"Reject from device, reason: {reason}") + { + Reason = reason; + } + } +} From caf3071fadfe0b2ed5f51a25641adac05d8b8359 Mon Sep 17 00:00:00 2001 From: DarkStarDS9 Date: Mon, 26 Feb 2018 05:11:39 +0100 Subject: [PATCH 12/31] chore: event data refactoring (#23) * refactored EventData and added ChangeOfLifeSafety EventData-decoding * better EventData-model * new: decode notification event values (#14) * Added basic implementation from the C counterpart * Changed base types for enums * fix: decoding change of life safety event * chore: create generic ASN1.decode_enumerated * fix: typo in BacnetPropertyState name * changes requested by @gralin for PR - Set 1 * safer Enum-generics * proposal: modify StateTransition * chore: rename back to StateTransition * added tests and fixed discovered issues * adjusted naming * added tests for issue #8 * added coverage-settings, tweaked tests * cleanup --- BACnet.csproj | 22 +- BACnet.sln.DotSettings | 1 + BACnetClient.cs | 16 +- Base/BacnetBinaryPv.cs | 11 + Base/BacnetBitString.cs | 88 +- Base/BacnetDeviceObjectPropertyReference.cs | 15 +- Base/BacnetEventNotificationData.cs | 131 --- Base/BacnetGenericTime.cs | 2 +- Base/BacnetObjectId.cs | 19 +- Base/BacnetProgramError.cs | 19 + Base/BacnetProgramRequest.cs | 12 + Base/BacnetProgramState.cs | 12 + Base/BacnetPropertyState.cs | 51 ++ Base/BacnetPropetyState.cs | 31 - Base/BacnetReadAccessSpecification.cs | 3 +- Base/DeviceReportingRecipient.cs | 2 +- Base/Enums/EnumUtils.cs | 36 + .../EventValues/BufferReady.cs | 9 + .../EventValues/ChangeOfBitString.cs | 8 + .../EventValues/ChangeOfLifeSafety.cs | 10 + .../EventValues/ChangeOfState.cs | 8 + .../EventValues/ChangeOfValue.cs | 10 + .../EventValues/EventValuesBase.cs | 6 + .../EventValues/FloatingLimit.cs | 10 + .../EventValues/OutOfRange.cs | 10 + .../EventValues/UnsignedRange.cs | 9 + Base/EventNotification/NotificationData.cs | 23 + Base/EventNotification/StateTransition.cs | 30 + Helpers/StringFormatterExtension.cs | 23 + Serialize/ASN1.cs | 429 +++++++--- Serialize/Services.cs | 763 +++++++++++------- Storage/DeviceStorage.cs | 14 +- Tests/BACNetClientTests.cs | 31 + Tests/BACnet.Tests.csproj | 11 +- Tests/Base/BacnetBitStringTests.cs | 66 +- .../EventNotificationTests.cs | 61 ++ Tests/Helper.cs | 53 ++ Tests/InMemoryTransport.cs | 43 + Tests/Serialize/ASN1Tests.cs | 29 + Tests/Serialize/ServicesTests.cs | 65 ++ Tests/packages.config | 1 + 41 files changed, 1528 insertions(+), 665 deletions(-) create mode 100644 Base/BacnetBinaryPv.cs delete mode 100644 Base/BacnetEventNotificationData.cs create mode 100644 Base/BacnetProgramError.cs create mode 100644 Base/BacnetProgramRequest.cs create mode 100644 Base/BacnetProgramState.cs create mode 100644 Base/BacnetPropertyState.cs delete mode 100644 Base/BacnetPropetyState.cs create mode 100644 Base/Enums/EnumUtils.cs create mode 100644 Base/EventNotification/EventValues/BufferReady.cs create mode 100644 Base/EventNotification/EventValues/ChangeOfBitString.cs create mode 100644 Base/EventNotification/EventValues/ChangeOfLifeSafety.cs create mode 100644 Base/EventNotification/EventValues/ChangeOfState.cs create mode 100644 Base/EventNotification/EventValues/ChangeOfValue.cs create mode 100644 Base/EventNotification/EventValues/EventValuesBase.cs create mode 100644 Base/EventNotification/EventValues/FloatingLimit.cs create mode 100644 Base/EventNotification/EventValues/OutOfRange.cs create mode 100644 Base/EventNotification/EventValues/UnsignedRange.cs create mode 100644 Base/EventNotification/NotificationData.cs create mode 100644 Base/EventNotification/StateTransition.cs create mode 100644 Helpers/StringFormatterExtension.cs create mode 100644 Tests/BACNetClientTests.cs create mode 100644 Tests/Base/EventNotification/EventNotificationTests.cs create mode 100644 Tests/Helper.cs create mode 100644 Tests/InMemoryTransport.cs create mode 100644 Tests/Serialize/ASN1Tests.cs create mode 100644 Tests/Serialize/ServicesTests.cs diff --git a/BACnet.csproj b/BACnet.csproj index 0a9a5180..1d16323c 100644 --- a/BACnet.csproj +++ b/BACnet.csproj @@ -21,6 +21,7 @@ DEBUG;TRACE prompt 4 + latest pdbonly @@ -29,6 +30,7 @@ TRACE prompt 4 + latest @@ -59,6 +61,10 @@ + + + + @@ -72,7 +78,18 @@ - + + + + + + + + + + + + @@ -91,7 +108,7 @@ - + @@ -123,6 +140,7 @@ + diff --git a/BACnet.sln.DotSettings b/BACnet.sln.DotSettings index 76153a73..2d443df8 100644 --- a/BACnet.sln.DotSettings +++ b/BACnet.sln.DotSettings @@ -14,4 +14,5 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="_test" Style="aa_bb" /></Policy> + <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="*" ModuleVersionMask="*" ClassMask="*UnitTests.*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="BACnet.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> \ No newline at end of file diff --git a/BACnetClient.cs b/BACnetClient.cs index 3cb69b7b..24761d4e 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.IO.BACnet.EventNotification; +using System.IO.BACnet.EventNotification.EventValues; using System.IO.BACnet.Serialize; using System.Linq; using System.Net; @@ -168,7 +170,7 @@ public void Start() public event AtomicReadFileRequestHandler OnAtomicReadFileRequest; public delegate void SubscribeCOVRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, BacnetMaxSegments maxSegments); public event SubscribeCOVRequestHandler OnSubscribeCOV; - public delegate void EventNotificationCallbackHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetEventNotificationData eventData, bool needConfirm); + public delegate void EventNotificationCallbackHandler(BacnetClient sender, BacnetAddress address, byte invokeId, NotificationData eventData, bool needConfirm); public event EventNotificationCallbackHandler OnEventNotify; public delegate void SubscribeCOVPropertyRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, BacnetPropertyReference monitoredProperty, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, float covIncrement, BacnetMaxSegments maxSegments); public event SubscribeCOVPropertyRequestHandler OnSubscribeCOVProperty; @@ -927,14 +929,14 @@ public void IHave(BacnetObjectId deviceId, BacnetObjectId objId, string objName) } - public void SendUnconfirmedEventNotification(BacnetAddress address, BacnetEventNotificationData eventData) + public void SendUnconfirmedEventNotification(BacnetAddress address, StateTransition eventData) { - Log.Debug($"Sending Event Notification {eventData.eventType} {eventData.eventObjectIdentifier}"); + Log.Debug($"Sending Event Notification {eventData.EventType} {eventData.EventObjectIdentifier}"); var b = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, address); APDU.EncodeUnconfirmedServiceRequest(b, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION); - Services.EncodeEventNotifyUnconfirmed(b, eventData); + Services.EncodeEventNotifyData(b, eventData); Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, address, false, 0); } @@ -1315,7 +1317,7 @@ public BacnetAsyncResult BeginDeleteObjectRequest(BacnetAddress address, BacnetO { Log.Debug("Sending DeleteObjectRequest"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT, - buffer => ASN1.encode_application_object_id(buffer, objectId.type, objectId.instance), waitForTransmit); + buffer => ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance), waitForTransmit); } public void EndDeleteObjectRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1368,7 +1370,7 @@ public BacnetAsyncResult BeginRawEncodedDecodedPropertyConfirmedRequest(BacnetAd Log.Debug("Sending RawEncodedDecodedProperty"); return BeginConfirmedServiceRequest(address, serviceId, buffer => { - ASN1.encode_context_object_id(buffer, 0, objectId.type, objectId.instance); + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); ASN1.encode_context_enumerated(buffer, 1, (byte) propertyId); if (inBuffer?.Length > 0) buffer.Add(inBuffer, inBuffer.Length); // No content encoding @@ -1396,7 +1398,7 @@ public byte[] EndRawEncodedDecodedPropertyConfirmedRequest(BacnetAsyncResult req len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 1) throw new Exception("Failed to decode"); - len += ASN1.decode_enumerated(buffer, offset + len, lenValueType, out _); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out _); var outBuffer = new byte[buffer.Length - len]; Array.Copy(buffer, len, outBuffer, 0, outBuffer.Length); diff --git a/Base/BacnetBinaryPv.cs b/Base/BacnetBinaryPv.cs new file mode 100644 index 00000000..a6aa02a8 --- /dev/null +++ b/Base/BacnetBinaryPv.cs @@ -0,0 +1,11 @@ +namespace System.IO.BACnet.Base +{ + public enum BacnetBinaryPv : byte + { + MIN_BINARY_PV = 0, /* for validating incoming values */ + BINARY_INACTIVE = 0, + BINARY_ACTIVE = 1, + MAX_BINARY_PV = 1, /* for validating incoming values */ + BINARY_NULL = 255 /* our homemade way of storing this info */ + } +} diff --git a/Base/BacnetBitString.cs b/Base/BacnetBitString.cs index 1e49ccc9..accac10a 100644 --- a/Base/BacnetBitString.cs +++ b/Base/BacnetBitString.cs @@ -1,94 +1,116 @@ using System.IO.BACnet.Serialize; +using System.Linq; namespace System.IO.BACnet { - public struct BacnetBitString + public struct BacnetBitString : IEquatable { - public byte bits_used; - public byte[] value; + public byte BitsUsed { get; } + public byte[] Value { get; } - public byte Length => bits_used; + public byte Length => BitsUsed; public bool this[byte bitNumber] => GetBit(bitNumber); + public BacnetBitString(byte[] value, byte bitsUsed) + { + BitsUsed = bitsUsed; + Value = value; + + if (Value.Length > ASN1.MAX_BITSTRING_BYTES) + throw new ArgumentException($"max length is {ASN1.MAX_BITSTRING_BYTES}"); + } + public override string ToString() { var ret = ""; - for (var i = 0; i < bits_used; i++) + for (var i = 0; i < BitsUsed; i++) { - ret = ret + ((value[i / 8] & (1 << (i % 8))) > 0 ? "1" : "0"); + ret = ret + ((Value[i / 8] & (1 << (i % 8))) > 0 ? "1" : "0"); } + return ret; } - public void SetBit(byte bitNumber, bool v) + public BacnetBitString SetBit(byte bitNumber, bool v) { var byteNumber = (byte)(bitNumber / 8); byte bitMask = 1; - if (value == null) - value = new byte[ASN1.MAX_BITSTRING_BYTES]; + var newValue = Value?.Clone() as byte[] ?? new byte[ASN1.MAX_BITSTRING_BYTES]; + byte newBitsUsed = 0; if (byteNumber < ASN1.MAX_BITSTRING_BYTES) { /* set max bits used */ - if (bits_used < bitNumber + 1) - bits_used = (byte)(bitNumber + 1); + newBitsUsed = BitsUsed < bitNumber + 1 ? (byte)(bitNumber + 1) : BitsUsed; bitMask = (byte)(bitMask << (bitNumber - byteNumber * 8)); if (v) - value[byteNumber] |= bitMask; + newValue[byteNumber] |= bitMask; else - value[byteNumber] &= (byte)~bitMask; + newValue[byteNumber] &= (byte)~bitMask; } + + return new BacnetBitString(newValue, newBitsUsed); } public bool GetBit(byte bitNumber) { var byteNumber = (byte)(bitNumber / 8); - if (byteNumber >= ASN1.MAX_BITSTRING_BYTES || bitNumber >= bits_used) + if (byteNumber >= ASN1.MAX_BITSTRING_BYTES || bitNumber >= BitsUsed) throw new ArgumentOutOfRangeException(nameof(bitNumber)); - if (value == null) + if (Value == null) return false; var bitMask = (byte)(1 << (bitNumber - byteNumber * 8)); - return (value[byteNumber] & bitMask) > 0; + return (Value[byteNumber] & bitMask) > 0; } public static BacnetBitString Parse(string str) { - var ret = new BacnetBitString - { - value = new byte[ASN1.MAX_BITSTRING_BYTES] - }; - if (string.IsNullOrEmpty(str)) - return ret; + return new BacnetBitString(new byte[ASN1.MAX_BITSTRING_BYTES], 0); + + var newBitsUsed = (byte)str.Length; + var newValue = new byte[ASN1.MAX_BITSTRING_BYTES]; - ret.bits_used = (byte)str.Length; - for (var i = 0; i < ret.bits_used; i++) + for (var i = 0; i < newBitsUsed; i++) { var isSet = str[i] == '1'; - if (isSet) ret.value[i / 8] |= (byte)(1 << (i % 8)); + if (isSet) newValue[i / 8] |= (byte)(1 << (i % 8)); } - return ret; + return new BacnetBitString(newValue, newBitsUsed); } public uint ConvertToInt() { - return value != null - ? BitConverter.ToUInt32(value, 0) + return Value != null + ? BitConverter.ToUInt32(Value, 0) : 0; } public static BacnetBitString ConvertFromInt(uint value, byte? bitsUsed = null) + => new BacnetBitString(BitConverter.GetBytes(value), bitsUsed ?? (byte)(Math.Log(value, 2) + 1)); + + public bool Equals(BacnetBitString other) + { + return BitsUsed == other.BitsUsed && Value.SequenceEqual(other.Value); + } + + public override bool Equals(object obj) { - return new BacnetBitString + if (obj is null) return false; + return obj is BacnetBitString s && Equals(s); + } + + public override int GetHashCode() + { + unchecked { - value = BitConverter.GetBytes(value), - bits_used = bitsUsed ?? (byte)(Math.Log(value, 2) + 1) - }; + return (BitsUsed.GetHashCode() * 397) ^ (Value != null ? Value.Aggregate(397, (i, b) => i ^ b) : 0); + } } - }; + } } \ No newline at end of file diff --git a/Base/BacnetDeviceObjectPropertyReference.cs b/Base/BacnetDeviceObjectPropertyReference.cs index 555cbce8..d78d4582 100644 --- a/Base/BacnetDeviceObjectPropertyReference.cs +++ b/Base/BacnetDeviceObjectPropertyReference.cs @@ -41,16 +41,11 @@ public int ArrayIndex // shows -1 when it's ASN1.BACNET_ARRAY_ALL public BacnetObjectId? DeviceId // shows null when it's not OBJECT_DEVICE { - get - { - return deviceIndentifier.type == BacnetObjectTypes.OBJECT_DEVICE - ? (BacnetObjectId?)deviceIndentifier - : null; - } - set - { - deviceIndentifier = value ?? new BacnetObjectId(); - } + get => deviceIndentifier.Type == BacnetObjectTypes.OBJECT_DEVICE + ? (BacnetObjectId?)deviceIndentifier + : null; + + set => deviceIndentifier = value ?? default(BacnetObjectId); } public BacnetPropertyIds PropertyId diff --git a/Base/BacnetEventNotificationData.cs b/Base/BacnetEventNotificationData.cs deleted file mode 100644 index 59f5c748..00000000 --- a/Base/BacnetEventNotificationData.cs +++ /dev/null @@ -1,131 +0,0 @@ -namespace System.IO.BACnet -{ - public struct BacnetEventNotificationData - { - public uint processIdentifier; - public BacnetObjectId initiatingObjectIdentifier; - public BacnetObjectId eventObjectIdentifier; - public BacnetGenericTime timeStamp; - public uint notificationClass; - public byte priority; - public BacnetEventTypes eventType; - public string messageText; /* OPTIONAL - Set to NULL if not being used */ - public BacnetNotifyTypes notifyType; - public bool ackRequired; - public BacnetEventStates fromState; - public BacnetEventStates toState; - - /* - ** Each of these structures in the union maps to a particular eventtype - ** Based on BACnetNotificationParameters - */ - - /* - ** EVENT_CHANGE_OF_BITSTRING - */ - public BacnetBitString changeOfBitstring_referencedBitString; - public BacnetBitString changeOfBitstring_statusFlags; - /* - ** EVENT_CHANGE_OF_STATE - */ - public BacnetPropetyState changeOfState_newState; - public BacnetBitString changeOfState_statusFlags; - /* - ** EVENT_CHANGE_OF_VALUE - */ - public BacnetBitString changeOfValue_changedBits; - public float changeOfValue_changeValue; - public BacnetCOVTypes changeOfValue_tag; - public BacnetBitString changeOfValue_statusFlags; - /* - ** EVENT_COMMAND_FAILURE - ** - ** Not Supported! - */ - /* - ** EVENT_FLOATING_LIMIT - */ - public float floatingLimit_referenceValue; - public BacnetBitString floatingLimit_statusFlags; - public float floatingLimit_setPointValue; - public float floatingLimit_errorLimit; - /* - ** EVENT_OUT_OF_RANGE - */ - public float outOfRange_exceedingValue; - public BacnetBitString outOfRange_statusFlags; - public float outOfRange_deadband; - public float outOfRange_exceededLimit; - /* - ** EVENT_CHANGE_OF_LIFE_SAFETY - */ - public BacnetLifeSafetyStates changeOfLifeSafety_newState; - public BacnetLifeSafetyModes changeOfLifeSafety_newMode; - public BacnetBitString changeOfLifeSafety_statusFlags; - public BacnetLifeSafetyOperations changeOfLifeSafety_operationExpected; - /* - ** EVENT_EXTENDED - ** - ** Not Supported! - */ - /* - ** EVENT_BUFFER_READY - */ - public BacnetDeviceObjectPropertyReference bufferReady_bufferProperty; - public uint bufferReady_previousNotification; - public uint bufferReady_currentNotification; - /* - ** EVENT_UNSIGNED_RANGE - */ - public uint unsignedRange_exceedingValue; - public BacnetBitString unsignedRange_statusFlags; - public uint unsignedRange_exceededLimit; - - public override string ToString() - { - return $"initiatingObject: {initiatingObjectIdentifier}, eventObject: {eventObjectIdentifier}, " - + $"eventType: {eventType}, notifyType: {notifyType}, timeStamp: {timeStamp}, " - + $"fromState: {fromState}, toState: {toState}, {GetEventDetails() ?? "no details"}"; - } - - private string GetEventDetails() - { - switch (eventType) - { - case BacnetEventTypes.EVENT_CHANGE_OF_BITSTRING: - return $"referencedBitString: {changeOfBitstring_referencedBitString}, statusFlags: {changeOfBitstring_statusFlags}"; - - case BacnetEventTypes.EVENT_CHANGE_OF_STATE: - return $"newState: {changeOfState_newState}, statusFlags: {changeOfState_statusFlags}"; - - case BacnetEventTypes.EVENT_CHANGE_OF_VALUE: - return $"changedBits: {changeOfValue_changedBits}, changeValue: {changeOfValue_changeValue}, " - + $"tag: {changeOfValue_tag}, statusFlags: {changeOfValue_statusFlags}"; - - case BacnetEventTypes.EVENT_FLOATING_LIMIT: - return $"referenceValue: {floatingLimit_referenceValue}, statusFlags: {floatingLimit_statusFlags}, " - + $"setPointValue: {floatingLimit_setPointValue}, errorLimit: {floatingLimit_errorLimit}"; - - case BacnetEventTypes.EVENT_OUT_OF_RANGE: - return $"exceedingValue: {outOfRange_exceedingValue}, statusFlags: {outOfRange_statusFlags}, " - + $"deadband: {outOfRange_deadband}, exceededLimit: {outOfRange_exceededLimit}"; - - case BacnetEventTypes.EVENT_CHANGE_OF_LIFE_SAFETY: - return $"newState: {changeOfLifeSafety_newState}, newMode: {changeOfLifeSafety_newMode}, " - + - $"statusFlags: {changeOfLifeSafety_statusFlags}, operationExpected: {changeOfLifeSafety_operationExpected}"; - - case BacnetEventTypes.EVENT_BUFFER_READY: - return $"bufferProperty: {bufferReady_bufferProperty}, previousNotification: {bufferReady_previousNotification}, " - + $"currentNotification: {bufferReady_currentNotification}"; - - case BacnetEventTypes.EVENT_UNSIGNED_RANGE: - return $"exceedingValue: {unsignedRange_exceedingValue}, statusFlags: {unsignedRange_statusFlags}, " - + $"exceededLimit: {unsignedRange_exceededLimit}"; - - default: - return null; - } - } - }; -} \ No newline at end of file diff --git a/Base/BacnetGenericTime.cs b/Base/BacnetGenericTime.cs index aa67d771..322a6dac 100644 --- a/Base/BacnetGenericTime.cs +++ b/Base/BacnetGenericTime.cs @@ -6,7 +6,7 @@ public struct BacnetGenericTime public DateTime Time; public ushort Sequence; - public BacnetGenericTime(DateTime time, BacnetTimestampTags tag, ushort sequence = 0) + public BacnetGenericTime(DateTime time, BacnetTimestampTags tag = BacnetTimestampTags.TIME_STAMP_NONE, ushort sequence = 0) { Time = time; Tag = tag; diff --git a/Base/BacnetObjectId.cs b/Base/BacnetObjectId.cs index aae5f1fc..5cf8123f 100644 --- a/Base/BacnetObjectId.cs +++ b/Base/BacnetObjectId.cs @@ -5,25 +5,14 @@ namespace System.IO.BACnet [Serializable] public struct BacnetObjectId : IComparable { - public BacnetObjectTypes type; - public uint instance; + public BacnetObjectTypes Type { get; } - public BacnetObjectTypes Type - { - get => type; - set => type = value; - } - - public uint Instance - { - get => instance; - set => instance = value; - } + public uint Instance { get; } public BacnetObjectId(BacnetObjectTypes type, uint instance) { - this.type = type; - this.instance = instance; + Type = type; + Instance = instance; } public override string ToString() diff --git a/Base/BacnetProgramError.cs b/Base/BacnetProgramError.cs new file mode 100644 index 00000000..b3232a28 --- /dev/null +++ b/Base/BacnetProgramError.cs @@ -0,0 +1,19 @@ +namespace System.IO.BACnet.Base +{ + public enum BacnetProgramError : ushort + { + PROGRAM_ERROR_NORMAL = 0, + PROGRAM_ERROR_LOAD_FAILED = 1, + PROGRAM_ERROR_INTERNAL = 2, + PROGRAM_ERROR_PROGRAM = 3, + PROGRAM_ERROR_OTHER = 4, + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ + /* do the max range inside of enum so that + compilers will allocate adequate sized datatype for enum + which is used to store decoding */ + PROGRAM_ERROR_PROPRIETARY_MIN = 64, + PROGRAM_ERROR_PROPRIETARY_MAX = 65535 + } +} diff --git a/Base/BacnetProgramRequest.cs b/Base/BacnetProgramRequest.cs new file mode 100644 index 00000000..fcdc2f3b --- /dev/null +++ b/Base/BacnetProgramRequest.cs @@ -0,0 +1,12 @@ +namespace System.IO.BACnet.Base +{ + public enum BacnetProgramRequest + { + PROGRAM_REQUEST_READY = 0, + PROGRAM_REQUEST_LOAD = 1, + PROGRAM_REQUEST_RUN = 2, + PROGRAM_REQUEST_HALT = 3, + PROGRAM_REQUEST_RESTART = 4, + PROGRAM_REQUEST_UNLOAD = 5 + } +} diff --git a/Base/BacnetProgramState.cs b/Base/BacnetProgramState.cs new file mode 100644 index 00000000..eaf667a7 --- /dev/null +++ b/Base/BacnetProgramState.cs @@ -0,0 +1,12 @@ +namespace System.IO.BACnet.Base +{ + public enum BacnetProgramState + { + PROGRAM_STATE_IDLE = 0, + PROGRAM_STATE_LOADING = 1, + PROGRAM_STATE_RUNNING = 2, + PROGRAM_STATE_WAITING = 3, + PROGRAM_STATE_HALTED = 4, + PROGRAM_STATE_UNLOADING = 5 + } +} diff --git a/Base/BacnetPropertyState.cs b/Base/BacnetPropertyState.cs new file mode 100644 index 00000000..e7a7da3f --- /dev/null +++ b/Base/BacnetPropertyState.cs @@ -0,0 +1,51 @@ +using System.IO.BACnet.Base; + +namespace System.IO.BACnet +{ + public struct BacnetPropertyState + { + public enum BacnetPropertyStateTypes + { + BOOLEAN_VALUE, + BINARY_VALUE, + EVENT_TYPE, + POLARITY, + PROGRAM_CHANGE, + PROGRAM_STATE, + REASON_FOR_HALT, + RELIABILITY, + STATE, + SYSTEM_STATUS, + UNITS, + UNSIGNED_VALUE, + LIFE_SAFETY_MODE, + LIFE_SAFETY_STATE + } + + public struct State + { + public bool boolean_value; + public BacnetBinaryPv binaryValue; + public BacnetEventTypes eventType; + public BacnetPolarity polarity; + public BacnetProgramRequest programChange; + public BacnetProgramState programState; + public BacnetProgramError programError; + public BacnetReliability reliability; + public BacnetEventStates state; + public BacnetDeviceStatus systemStatus; + public BacnetUnitsId units; + public uint unsignedValue; + public BacnetLifeSafetyModes lifeSafetyMode; + public BacnetLifeSafetyStates lifeSafetyState; + } + + public BacnetPropertyStateTypes tag; + public State state; + + public override string ToString() + { + return $"{tag}:{state}"; + } + } +} diff --git a/Base/BacnetPropetyState.cs b/Base/BacnetPropetyState.cs deleted file mode 100644 index 7d34e320..00000000 --- a/Base/BacnetPropetyState.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace System.IO.BACnet -{ - public struct BacnetPropetyState - { - public enum BacnetPropertyStateTypes - { - BOOLEAN_VALUE, - BINARY_VALUE, - EVENT_TYPE, - POLARITY, - PROGRAM_CHANGE, - PROGRAM_STATE, - REASON_FOR_HALT, - RELIABILITY, - STATE, - SYSTEM_STATUS, - UNITS, - UNSIGNED_VALUE, - LIFE_SAFETY_MODE, - LIFE_SAFETY_STATE - } - - public BacnetPropertyStateTypes tag; - public uint state; - - public override string ToString() - { - return $"{tag}:{state}"; - } - } -} \ No newline at end of file diff --git a/Base/BacnetReadAccessSpecification.cs b/Base/BacnetReadAccessSpecification.cs index 98e111c2..f2df4ba1 100644 --- a/Base/BacnetReadAccessSpecification.cs +++ b/Base/BacnetReadAccessSpecification.cs @@ -21,8 +21,7 @@ public static object Parse(string value) if (string.IsNullOrEmpty(value)) return ret; var tmp = value.Split(':'); if (tmp.Length < 2) return ret; - ret.objectIdentifier.type = (BacnetObjectTypes)Enum.Parse(typeof(BacnetObjectTypes), tmp[0]); - ret.objectIdentifier.instance = uint.Parse(tmp[1]); + ret.objectIdentifier = new BacnetObjectId((BacnetObjectTypes)Enum.Parse(typeof(BacnetObjectTypes), tmp[0]), uint.Parse(tmp[1])); var refs = new List(); for (var i = 2; i < tmp.Length; i++) { diff --git a/Base/DeviceReportingRecipient.cs b/Base/DeviceReportingRecipient.cs index 2332d935..838b7824 100644 --- a/Base/DeviceReportingRecipient.cs +++ b/Base/DeviceReportingRecipient.cs @@ -76,7 +76,7 @@ public void Encode(EncodeBuffer buffer) else { // BacnetObjectId is context specific encoded - ASN1.encode_context_object_id(buffer, 0, Id.type, Id.instance); + ASN1.encode_context_object_id(buffer, 0, Id.Type, Id.Instance); } ASN1.bacapp_encode_application_data(buffer, new BacnetValue(processIdentifier)); diff --git a/Base/Enums/EnumUtils.cs b/Base/Enums/EnumUtils.cs new file mode 100644 index 00000000..472a6bf2 --- /dev/null +++ b/Base/Enums/EnumUtils.cs @@ -0,0 +1,36 @@ +/* + * This is a hack, and should go away! + * + * discovered here: https://stackoverflow.com/a/28527552 + */ + +using System.IO.BACnet.Serialize; + +namespace System.IO.BACnet +{ + public abstract class EnumClassUtils where TClass : class + { + public static int DecodeEnumerated(byte[] buffer, int offset, uint lenValue, out TEnum value) where TEnum: struct, TClass + { + var len = ASN1.decode_unsigned(buffer, offset, lenValue, out var rawValue); + value = (TEnum)(dynamic)rawValue; + return len; + } + + public static int DecodeContextEnumerated(byte[] buffer, int offset, byte tagNumber, out TEnum value) where TEnum : struct, TClass + { + if (!ASN1.decode_is_context_tag(buffer, offset, tagNumber) || ASN1.decode_is_closing_tag(buffer, offset)) + { + value = default; + return -1; + } + + var len = ASN1.decode_tag_number_and_value(buffer, offset, out _, out var lenValue); + return len + DecodeEnumerated(buffer, offset + len, lenValue, out value); + } + } + + public class EnumUtils : EnumClassUtils + { + } +} diff --git a/Base/EventNotification/EventValues/BufferReady.cs b/Base/EventNotification/EventValues/BufferReady.cs new file mode 100644 index 00000000..15c7aa5a --- /dev/null +++ b/Base/EventNotification/EventValues/BufferReady.cs @@ -0,0 +1,9 @@ +namespace System.IO.BACnet.EventNotification.EventValues +{ + public class BufferReady : EventValuesBase + { + public BacnetDeviceObjectPropertyReference BufferProperty { get; set; } + public uint PreviousNotification { get; set; } + public uint CurrentNotification { get; set; } + } +} \ No newline at end of file diff --git a/Base/EventNotification/EventValues/ChangeOfBitString.cs b/Base/EventNotification/EventValues/ChangeOfBitString.cs new file mode 100644 index 00000000..e8263ad8 --- /dev/null +++ b/Base/EventNotification/EventValues/ChangeOfBitString.cs @@ -0,0 +1,8 @@ +namespace System.IO.BACnet.EventNotification.EventValues +{ + public class ChangeOfBitString : EventValuesBase + { + public BacnetBitString ReferencedBitString { get; set; } + public BacnetBitString StatusFlags { get; set; } + } +} \ No newline at end of file diff --git a/Base/EventNotification/EventValues/ChangeOfLifeSafety.cs b/Base/EventNotification/EventValues/ChangeOfLifeSafety.cs new file mode 100644 index 00000000..3e513e13 --- /dev/null +++ b/Base/EventNotification/EventValues/ChangeOfLifeSafety.cs @@ -0,0 +1,10 @@ +namespace System.IO.BACnet.EventNotification.EventValues +{ + public class ChangeOfLifeSafety : EventValuesBase + { + public BacnetLifeSafetyStates NewState { get; set; } + public BacnetLifeSafetyModes NewMode { get; set; } + public BacnetBitString StatusFlags { get; set; } + public BacnetLifeSafetyOperations OperationExpected { get; set; } + } +} \ No newline at end of file diff --git a/Base/EventNotification/EventValues/ChangeOfState.cs b/Base/EventNotification/EventValues/ChangeOfState.cs new file mode 100644 index 00000000..c594630f --- /dev/null +++ b/Base/EventNotification/EventValues/ChangeOfState.cs @@ -0,0 +1,8 @@ +namespace System.IO.BACnet.EventNotification.EventValues +{ + public class ChangeOfState : EventValuesBase + { + public BacnetPropertyState NewState { get; set; } + public BacnetBitString StatusFlags { get; set; } + } +} \ No newline at end of file diff --git a/Base/EventNotification/EventValues/ChangeOfValue.cs b/Base/EventNotification/EventValues/ChangeOfValue.cs new file mode 100644 index 00000000..9668eaf7 --- /dev/null +++ b/Base/EventNotification/EventValues/ChangeOfValue.cs @@ -0,0 +1,10 @@ +namespace System.IO.BACnet.EventNotification.EventValues +{ + public class ChangeOfValue : EventValuesBase + { + public BacnetBitString ChangedBits { get; set; } + public float ChangeValue { get; set; } + public BacnetCOVTypes Tag { get; set; } + public BacnetBitString StatusFlags { get; set; } + } +} \ No newline at end of file diff --git a/Base/EventNotification/EventValues/EventValuesBase.cs b/Base/EventNotification/EventValues/EventValuesBase.cs new file mode 100644 index 00000000..394bd537 --- /dev/null +++ b/Base/EventNotification/EventValues/EventValuesBase.cs @@ -0,0 +1,6 @@ +namespace System.IO.BACnet.EventNotification.EventValues +{ + public abstract class EventValuesBase + { + } +} diff --git a/Base/EventNotification/EventValues/FloatingLimit.cs b/Base/EventNotification/EventValues/FloatingLimit.cs new file mode 100644 index 00000000..98f5448c --- /dev/null +++ b/Base/EventNotification/EventValues/FloatingLimit.cs @@ -0,0 +1,10 @@ +namespace System.IO.BACnet.EventNotification.EventValues +{ + public class FloatingLimit : EventValuesBase + { + public float ReferenceValue { get; set; } + public BacnetBitString StatusFlags { get; set; } + public float SetPointValue { get; set; } + public float ErrorLimit { get; set; } + } +} \ No newline at end of file diff --git a/Base/EventNotification/EventValues/OutOfRange.cs b/Base/EventNotification/EventValues/OutOfRange.cs new file mode 100644 index 00000000..ef022961 --- /dev/null +++ b/Base/EventNotification/EventValues/OutOfRange.cs @@ -0,0 +1,10 @@ +namespace System.IO.BACnet.EventNotification.EventValues +{ + public class OutOfRange : EventValuesBase + { + public float ExceedingValue { get; set; } + public BacnetBitString StatusFlags { get; set; } + public float Deadband { get; set; } + public float ExceededLimit { get; set; } + } +} \ No newline at end of file diff --git a/Base/EventNotification/EventValues/UnsignedRange.cs b/Base/EventNotification/EventValues/UnsignedRange.cs new file mode 100644 index 00000000..b488eac5 --- /dev/null +++ b/Base/EventNotification/EventValues/UnsignedRange.cs @@ -0,0 +1,9 @@ +namespace System.IO.BACnet.EventNotification.EventValues +{ + public class UnsignedRange : EventValuesBase + { + public uint ExceedingValue { get; set; } + public BacnetBitString StatusFlags { get; set; } + public uint ExceededLimit { get; set; } + } +} \ No newline at end of file diff --git a/Base/EventNotification/NotificationData.cs b/Base/EventNotification/NotificationData.cs new file mode 100644 index 00000000..2211970c --- /dev/null +++ b/Base/EventNotification/NotificationData.cs @@ -0,0 +1,23 @@ +using System.IO.BACnet.Helpers; + +namespace System.IO.BACnet.EventNotification +{ + public class NotificationData + { + public virtual uint ProcessIdentifier { get; set; } + // public BacnetObjectId initiatingDeviceIdentifier; // TODO? + public virtual BacnetObjectId InitiatingObjectIdentifier { get; set; } + public virtual BacnetObjectId EventObjectIdentifier { get; set; } + public virtual BacnetGenericTime TimeStamp { get; set; } + public virtual uint NotificationClass { get; set; } + public virtual byte Priority { get; set; } + public virtual string MessageText { get; set; } /* OPTIONAL - Set to NULL if not being used */ + public virtual BacnetNotifyTypes NotifyType { get; set; } + public virtual BacnetEventStates ToState { get; set; } + + public override string ToString() + { + return string.Join(", ", this.PropertiesWithValues()); + } + }; +} \ No newline at end of file diff --git a/Base/EventNotification/StateTransition.cs b/Base/EventNotification/StateTransition.cs new file mode 100644 index 00000000..1eafa0eb --- /dev/null +++ b/Base/EventNotification/StateTransition.cs @@ -0,0 +1,30 @@ +using System.IO.BACnet.EventNotification.EventValues; +using System.IO.BACnet.Helpers; +using System.Linq; + +namespace System.IO.BACnet.EventNotification +{ + public class StateTransition : NotificationData + { + public BacnetEventTypes EventType { get; set; } + public bool AckRequired { get; set; } + public BacnetEventStates FromState { get; set; } + } + + public class StateTransition : StateTransition + where TEventValuesBase : EventValuesBase + { + public TEventValuesBase EventValues { get; } + + public StateTransition(TEventValuesBase eventValues) + { + EventValues = eventValues ?? throw new ArgumentNullException(nameof(eventValues)); + } + + public override string ToString() + { + return string.Join(", ", this.PropertiesWithValues(except: nameof(EventValues)) + .Concat(EventValues.PropertiesWithValues())); + } + } +} \ No newline at end of file diff --git a/Helpers/StringFormatterExtension.cs b/Helpers/StringFormatterExtension.cs new file mode 100644 index 00000000..4e93d232 --- /dev/null +++ b/Helpers/StringFormatterExtension.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Linq; + +namespace System.IO.BACnet.Helpers +{ + public static class StringFormatterExtension + { + public static IEnumerable PropertiesWithValues(this TType obj, params string[] except) + where TType : class + { + if (obj == null) + return new string[0]; + + return obj.GetType().GetProperties() + .Where(p => !except.Contains(p.Name)) + .Select(p => + { + var propertyName = char.ToLower(p.Name[0]) + p.Name.Substring(1); + return $"{propertyName}: {p.GetValue(obj, null)}"; + }); + } + } +} \ No newline at end of file diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index 08d2160e..d67e94e4 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -278,11 +278,11 @@ private static byte bitstring_bytesUsed(BacnetBitString bitString) { byte len = 0; /* return value */ - if (bitString.bits_used <= 0) + if (bitString.BitsUsed <= 0) return len; - var lastBit = (byte)(bitString.bits_used - 1); - var usedBytes = (byte)(lastBit/8); + var lastBit = (byte)(bitString.BitsUsed - 1); + var usedBytes = (byte)(lastBit / 8); /* add one for the first byte */ usedBytes++; len = usedBytes; @@ -330,34 +330,21 @@ private static byte byte_reverse_bits(byte inByte) return outByte; } - private static byte bitstring_octet(BacnetBitString bitString, byte octetIndex) - { - byte octet = 0; - - if (bitString.value == null) - return octet; - - if (octetIndex < MAX_BITSTRING_BYTES) - octet = bitString.value[octetIndex]; - - return octet; - } - public static void encode_bitstring(EncodeBuffer buffer, BacnetBitString bitString) { /* if the bit string is empty, then the first octet shall be zero */ - if (bitString.bits_used == 0) + if (bitString.BitsUsed == 0) { buffer.Add(0); } else { var usedBytes = bitstring_bytesUsed(bitString); - var remainingUsedBits = (byte)(bitString.bits_used - (usedBytes - 1)*8); + var remainingUsedBits = (byte)(bitString.BitsUsed - (usedBytes - 1) * 8); /* number of unused bits in the subsequent final octet */ buffer.Add((byte)(8 - remainingUsedBits)); for (byte i = 0; i < usedBytes; i++) - buffer.Add(byte_reverse_bits(bitstring_octet(bitString, i))); + buffer.Add(byte_reverse_bits(bitString.Value[i])); } } @@ -371,6 +358,19 @@ public static void encode_application_bitstring(EncodeBuffer buffer, BacnetBitSt encode_bitstring(buffer, bitString); } + public static int DecodeApplicationBitstring(byte[] buffer, int offset, byte tagNumber, out BacnetBitString value) + { + var len = decode_tag_number_and_value(buffer, offset, out var decodedTagNumber, out var lenValue); + + if(decodedTagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING) + { + value = new BacnetBitString(); + return -1; // BACNET_STATUS_ERROR + } + + return len + decode_bitstring(buffer, offset + len, lenValue, out value); + } + public static void EncodeApplicationDestination(EncodeBuffer buffer, BacnetObjectTypes objectType, uint instance) { var tempBuffer = new EncodeBuffer(); @@ -398,67 +398,83 @@ public static void bacapp_encode_application_data(EncodeBuffer buffer, BacnetVal case BacnetApplicationTags.BACNET_APPLICATION_TAG_NULL: /* don't encode anything */ break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_BOOLEAN: encode_application_boolean(buffer, (bool)value.Value); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT: encode_application_unsigned(buffer, (uint)value.Value); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT: encode_application_signed(buffer, (int)value.Value); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_REAL: encode_application_real(buffer, (float)value.Value); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_DOUBLE: encode_application_double(buffer, (double)value.Value); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING: encode_application_octet_string(buffer, (byte[])value.Value, 0, ((byte[])value.Value).Length); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_CHARACTER_STRING: encode_application_character_string(buffer, (string)value.Value); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING: encode_application_bitstring(buffer, (BacnetBitString)value.Value); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED: encode_application_enumerated(buffer, (uint)(dynamic)value.Value); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_DATE: encode_application_date(buffer, (DateTime)value.Value); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_TIME: encode_application_time(buffer, (DateTime)value.Value); break; - // Added for EventTimeStamp + // Added for EventTimeStamp case BacnetApplicationTags.BACNET_APPLICATION_TAG_TIMESTAMP: bacapp_encode_timestamp(buffer, (BacnetGenericTime)value.Value); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_DATETIME: bacapp_encode_datetime(buffer, (DateTime)value.Value); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID: - encode_application_object_id(buffer, ((BacnetObjectId)value.Value).type, - ((BacnetObjectId)value.Value).instance); + encode_application_object_id(buffer, ((BacnetObjectId)value.Value).Type, + ((BacnetObjectId)value.Value).Instance); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_COV_SUBSCRIPTION: encode_cov_subscription(buffer, (BacnetCOVSubscription)value.Value); - //is this the right way to do it, I wonder? + //is this the right way to do it, I wonder? break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_READ_ACCESS_RESULT: encode_read_access_result(buffer, (BacnetReadAccessResult)value.Value); - //is this the right way to do it, I wonder? + //is this the right way to do it, I wonder? break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_READ_ACCESS_SPECIFICATION: encode_read_access_specification(buffer, (BacnetReadAccessSpecification)value.Value); - //is this the right way to do it, I wonder? + //is this the right way to do it, I wonder? break; case BacnetApplicationTags.BACNET_APPLICATION_TAG_DESTINATION: switch (value.Value) { case BacnetObjectId oid: - EncodeApplicationDestination(buffer, oid.type, oid.instance); + EncodeApplicationDestination(buffer, oid.Type, oid.Instance); break; case BacnetAddress adr: EncodeApplicationDestination(buffer, adr); @@ -502,7 +518,7 @@ public static void bacapp_encode_application_data(EncodeBuffer buffer, BacnetVal public static void bacapp_encode_device_obj_property_ref(EncodeBuffer buffer, BacnetDeviceObjectPropertyReference value) { - encode_context_object_id(buffer, 0, value.objectIdentifier.type, value.objectIdentifier.instance); + encode_context_object_id(buffer, 0, value.objectIdentifier.Type, value.objectIdentifier.Instance); encode_context_enumerated(buffer, 1, (uint)value.propertyIdentifier); /* Array index is optional so check if needed before inserting */ @@ -511,8 +527,8 @@ public static void bacapp_encode_device_obj_property_ref(EncodeBuffer buffer, Ba /* Likewise, device id is optional so see if needed * (set type to non device to omit */ - if (value.deviceIndentifier.type == BacnetObjectTypes.OBJECT_DEVICE) - encode_context_object_id(buffer, 3, value.deviceIndentifier.type, value.deviceIndentifier.instance); + if (value.deviceIndentifier.Type == BacnetObjectTypes.OBJECT_DEVICE) + encode_context_object_id(buffer, 3, value.deviceIndentifier.Type, value.deviceIndentifier.Instance); } public static void bacapp_encode_context_device_obj_property_ref(EncodeBuffer buffer, byte tagNumber, BacnetDeviceObjectPropertyReference value) @@ -522,52 +538,66 @@ public static void bacapp_encode_context_device_obj_property_ref(EncodeBuffer bu encode_closing_tag(buffer, tagNumber); } - public static void bacapp_encode_property_state(EncodeBuffer buffer, BacnetPropetyState value) + public static void bacapp_encode_property_state(EncodeBuffer buffer, BacnetPropertyState value) { switch (value.tag) { - case BacnetPropetyState.BacnetPropertyStateTypes.BOOLEAN_VALUE: - encode_context_boolean(buffer, 0, value.state == 1); + case BacnetPropertyState.BacnetPropertyStateTypes.BOOLEAN_VALUE: + encode_context_boolean(buffer, 0, value.state.boolean_value); break; - case BacnetPropetyState.BacnetPropertyStateTypes.BINARY_VALUE: - encode_context_enumerated(buffer, 1, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.BINARY_VALUE: + encode_context_enumerated(buffer, 1, (uint)value.state.binaryValue); break; - case BacnetPropetyState.BacnetPropertyStateTypes.EVENT_TYPE: - encode_context_enumerated(buffer, 2, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.EVENT_TYPE: + encode_context_enumerated(buffer, 2, (uint)value.state.eventType); break; - case BacnetPropetyState.BacnetPropertyStateTypes.POLARITY: - encode_context_enumerated(buffer, 3, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.POLARITY: + encode_context_enumerated(buffer, 3, (uint)value.state.polarity); break; - case BacnetPropetyState.BacnetPropertyStateTypes.PROGRAM_CHANGE: - encode_context_enumerated(buffer, 4, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.PROGRAM_CHANGE: + encode_context_enumerated(buffer, 4, (uint)value.state.programChange); break; - case BacnetPropetyState.BacnetPropertyStateTypes.PROGRAM_STATE: - encode_context_enumerated(buffer, 5, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.PROGRAM_STATE: + encode_context_enumerated(buffer, 5, (uint)value.state.programState); break; - case BacnetPropetyState.BacnetPropertyStateTypes.REASON_FOR_HALT: - encode_context_enumerated(buffer, 6, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.REASON_FOR_HALT: + encode_context_enumerated(buffer, 6, (uint)value.state.programError); break; - case BacnetPropetyState.BacnetPropertyStateTypes.RELIABILITY: - encode_context_enumerated(buffer, 7, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.RELIABILITY: + encode_context_enumerated(buffer, 7, (uint)value.state.reliability); break; - case BacnetPropetyState.BacnetPropertyStateTypes.STATE: - encode_context_enumerated(buffer, 8, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.STATE: + encode_context_enumerated(buffer, 8, (uint)value.state.state); break; - case BacnetPropetyState.BacnetPropertyStateTypes.SYSTEM_STATUS: - encode_context_enumerated(buffer, 9, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.SYSTEM_STATUS: + encode_context_enumerated(buffer, 9, (uint)value.state.systemStatus); break; - case BacnetPropetyState.BacnetPropertyStateTypes.UNITS: - encode_context_enumerated(buffer, 10, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.UNITS: + encode_context_enumerated(buffer, 10, (uint)value.state.units); break; - case BacnetPropetyState.BacnetPropertyStateTypes.UNSIGNED_VALUE: - encode_context_unsigned(buffer, 11, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.UNSIGNED_VALUE: + encode_context_unsigned(buffer, 11, value.state.unsignedValue); break; - case BacnetPropetyState.BacnetPropertyStateTypes.LIFE_SAFETY_MODE: - encode_context_enumerated(buffer, 12, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.LIFE_SAFETY_MODE: + encode_context_enumerated(buffer, 12, (uint)value.state.lifeSafetyMode); break; - case BacnetPropetyState.BacnetPropertyStateTypes.LIFE_SAFETY_STATE: - encode_context_enumerated(buffer, 13, value.state); + + case BacnetPropertyState.BacnetPropertyStateTypes.LIFE_SAFETY_STATE: + encode_context_enumerated(buffer, 13, (uint)value.state.lifeSafetyState); break; + default: /* FIXME: assert(0); - return a negative len? */ break; @@ -661,7 +691,7 @@ public static void encode_bacnet_time(EncodeBuffer buffer, DateTime value) buffer.Add((byte)value.Hour); buffer.Add((byte)value.Minute); buffer.Add((byte)value.Second); - buffer.Add((byte)(value.Millisecond/10)); + buffer.Add((byte)(value.Millisecond / 10)); } public static void encode_context_time(EncodeBuffer buffer, byte tagNumber, DateTime value) @@ -734,14 +764,18 @@ public static void bacapp_encode_timestamp(EncodeBuffer buffer, BacnetGenericTim case BacnetTimestampTags.TIME_STAMP_TIME: encode_context_time(buffer, 0, value.Time); break; + case BacnetTimestampTags.TIME_STAMP_SEQUENCE: encode_context_unsigned(buffer, 1, value.Sequence); break; + case BacnetTimestampTags.TIME_STAMP_DATETIME: bacapp_encode_context_datetime(buffer, 2, value.Time); break; + case BacnetTimestampTags.TIME_STAMP_NONE: break; + default: throw new NotImplementedException(); } @@ -770,7 +804,7 @@ public static void encode_application_character_string(EncodeBuffer buffer, stri public static void encode_bacnet_character_string(EncodeBuffer buffer, string value) { buffer.Add((byte)BacnetCharacterStringEncodings.CHARACTER_UTF8); - var bufUtf8 = Encoding.UTF8.GetBytes(value); // Encoding.ASCII depreciated : Addendum 135-2008k + var bufUtf8 = Encoding.UTF8.GetBytes(value); // Encoding.ASCII depreciated : Addendum 135-2008k buffer.Add(bufUtf8, bufUtf8.Length); } @@ -819,7 +853,7 @@ public static void encode_signed32(EncodeBuffer buffer, int value) public static void encode_read_access_specification(EncodeBuffer buffer, BacnetReadAccessSpecification value) { /* Tag 0: BACnetObjectIdentifier */ - encode_context_object_id(buffer, 0, value.objectIdentifier.type, value.objectIdentifier.instance); + encode_context_object_id(buffer, 0, value.objectIdentifier.Type, value.objectIdentifier.Instance); /* Tag 1: sequence of BACnetPropertyReference */ encode_opening_tag(buffer, 1); @@ -837,7 +871,7 @@ public static void encode_read_access_specification(EncodeBuffer buffer, BacnetR public static void encode_read_access_result(EncodeBuffer buffer, BacnetReadAccessResult value) { /* Tag 0: BACnetObjectIdentifier */ - encode_context_object_id(buffer, 0, value.objectIdentifier.type, value.objectIdentifier.instance); + encode_context_object_id(buffer, 0, value.objectIdentifier.Type, value.objectIdentifier.Instance); /* Tag 1: listOfResults */ encode_opening_tag(buffer, 1); @@ -880,8 +914,8 @@ public static int decode_read_access_result(BacnetAddress address, byte[] buffer if (!decode_is_context_tag(buffer, offset + len, 0)) return -1; len = 1; - len += decode_object_id(buffer, offset + len, out value.objectIdentifier.type, - out value.objectIdentifier.instance); + len += decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + value.objectIdentifier = new BacnetObjectId(type, instance); /* Tag 1: listOfResults */ if (!decode_is_opening_tag_number(buffer, offset + len, 1)) @@ -901,12 +935,10 @@ public static int decode_read_access_result(BacnetAddress address, byte[] buffer } /* Tag 2: propertyIdentifier */ - byte tagNumber; - uint lenValueType; - len += decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 2) return -1; - len += decode_enumerated(buffer, offset + len, lenValueType, out new_entry.property.propertyIdentifier); + len += decode_unsigned(buffer, offset + len, lenValueType, out new_entry.property.propertyIdentifier); /* Tag 3: Optional Array Index */ var tagLen = decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); if (tagNumber == 3) @@ -927,7 +959,7 @@ public static int decode_read_access_result(BacnetAddress address, byte[] buffer while (!decode_is_closing_tag_number(buffer, offset + len, 4)) { tagLen = bacapp_decode_application_data(address, buffer, offset + len, apdu_len + offset - 1, - value.objectIdentifier.type, (BacnetPropertyIds)new_entry.property.propertyIdentifier, out var v); + value.objectIdentifier.Type, (BacnetPropertyIds)new_entry.property.propertyIdentifier, out var v); if (tagLen < 0) return -1; len += tagLen; localValueList.Add(v); @@ -951,16 +983,14 @@ public static int decode_read_access_result(BacnetAddress address, byte[] buffer /* Tag 5: Error */ var err = new BacnetError(); len += decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += decode_enumerated(buffer, offset + len, lenValueType, out lenValueType); //error_class - err.error_class = (BacnetErrorClasses)lenValueType; + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out err.error_class); len += decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += decode_enumerated(buffer, offset + len, lenValueType, out lenValueType); //error_code - err.error_code = (BacnetErrorCodes)lenValueType; + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out err.error_code); if (!decode_is_closing_tag_number(buffer, offset + len, 5)) return -1; len++; - new_entry.value = new[] {new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_ERROR, err)}; + new_entry.value = new[] { new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_ERROR, err) }; } valueList.Add(new_entry); @@ -970,7 +1000,7 @@ public static int decode_read_access_result(BacnetAddress address, byte[] buffer return len; } - public static int decode_read_access_specification(byte[] buffer, int offset, int apdu_len, out BacnetReadAccessSpecification value) + public static int decode_read_access_specification(byte[] buffer, int offset, int apduLen, out BacnetReadAccessSpecification value) { var len = 0; @@ -980,8 +1010,8 @@ public static int decode_read_access_specification(byte[] buffer, int offset, in if (!decode_is_context_tag(buffer, offset + len, 0)) return -1; len++; - len += decode_object_id(buffer, offset + len, out value.objectIdentifier.type, - out value.objectIdentifier.instance); + len += decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + value.objectIdentifier = new BacnetObjectId(type, instance); /* Tag 1: sequence of ReadAccessSpecification */ if (!decode_is_opening_tag_number(buffer, offset + len, 1)) @@ -990,38 +1020,38 @@ public static int decode_read_access_specification(byte[] buffer, int offset, in /* properties */ var propertyIdAndArrayIndex = new List(); - while (apdu_len - len > 1 && !decode_is_closing_tag_number(buffer, offset + len, 1)) + while (apduLen - len > 1 && !decode_is_closing_tag_number(buffer, offset + len, 1)) { - var p_ref = new BacnetPropertyReference(); + var pRef = new BacnetPropertyReference(); /* Tag 0: propertyIdentifier */ if (!IS_CONTEXT_SPECIFIC(buffer[offset + len])) return -1; - len += decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue_type); + len += decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 0) return -1; /* Should be at least the unsigned value + 1 tag left */ - if (len + lenValue_type >= apdu_len) + if (len + lenValueType >= apduLen) return -1; - len += decode_enumerated(buffer, offset + len, lenValue_type, out p_ref.propertyIdentifier); + len += decode_unsigned(buffer, offset + len, lenValueType, out pRef.propertyIdentifier); /* Assume most probable outcome */ - p_ref.propertyArrayIndex = BACNET_ARRAY_ALL; + pRef.propertyArrayIndex = BACNET_ARRAY_ALL; /* Tag 1: Optional propertyArrayIndex */ if (IS_CONTEXT_SPECIFIC(buffer[offset + len]) && !IS_CLOSING_TAG(buffer[offset + len])) { - var tmp = decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue_type); + var tmp = decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); if (tagNumber == 1) { len += tmp; /* Should be at least the unsigned array index + 1 tag left */ - if (len + lenValue_type >= apdu_len) + if (len + lenValueType >= apduLen) return -1; - len += decode_unsigned(buffer, offset + len, lenValue_type, out p_ref.propertyArrayIndex); + len += decode_unsigned(buffer, offset + len, lenValueType, out pRef.propertyArrayIndex); } } - propertyIdAndArrayIndex.Add(p_ref); + propertyIdAndArrayIndex.Add(pRef); } /* closing tag */ @@ -1044,16 +1074,15 @@ public static int decode_device_obj_property_ref(byte[] buffer, int offset, int return -1; len++; - len += decode_object_id(buffer, offset + len, out value.objectIdentifier.type, - out value.objectIdentifier.instance); + len += decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + value.objectIdentifier = new BacnetObjectId(type, instance); /* Tag 1 : Property identifier */ len += decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 1) return -1; - len += decode_enumerated(buffer, offset + len, lenValueType, out var propertyIdentifier); - value.propertyIdentifier = (BacnetPropertyIds)propertyIdentifier; + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out value.propertyIdentifier); /* Tag 2: Optional Array Index */ var tagLen = decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); @@ -1070,8 +1099,8 @@ public static int decode_device_obj_property_ref(byte[] buffer, int offset, int len++; - len += decode_object_id(buffer, offset + len, out value.deviceIndentifier.type, - out value.deviceIndentifier.instance); + len += decode_object_id(buffer, offset + len, out BacnetObjectTypes deviceType, out var devideInstance); + value.deviceIndentifier = new BacnetObjectId(deviceType, devideInstance); return len; } @@ -1083,16 +1112,20 @@ public static int decode_unsigned(byte[] buffer, int offset, uint lenValue, out case 1: value = buffer[offset]; break; + case 2: decode_unsigned16(buffer, offset, out var unsigned16Value); value = unsigned16Value; break; + case 3: decode_unsigned24(buffer, offset, out value); break; + case 4: decode_unsigned32(buffer, offset, out value); break; + default: value = 0; break; @@ -1213,16 +1246,20 @@ public static int decode_signed(byte[] buffer, int offset, uint lenValue, out in decode_signed8(buffer, offset, out var sbyteValue); value = sbyteValue; break; + case 2: decode_signed16(buffer, offset, out var shortValue); value = shortValue; break; + case 3: decode_signed24(buffer, offset, out value); break; + case 4: decode_signed32(buffer, offset, out value); break; + default: value = 0; break; @@ -1233,7 +1270,7 @@ public static int decode_signed(byte[] buffer, int offset, uint lenValue, out in public static int decode_real(byte[] buffer, int offset, out float value) { - byte[] tmp = {buffer[offset + 3], buffer[offset + 2], buffer[offset + 1], buffer[offset + 0]}; + byte[] tmp = { buffer[offset + 3], buffer[offset + 2], buffer[offset + 1], buffer[offset + 0] }; value = BitConverter.ToSingle(tmp, 0); return 4; } @@ -1326,7 +1363,7 @@ private static bool multi_charset_characterstring_decode(byte[] buffer, int offs // http://hackipedia.org/Character%20sets/Unicode,%20UTF%20and%20UCS%20encodings/UCS-2.htm // https://en.wikipedia.org/wiki/Byte_order_mark case BacnetCharacterStringEncodings.CHARACTER_UCS2: - if (buffer[offset] == 0xFF && buffer[offset + 1] == 0xFE) // Byte Order Mark + if (buffer[offset] == 0xFF && buffer[offset + 1] == 0xFE) // Byte Order Mark e = Encoding.Unicode; // little endian encoding else e = Encoding.BigEndianUnicode; // big endian encoding if BOM is not set, or 0xFE-0xFF @@ -1339,7 +1376,7 @@ private static bool multi_charset_characterstring_decode(byte[] buffer, int offs e = Encoding.UTF32; // UTF32 little endian encoding else e = Encoding.GetEncoding(12001); - // UTF32 big endian encoding if BOM is not set, or 0-0-0xFE-0xFF + // UTF32 big endian encoding if BOM is not set, or 0-0-0xFE-0xFF break; case BacnetCharacterStringEncodings.CHARACTER_ISO8859: @@ -1358,7 +1395,7 @@ private static bool multi_charset_characterstring_decode(byte[] buffer, int offs e = Encoding.GetEncoding("shift_jis"); // maybe "iso-2022-jp" ? break; - // unknown code (wrong code, experimental, ...) + // unknown code (wrong code, experimental, ...) // decoded as ISO-8859-1 (removing controls) : displays certainly a strange content ! default: var sb = new StringBuilder(); @@ -1394,24 +1431,13 @@ public static int decode_character_string(byte[] buffer, int offset, int maxLeng return len; } - private static void bitstring_set_octet(ref BacnetBitString bitString, byte index, byte octet) - { - if (index < MAX_BITSTRING_BYTES) - bitString.value[index] = octet; - } - - private static void bitstring_set_bits_used(ref BacnetBitString bitString, byte bytesUsed, byte unusedBits) - { - /* FIXME: check that bytesUsed is at least one? */ - bitString.bits_used = (byte)(bytesUsed*8); - bitString.bits_used -= unusedBits; - } - public static int decode_bitstring(byte[] buffer, int offset, uint lenValue, out BacnetBitString bitString) { var len = 0; - bitString = new BacnetBitString {value = new byte[MAX_BITSTRING_BYTES]}; + var value = new byte[MAX_BITSTRING_BYTES]; + byte bitsUsed = 0; + if (lenValue > 0) { /* the first octet contains the unused bits */ @@ -1421,13 +1447,15 @@ public static int decode_bitstring(byte[] buffer, int offset, uint lenValue, out len = 1; for (uint i = 0; i < bytesUsed; i++) { - bitstring_set_octet(ref bitString, (byte)i, byte_reverse_bits(buffer[offset + len++])); + value[i] = byte_reverse_bits(buffer[offset + len++]); } var unusedBits = (byte)(buffer[offset] & 0x07); - bitstring_set_bits_used(ref bitString, (byte)bytesUsed, unusedBits); + bitsUsed = (byte)((bytesUsed * 8) - unusedBits); // FIXME: check that bitsUsed is at least one? } } + bitString = new BacnetBitString(value, bitsUsed); + return len; } @@ -1488,7 +1516,7 @@ public static int decode_bacnet_time(byte[] buffer, int offset, out DateTime bti else { if (hundredths > 100) hundredths = 0; // sometimes set to 255 - btime = new DateTime(1, 1, 1, hour, min, sec, hundredths*10); + btime = new DateTime(1, 1, 1, hour, min, sec, hundredths * 10); } return 4; } @@ -1626,57 +1654,69 @@ public static int bacapp_decode_data(byte[] buffer, int offset, int maxLength, B var len = 0; uint uintValue; - value = new BacnetValue {Tag = tagDataType}; + value = new BacnetValue { Tag = tagDataType }; switch (tagDataType) { case BacnetApplicationTags.BACNET_APPLICATION_TAG_NULL: /* nothing else to do */ break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_BOOLEAN: value.Value = lenValueType > 0; break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT: len = decode_unsigned(buffer, offset, lenValueType, out uintValue); value.Value = uintValue; break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT: len = decode_signed(buffer, offset, lenValueType, out var intValue); value.Value = intValue; break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_REAL: len = decode_real_safe(buffer, offset, lenValueType, out var floatValue); value.Value = floatValue; break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_DOUBLE: len = decode_double_safe(buffer, offset, lenValueType, out var doubleValue); value.Value = doubleValue; break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING: var octetString = new byte[lenValueType]; len = decode_octet_string(buffer, offset, maxLength, octetString, 0, lenValueType); value.Value = octetString; break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_CHARACTER_STRING: len = decode_character_string(buffer, offset, maxLength, lenValueType, out var stringValue); value.Value = stringValue; break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING: len = decode_bitstring(buffer, offset, lenValueType, out var bitValue); value.Value = bitValue; break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED: - len = decode_enumerated(buffer, offset, lenValueType, out uintValue); - value.Value = uintValue; + len = decode_unsigned(buffer, offset, lenValueType, out var enumeratedValue); + value.Value = enumeratedValue; break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_DATE: len = decode_date_safe(buffer, offset, lenValueType, out var dateValue); value.Value = dateValue; break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_TIME: len = decode_bacnet_time_safe(buffer, offset, lenValueType, out var timeValue); value.Value = timeValue; break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID: len = decode_object_id_safe(buffer, offset, lenValueType, out var objectType, out var instance); value.Value = new BacnetObjectId((BacnetObjectTypes)objectType, instance); @@ -1703,11 +1743,13 @@ private static BacnetApplicationTags bacapp_context_tag_type(BacnetAddress addre case 1: tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT; break; + case 2: tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_REAL; break; } break; + case BacnetPropertyIds.PROP_ACTION: switch (tagNumber) { @@ -1715,22 +1757,27 @@ private static BacnetApplicationTags bacapp_context_tag_type(BacnetAddress addre case 1: tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID; break; + case 2: tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED; break; + case 3: case 5: case 6: tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT; break; + case 7: case 8: tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_BOOLEAN; break; + case 4: /* propertyValue: abstract syntax */ break; } break; + case BacnetPropertyIds.PROP_LIST_OF_GROUP_MEMBERS: /* Sequence of ReadAccessSpecification */ switch (tagNumber) @@ -1740,20 +1787,24 @@ private static BacnetApplicationTags bacapp_context_tag_type(BacnetAddress addre break; } break; + case BacnetPropertyIds.PROP_EXCEPTION_SCHEDULE: switch (tagNumber) { case 1: tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID; break; + case 3: tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT; break; + case 0: /* calendarEntry: abstract syntax + context */ case 2: /* list of BACnetTimeValue: abstract syntax */ break; } break; + case BacnetPropertyIds.PROP_LOG_DEVICE_OBJECT_PROPERTY: switch (tagNumber) { @@ -1761,14 +1812,17 @@ private static BacnetApplicationTags bacapp_context_tag_type(BacnetAddress addre case 3: /* Device ID */ tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID; break; + case 1: /* Property ID */ tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED; break; + case 2: /* Array index */ tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT; break; } break; + case BacnetPropertyIds.PROP_SUBORDINATE_LIST: case BacnetPropertyIds.PROP_ZONE_MEMBERS: /* BACnetARRAY[N] of BACnetDeviceObjectReference */ @@ -1790,6 +1844,7 @@ private static BacnetApplicationTags bacapp_context_tag_type(BacnetAddress addre break; } break; + case BacnetPropertyIds.PROP_ACTIVE_COV_SUBSCRIPTIONS: /* BACnetCOVSubscription */ switch (tagNumber) @@ -1797,12 +1852,15 @@ private static BacnetApplicationTags bacapp_context_tag_type(BacnetAddress addre case 0: /* BACnetRecipientProcess */ case 1: /* BACnetObjectPropertyReference */ break; + case 2: /* issueConfirmedNotifications */ tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_BOOLEAN; break; + case 3: /* timeRemaining */ tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT; break; + case 4: /* covIncrement */ tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_REAL; break; @@ -1863,12 +1921,11 @@ public static int bacapp_decode_application_data(BacnetAddress address, byte[] b /* FIXME: use max_apdu_len! */ if (!IS_CONTEXT_SPECIFIC(buffer[offset])) { - var tagLen = decode_tag_number_and_value(buffer, offset, out var tagNumber, out var lenValueType); + var tagLen = decode_tag_number_and_value(buffer, offset, out BacnetApplicationTags tagNumber, out uint lenValueType); if (tagLen > 0) { len += tagLen; - var decodeLen = bacapp_decode_data(buffer, offset + len, maxOffset, (BacnetApplicationTags)tagNumber, - lenValueType, out value); + var decodeLen = bacapp_decode_data(buffer, offset + len, maxOffset, tagNumber, lenValueType, out value); if (decodeLen < 0) return decodeLen; len += decodeLen; } @@ -2070,12 +2127,6 @@ public static int decode_object_id(byte[] buffer, int offset, out BacnetObjectTy return len; } - public static int decode_enumerated(byte[] buffer, int offset, uint lenValue, out uint value) - { - var len = decode_unsigned(buffer, offset, lenValue, out value); - return len; - } - public static bool decode_is_context_tag(byte[] buffer, int offset, byte tagNumber) { decode_tag_number(buffer, offset, out var myTagNumber); @@ -2148,6 +2199,14 @@ public static int decode_tag_number_and_value(byte[] buffer, int offset, out byt return len; } + public static int decode_tag_number_and_value(byte[] buffer, int offset, out TTag tag, out TValue value) + { + var len = decode_tag_number_and_value(buffer, offset, out var rawByte, out var rawValue); + tag = (TTag)(dynamic)rawByte; + value = (TValue)(dynamic)rawValue; + return len; + } + /// /// This is used by the Active_COV_Subscriptions property in DEVICE /// @@ -2183,8 +2242,8 @@ public static void encode_cov_subscription(EncodeBuffer buffer, BacnetCOVSubscri /* MonitoredPropertyReference [1] BACnetObjectPropertyReference, */ encode_opening_tag(buffer, 1); /* objectIdentifier [0] */ - encode_context_object_id(buffer, 0, value.monitoredObjectIdentifier.type, - value.monitoredObjectIdentifier.instance); + encode_context_object_id(buffer, 0, value.monitoredObjectIdentifier.Type, + value.monitoredObjectIdentifier.Instance); /* propertyIdentifier [1] */ /* FIXME: we are monitoring 2 properties! How to encode? */ encode_context_enumerated(buffer, 1, value.monitoredProperty.propertyIdentifier); @@ -2206,7 +2265,7 @@ public static int decode_cov_subscription(byte[] buffer, int offset, int apduLen { var len = 0; - value = new BacnetCOVSubscription {Recipient = new BacnetAddress(BacnetAddressTypes.None, 0, null)}; + value = new BacnetCOVSubscription { Recipient = new BacnetAddress(BacnetAddressTypes.None, 0, null) }; if (!decode_is_opening_tag_number(buffer, offset + len, 0)) return -1; @@ -2248,13 +2307,13 @@ public static int decode_cov_subscription(byte[] buffer, int offset, int apduLen len += decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); if (tagNumber != 0) return -1; - len += decode_object_id(buffer, offset + len, out value.monitoredObjectIdentifier.type, - out value.monitoredObjectIdentifier.instance); + len += decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + value.monitoredObjectIdentifier = new BacnetObjectId(type, instance); + len += decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); if (tagNumber != 1) return -1; - len += decode_enumerated(buffer, offset + len, lenValueType, - out value.monitoredProperty.propertyIdentifier); + len += decode_unsigned(buffer, offset + len, lenValueType, out value.monitoredProperty.propertyIdentifier); var tagLen = decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); if (tagNumber == 2) { @@ -2290,5 +2349,107 @@ public static int decode_cov_subscription(byte[] buffer, int offset, int apduLen return len; } + + public static int decode_context_property_state(byte[] buffer, int offset, byte tagNumber, out BacnetPropertyState value) + { + if (!decode_is_opening_tag_number(buffer, offset, tagNumber)) + { + value = default(BacnetPropertyState); + return -1; + } + + var len = 1; + var sectionLength = decode_property_state(buffer, offset + len, out value); + if (sectionLength == -1) + return -1; + + len += sectionLength; + if (!decode_is_closing_tag_number(buffer, offset + len, tagNumber)) + return -1; + + return len + 1; + } + + public static int decode_property_state(byte[] buffer, int offset, out BacnetPropertyState value) + { + value = default(BacnetPropertyState); + + var len = decode_tag_number_and_value(buffer, offset, out value.tag, out uint lenValueType); + if (len == -1) + return -1; + + var sectionLength = 0; + switch (value.tag) + { + case BacnetPropertyState.BacnetPropertyStateTypes.BOOLEAN_VALUE: + value.state.boolean_value = lenValueType == 1; + break; + + case BacnetPropertyState.BacnetPropertyStateTypes.BINARY_VALUE: + sectionLength = EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out value.state.binaryValue); + if (sectionLength == -1) + return -1; + break; + + case BacnetPropertyState.BacnetPropertyStateTypes.EVENT_TYPE: + sectionLength = EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out value.state.eventType); + if (sectionLength == -1) + return -1; + break; + + case BacnetPropertyState.BacnetPropertyStateTypes.STATE: + sectionLength = EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out value.state.state); + if (sectionLength == -1) + return -1; + break; + + case BacnetPropertyState.BacnetPropertyStateTypes.UNSIGNED_VALUE: + sectionLength = decode_unsigned(buffer, offset + len, lenValueType, out value.state.unsignedValue); + if (sectionLength == -1) + return -1; + break; + + default: + return -1; + } + + return len + sectionLength; + } + + public static int decode_context_bitstring(byte[] buffer, int offset, byte tagNumber, out BacnetBitString value) + { + if (!decode_is_context_tag(buffer, offset, tagNumber) || decode_is_closing_tag(buffer, offset)) + { + value = new BacnetBitString(); + return -1; // BACNET_STATUS_ERROR + } + + var len = decode_tag_number_and_value(buffer, offset, out _, out var lenValue); + return len + decode_bitstring(buffer, offset + len, lenValue, out value); + } + + public static int decode_context_real(byte[] buffer, int offset, byte tagNumber, out float value) + { + if (!decode_is_context_tag(buffer, offset, tagNumber)) + { + value = 0; + return -1; + } + + var len = decode_tag_number_and_value(buffer, offset, out _, out _); + return len + decode_real(buffer, offset + len, out value); + } + + public static int decode_context_unsigned(byte[] buffer, int offset, byte tagNumber, out uint value) + { + if (!decode_is_context_tag(buffer, offset, tagNumber) || decode_is_closing_tag(buffer, offset)) + { + value = 0; + return -1; + } + + var len = decode_tag_number_and_value(buffer, offset, out _, out var lenValue); + return len + decode_unsigned(buffer, offset + len, lenValue, out value); + } } -} \ No newline at end of file +} diff --git a/Serialize/Services.cs b/Serialize/Services.cs index d52636cd..7b54c2ef 100644 --- a/Serialize/Services.cs +++ b/Serialize/Services.cs @@ -1,4 +1,7 @@ using System.Collections.Generic; +using System.IO.BACnet.EventNotification; +using System.IO.BACnet.EventNotification.EventValues; +using System.Linq; namespace System.IO.BACnet.Serialize { @@ -16,7 +19,6 @@ public static int DecodeIamBroadcast(byte[] buffer, int offset, out uint deviceI { var apduLen = 0; var orgOffset = offset; - BacnetObjectId objectId; deviceId = 0; maxApdu = 0; @@ -28,11 +30,12 @@ public static int DecodeIamBroadcast(byte[] buffer, int offset, out uint deviceI apduLen += len; if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID) return -1; - len = ASN1.decode_object_id(buffer, offset + apduLen, out objectId.type, out objectId.instance); + len = ASN1.decode_object_id(buffer, offset + apduLen, out BacnetObjectTypes type, out var instance); apduLen += len; - if (objectId.type != BacnetObjectTypes.OBJECT_DEVICE) + var objectId = new BacnetObjectId(type, instance); + if (objectId.Type != BacnetObjectTypes.OBJECT_DEVICE) return -1; - deviceId = objectId.instance; + deviceId = objectId.Instance; /* MAX APDU - unsigned */ len = ASN1.decode_tag_number_and_value(buffer, offset + apduLen, out tagNumber, out lenValue); @@ -48,7 +51,7 @@ public static int DecodeIamBroadcast(byte[] buffer, int offset, out uint deviceI apduLen += len; if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED) return -1; - len = ASN1.decode_enumerated(buffer, offset + apduLen, lenValue, out decodedValue); + len = ASN1.decode_unsigned(buffer, offset + apduLen, lenValue, out decodedValue); apduLen += len; if (decodedValue > (uint)BacnetSegmentations.SEGMENTATION_NONE) return -1; @@ -70,9 +73,9 @@ public static int DecodeIamBroadcast(byte[] buffer, int offset, out uint deviceI public static void EncodeIhaveBroadcast(EncodeBuffer buffer, BacnetObjectId deviceId, BacnetObjectId objectId, string objectName) { /* deviceIdentifier */ - ASN1.encode_application_object_id(buffer, deviceId.type, deviceId.instance); + ASN1.encode_application_object_id(buffer, deviceId.Type, deviceId.Instance); /* objectIdentifier */ - ASN1.encode_application_object_id(buffer, objectId.type, objectId.instance); + ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); /* objectName */ ASN1.encode_application_character_string(buffer, objectName); } @@ -91,7 +94,7 @@ public static void EncodeWhoHasBroadcast(EncodeBuffer buffer, int lowLimit, int } else { - ASN1.encode_context_object_id(buffer, 2, objectId.type, objectId.instance); + ASN1.encode_context_object_id(buffer, 2, objectId.Type, objectId.Instance); } } @@ -146,7 +149,7 @@ public static int DecodeWhoIsBroadcast(byte[] buffer, int offset, int apduLen, o return len; } - + // Added by thamersalek public static int DecodeWhoHasBroadcast(byte[] buffer, int offset, int apduLen, out int lowLimit, out int highLimit, out BacnetObjectId objId, out string objName) { @@ -191,7 +194,7 @@ public static int DecodeWhoHasBroadcast(byte[] buffer, int offset, int apduLen, public static void EncodeAlarmAcknowledge(EncodeBuffer buffer, uint ackProcessIdentifier, BacnetObjectId eventObjectIdentifier, uint eventStateAcked, string ackSource, BacnetGenericTime eventTimeStamp, BacnetGenericTime ackTimeStamp) { ASN1.encode_context_unsigned(buffer, 0, ackProcessIdentifier); - ASN1.encode_context_object_id(buffer, 1, eventObjectIdentifier.type, eventObjectIdentifier.instance); + ASN1.encode_context_object_id(buffer, 1, eventObjectIdentifier.Type, eventObjectIdentifier.Instance); ASN1.encode_context_enumerated(buffer, 2, eventStateAcked); ASN1.bacapp_encode_context_timestamp(buffer, 3, eventTimeStamp); ASN1.encode_context_character_string(buffer, 4, ackSource); @@ -200,7 +203,7 @@ public static void EncodeAlarmAcknowledge(EncodeBuffer buffer, uint ackProcessId public static void EncodeAtomicReadFile(EncodeBuffer buffer, bool isStream, BacnetObjectId objectId, int position, uint count) { - ASN1.encode_application_object_id(buffer, objectId.type, objectId.instance); + ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); var tagNumber = (byte)(isStream ? 0 : 1); ASN1.encode_opening_tag(buffer, tagNumber); ASN1.encode_application_signed(buffer, position); @@ -210,18 +213,22 @@ public static void EncodeAtomicReadFile(EncodeBuffer buffer, bool isStream, Bacn public static int DecodeAtomicReadFile(byte[] buffer, int offset, int apduLen, out bool isStream, out BacnetObjectId objectId, out int position, out uint count) { + objectId = default(BacnetObjectId); + var len = 0; int tagLen; isStream = true; - objectId = new BacnetObjectId(); position = -1; count = 0; len = ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID) return -1; - len += ASN1.decode_object_id(buffer, offset + len, out objectId.type, out objectId.instance); + + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); + if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 0)) { /* a tag number is not extended so only one octet */ @@ -296,7 +303,7 @@ public static void EncodeAtomicReadFileAcknowledge(EncodeBuffer buffer, bool isS public static void EncodeAtomicWriteFile(EncodeBuffer buffer, bool isStream, BacnetObjectId objectId, int position, uint blockCount, byte[][] blocks, int[] counts) { - ASN1.encode_application_object_id(buffer, objectId.type, objectId.instance); + ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); var tagNumber = (byte)(isStream ? 0 : 1); ASN1.encode_opening_tag(buffer, tagNumber); @@ -321,7 +328,7 @@ public static int DecodeAtomicWriteFile(byte[] buffer, int offset, int apduLen, var len = 0; int tagLen; - objectId = new BacnetObjectId(); + objectId = default(BacnetObjectId); isStream = true; position = -1; blockCount = 0; @@ -331,7 +338,10 @@ public static int DecodeAtomicWriteFile(byte[] buffer, int offset, int apduLen, len = ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID) return -1; - len += ASN1.decode_object_id(buffer, offset + len, out objectId.type, out objectId.instance); + + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); + if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 0)) { /* a tag number of 2 is not extended so only one octet */ @@ -403,7 +413,7 @@ public static void EncodeCreateProperty(EncodeBuffer buffer, BacnetObjectId obje { /* Tag 1: sequence of WriteAccessSpecification */ ASN1.encode_opening_tag(buffer, 0); - ASN1.encode_context_object_id(buffer, 1, objectId.type, objectId.instance); + ASN1.encode_context_object_id(buffer, 1, objectId.Type, objectId.Instance); ASN1.encode_closing_tag(buffer, 0); if (valueList == null) @@ -425,7 +435,6 @@ public static void EncodeCreateProperty(EncodeBuffer buffer, BacnetObjectId obje } ASN1.encode_closing_tag(buffer, 2); - if (pValue.priority != ASN1.BACNET_NO_PRIORITY) ASN1.encode_context_unsigned(buffer, 3, pValue.priority); } @@ -435,7 +444,7 @@ public static void EncodeCreateProperty(EncodeBuffer buffer, BacnetObjectId obje public static void EncodeAddListElement(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, IList valueList) { - ASN1.encode_context_object_id(buffer, 0, objectId.type, objectId.instance); + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); ASN1.encode_context_enumerated(buffer, 1, propertyId); if (arrayIndex != ASN1.BACNET_ARRAY_ALL) @@ -464,7 +473,7 @@ public static void EncodeCOVNotifyConfirmed(EncodeBuffer buffer, uint subscriber /* tag 1 - initiatingDeviceIdentifier */ ASN1.encode_context_object_id(buffer, 1, BacnetObjectTypes.OBJECT_DEVICE, initiatingDeviceIdentifier); /* tag 2 - monitoredObjectIdentifier */ - ASN1.encode_context_object_id(buffer, 2, monitoredObjectIdentifier.type, monitoredObjectIdentifier.instance); + ASN1.encode_context_object_id(buffer, 2, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); /* tag 3 - timeRemaining */ ASN1.encode_context_unsigned(buffer, 3, timeRemaining); /* tag 4 - listOfValues */ @@ -504,7 +513,7 @@ public static void EncodeCOVNotifyUnconfirmed(EncodeBuffer buffer, uint subscrib /* tag 1 - initiatingDeviceIdentifier */ ASN1.encode_context_object_id(buffer, 1, BacnetObjectTypes.OBJECT_DEVICE, initiatingDeviceIdentifier); /* tag 2 - monitoredObjectIdentifier */ - ASN1.encode_context_object_id(buffer, 2, monitoredObjectIdentifier.type, monitoredObjectIdentifier.instance); + ASN1.encode_context_object_id(buffer, 2, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); /* tag 3 - timeRemaining */ ASN1.encode_context_unsigned(buffer, 3, timeRemaining); /* tag 4 - listOfValues */ @@ -542,7 +551,7 @@ public static void EncodeSubscribeCOV(EncodeBuffer buffer, uint subscriberProces /* tag 0 - subscriberProcessIdentifier */ ASN1.encode_context_unsigned(buffer, 0, subscriberProcessIdentifier); /* tag 1 - monitoredObjectIdentifier */ - ASN1.encode_context_object_id(buffer, 1, monitoredObjectIdentifier.type, monitoredObjectIdentifier.instance); + ASN1.encode_context_object_id(buffer, 1, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); /* If both the 'Issue Confirmed Notifications' and 'Lifetime' parameters are absent, then this shall @@ -562,7 +571,7 @@ public static int DecodeSubscribeCOV(byte[] buffer, int offset, int apduLen, out uint lenValue; subscriberProcessIdentifier = 0; - monitoredObjectIdentifier = new BacnetObjectId(); + monitoredObjectIdentifier = default(BacnetObjectId); cancellationRequest = false; issueConfirmedNotifications = false; lifetime = 0; @@ -579,7 +588,8 @@ public static int DecodeSubscribeCOV(byte[] buffer, int offset, int apduLen, out if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out monitoredObjectIdentifier.type, out monitoredObjectIdentifier.instance); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + monitoredObjectIdentifier = new BacnetObjectId(type, instance); } else return -1; @@ -621,7 +631,7 @@ public static void EncodeSubscribeProperty(EncodeBuffer buffer, uint subscriberP /* tag 0 - subscriberProcessIdentifier */ ASN1.encode_context_unsigned(buffer, 0, subscriberProcessIdentifier); /* tag 1 - monitoredObjectIdentifier */ - ASN1.encode_context_object_id(buffer, 1, monitoredObjectIdentifier.type, monitoredObjectIdentifier.instance); + ASN1.encode_context_object_id(buffer, 1, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); if (!cancellationRequest) { /* tag 2 - issueConfirmedNotifications */ @@ -635,7 +645,6 @@ public static void EncodeSubscribeProperty(EncodeBuffer buffer, uint subscriberP if (monitoredProperty.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) { ASN1.encode_context_unsigned(buffer, 1, monitoredProperty.propertyArrayIndex); - } ASN1.encode_closing_tag(buffer, 4); @@ -651,7 +660,7 @@ public static int DecodeSubscribeProperty(byte[] buffer, int offset, int apduLen uint decodedValue; subscriberProcessIdentifier = 0; - monitoredObjectIdentifier = new BacnetObjectId(); + monitoredObjectIdentifier = default(BacnetObjectId); cancellationRequest = false; issueConfirmedNotifications = false; lifetime = 0; @@ -671,7 +680,8 @@ public static int DecodeSubscribeProperty(byte[] buffer, int offset, int apduLen if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out monitoredObjectIdentifier.type, out monitoredObjectIdentifier.instance); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + monitoredObjectIdentifier = new BacnetObjectId(type, instance); } else return -1; @@ -709,8 +719,7 @@ public static int DecodeSubscribeProperty(byte[] buffer, int offset, int apduLen if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out decodedValue); - monitoredProperty.propertyIdentifier = decodedValue; + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out monitoredProperty.propertyIdentifier); } else return -1; @@ -747,18 +756,24 @@ public static int DecodeSubscribeProperty(byte[] buffer, int offset, int apduLen } // F Chaxel - public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, out BacnetEventNotificationData eventData) + public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, out NotificationData eventData) { var len = 0; uint lenValue; - eventData = new BacnetEventNotificationData(); + eventData = new NotificationData(); + eventData = default; + BacnetNotifyTypes? notifyType; + BacnetEventTypes? eventType = default; + var decodedNotificationData = new List>(); + var decodedStateTransition = new List>(); /* tag 0 - processIdentifier */ if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out eventData.processIdentifier); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var processIdentifier); + decodedNotificationData.Add(e => e.ProcessIdentifier = processIdentifier); } else return -1; @@ -767,7 +782,8 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out eventData.initiatingObjectIdentifier.type, out eventData.initiatingObjectIdentifier.instance); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + decodedNotificationData.Add(e => e.InitiatingObjectIdentifier = new BacnetObjectId(type, instance)); } else return -1; @@ -776,7 +792,8 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, if (ASN1.decode_is_context_tag(buffer, offset + len, 2)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out eventData.eventObjectIdentifier.type, out eventData.eventObjectIdentifier.instance); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + decodedNotificationData.Add(e => e.EventObjectIdentifier = new BacnetObjectId(type, instance)); } else return -1; @@ -787,8 +804,9 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, len += 2; // opening Tag 3 then 2 len += ASN1.decode_application_date(buffer, offset + len, out var date); len += ASN1.decode_application_time(buffer, offset + len, out var time); - eventData.timeStamp.Time = new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond); - + decodedNotificationData.Add(e => e.TimeStamp = new BacnetGenericTime(new DateTime( + date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond), + BacnetTimestampTags.TIME_STAMP_DATETIME)); len += 2; // closing tag 2 then 3 } else @@ -798,7 +816,8 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, if (ASN1.decode_is_context_tag(buffer, offset + len, 4)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out eventData.notificationClass); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var notificationClass); + decodedNotificationData.Add(e => e.NotificationClass = notificationClass); } else return -1; @@ -809,7 +828,7 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var priority); if (priority > 0xFF) return -1; - eventData.priority = (byte)priority; + decodedNotificationData.Add(e => e.Priority = (byte) priority); } else return -1; @@ -818,42 +837,46 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, if (ASN1.decode_is_context_tag(buffer, offset + len, 6)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out var eventType); - eventData.eventType = (BacnetEventTypes)eventType; + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out var eventTypeValue); + decodedStateTransition.Add(e => e.EventType = eventTypeValue); + eventType = eventTypeValue; } - else - return -1; + //else + // return -1; + // shouldn't be present in ack transitions (according to the spec), but still is with some hardware /* optional tag 7 - messageText : never tested */ if (ASN1.decode_is_context_tag(buffer, offset + len, 7)) { // max_lenght 20000 sound like a joke - len += ASN1.decode_context_character_string(buffer, offset + len, 20000, 7, out eventData.messageText); + len += ASN1.decode_context_character_string(buffer, offset + len, 20000, 7, out var messageText); + decodedNotificationData.Add(e => e.MessageText = messageText); } /* tag 8 - notifyType */ if (ASN1.decode_is_context_tag(buffer, offset + len, 8)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out var notifyType); - eventData.notifyType = (BacnetNotifyTypes)notifyType; + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out var notifyTypeValue); + decodedStateTransition.Add(e => e.NotifyType = notifyTypeValue); + notifyType = notifyTypeValue; } else return -1; - switch (eventData.notifyType) + switch (notifyType) { case BacnetNotifyTypes.NOTIFY_ALARM: case BacnetNotifyTypes.NOTIFY_EVENT: /* tag 9 - ackRequired */ len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); len += ASN1.decode_unsigned8(buffer, offset + len, out var val); - eventData.ackRequired = Convert.ToBoolean(val); + decodedStateTransition.Add(e => e.AckRequired = Convert.ToBoolean(val)); /* tag 10 - fromState */ len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out var fromstate); - eventData.fromState = (BacnetEventStates)fromstate; + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var fromstate); + decodedStateTransition.Add(e => e.FromState = (BacnetEventStates) fromstate); break; } @@ -861,178 +884,333 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, if (ASN1.decode_is_context_tag(buffer, offset + len, 11)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out var toState); - eventData.toState = (BacnetEventStates)toState; + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var toState); + decodedNotificationData.Add(e => e.ToState = (BacnetEventStates) toState); } else return -1; - // some work to do for Tag 12 - // somebody want to do it ? + /* tag 12 - event values */ + switch (notifyType) + { + case BacnetNotifyTypes.NOTIFY_ALARM when eventType.HasValue: + case BacnetNotifyTypes.NOTIFY_EVENT when eventType.HasValue: + if (!DecodeEventValues(buffer, offset, eventType.Value, ref len, out var eventValues)) + return -1; + + var targetType = typeof(StateTransition<>).MakeGenericType(eventValues.GetType()); + eventData = Activator.CreateInstance(targetType, eventValues) as StateTransition; + break; + } + + eventData = eventData ?? (decodedStateTransition.Any() + ? new StateTransition() + : new NotificationData()); + + if (eventData is StateTransition stateTransition) + foreach (var setValue in decodedStateTransition) + setValue(stateTransition); + + foreach (var setValue in decodedNotificationData) + setValue(eventData); return len; + } + + private static bool DecodeEventValues(byte[] buffer, int offset, BacnetEventTypes eventType, ref int len, + out EventValuesBase eventValues) + { + eventValues = default; + + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 12)) + return false; + + len++; + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, (byte)eventType)) + return false; + len++; + switch (eventType) + { + case BacnetEventTypes.EVENT_CHANGE_OF_BITSTRING: + len += ASN1.decode_context_bitstring(buffer, offset + len, 0, out var referencedBitString); + len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var changeOfBitStringStatusFlags); + eventValues = new ChangeOfBitString + { + ReferencedBitString = referencedBitString, + StatusFlags = changeOfBitStringStatusFlags + }; + break; + + case BacnetEventTypes.EVENT_CHANGE_OF_STATE: + len += ASN1.decode_context_property_state(buffer, offset + len, 0, out var newState); + len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var changeOfStateStatusFlags); + eventValues = new ChangeOfState + { + NewState = newState, + StatusFlags = changeOfStateStatusFlags + }; + break; + + case BacnetEventTypes.EVENT_CHANGE_OF_VALUE: + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 0)) + return false; + + len++; + if (ASN1.decode_is_context_tag(buffer, offset + len, (byte)BacnetCOVTypes.CHANGE_OF_VALUE_BITS)) + { + len += ASN1.decode_context_bitstring(buffer, offset + len, 0, out var changedBits); + eventValues = new ChangeOfValue + { + ChangedBits = changedBits, + Tag = BacnetCOVTypes.CHANGE_OF_VALUE_BITS + }; + } + else if (ASN1.decode_is_context_tag(buffer, offset + len, (byte)BacnetCOVTypes.CHANGE_OF_VALUE_REAL)) + { + len += ASN1.decode_context_real(buffer, offset + len, 1, out var changeValue); + eventValues = new ChangeOfValue + { + ChangeValue = changeValue, + Tag = BacnetCOVTypes.CHANGE_OF_VALUE_REAL + }; + } + else + { + return false; + } + + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 0)) + return false; + + len++; + len += ASN1.decode_context_bitstring(buffer, offset + len, 0, out var changeOfValueStatusFlags); + ((ChangeOfValue)eventValues).StatusFlags = changeOfValueStatusFlags; + break; + + case BacnetEventTypes.EVENT_FLOATING_LIMIT: + len += ASN1.decode_context_real(buffer, offset + len, 0, out var referenceValue); + len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var floatingLimitStatusFlags); + len += ASN1.decode_context_real(buffer, offset + len, 2, out var setPointValue); + len += ASN1.decode_context_real(buffer, offset + len, 3, out var errorLimit); + eventValues = new FloatingLimit + { + ReferenceValue = referenceValue, + StatusFlags = floatingLimitStatusFlags, + SetPointValue = setPointValue, + ErrorLimit = errorLimit + }; + break; + + case BacnetEventTypes.EVENT_OUT_OF_RANGE: + len += ASN1.decode_context_real(buffer, offset + len, 0, out var outOfRangeExceedingValue); + len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var outOfRangeStatusFlags); + len += ASN1.decode_context_real(buffer, offset + len, 2, out var deadband); + len += ASN1.decode_context_real(buffer, offset + len, 3, out var outOfRangeExceededLimit); + eventValues = new OutOfRange + { + ExceedingValue = outOfRangeExceedingValue, + StatusFlags = outOfRangeStatusFlags, + Deadband = deadband, + ExceededLimit = outOfRangeExceededLimit + }; + break; + + case BacnetEventTypes.EVENT_CHANGE_OF_LIFE_SAFETY: + len += EnumUtils.DecodeContextEnumerated(buffer, offset + len, 0, out BacnetLifeSafetyStates lifeSafetyNewState); + len += EnumUtils.DecodeContextEnumerated(buffer, offset + len, 1, out BacnetLifeSafetyModes lifeSafetyNewMode); + len += ASN1.decode_context_bitstring(buffer, offset + len, 2, out var lifeSafetyStatusFlags); + len += EnumUtils.DecodeContextEnumerated(buffer, offset + len, 3, out BacnetLifeSafetyOperations operationExpected); + eventValues = new ChangeOfLifeSafety + { + NewState = lifeSafetyNewState, + NewMode = lifeSafetyNewMode, + StatusFlags = lifeSafetyStatusFlags, + OperationExpected = operationExpected + }; + break; + + case BacnetEventTypes.EVENT_BUFFER_READY: + // Too lazy for this one and not sure if really needed, somebody want to do it ? :) + break; + + case BacnetEventTypes.EVENT_UNSIGNED_RANGE: + len += ASN1.decode_context_unsigned(buffer, offset + len, 0, out var unsignedRangeExceedingValue); + len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var unsignedRangeStatusFlags); + len += ASN1.decode_context_unsigned(buffer, offset + len, 2, out var unsignedRangeExceededLimit); + eventValues = new UnsignedRange + { + ExceedingValue = unsignedRangeExceedingValue, + StatusFlags = unsignedRangeStatusFlags, + ExceededLimit = unsignedRangeExceededLimit + }; + break; + + default: + return false; + } + + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, (byte)eventType)) + return false; + + len++; + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 12)) + return false; + + len++; + return eventValues != null; } - private static void EncodeEventNotifyData(EncodeBuffer buffer, BacnetEventNotificationData data) + public static void EncodeEventNotifyData(EncodeBuffer buffer, NotificationData data) { /* tag 0 - processIdentifier */ - ASN1.encode_context_unsigned(buffer, 0, data.processIdentifier); + ASN1.encode_context_unsigned(buffer, 0, data.ProcessIdentifier); /* tag 1 - initiatingObjectIdentifier */ - ASN1.encode_context_object_id(buffer, 1, data.initiatingObjectIdentifier.type, data.initiatingObjectIdentifier.instance); + ASN1.encode_context_object_id( + buffer, 1, data.InitiatingObjectIdentifier.Type, data.InitiatingObjectIdentifier.Instance); /* tag 2 - eventObjectIdentifier */ - ASN1.encode_context_object_id(buffer, 2, data.eventObjectIdentifier.type, data.eventObjectIdentifier.instance); + ASN1.encode_context_object_id( + buffer, 2, data.EventObjectIdentifier.Type, data.EventObjectIdentifier.Instance); /* tag 3 - timeStamp */ - ASN1.bacapp_encode_context_timestamp(buffer, 3, data.timeStamp); + ASN1.bacapp_encode_context_timestamp(buffer, 3, data.TimeStamp); /* tag 4 - noticicationClass */ - ASN1.encode_context_unsigned(buffer, 4, data.notificationClass); + ASN1.encode_context_unsigned(buffer, 4, data.NotificationClass); /* tag 5 - priority */ - ASN1.encode_context_unsigned(buffer, 5, data.priority); + ASN1.encode_context_unsigned(buffer, 5, data.Priority); - /* tag 6 - eventType */ - ASN1.encode_context_enumerated(buffer, 6, (uint)data.eventType); + var stateTransition = data as StateTransition; + + if (stateTransition != null) + { + /* tag 6 - eventType */ + ASN1.encode_context_enumerated(buffer, 6, (uint) stateTransition.EventType); + } /* tag 7 - messageText */ - if (!string.IsNullOrEmpty(data.messageText)) - ASN1.encode_context_character_string(buffer, 7, data.messageText); + if (!string.IsNullOrEmpty(data.MessageText)) + ASN1.encode_context_character_string(buffer, 7, data.MessageText); /* tag 8 - notifyType */ - ASN1.encode_context_enumerated(buffer, 8, (uint)data.notifyType); + ASN1.encode_context_enumerated(buffer, 8, (uint) data.NotifyType); - switch (data.notifyType) + switch (stateTransition?.NotifyType) { case BacnetNotifyTypes.NOTIFY_ALARM: case BacnetNotifyTypes.NOTIFY_EVENT: /* tag 9 - ackRequired */ - ASN1.encode_context_boolean(buffer, 9, data.ackRequired); + ASN1.encode_context_boolean(buffer, 9, stateTransition.AckRequired); /* tag 10 - fromState */ - ASN1.encode_context_enumerated(buffer, 10, (uint)data.fromState); + ASN1.encode_context_enumerated(buffer, 10, (uint) stateTransition.FromState); break; } /* tag 11 - toState */ - ASN1.encode_context_enumerated(buffer, 11, (uint)data.toState); + ASN1.encode_context_enumerated(buffer, 11, (uint) data.ToState); + + if(stateTransition == null || !stateTransition.GetType().IsGenericType) + return; // there are no EventValues if we're not processing a StateTransition - switch (data.notifyType) + ASN1.encode_opening_tag(buffer, 12); + + switch (stateTransition) { - case BacnetNotifyTypes.NOTIFY_ALARM: - case BacnetNotifyTypes.NOTIFY_EVENT: - /* tag 12 - event values */ - ASN1.encode_opening_tag(buffer, 12); + case StateTransition changeOfBitString: + ASN1.encode_opening_tag(buffer, 0); + ASN1.encode_context_bitstring(buffer, 0, changeOfBitString.EventValues.ReferencedBitString); + ASN1.encode_context_bitstring(buffer, 1, changeOfBitString.EventValues.StatusFlags); + ASN1.encode_closing_tag(buffer, 0); + break; - switch (data.eventType) - { - case BacnetEventTypes.EVENT_CHANGE_OF_BITSTRING: - ASN1.encode_opening_tag(buffer, 0); - ASN1.encode_context_bitstring(buffer, 0, data.changeOfBitstring_referencedBitString); - ASN1.encode_context_bitstring(buffer, 1, data.changeOfBitstring_statusFlags); - ASN1.encode_closing_tag(buffer, 0); - break; + case StateTransition changeOfState: + ASN1.encode_opening_tag(buffer, 1); + ASN1.encode_opening_tag(buffer, 0); + ASN1.bacapp_encode_property_state(buffer, changeOfState.EventValues.NewState); + ASN1.encode_closing_tag(buffer, 0); + ASN1.encode_context_bitstring(buffer, 1, changeOfState.EventValues.StatusFlags); + ASN1.encode_closing_tag(buffer, 1); + break; - case BacnetEventTypes.EVENT_CHANGE_OF_STATE: - ASN1.encode_opening_tag(buffer, 1); - ASN1.encode_opening_tag(buffer, 0); - ASN1.bacapp_encode_property_state(buffer, data.changeOfState_newState); - ASN1.encode_closing_tag(buffer, 0); - ASN1.encode_context_bitstring(buffer, 1, data.changeOfState_statusFlags); - ASN1.encode_closing_tag(buffer, 1); - break; + case StateTransition changeOfValue: + ASN1.encode_opening_tag(buffer, 2); + ASN1.encode_opening_tag(buffer, 0); - case BacnetEventTypes.EVENT_CHANGE_OF_VALUE: - ASN1.encode_opening_tag(buffer, 2); - ASN1.encode_opening_tag(buffer, 0); - - switch (data.changeOfValue_tag) - { - case BacnetCOVTypes.CHANGE_OF_VALUE_REAL: - ASN1.encode_context_real(buffer, 1, data.changeOfValue_changeValue); - break; - case BacnetCOVTypes.CHANGE_OF_VALUE_BITS: - ASN1.encode_context_bitstring(buffer, 0, data.changeOfValue_changedBits); - break; - default: - throw new Exception("Hmm?"); - } - - ASN1.encode_closing_tag(buffer, 0); - ASN1.encode_context_bitstring(buffer, 1, data.changeOfValue_statusFlags); - ASN1.encode_closing_tag(buffer, 2); + switch (changeOfValue.EventValues.Tag) + { + case BacnetCOVTypes.CHANGE_OF_VALUE_REAL: + ASN1.encode_context_real(buffer, 1, changeOfValue.EventValues.ChangeValue); break; - - case BacnetEventTypes.EVENT_FLOATING_LIMIT: - ASN1.encode_opening_tag(buffer, 4); - ASN1.encode_context_real(buffer, 0, data.floatingLimit_referenceValue); - ASN1.encode_context_bitstring(buffer, 1, data.floatingLimit_statusFlags); - ASN1.encode_context_real(buffer, 2, data.floatingLimit_setPointValue); - ASN1.encode_context_real(buffer, 3, data.floatingLimit_errorLimit); - ASN1.encode_closing_tag(buffer, 4); + case BacnetCOVTypes.CHANGE_OF_VALUE_BITS: + ASN1.encode_context_bitstring(buffer, 0, changeOfValue.EventValues.ChangedBits); break; + default: + throw new ArgumentOutOfRangeException($"Unexpected Tag '{changeOfValue.EventValues.Tag}'"); + } - case BacnetEventTypes.EVENT_OUT_OF_RANGE: - ASN1.encode_opening_tag(buffer, 5); - ASN1.encode_context_real(buffer, 0, data.outOfRange_exceedingValue); - ASN1.encode_context_bitstring(buffer, 1, data.outOfRange_statusFlags); - ASN1.encode_context_real(buffer, 2, data.outOfRange_deadband); - ASN1.encode_context_real(buffer, 3, data.outOfRange_exceededLimit); - ASN1.encode_closing_tag(buffer, 5); - break; + ASN1.encode_closing_tag(buffer, 0); + ASN1.encode_context_bitstring(buffer, 1, changeOfValue.EventValues.StatusFlags); + ASN1.encode_closing_tag(buffer, 2); + break; - case BacnetEventTypes.EVENT_CHANGE_OF_LIFE_SAFETY: - ASN1.encode_opening_tag(buffer, 8); - ASN1.encode_context_enumerated(buffer, 0, (uint)data.changeOfLifeSafety_newState); - ASN1.encode_context_enumerated(buffer, 1, (uint)data.changeOfLifeSafety_newMode); - ASN1.encode_context_bitstring(buffer, 2, data.changeOfLifeSafety_statusFlags); - ASN1.encode_context_enumerated(buffer, 3, (uint)data.changeOfLifeSafety_operationExpected); - ASN1.encode_closing_tag(buffer, 8); - break; + case StateTransition floatingLimit: + ASN1.encode_opening_tag(buffer, 4); + ASN1.encode_context_real(buffer, 0, floatingLimit.EventValues.ReferenceValue); + ASN1.encode_context_bitstring(buffer, 1, floatingLimit.EventValues.StatusFlags); + ASN1.encode_context_real(buffer, 2, floatingLimit.EventValues.SetPointValue); + ASN1.encode_context_real(buffer, 3, floatingLimit.EventValues.ErrorLimit); + ASN1.encode_closing_tag(buffer, 4); + break; - case BacnetEventTypes.EVENT_BUFFER_READY: - ASN1.encode_opening_tag(buffer, 10); - ASN1.bacapp_encode_context_device_obj_property_ref(buffer, 0, data.bufferReady_bufferProperty); - ASN1.encode_context_unsigned(buffer, 1, data.bufferReady_previousNotification); - ASN1.encode_context_unsigned(buffer, 2, data.bufferReady_currentNotification); - ASN1.encode_closing_tag(buffer, 10); + case StateTransition outOfRange: + ASN1.encode_opening_tag(buffer, 5); + ASN1.encode_context_real(buffer, 0, outOfRange.EventValues.ExceedingValue); + ASN1.encode_context_bitstring(buffer, 1, outOfRange.EventValues.StatusFlags); + ASN1.encode_context_real(buffer, 2, outOfRange.EventValues.Deadband); + ASN1.encode_context_real(buffer, 3, outOfRange.EventValues.ExceededLimit); + ASN1.encode_closing_tag(buffer, 5); + break; - break; - case BacnetEventTypes.EVENT_UNSIGNED_RANGE: - ASN1.encode_opening_tag(buffer, 11); - ASN1.encode_context_unsigned(buffer, 0, data.unsignedRange_exceedingValue); - ASN1.encode_context_bitstring(buffer, 1, data.unsignedRange_statusFlags); - ASN1.encode_context_unsigned(buffer, 2, data.unsignedRange_exceededLimit); - ASN1.encode_closing_tag(buffer, 11); - break; + case StateTransition changeOfLifeSafety: + ASN1.encode_opening_tag(buffer, 8); + ASN1.encode_context_enumerated(buffer, 0, (uint) changeOfLifeSafety.EventValues.NewState); + ASN1.encode_context_enumerated(buffer, 1, (uint) changeOfLifeSafety.EventValues.NewMode); + ASN1.encode_context_bitstring(buffer, 2, changeOfLifeSafety.EventValues.StatusFlags); + ASN1.encode_context_enumerated(buffer, 3, (uint) changeOfLifeSafety.EventValues.OperationExpected); + ASN1.encode_closing_tag(buffer, 8); + break; - default: - throw new NotImplementedException(); - } - ASN1.encode_closing_tag(buffer, 12); + case StateTransition bufferReady: + ASN1.encode_opening_tag(buffer, 10); + ASN1.bacapp_encode_context_device_obj_property_ref(buffer, 0, bufferReady.EventValues.BufferProperty); + ASN1.encode_context_unsigned(buffer, 1, bufferReady.EventValues.PreviousNotification); + ASN1.encode_context_unsigned(buffer, 2, bufferReady.EventValues.CurrentNotification); + ASN1.encode_closing_tag(buffer, 10); break; - case BacnetNotifyTypes.NOTIFY_ACK_NOTIFICATION: - /* FIXME: handle this case */ + case StateTransition unsignedRange: + ASN1.encode_opening_tag(buffer, 11); + ASN1.encode_context_unsigned(buffer, 0, unsignedRange.EventValues.ExceedingValue); + ASN1.encode_context_bitstring(buffer, 1, unsignedRange.EventValues.StatusFlags); + ASN1.encode_context_unsigned(buffer, 2, unsignedRange.EventValues.ExceededLimit); + ASN1.encode_closing_tag(buffer, 11); break; - } - } - public static void EncodeEventNotifyConfirmed(EncodeBuffer buffer, BacnetEventNotificationData data) - { - EncodeEventNotifyData(buffer, data); - } + default: + var eventValuesType = stateTransition.GetType().GetGenericArguments().First(); + throw new NotImplementedException($"EventValues of type {eventValuesType} is not implemented"); + } - public static void EncodeEventNotifyUnconfirmed(EncodeBuffer buffer, BacnetEventNotificationData data) - { - EncodeEventNotifyData(buffer, data); + ASN1.encode_closing_tag(buffer, 12); } public static void EncodeAlarmSummary(EncodeBuffer buffer, BacnetObjectId objectIdentifier, BacnetEventStates alarmState, BacnetBitString acknowledgedTransitions) { /* tag 0 - Object Identifier */ - ASN1.encode_application_object_id(buffer, objectIdentifier.type, objectIdentifier.instance); + ASN1.encode_application_object_id(buffer, objectIdentifier.Type, objectIdentifier.Instance); /* tag 1 - Alarm State */ ASN1.encode_application_enumerated(buffer, (uint)alarmState); /* tag 2 - Acknowledged Transitions */ @@ -1048,10 +1226,11 @@ public static int DecodeEventInformation(byte[] buffer, int offset, int apduLen, var value = new BacnetGetEventInformationData(); len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out value.objectIdentifier.type, out value.objectIdentifier.instance); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + value.objectIdentifier = new BacnetObjectId(type, instance); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out var tmp); - value.eventState = (BacnetEventStates)tmp; + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out value.eventState); len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.acknowledgedTransitions); @@ -1076,12 +1255,10 @@ public static int DecodeEventInformation(byte[] buffer, int offset, int apduLen, len++; // closing Tag 3 - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out tmp); - value.notifyType = (BacnetNotifyTypes)tmp; - - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.eventEnable); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out value.notifyType); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.eventEnable); len++; // opening tag 6; value.eventPriorities = new uint[3]; @@ -1108,10 +1285,10 @@ public static int DecodeAlarmSummary(byte[] buffer, int offset, int apduLen, ref var value = new BacnetAlarmSummaryData(); len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out value.objectIdentifier.type, out value.objectIdentifier.instance); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + value.objectIdentifier = new BacnetObjectId(type, instance); len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out var tmp); - value.alarmState = (BacnetEventStates)tmp; + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out value.alarmState); len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.acknowledgedTransitions); @@ -1125,7 +1302,7 @@ public static void EncodeGetEventInformation(EncodeBuffer buffer, BacnetObjectId { /* encode optional parameter */ if (lastReceivedObjectIdentifier != null) - ASN1.encode_context_object_id(buffer, 0, lastReceivedObjectIdentifier.Value.type, lastReceivedObjectIdentifier.Value.instance); + ASN1.encode_context_object_id(buffer, 0, lastReceivedObjectIdentifier.Value.Type, lastReceivedObjectIdentifier.Value.Instance); } public static void EncodeGetEventInformationAcknowledge(EncodeBuffer buffer, BacnetGetEventInformationData[] events, bool moreEvents) @@ -1136,7 +1313,7 @@ public static void EncodeGetEventInformationAcknowledge(EncodeBuffer buffer, Bac foreach (var eventData in events) { /* Tag 0: objectIdentifier */ - ASN1.encode_context_object_id(buffer, 0, eventData.objectIdentifier.type, eventData.objectIdentifier.instance); + ASN1.encode_context_object_id(buffer, 0, eventData.objectIdentifier.Type, eventData.objectIdentifier.Instance); /* Tag 1: eventState */ ASN1.encode_context_enumerated(buffer, 1, (uint)eventData.eventState); /* Tag 2: acknowledgedTransitions */ @@ -1173,7 +1350,7 @@ public static void EncodeLifeSafetyOperation(EncodeBuffer buffer, uint processId /* Operation */ ASN1.encode_context_enumerated(buffer, 2, operation); /* Object ID */ - ASN1.encode_context_object_id(buffer, 3, targetObject.type, targetObject.instance); + ASN1.encode_context_object_id(buffer, 3, targetObject.Type, targetObject.Instance); } public static void EncodePrivateTransferConfirmed(EncodeBuffer buffer, uint vendorID, uint serviceNumber, byte[] data) @@ -1242,7 +1419,7 @@ public static int DecodeDeviceCommunicationControl(byte[] buffer, int offset, in if (!ASN1.decode_is_context_tag(buffer, offset + len, 1)) return -1; len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValueType); - len += ASN1.decode_enumerated(buffer, offset + len, lenValueType, out enableDisable); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out enableDisable); /* Tag 2: password --optional-- */ if (len < apduLen) @@ -1279,8 +1456,7 @@ public static int DecodeReinitializeDevice(byte[] buffer, int offset, int apduLe if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) return -1; len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out var lenValueType); - len += ASN1.decode_enumerated(buffer, offset + len, lenValueType, out var value); - state = (BacnetReinitializedStates)value; + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out state); /* Tag 1: password - optional */ if (len < apduLen) { @@ -1295,7 +1471,7 @@ public static int DecodeReinitializeDevice(byte[] buffer, int offset, int apduLe public static void EncodeReadRange(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, BacnetReadRangeRequestTypes requestType, uint position, DateTime time, int count) { - ASN1.encode_context_object_id(buffer, 0, objectId.type, objectId.instance); + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); ASN1.encode_context_enumerated(buffer, 1, propertyId); /* optional array index */ @@ -1338,7 +1514,7 @@ public static int DecodeReadRange(byte[] buffer, int offset, int apduLen, out Ba { var len = 0; - objectId = new BacnetObjectId(); + objectId = default(BacnetObjectId); property = new BacnetPropertyReference(); requestType = BacnetReadRangeRequestTypes.RR_READ_ALL; position = 0; @@ -1349,14 +1525,14 @@ public static int DecodeReadRange(byte[] buffer, int offset, int apduLen, out Ba if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) return -1; len++; - len += ASN1.decode_object_id(buffer, offset + len, out ushort type, out objectId.instance); - objectId.type = (BacnetObjectTypes)type; + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); /* Tag 1: Property ID */ len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 1) return -1; - len += ASN1.decode_enumerated(buffer, offset + len, lenValueType, out property.propertyIdentifier); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyIdentifier); /* Tag 2: Optional Array Index */ if (len < apduLen && ASN1.decode_is_context_tag(buffer, offset + len, 0)) @@ -1380,6 +1556,7 @@ public static int DecodeReadRange(byte[] buffer, int offset, int apduLen, out Ba len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); len += ASN1.decode_signed(buffer, offset + len, lenValueType, out count); break; + case 6: requestType = BacnetReadRangeRequestTypes.RR_BY_SEQUENCE; len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); @@ -1387,6 +1564,7 @@ public static int DecodeReadRange(byte[] buffer, int offset, int apduLen, out Ba len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); len += ASN1.decode_signed(buffer, offset + len, lenValueType, out count); break; + case 7: requestType = BacnetReadRangeRequestTypes.RR_BY_TIME; len += ASN1.decode_application_date(buffer, offset + len, out var date); @@ -1395,6 +1573,7 @@ public static int DecodeReadRange(byte[] buffer, int offset, int apduLen, out Ba len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); len += ASN1.decode_signed(buffer, offset + len, lenValueType, out count); break; + default: return -1; //don't know this type yet } @@ -1406,7 +1585,7 @@ public static int DecodeReadRange(byte[] buffer, int offset, int apduLen, out Ba public static void EncodeReadRangeAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, BacnetBitString resultFlags, uint itemCount, byte[] applicationData, BacnetReadRangeRequestTypes requestType, uint firstSequence) { /* service ack follows */ - ASN1.encode_context_object_id(buffer, 0, objectId.type, objectId.instance); + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); ASN1.encode_context_enumerated(buffer, 1, propertyId); /* context 2 array index is optional */ if (arrayIndex != ASN1.BACNET_ARRAY_ALL) @@ -1417,7 +1596,7 @@ public static void EncodeReadRangeAcknowledge(EncodeBuffer buffer, BacnetObjectI ASN1.encode_context_bitstring(buffer, 3, resultFlags); /* Context 4 Item Count */ ASN1.encode_context_unsigned(buffer, 4, itemCount); - /* Context 5 Property list - reading the standard it looks like an empty list still + /* Context 5 Property list - reading the standard it looks like an empty list still * requires an opening and closing tag as the tagged parameter is not optional */ ASN1.encode_opening_tag(buffer, 5); @@ -1451,7 +1630,7 @@ public static uint DecodeReadRangeAcknowledge(byte[] buffer, int offset, int apd len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 1) return 0; - len += ASN1.decode_enumerated(buffer, offset + len, lenValueType, out _); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out _); /* Tag 2: Optional Array Index or Tag 3: BACnet Result Flags */ len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); @@ -1478,11 +1657,11 @@ public static uint DecodeReadRangeAcknowledge(byte[] buffer, int offset, int apd public static void EncodeReadProperty(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) { - if ((int)objectId.type <= ASN1.BACNET_MAX_OBJECT) + if ((int)objectId.Type <= ASN1.BACNET_MAX_OBJECT) { /* check bounds so that we could create malformed messages for testing */ - ASN1.encode_context_object_id(buffer, 0, objectId.type, objectId.instance); + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); } if (propertyId <= (uint)BacnetPropertyIds.MAX_BACNET_PROPERTY_ID) { @@ -1511,9 +1690,11 @@ public static int DecodeAtomicWriteFileAcknowledge(byte[] buffer, int offset, in isStream = true; len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); break; + case 1: len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); break; + default: return -1; } @@ -1604,7 +1785,7 @@ public static int DecodeReadProperty(byte[] buffer, int offset, int apduLen, out { var len = 0; - objectId = new BacnetObjectId(); + objectId = default(BacnetObjectId); property = new BacnetPropertyReference(); // must have at least 2 tags , otherwise return reject code: Missing required parameter @@ -1616,15 +1797,15 @@ public static int DecodeReadProperty(byte[] buffer, int offset, int apduLen, out return -2; len++; - len += ASN1.decode_object_id(buffer, offset + len, out ushort type, out objectId.instance); - objectId.type = (BacnetObjectTypes)type; + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); /* Tag 1: Property ID */ len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 1) return -2; - len += ASN1.decode_enumerated(buffer, offset + len, lenValueType, out property.propertyIdentifier); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyIdentifier); /* Tag 2: Optional Array Index */ if (len < apduLen) @@ -1650,7 +1831,7 @@ public static int DecodeReadProperty(byte[] buffer, int offset, int apduLen, out public static void EncodeReadPropertyAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, IEnumerable valueList) { /* service ack follows */ - ASN1.encode_context_object_id(buffer, 0, objectId.type, objectId.instance); + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); ASN1.encode_context_enumerated(buffer, 1, propertyId); /* context 2 array index is optional */ if (arrayIndex != ASN1.BACNET_ARRAY_ALL) @@ -1669,7 +1850,7 @@ public static void EncodeReadPropertyAcknowledge(EncodeBuffer buffer, BacnetObje public static int DecodeReadPropertyAcknowledge(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyReference property, out IList valueList) { - objectId = new BacnetObjectId(); + objectId = default(BacnetObjectId); property = new BacnetPropertyReference(); valueList = new List(); @@ -1678,12 +1859,13 @@ public static int DecodeReadPropertyAcknowledge(BacnetAddress address, byte[] bu if (!ASN1.decode_is_context_tag(buffer, offset, 0)) return -1; var len = 1; - len += ASN1.decode_object_id(buffer, offset + len, out objectId.type, out objectId.instance); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); /* Tag 1: Property ID */ len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 1) return -1; - len += ASN1.decode_enumerated(buffer, offset + len, lenValueType, out property.propertyIdentifier); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyIdentifier); /* Tag 2: Optional Array Index */ var tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); if (tagNumber == 2) @@ -1702,7 +1884,7 @@ public static int DecodeReadPropertyAcknowledge(BacnetAddress address, byte[] bu while (apduLen - len > 1) { - tagLen = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, objectId.type, (BacnetPropertyIds)property.propertyIdentifier, out var value); + tagLen = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, objectId.Type, (BacnetPropertyIds)property.propertyIdentifier, out var value); if (tagLen < 0) return -1; len += tagLen; valueList.Add(value); @@ -1778,7 +1960,7 @@ public static int DecodeReadPropertyMultipleAcknowledge(BacnetAddress address, b public static void EncodeWriteProperty(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, uint priority, IEnumerable valueList) { - ASN1.encode_context_object_id(buffer, 0, objectId.type, objectId.instance); + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); ASN1.encode_context_enumerated(buffer, 1, propertyId); /* optional array index; ALL is -1 which is assumed when missing */ @@ -1808,8 +1990,8 @@ public static int DecodeCOVNotifyUnconfirmed(BacnetAddress address, byte[] buffe uint lenValue; subscriberProcessIdentifier = 0; - initiatingDeviceIdentifier = new BacnetObjectId(); - monitoredObjectIdentifier = new BacnetObjectId(); + initiatingDeviceIdentifier = default(BacnetObjectId); + monitoredObjectIdentifier = default(BacnetObjectId); timeRemaining = 0; values = null; @@ -1826,7 +2008,8 @@ public static int DecodeCOVNotifyUnconfirmed(BacnetAddress address, byte[] buffe if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out initiatingDeviceIdentifier.type, out initiatingDeviceIdentifier.instance); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + initiatingDeviceIdentifier = new BacnetObjectId(type, instance); } else return -1; @@ -1835,7 +2018,8 @@ public static int DecodeCOVNotifyUnconfirmed(BacnetAddress address, byte[] buffe if (ASN1.decode_is_context_tag(buffer, offset + len, 2)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out monitoredObjectIdentifier.type, out monitoredObjectIdentifier.instance); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + monitoredObjectIdentifier = new BacnetObjectId(type, instance); } else return -1; @@ -1864,7 +2048,7 @@ public static int DecodeCOVNotifyUnconfirmed(BacnetAddress address, byte[] buffe if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out newEntry.property.propertyIdentifier); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out newEntry.property.propertyIdentifier); } else return -1; @@ -1887,7 +2071,10 @@ public static int DecodeCOVNotifyUnconfirmed(BacnetAddress address, byte[] buffe var bValues = new List(); while (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 2)) { - var tmp = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, monitoredObjectIdentifier.type, (BacnetPropertyIds)newEntry.property.propertyIdentifier, out var bValue); + var tmp = ASN1.bacapp_decode_application_data( + address, buffer, offset + len, apduLen + offset, monitoredObjectIdentifier.Type, + (BacnetPropertyIds) newEntry.property.propertyIdentifier, out var bValue); + if (tmp < 0) return -1; len += tmp; bValues.Add(bValue); @@ -1917,19 +2104,20 @@ public static int DecodeWriteProperty(BacnetAddress address, byte[] buffer, int { var len = 0; - objectId = new BacnetObjectId(); + objectId = default(BacnetObjectId); value = new BacnetPropertyValue(); /* Tag 0: Object ID */ if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) return -1; len++; - len += ASN1.decode_object_id(buffer, offset + len, out objectId.type, out objectId.instance); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); /* Tag 1: Property ID */ len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 1) return -1; - len += ASN1.decode_enumerated(buffer, offset + len, lenValueType, out value.property.propertyIdentifier); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out value.property.propertyIdentifier); /* Tag 2: Optional Array Index */ /* note: decode without incrementing len so we can check for opening tag */ var tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); @@ -1949,7 +2137,10 @@ public static int DecodeWriteProperty(BacnetAddress address, byte[] buffer, int var valueList = new List(); while (apduLen - len > 1 && !ASN1.decode_is_closing_tag_number(buffer, offset + len, 3)) { - var l = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, objectId.type, (BacnetPropertyIds)value.property.propertyIdentifier, out var bValue); + var l = ASN1.bacapp_decode_application_data( + address, buffer, offset + len, apduLen + offset, objectId.Type, + (BacnetPropertyIds) value.property.propertyIdentifier, out var bValue); + if (l <= 0) return -1; len += l; valueList.Add(bValue); @@ -1981,7 +2172,7 @@ public static int DecodeWriteProperty(BacnetAddress address, byte[] buffer, int public static void EncodeWritePropertyMultiple(EncodeBuffer buffer, BacnetObjectId objectId, ICollection valueList) { - ASN1.encode_context_object_id(buffer, 0, objectId.type, objectId.instance); + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); /* Tag 1: sequence of WriteAccessSpecification */ ASN1.encode_opening_tag(buffer, 1); @@ -2015,14 +2206,14 @@ public static void EncodeWriteObjectMultiple(EncodeBuffer buffer, ICollection valuesRefs) { var len = 0; - objectId = new BacnetObjectId(); + objectId = default(BacnetObjectId); valuesRefs = null; //object id @@ -2033,8 +2224,8 @@ public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int o apduLen -= len; if (apduLen >= 4) { - len += ASN1.decode_context_object_id(buffer, offset + len, 1, out var typenr, out objectId.instance); - objectId.type = (BacnetObjectTypes)typenr; + len += ASN1.decode_context_object_id(buffer, offset + len, 1, out var typenr, out var instance); + objectId = new BacnetObjectId((BacnetObjectTypes) typenr, instance); } else return -1; @@ -2063,7 +2254,7 @@ public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int o len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); uint propertyId; if (tagNumber == 0) - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out propertyId); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out propertyId); else return -1; @@ -2083,7 +2274,10 @@ public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int o var values = new List(); while (!ASN1.decode_is_closing_tag(buffer, offset + len)) { - var l = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, objectId.type, (BacnetPropertyIds)propertyId, out var value); + var l = ASN1.bacapp_decode_application_data( + address, buffer, offset + len, apduLen + offset, objectId.Type, + (BacnetPropertyIds) propertyId, out var value); + if (l <= 0) return -1; len += l; values.Add(value); @@ -2109,14 +2303,16 @@ public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int o public static int DecodeDeleteObject(byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId) { - objectId = new BacnetObjectId(); + objectId = default(BacnetObjectId); + ASN1.decode_tag_number_and_value(buffer, offset, out var tagNumber, out _); if (tagNumber != 12) return -1; var len = 1; - len += ASN1.decode_object_id(buffer, offset + len, out objectId.type, out objectId.instance); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); if (len == apduLen) //check if packet was correct! return len; @@ -2126,13 +2322,13 @@ public static int DecodeDeleteObject(byte[] buffer, int offset, int apduLen, out public static void EncodeCreateObjectAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId) { - ASN1.encode_application_object_id(buffer, objectId.type, objectId.instance); + ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); } public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out ICollection valuesRefs) { var len = 0; - objectId = new BacnetObjectId(); + objectId = default(BacnetObjectId); valuesRefs = null; /* Context tag 0 - Object ID */ @@ -2142,7 +2338,8 @@ public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buff apduLen -= len; if (apduLen >= 4) { - len += ASN1.decode_object_id(buffer, offset + len, out objectId.type, out objectId.instance); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); } else return -1; @@ -2155,7 +2352,7 @@ public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buff return -1; len++; - var _values = new LinkedList(); + var linkedList = new LinkedList(); while (apduLen - len > 1) { var newEntry = new BacnetPropertyValue(); @@ -2164,7 +2361,7 @@ public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buff len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); uint propertyId; if (tagNumber == 0) - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out propertyId); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out propertyId); else return -1; @@ -2181,16 +2378,19 @@ public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buff /* tag 2 - Property Value */ if (tagNumber == 2 && ASN1.decode_is_opening_tag(buffer, offset + len - 1)) { - var values = new List(); + var list = new List(); while (!ASN1.decode_is_closing_tag(buffer, offset + len)) { - var l = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, objectId.type, (BacnetPropertyIds)propertyId, out var value); + var l = ASN1.bacapp_decode_application_data( + address, buffer, offset + len, apduLen + offset, objectId.Type, + (BacnetPropertyIds) propertyId, out var value); + if (l <= 0) return -1; len += l; - values.Add(value); + list.Add(value); } len++; - newEntry.value = values; + newEntry.value = list; } else return -1; @@ -2204,7 +2404,7 @@ public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buff len--; newEntry.priority = (byte)ulVal; - _values.AddLast(newEntry); + linkedList.AddLast(newEntry); } /* Closing tag 1 - List of Properties */ @@ -2212,7 +2412,7 @@ public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buff return -1; len++; - valuesRefs = _values; + valuesRefs = linkedList; return len; } @@ -2257,12 +2457,10 @@ public static int DecodeError(byte[] buffer, int offset, int length, out BacnetE offset += ASN1.decode_tag_number_and_value(buffer, offset, out _, out var lenValueType); /* FIXME: we could validate that the tag is enumerated... */ - offset += ASN1.decode_enumerated(buffer, offset, lenValueType, out var tmp); - errorClass = (BacnetErrorClasses)tmp; + offset += EnumUtils.DecodeEnumerated(buffer, offset, lenValueType, out errorClass); offset += ASN1.decode_tag_number_and_value(buffer, offset, out _, out lenValueType); /* FIXME: we could validate that the tag is enumerated... */ - offset += ASN1.decode_enumerated(buffer, offset, lenValueType, out tmp); - errorCode = (BacnetErrorCodes)tmp; + offset += EnumUtils.DecodeEnumerated(buffer, offset, lenValueType, out errorCode); return offset - orgOffset; } @@ -2278,7 +2476,6 @@ public static void EncodeLogRecord(EncodeBuffer buffer, BacnetLogRecord record) /* Tag 1: logDatum */ if (record.type != BacnetTrendLogValueType.TL_TYPE_NULL) { - if (record.type == BacnetTrendLogValueType.TL_TYPE_ERROR) { ASN1.encode_opening_tag(buffer, 1); @@ -2299,24 +2496,31 @@ public static void EncodeLogRecord(EncodeBuffer buffer, BacnetLogRecord record) case BacnetTrendLogValueType.TL_TYPE_BITS: ASN1.encode_bitstring(tmp1, record.GetValue()); break; + case BacnetTrendLogValueType.TL_TYPE_BOOL: tmp1.Add(record.GetValue() ? (byte)1 : (byte)0); break; + case BacnetTrendLogValueType.TL_TYPE_DELTA: ASN1.encode_bacnet_real(tmp1, record.GetValue()); break; + case BacnetTrendLogValueType.TL_TYPE_ENUM: ASN1.encode_application_enumerated(tmp1, record.GetValue()); break; + case BacnetTrendLogValueType.TL_TYPE_REAL: ASN1.encode_bacnet_real(tmp1, record.GetValue()); break; + case BacnetTrendLogValueType.TL_TYPE_SIGN: ASN1.encode_bacnet_signed(tmp1, record.GetValue()); break; + case BacnetTrendLogValueType.TL_TYPE_STATUS: ASN1.encode_bitstring(tmp1, record.GetValue()); break; + case BacnetTrendLogValueType.TL_TYPE_UNSIGN: ASN1.encode_bacnet_unsigned(tmp1, record.GetValue()); break; @@ -2328,77 +2532,83 @@ public static void EncodeLogRecord(EncodeBuffer buffer, BacnetLogRecord record) /* Tag 2: status */ var recordStatusFlags = BacnetBitString.ConvertFromInt((uint)record.statusFlags, 4); - if (recordStatusFlags.bits_used > 0) + if (recordStatusFlags.BitsUsed > 0) { - ASN1.encode_opening_tag(buffer, 2); - ASN1.encode_application_bitstring(buffer, recordStatusFlags); - ASN1.encode_closing_tag(buffer, 2); + ASN1.encode_tag(buffer, 2, true, 2); + ASN1.encode_bitstring(buffer, recordStatusFlags); } } - public static int DecodeLogRecord(byte[] buffer, int offset, int length, int nCurves, out BacnetLogRecord[] records) + public static int DecodeLogRecord(byte[] buffer, int offset, int length, int nCurves, + out BacnetLogRecord[] records) { var len = 0; records = new BacnetLogRecord[nCurves]; - len += ASN1.decode_tag_number(buffer, offset + len, out var tagNumber); - if (tagNumber != 0) return -1; + for (var curveNumber = 0; curveNumber < nCurves; curveNumber++) + { + len += ASN1.decode_tag_number(buffer, offset + len, out var tagNumber); + if (tagNumber != 0) return -1; - // Date and Time in Tag 0 - len += ASN1.decode_application_date(buffer, offset + len, out var date); - len += ASN1.decode_application_time(buffer, offset + len, out var time); + // Date and Time in Tag 0 + len += ASN1.decode_application_date(buffer, offset + len, out var date); + len += ASN1.decode_application_time(buffer, offset + len, out var time); - var dt = new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond); + var dt = new DateTime( + date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond); - if (!ASN1.decode_is_closing_tag(buffer, offset + len)) return -1; - len++; + if (!ASN1.decode_is_closing_tag(buffer, offset + len)) return -1; + len++; - // Value or error in Tag 1 - len += ASN1.decode_tag_number(buffer, offset + len, out tagNumber); - if (tagNumber != 1) return -1; + // Value or error in Tag 1 + len += ASN1.decode_tag_number(buffer, offset + len, out tagNumber); + if (tagNumber != 1) return -1; - // Not test for TrendLogMultiple - // Seems to be encoded like this somewhere in an Ashrae document - for (var curveNumber = 0; curveNumber < nCurves; curveNumber++) - { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var contextTagType, out var lenValue); records[curveNumber] = new BacnetLogRecord { timestamp = dt, - type = (BacnetTrendLogValueType)contextTagType + type = (BacnetTrendLogValueType) contextTagType }; - switch ((BacnetTrendLogValueType)contextTagType) + switch ((BacnetTrendLogValueType) contextTagType) { case BacnetTrendLogValueType.TL_TYPE_STATUS: len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out var sval); records[curveNumber].Value = sval; break; + case BacnetTrendLogValueType.TL_TYPE_BOOL: records[curveNumber].Value = buffer[offset + len] > 0; len++; break; + case BacnetTrendLogValueType.TL_TYPE_REAL: len += ASN1.decode_real(buffer, offset + len, out var rval); records[curveNumber].Value = rval; break; + case BacnetTrendLogValueType.TL_TYPE_ENUM: - len += ASN1.decode_enumerated(buffer, offset + len, lenValue, out var eval); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var eval); records[curveNumber].Value = eval; break; + case BacnetTrendLogValueType.TL_TYPE_SIGN: len += ASN1.decode_signed(buffer, offset + len, lenValue, out var ival); records[curveNumber].Value = ival; break; + case BacnetTrendLogValueType.TL_TYPE_UNSIGN: len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var uinval); records[curveNumber].Value = uinval; break; + case BacnetTrendLogValueType.TL_TYPE_ERROR: len += DecodeError(buffer, offset + len, length, out var errclass, out var errcode); records[curveNumber].Value = new BacnetError(errclass, errcode); len++; // Closing Tag 8 break; + case BacnetTrendLogValueType.TL_TYPE_NULL: len++; records[curveNumber].Value = null; @@ -2415,34 +2625,35 @@ public static int DecodeLogRecord(byte[] buffer, int offset, int length, int nCu len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out var bval); records[curveNumber].Value = bval; break; + default: return 0; } - } - if (!ASN1.decode_is_closing_tag(buffer, offset + len)) - return -1; - len++; + if (!ASN1.decode_is_closing_tag(buffer, offset + len)) + return -1; - if (len >= length) - return len; + len++; - var l = ASN1.decode_tag_number(buffer, offset + len, out tagNumber); + if (len >= length) + return len; - // Optional Tag 2 - if (tagNumber != 2) - return len; + ASN1.decode_tag_number(buffer, offset + len, out tagNumber); - len += l; - len += ASN1.decode_bitstring(buffer, offset + len, 2, out var statusFlagsBits); + // Optional Tag 2 + if (tagNumber != 2) + return len; - //set status to all returns - var statusFlags = (BacnetStatusFlags)statusFlagsBits.ConvertToInt(); - for (var curveNumber = 0; curveNumber < nCurves; curveNumber++) + len++; + len += ASN1.decode_bitstring(buffer, offset + len, 2, out var statusFlagsBits); + + //set status to all returns + var statusFlags = (BacnetStatusFlags) statusFlagsBits.ConvertToInt(); records[curveNumber].statusFlags = statusFlags; + } return len; } } -} \ No newline at end of file +} diff --git a/Storage/DeviceStorage.cs b/Storage/DeviceStorage.cs index c94219ea..75ef5d64 100644 --- a/Storage/DeviceStorage.cs +++ b/Storage/DeviceStorage.cs @@ -51,7 +51,7 @@ private Object FindObject(BacnetObjectTypes objectType) public Object FindObject(BacnetObjectId objectId) { //liniear search - return Objects.FirstOrDefault(obj => obj.Type == objectId.type && obj.Instance == objectId.instance); + return Objects.FirstOrDefault(obj => obj.Type == objectId.Type && obj.Instance == objectId.Instance); } public enum ErrorCodes @@ -83,8 +83,8 @@ public ErrorCodes ReadProperty(BacnetObjectId objectId, BacnetPropertyIds proper value = new BacnetValue[0]; //wildcard device_id - if (objectId.type == BacnetObjectTypes.OBJECT_DEVICE && objectId.instance >= Serialize.ASN1.BACNET_MAX_INSTANCE) - objectId.instance = DeviceId; + if (objectId.Type == BacnetObjectTypes.OBJECT_DEVICE && objectId.Instance >= Serialize.ASN1.BACNET_MAX_INSTANCE) + objectId = new BacnetObjectId(objectId.Type, DeviceId); //overrides if (ReadOverride != null) @@ -208,8 +208,8 @@ public void WritePropertyValue(BacnetObjectId objectId, BacnetPropertyIds proper public ErrorCodes WriteProperty(BacnetObjectId objectId, BacnetPropertyIds propertyId, uint arrayIndex, IList value, bool addIfNotExits = false) { //wildcard device_id - if (objectId.type == BacnetObjectTypes.OBJECT_DEVICE && objectId.instance >= Serialize.ASN1.BACNET_MAX_INSTANCE) - objectId.instance = DeviceId; + if (objectId.Type == BacnetObjectTypes.OBJECT_DEVICE && objectId.Instance >= Serialize.ASN1.BACNET_MAX_INSTANCE) + objectId = new BacnetObjectId(objectId.Type, DeviceId); //overrides if (WriteOverride != null) @@ -233,8 +233,8 @@ public ErrorCodes WriteProperty(BacnetObjectId objectId, BacnetPropertyIds prope { obj = new Object { - Type = objectId.type, - Instance = objectId.instance + Type = objectId.Type, + Instance = objectId.Instance }; var arr = Objects; Array.Resize(ref arr, arr.Length + 1); diff --git a/Tests/BACNetClientTests.cs b/Tests/BACNetClientTests.cs new file mode 100644 index 00000000..ad32185b --- /dev/null +++ b/Tests/BACNetClientTests.cs @@ -0,0 +1,31 @@ +using NUnit.Framework; + +namespace System.IO.BACnet.Tests +{ + [TestFixture] + public class BacNetClientTests + { + [Test] + public void should_send_and_raise_iam() + { + // arrange + const int sendingDeviceId = 123; + var iAmCount = 0; + var lastReceivedId = -1; + var (client1, client2) = Helper.CreateConnectedClients(); + + client2.OnIam += (sender, address, id, apdu, segmentation, vendorId) => + { + iAmCount++; + lastReceivedId = (int)id; + }; + + // act + client1.Iam(sendingDeviceId); + + // assert + Assert.That(iAmCount, Is.EqualTo(1)); + Assert.That(lastReceivedId, Is.EqualTo(sendingDeviceId)); + } + } +} diff --git a/Tests/BACnet.Tests.csproj b/Tests/BACnet.Tests.csproj index c18b7b9f..c194ae93 100644 --- a/Tests/BACnet.Tests.csproj +++ b/Tests/BACnet.Tests.csproj @@ -7,7 +7,7 @@ {5DC7DC72-E57B-418C-ABE7-8A26B28AD953} Library Properties - System.IO.BACnet + System.IO.BACnet.Tests BACnet.Tests v4.0 512 @@ -36,11 +36,20 @@ + + ..\packages\System.ValueTuple.4.4.0\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll + + + + + + + diff --git a/Tests/Base/BacnetBitStringTests.cs b/Tests/Base/BacnetBitStringTests.cs index 577b8175..8032d2bc 100644 --- a/Tests/Base/BacnetBitStringTests.cs +++ b/Tests/Base/BacnetBitStringTests.cs @@ -1,7 +1,10 @@ -using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; -namespace System.IO.BACnet.Base +namespace System.IO.BACnet.Tests.Base { + [TestFixture] public class BacnetBitStringTests { [Test] @@ -9,8 +12,7 @@ public void should_set_bit_test() { for (byte i = 0; i < 32; i++) { - var bitString = new BacnetBitString(); - bitString.SetBit(i, true); + var bitString = new BacnetBitString().SetBit(i, true); Assert.AreEqual(true, bitString.GetBit(i)); } } @@ -18,9 +20,8 @@ public void should_set_bit_test() [Test] public void should_calculate_bits_used_when_setting_bit_to_false_test() { - var bitString = new BacnetBitString(); - bitString.SetBit(2, false); - Assert.AreEqual(3, bitString.bits_used); + var bitString = new BacnetBitString().SetBit(2, false); + Assert.AreEqual(3, bitString.BitsUsed); } [Test] @@ -28,9 +29,8 @@ public void should_calculate_bits_used_test() { for (byte i = 0; i < 32; i++) { - var bitString = new BacnetBitString(); - bitString.SetBit(i, true); - Assert.AreEqual(i + 1, bitString.bits_used); + var bitString = new BacnetBitString().SetBit(i, true); + Assert.AreEqual(i + 1, bitString.BitsUsed); } } @@ -39,8 +39,7 @@ public void should_convert_to_uint_test() { for (byte i = 0; i < 32; i++) { - var bitString = new BacnetBitString(); - bitString.SetBit(i, true); + var bitString = new BacnetBitString().SetBit(i, true); Assert.AreEqual((uint)(1 << i), bitString.ConvertToInt()); } } @@ -60,7 +59,7 @@ public void should_respect_provided_bits_used_value_whe_converting_from_uint_tes { const byte bitsUsed = 3; var bitString = BacnetBitString.ConvertFromInt(0, bitsUsed); - Assert.AreEqual(bitsUsed, bitString.bits_used); + Assert.AreEqual(bitsUsed, bitString.BitsUsed); } [TestCase((uint)0b00000000000000000000000000000000, 0)] @@ -73,7 +72,7 @@ public void should_respect_provided_bits_used_value_whe_converting_from_uint_tes public void should_calculate_bits_used_when_converting_from_uint_test(uint value, byte bitsUsed) { var bitString = BacnetBitString.ConvertFromInt(value); - Assert.AreEqual(bitsUsed, bitString.bits_used); + Assert.AreEqual(bitsUsed, bitString.BitsUsed); } [TestCase((uint)0b00000000000000000000000000000000)] @@ -83,10 +82,49 @@ public void should_calculate_bits_used_when_converting_from_uint_test(uint value [TestCase(0b10101010101010101010101010101010)] [TestCase(0b10000000000000000000000000000000)] [TestCase(0b11111111111111111111111111111111)] + [TestCase((uint)0b00001000)] public void should_convert_back_to_uint_when_converting_from_uint_test(uint value) { var bitString = BacnetBitString.ConvertFromInt(value); Assert.AreEqual(value, bitString.ConvertToInt()); } + + [Test] + public void should_be_equal_when_parsed_from_same_string() + { + // arrange + const string stringForInit = "010101110010011"; + + // act + var bs1 = BacnetBitString.Parse(stringForInit); + var bs2 = BacnetBitString.Parse(stringForInit); + + // assert + Assert.That(bs1, Is.EqualTo(bs2)); + Assert.That(bs1.Value, Is.Not.SameAs(bs2.Value)); + } + + [Test] + public void should_not_break_hashset() + { + // arrange + const string stringForInit = "010101110010011"; + var bs1 = BacnetBitString.Parse(stringForInit); + var bs2 = BacnetBitString.Parse(stringForInit); + var hash = new HashSet(); + + // act + hash.Add(bs1); + + // assert + Assert.That(hash.Add(bs2), Is.False); + + // act + bs1 = bs1.SetBit(0, true); + + // assert + Assert.That(hash.Add(bs1), Is.True); + Assert.That(hash.First(), Is.Not.EqualTo(hash.Last())); + } } } \ No newline at end of file diff --git a/Tests/Base/EventNotification/EventNotificationTests.cs b/Tests/Base/EventNotification/EventNotificationTests.cs new file mode 100644 index 00000000..c993a6a0 --- /dev/null +++ b/Tests/Base/EventNotification/EventNotificationTests.cs @@ -0,0 +1,61 @@ +using System.IO.BACnet.EventNotification; +using System.IO.BACnet.EventNotification.EventValues; +using NUnit.Framework; + +namespace System.IO.BACnet.Tests.Base.EventNotification +{ + [TestFixture] + public class EventNotificationTests + { + [TestCase(typeof(StateTransition))] + [TestCase(typeof(NotificationData))] + public void should_override_tostring(Type type) + { + var args = type.GetGenericArguments(); + + var instance = args.Length > 0 + ? Activator.CreateInstance(type, Activator.CreateInstance(args[0])) + : Activator.CreateInstance(type); + + Assert.That(instance.ToString(), Is.Not.EqualTo(type.ToString())); + } + + [Test] + public void should_raise_oneventnotify_when_sending_changeoflifesafety_data() + { + // arrange + var (client1, client2) = Helper.CreateConnectedClients(); + StateTransition receivedData = null; + client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; + + var sentData = new StateTransition(new ChangeOfLifeSafety() + { + NewMode = BacnetLifeSafetyModes.LIFE_SAFETY_MODE_DEFAULT, + NewState = BacnetLifeSafetyStates.LIFE_SAFETY_STATE_QUIET, + OperationExpected = BacnetLifeSafetyOperations.LIFE_SAFETY_OP_NONE, + StatusFlags = BacnetBitString.Parse("01") + }) + { + AckRequired = false, + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), + EventType = BacnetEventTypes.EVENT_CHANGE_OF_LIFE_SAFETY, + FromState = BacnetEventStates.EVENT_STATE_OFFNORMAL, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1), + MessageText = "Dummy Operation", + NotificationClass = 10, + NotifyType = BacnetNotifyTypes.NOTIFY_EVENT, + Priority = 1, + TimeStamp = new BacnetGenericTime(new DateTime(2018, 2, 22, 16, 14, 15), BacnetTimestampTags.TIME_STAMP_DATETIME), + ProcessIdentifier = 1, + ToState = BacnetEventStates.EVENT_STATE_NORMAL + }; + + // act + client1.SendUnconfirmedEventNotification(Helper.DummyAddress, sentData); + + // assert + Assert.That(receivedData, Is.Not.SameAs(sentData)); + Helper.AssertPropertiesAndFieldsAreEqual(sentData, receivedData); + } + } +} diff --git a/Tests/Helper.cs b/Tests/Helper.cs new file mode 100644 index 00000000..566e28f2 --- /dev/null +++ b/Tests/Helper.cs @@ -0,0 +1,53 @@ +using System.Collections; +using NUnit.Framework; + +namespace System.IO.BACnet.Tests +{ + public static class Helper + { + public static readonly BacnetAddress DummyAddress = new BacnetAddress(BacnetAddressTypes.None, 0, new byte[] { 42 }); + + public static (BacnetClient, BacnetClient) CreateConnectedClients() + { + var transport1 = new InMemoryTransport(); + var client1 = new BacnetClient(transport1); + var transport2 = new InMemoryTransport(); + var client2 = new BacnetClient(transport2); + + transport1.BytesSent += transport2.ReceiveBytes; + transport2.BytesSent += transport1.ReceiveBytes; + + client1.Start(); + client2.Start(); + + return (client1, client2); + } + + public static void AssertPropertiesAndFieldsAreEqual(object expected, object actual) + { + var t = expected.GetType(); + + foreach (var pi in t.GetProperties()) + { + if (pi.PropertyType.IsValueType || pi.PropertyType == typeof(string)) + Assert.AreEqual(pi.GetValue(expected, null), pi.GetValue(actual, null), "Property: " + pi.Name); + else + AssertPropertiesAndFieldsAreEqual(pi.GetValue(expected, null), pi.GetValue(actual, null)); + } + foreach (var fi in t.GetFields()) + { + var expectedValue = fi.GetValue(expected); + + if (expectedValue is IList expectedList) + { + for (var i = 0; i < expectedList.Count; i++) + { + AssertPropertiesAndFieldsAreEqual(expectedList[i], ((IList)fi.GetValue(actual))[i]); + } + } + else + Assert.AreEqual(expectedValue, fi.GetValue(actual), "Field: " + fi.Name); + } + } + } +} diff --git a/Tests/InMemoryTransport.cs b/Tests/InMemoryTransport.cs new file mode 100644 index 00000000..22473ac2 --- /dev/null +++ b/Tests/InMemoryTransport.cs @@ -0,0 +1,43 @@ +using System.Linq; + +namespace System.IO.BACnet.Tests +{ + class InMemoryTransport : IBacnetTransport + { + public void Dispose() + { + throw new NotImplementedException(); + } + + public byte MaxInfoFrames { get; set; } + public int HeaderLength { get; } + public int MaxBufferLength => 1024; + public BacnetAddressTypes Type => BacnetAddressTypes.None; + public BacnetMaxAdpu MaxAdpuLength { get; } + public void Start() + { + // nothing to do + } + + public BacnetAddress GetBroadcastAddress() + => new BacnetAddress(Type, 0, new byte[] {42}); + + public bool WaitForAllTransmits(int timeout) + { + throw new NotImplementedException(); + } + + public int Send(byte[] buffer, int offset, int dataLength, BacnetAddress address, bool waitForTransmission, int timeout) + { + BytesSent?.Invoke(address, buffer.Skip(offset).Take(dataLength).ToArray()); + return dataLength; + } + + public void ReceiveBytes(BacnetAddress address, byte[] bytes) + => MessageRecieved?.Invoke(this, bytes, 0, bytes.Length, address); + + public event MessageRecievedHandler MessageRecieved; + + public event Action BytesSent; + } +} diff --git a/Tests/Serialize/ASN1Tests.cs b/Tests/Serialize/ASN1Tests.cs new file mode 100644 index 00000000..c9c18bc0 --- /dev/null +++ b/Tests/Serialize/ASN1Tests.cs @@ -0,0 +1,29 @@ +using System.IO.BACnet.Serialize; +using NUnit.Framework; + +namespace System.IO.BACnet.Tests.Serialize +{ + [TestFixture] + public class ASN1Tests + { + [TestCase("2018-02-26", 1)] + [TestCase("2018-02-27", 2)] + [TestCase("2018-02-28", 3)] + [TestCase("2018-03-01", 4)] + [TestCase("2018-03-02", 5)] + [TestCase("2018-03-03", 6)] + [TestCase("2018-03-04", 7)] + public void should_encode_dayofweek_according_to_standard(DateTime value, byte expectedDayOfWeek) // 1 = Monday .. 7 = Sunday + { + // arrange + var buffer = new EncodeBuffer(); + + // act + ASN1.encode_bacnet_date(buffer, value); + + // assert + Assert.That(buffer.GetLength(), Is.EqualTo(4)); + Assert.That(buffer.buffer[3], Is.EqualTo(expectedDayOfWeek)); + } + } +} diff --git a/Tests/Serialize/ServicesTests.cs b/Tests/Serialize/ServicesTests.cs new file mode 100644 index 00000000..c3ad570c --- /dev/null +++ b/Tests/Serialize/ServicesTests.cs @@ -0,0 +1,65 @@ +using System.IO.BACnet.Serialize; +using NUnit.Framework; + +namespace System.IO.BACnet.Tests.Serialize +{ + [TestFixture] + public class ServicesTests + { + [Test] + public void should_encode_logrecord_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, new DateTime(1998,3,23,19,54,27), 0); + var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, new DateTime(1998,3,23,19,56,27), 0); + + // example taken from ANNEX F - Examples of APDU Encoding + var expectedBytes = new byte[] + { + 0x0E, 0xA4, 0x62, 0x03, 0x17, 0x01, 0xB4, 0x13, 0x36, 0x1B, 0x00, 0x0F, + 0x1E, 0x2C, 0x41, 0x90, 0x00, 0x00, 0x1F, 0x2A, 0x04, 0x00, 0x0E, 0xA4, + 0x62, 0x03, 0x17, 0x01, 0xB4, 0x13, 0x38, 0x1B, 0x00, 0x0F, 0x1E, 0x2C, + 0x41, 0x90, 0xCC, 0xCD, 0x1F, 0x2A, 0x04, 0x00 + }; + + // act + Services.EncodeLogRecord(buffer, record1); + Services.EncodeLogRecord(buffer, record2); + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_decode_multiple_logrecords() + { + // arrange + var buffer = new EncodeBuffer(); + var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, new DateTime(1998, 3, 23, 19, 54, 27), 0); + var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, new DateTime(1998, 3, 23, 19, 56, 27), 0); + + // act + Services.EncodeLogRecord(buffer, record1); + Services.EncodeLogRecord(buffer, record2); + Services.DecodeLogRecord(buffer.buffer, 0, buffer.GetLength(), 2, out var decodedRecords); + + /* + * Debug - write packet to network to analyze in WireShark + * + + var client = new BacnetClient(new BacnetIpUdpProtocolTransport(48708, true)); + client.Start(); + client.ReadRangeResponse(new BacnetAddress(BacnetAddressTypes.IP, "192.168.1.99"), 42, null, + new BacnetObjectId(BacnetObjectTypes.OBJECT_TRENDLOG, 1), + new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_LOG_BUFFER, ASN1.BACNET_ARRAY_ALL), + BacnetResultFlags.LAST_ITEM, 2, buffer.ToArray(), BacnetReadRangeRequestTypes.RR_BY_TIME, 79201); + */ + + // assert + Helper.AssertPropertiesAndFieldsAreEqual(record1, decodedRecords[0]); + Helper.AssertPropertiesAndFieldsAreEqual(record2, decodedRecords[1]); + } + } +} diff --git a/Tests/packages.config b/Tests/packages.config index 82758b0e..2e6209b3 100644 --- a/Tests/packages.config +++ b/Tests/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file From 72019855bd54b227b86469710e73cf3dc2982651 Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Mon, 26 Feb 2018 14:25:28 +0100 Subject: [PATCH 13/31] Added tests for more EventValues --- Base/BacnetBitString.cs | 3 +- .../EventValues/BufferReady.cs | 2 + .../EventValues/ChangeOfBitString.cs | 2 + .../EventValues/ChangeOfLifeSafety.cs | 2 + .../EventValues/ChangeOfState.cs | 2 + .../EventValues/ChangeOfValue.cs | 2 + .../EventValues/EventValuesBase.cs | 1 + .../EventValues/FloatingLimit.cs | 2 + .../EventValues/OutOfRange.cs | 2 + .../EventValues/UnsignedRange.cs | 2 + Base/EventNotification/StateTransition.cs | 10 +- Serialize/ASN1.cs | 47 +-- Serialize/Services.cs | 21 +- .../EventNotificationTests.cs | 304 +++++++++++++++++- Tests/Helper.cs | 8 + 15 files changed, 379 insertions(+), 31 deletions(-) diff --git a/Base/BacnetBitString.cs b/Base/BacnetBitString.cs index accac10a..3d81123e 100644 --- a/Base/BacnetBitString.cs +++ b/Base/BacnetBitString.cs @@ -96,7 +96,8 @@ public static BacnetBitString ConvertFromInt(uint value, byte? bitsUsed = null) public bool Equals(BacnetBitString other) { - return BitsUsed == other.BitsUsed && Value.SequenceEqual(other.Value); + return BitsUsed == other.BitsUsed + && (BitsUsed == 0 || Value.SequenceEqual(other.Value)); } public override bool Equals(object obj) diff --git a/Base/EventNotification/EventValues/BufferReady.cs b/Base/EventNotification/EventValues/BufferReady.cs index 15c7aa5a..751c2999 100644 --- a/Base/EventNotification/EventValues/BufferReady.cs +++ b/Base/EventNotification/EventValues/BufferReady.cs @@ -2,6 +2,8 @@ namespace System.IO.BACnet.EventNotification.EventValues { public class BufferReady : EventValuesBase { + public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_BUFFER_READY; + public BacnetDeviceObjectPropertyReference BufferProperty { get; set; } public uint PreviousNotification { get; set; } public uint CurrentNotification { get; set; } diff --git a/Base/EventNotification/EventValues/ChangeOfBitString.cs b/Base/EventNotification/EventValues/ChangeOfBitString.cs index e8263ad8..1138d966 100644 --- a/Base/EventNotification/EventValues/ChangeOfBitString.cs +++ b/Base/EventNotification/EventValues/ChangeOfBitString.cs @@ -2,6 +2,8 @@ namespace System.IO.BACnet.EventNotification.EventValues { public class ChangeOfBitString : EventValuesBase { + public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_CHANGE_OF_BITSTRING; + public BacnetBitString ReferencedBitString { get; set; } public BacnetBitString StatusFlags { get; set; } } diff --git a/Base/EventNotification/EventValues/ChangeOfLifeSafety.cs b/Base/EventNotification/EventValues/ChangeOfLifeSafety.cs index 3e513e13..4442949f 100644 --- a/Base/EventNotification/EventValues/ChangeOfLifeSafety.cs +++ b/Base/EventNotification/EventValues/ChangeOfLifeSafety.cs @@ -2,6 +2,8 @@ namespace System.IO.BACnet.EventNotification.EventValues { public class ChangeOfLifeSafety : EventValuesBase { + public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_CHANGE_OF_LIFE_SAFETY; + public BacnetLifeSafetyStates NewState { get; set; } public BacnetLifeSafetyModes NewMode { get; set; } public BacnetBitString StatusFlags { get; set; } diff --git a/Base/EventNotification/EventValues/ChangeOfState.cs b/Base/EventNotification/EventValues/ChangeOfState.cs index c594630f..f6f96577 100644 --- a/Base/EventNotification/EventValues/ChangeOfState.cs +++ b/Base/EventNotification/EventValues/ChangeOfState.cs @@ -2,6 +2,8 @@ namespace System.IO.BACnet.EventNotification.EventValues { public class ChangeOfState : EventValuesBase { + public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_CHANGE_OF_STATE; + public BacnetPropertyState NewState { get; set; } public BacnetBitString StatusFlags { get; set; } } diff --git a/Base/EventNotification/EventValues/ChangeOfValue.cs b/Base/EventNotification/EventValues/ChangeOfValue.cs index 9668eaf7..0542ec5e 100644 --- a/Base/EventNotification/EventValues/ChangeOfValue.cs +++ b/Base/EventNotification/EventValues/ChangeOfValue.cs @@ -2,6 +2,8 @@ namespace System.IO.BACnet.EventNotification.EventValues { public class ChangeOfValue : EventValuesBase { + public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_CHANGE_OF_VALUE; + public BacnetBitString ChangedBits { get; set; } public float ChangeValue { get; set; } public BacnetCOVTypes Tag { get; set; } diff --git a/Base/EventNotification/EventValues/EventValuesBase.cs b/Base/EventNotification/EventValues/EventValuesBase.cs index 394bd537..51711dc6 100644 --- a/Base/EventNotification/EventValues/EventValuesBase.cs +++ b/Base/EventNotification/EventValues/EventValuesBase.cs @@ -2,5 +2,6 @@ { public abstract class EventValuesBase { + public abstract BacnetEventTypes EventType { get; } } } diff --git a/Base/EventNotification/EventValues/FloatingLimit.cs b/Base/EventNotification/EventValues/FloatingLimit.cs index 98f5448c..e5ec45ea 100644 --- a/Base/EventNotification/EventValues/FloatingLimit.cs +++ b/Base/EventNotification/EventValues/FloatingLimit.cs @@ -2,6 +2,8 @@ namespace System.IO.BACnet.EventNotification.EventValues { public class FloatingLimit : EventValuesBase { + public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_FLOATING_LIMIT; + public float ReferenceValue { get; set; } public BacnetBitString StatusFlags { get; set; } public float SetPointValue { get; set; } diff --git a/Base/EventNotification/EventValues/OutOfRange.cs b/Base/EventNotification/EventValues/OutOfRange.cs index ef022961..a77107e4 100644 --- a/Base/EventNotification/EventValues/OutOfRange.cs +++ b/Base/EventNotification/EventValues/OutOfRange.cs @@ -2,6 +2,8 @@ namespace System.IO.BACnet.EventNotification.EventValues { public class OutOfRange : EventValuesBase { + public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_OUT_OF_RANGE; + public float ExceedingValue { get; set; } public BacnetBitString StatusFlags { get; set; } public float Deadband { get; set; } diff --git a/Base/EventNotification/EventValues/UnsignedRange.cs b/Base/EventNotification/EventValues/UnsignedRange.cs index b488eac5..2395ab70 100644 --- a/Base/EventNotification/EventValues/UnsignedRange.cs +++ b/Base/EventNotification/EventValues/UnsignedRange.cs @@ -2,6 +2,8 @@ namespace System.IO.BACnet.EventNotification.EventValues { public class UnsignedRange : EventValuesBase { + public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_UNSIGNED_RANGE; + public uint ExceedingValue { get; set; } public BacnetBitString StatusFlags { get; set; } public uint ExceededLimit { get; set; } diff --git a/Base/EventNotification/StateTransition.cs b/Base/EventNotification/StateTransition.cs index 1eafa0eb..df0dad9c 100644 --- a/Base/EventNotification/StateTransition.cs +++ b/Base/EventNotification/StateTransition.cs @@ -6,9 +6,14 @@ namespace System.IO.BACnet.EventNotification { public class StateTransition : NotificationData { - public BacnetEventTypes EventType { get; set; } + public BacnetEventTypes EventType { get; } public bool AckRequired { get; set; } public BacnetEventStates FromState { get; set; } + + public StateTransition(BacnetEventTypes eventType) + { + EventType = eventType; + } } public class StateTransition : StateTransition @@ -17,8 +22,9 @@ public class StateTransition : StateTransition public TEventValuesBase EventValues { get; } public StateTransition(TEventValuesBase eventValues) + : base((eventValues ?? throw new ArgumentNullException(nameof(eventValues))).EventType) { - EventValues = eventValues ?? throw new ArgumentNullException(nameof(eventValues)); + EventValues = eventValues; } public override string ToString() diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index d67e94e4..ccb9cdd4 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -538,8 +538,9 @@ public static void bacapp_encode_context_device_obj_property_ref(EncodeBuffer bu encode_closing_tag(buffer, tagNumber); } - public static void bacapp_encode_property_state(EncodeBuffer buffer, BacnetPropertyState value) + public static void encode_context_property_state(EncodeBuffer buffer, byte tagNumber, BacnetPropertyState value) { + encode_opening_tag(buffer, tagNumber); switch (value.tag) { case BacnetPropertyState.BacnetPropertyStateTypes.BOOLEAN_VALUE: @@ -599,9 +600,9 @@ public static void bacapp_encode_property_state(EncodeBuffer buffer, BacnetPrope break; default: - /* FIXME: assert(0); - return a negative len? */ - break; + throw new ArgumentOutOfRangeException($"tag {value.tag} is not supported"); } + encode_closing_tag(buffer, tagNumber); } public static void encode_context_bitstring(EncodeBuffer buffer, byte tagNumber, BacnetBitString bitString) @@ -1063,7 +1064,21 @@ public static int decode_read_access_specification(byte[] buffer, int offset, in return len; } - public static int decode_device_obj_property_ref(byte[] buffer, int offset, int apdu_len, out BacnetDeviceObjectPropertyReference value) + public static int decode_context_device_obj_property_ref(byte[] buffer, int offset, byte tagNumber, out BacnetDeviceObjectPropertyReference value) + { + if (decode_is_context_tag_with_length(buffer, offset, tagNumber, out var len)) + { + len += decode_device_obj_property_ref(buffer, offset + len, out value); + } + else + { + value = default; + } + + return ++len; // closing tag + } + + public static int decode_device_obj_property_ref(byte[] buffer, int offset, out BacnetDeviceObjectPropertyReference value) { var len = 0; @@ -1981,7 +1996,7 @@ public static int bacapp_decode_context_application_data(BacnetAddress address, propertyId == BacnetPropertyIds.PROP_LOG_DEVICE_OBJECT_PROPERTY || propertyId == BacnetPropertyIds.PROP_OBJECT_PROPERTY_REFERENCE) { - tagLen = decode_device_obj_property_ref(buffer, offset, maxOffset, out var v); + tagLen = decode_device_obj_property_ref(buffer, offset, out var v); if (tagLen < 0) return -1; value.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE; value.Value = v; @@ -2353,17 +2368,11 @@ public static int decode_cov_subscription(byte[] buffer, int offset, int apduLen public static int decode_context_property_state(byte[] buffer, int offset, byte tagNumber, out BacnetPropertyState value) { if (!decode_is_opening_tag_number(buffer, offset, tagNumber)) - { - value = default(BacnetPropertyState); - return -1; - } + throw new InvalidOperationException($"expected tag {tagNumber} not found"); var len = 1; - var sectionLength = decode_property_state(buffer, offset + len, out value); - if (sectionLength == -1) - return -1; + len += decode_property_state(buffer, offset + len, out value); - len += sectionLength; if (!decode_is_closing_tag_number(buffer, offset + len, tagNumber)) return -1; @@ -2372,17 +2381,18 @@ public static int decode_context_property_state(byte[] buffer, int offset, byte public static int decode_property_state(byte[] buffer, int offset, out BacnetPropertyState value) { - value = default(BacnetPropertyState); + value = default; var len = decode_tag_number_and_value(buffer, offset, out value.tag, out uint lenValueType); if (len == -1) - return -1; + throw new InvalidOperationException($"expected tag {value.tag} not found"); var sectionLength = 0; switch (value.tag) { case BacnetPropertyState.BacnetPropertyStateTypes.BOOLEAN_VALUE: - value.state.boolean_value = lenValueType == 1; + sectionLength = 1; + value.state.boolean_value = buffer[offset+len] == 1; break; case BacnetPropertyState.BacnetPropertyStateTypes.BINARY_VALUE: @@ -2410,7 +2420,7 @@ public static int decode_property_state(byte[] buffer, int offset, out BacnetPro break; default: - return -1; + throw new ArgumentOutOfRangeException($"tag {value.tag} is not supported"); } return len + sectionLength; @@ -2444,8 +2454,7 @@ public static int decode_context_unsigned(byte[] buffer, int offset, byte tagNum { if (!decode_is_context_tag(buffer, offset, tagNumber) || decode_is_closing_tag(buffer, offset)) { - value = 0; - return -1; + throw new InvalidOperationException($"expected tag {tagNumber} not found"); } var len = decode_tag_number_and_value(buffer, offset, out _, out var lenValue); diff --git a/Serialize/Services.cs b/Serialize/Services.cs index 7b54c2ef..ab0a50e7 100644 --- a/Serialize/Services.cs +++ b/Serialize/Services.cs @@ -838,7 +838,6 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out var eventTypeValue); - decodedStateTransition.Add(e => e.EventType = eventTypeValue); eventType = eventTypeValue; } //else @@ -904,7 +903,7 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, } eventData = eventData ?? (decodedStateTransition.Any() - ? new StateTransition() + ? new StateTransition(eventType ?? throw new ArgumentNullException($"Need eventType to initialize StateTransition")) : new NotificationData()); if (eventData is StateTransition stateTransition) @@ -984,7 +983,7 @@ private static bool DecodeEventValues(byte[] buffer, int offset, BacnetEventType return false; len++; - len += ASN1.decode_context_bitstring(buffer, offset + len, 0, out var changeOfValueStatusFlags); + len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var changeOfValueStatusFlags); ((ChangeOfValue)eventValues).StatusFlags = changeOfValueStatusFlags; break; @@ -1031,7 +1030,15 @@ private static bool DecodeEventValues(byte[] buffer, int offset, BacnetEventType break; case BacnetEventTypes.EVENT_BUFFER_READY: - // Too lazy for this one and not sure if really needed, somebody want to do it ? :) + len += ASN1.decode_context_device_obj_property_ref(buffer, offset + len, 0, out var bufferProperty); + len += ASN1.decode_context_unsigned(buffer, offset + len, 1, out var previousNotification); + len += ASN1.decode_context_unsigned(buffer, offset + len, 2, out var currentNotification); + eventValues = new BufferReady + { + BufferProperty = bufferProperty, + CurrentNotification = currentNotification, + PreviousNotification = previousNotification + }; break; case BacnetEventTypes.EVENT_UNSIGNED_RANGE: @@ -1047,7 +1054,7 @@ private static bool DecodeEventValues(byte[] buffer, int offset, BacnetEventType break; default: - return false; + throw new ArgumentOutOfRangeException($"Event-Type {eventType} is not supported"); } if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, (byte)eventType)) @@ -1128,9 +1135,7 @@ public static void EncodeEventNotifyData(EncodeBuffer buffer, NotificationData d case StateTransition changeOfState: ASN1.encode_opening_tag(buffer, 1); - ASN1.encode_opening_tag(buffer, 0); - ASN1.bacapp_encode_property_state(buffer, changeOfState.EventValues.NewState); - ASN1.encode_closing_tag(buffer, 0); + ASN1.encode_context_property_state(buffer, 0, changeOfState.EventValues.NewState); ASN1.encode_context_bitstring(buffer, 1, changeOfState.EventValues.StatusFlags); ASN1.encode_closing_tag(buffer, 1); break; diff --git a/Tests/Base/EventNotification/EventNotificationTests.cs b/Tests/Base/EventNotification/EventNotificationTests.cs index c993a6a0..00f63f88 100644 --- a/Tests/Base/EventNotification/EventNotificationTests.cs +++ b/Tests/Base/EventNotification/EventNotificationTests.cs @@ -7,6 +7,13 @@ namespace System.IO.BACnet.Tests.Base.EventNotification [TestFixture] public class EventNotificationTests { + [TestCase(typeof(StateTransition))] + [TestCase(typeof(StateTransition))] + [TestCase(typeof(StateTransition))] + [TestCase(typeof(StateTransition))] + [TestCase(typeof(StateTransition))] + [TestCase(typeof(StateTransition))] + [TestCase(typeof(StateTransition))] [TestCase(typeof(StateTransition))] [TestCase(typeof(NotificationData))] public void should_override_tostring(Type type) @@ -38,7 +45,6 @@ public void should_raise_oneventnotify_when_sending_changeoflifesafety_data() { AckRequired = false, EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), - EventType = BacnetEventTypes.EVENT_CHANGE_OF_LIFE_SAFETY, FromState = BacnetEventStates.EVENT_STATE_OFFNORMAL, InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1), MessageText = "Dummy Operation", @@ -57,5 +63,301 @@ public void should_raise_oneventnotify_when_sending_changeoflifesafety_data() Assert.That(receivedData, Is.Not.SameAs(sentData)); Helper.AssertPropertiesAndFieldsAreEqual(sentData, receivedData); } + + [Test] + public void should_raise_oneventnotify_when_sending_changeofbitstring_data() + { + // arrange + var (client1, client2) = Helper.CreateConnectedClients(); + StateTransition receivedData = null; + client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; + + var sentData = new StateTransition(new ChangeOfBitString() + { + ReferencedBitString = BacnetBitString.Parse("101"), + StatusFlags = BacnetBitString.Parse("010") + }) + { + AckRequired = false, + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), + FromState = BacnetEventStates.EVENT_STATE_NORMAL, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1), + MessageText = "Dummy Operation", + NotificationClass = 10, + NotifyType = BacnetNotifyTypes.NOTIFY_EVENT, + Priority = 1, + TimeStamp = new BacnetGenericTime(new DateTime(2018, 2, 22, 16, 14, 15), BacnetTimestampTags.TIME_STAMP_DATETIME), + ProcessIdentifier = 1, + ToState = BacnetEventStates.EVENT_STATE_NORMAL + }; + + // act + client1.SendUnconfirmedEventNotification(Helper.DummyAddress, sentData); + + // assert + Assert.That(receivedData, Is.Not.SameAs(sentData)); + Helper.AssertPropertiesAndFieldsAreEqual(sentData, receivedData); + } + + [Test] + public void should_raise_oneventnotify_when_sending_unsignedrange_data() + { + // arrange + var (client1, client2) = Helper.CreateConnectedClients(); + StateTransition receivedData = null; + client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; + + var sentData = new StateTransition(new UnsignedRange() + { + ExceededLimit = 100, + ExceedingValue = 110, + StatusFlags = BacnetBitString.Parse("010") + }) + { + AckRequired = false, + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), + FromState = BacnetEventStates.EVENT_STATE_NORMAL, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1), + MessageText = "Dummy Operation", + NotificationClass = 10, + NotifyType = BacnetNotifyTypes.NOTIFY_EVENT, + Priority = 1, + TimeStamp = new BacnetGenericTime(new DateTime(2018, 2, 22, 16, 14, 15), BacnetTimestampTags.TIME_STAMP_DATETIME), + ProcessIdentifier = 1, + ToState = BacnetEventStates.EVENT_STATE_NORMAL + }; + + // act + client1.SendUnconfirmedEventNotification(Helper.DummyAddress, sentData); + + // assert + Assert.That(receivedData, Is.Not.SameAs(sentData)); + Helper.AssertPropertiesAndFieldsAreEqual(sentData, receivedData); + } + + [Test] + public void should_raise_oneventnotify_when_sending_outofrange_data() + { + // arrange + var (client1, client2) = Helper.CreateConnectedClients(); + StateTransition receivedData = null; + client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; + + var sentData = new StateTransition(new OutOfRange + { + ExceededLimit = float.MinValue, + ExceedingValue = float.MaxValue, + Deadband = (float)17.01, + StatusFlags = BacnetBitString.Parse("010") + }) + { + AckRequired = false, + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), + FromState = BacnetEventStates.EVENT_STATE_NORMAL, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1), + MessageText = "Dummy Operation", + NotificationClass = 10, + NotifyType = BacnetNotifyTypes.NOTIFY_EVENT, + Priority = 1, + TimeStamp = new BacnetGenericTime(new DateTime(2018, 2, 22, 16, 14, 15), BacnetTimestampTags.TIME_STAMP_DATETIME), + ProcessIdentifier = 1, + ToState = BacnetEventStates.EVENT_STATE_NORMAL + }; + + // act + client1.SendUnconfirmedEventNotification(Helper.DummyAddress, sentData); + + // assert + Assert.That(receivedData, Is.Not.SameAs(sentData)); + Helper.AssertPropertiesAndFieldsAreEqual(sentData, receivedData); + } + + [TestCase(true)] + [TestCase(false)] + public void should_raise_oneventnotify_when_sending_changeofstate_data_bool(bool value) + { + // arrange + var (client1, client2) = Helper.CreateConnectedClients(); + StateTransition receivedData = null; + client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; + + var sentData = new StateTransition(new ChangeOfState() + { + NewState = new BacnetPropertyState + { + tag = BacnetPropertyState.BacnetPropertyStateTypes.BOOLEAN_VALUE, + state = new BacnetPropertyState.State() { boolean_value = value } + }, + StatusFlags = BacnetBitString.Parse("010") + }) + { + AckRequired = false, + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), + FromState = BacnetEventStates.EVENT_STATE_NORMAL, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1), + MessageText = "Dummy Operation", + NotificationClass = 10, + NotifyType = BacnetNotifyTypes.NOTIFY_EVENT, + Priority = 1, + TimeStamp = new BacnetGenericTime(new DateTime(2018, 2, 22, 16, 14, 15), BacnetTimestampTags.TIME_STAMP_DATETIME), + ProcessIdentifier = 1, + ToState = BacnetEventStates.EVENT_STATE_NORMAL + }; + + // act + client1.SendUnconfirmedEventNotification(Helper.DummyAddress, sentData); + + // assert + Assert.That(receivedData, Is.Not.SameAs(sentData)); + Helper.AssertPropertiesAndFieldsAreEqual(sentData, receivedData); + } + + [Test] + public void should_raise_oneventnotify_when_sending_changeofvalue_data_real() + { + // arrange + var (client1, client2) = Helper.CreateConnectedClients(); + StateTransition receivedData = null; + client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; + + var sentData = new StateTransition(new ChangeOfValue() + { + Tag = BacnetCOVTypes.CHANGE_OF_VALUE_REAL, + ChangeValue = (float)123.456, + StatusFlags = BacnetBitString.Parse("010") + }) + { + AckRequired = false, + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), + FromState = BacnetEventStates.EVENT_STATE_NORMAL, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1), + MessageText = "Dummy Operation", + NotificationClass = 10, + NotifyType = BacnetNotifyTypes.NOTIFY_EVENT, + Priority = 1, + TimeStamp = new BacnetGenericTime(new DateTime(2018, 2, 22, 16, 14, 15), BacnetTimestampTags.TIME_STAMP_DATETIME), + ProcessIdentifier = 1, + ToState = BacnetEventStates.EVENT_STATE_NORMAL + }; + + // act + client1.SendUnconfirmedEventNotification(Helper.DummyAddress, sentData); + + // assert + Assert.That(receivedData, Is.Not.SameAs(sentData)); + Helper.AssertPropertiesAndFieldsAreEqual(sentData, receivedData); + } + + [Test] + public void should_raise_oneventnotify_when_sending_changeofvalue_data_bits() + { + // arrange + var (client1, client2) = Helper.CreateConnectedClients(); + StateTransition receivedData = null; + client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; + + var sentData = new StateTransition(new ChangeOfValue() + { + Tag = BacnetCOVTypes.CHANGE_OF_VALUE_BITS, + ChangedBits = BacnetBitString.Parse("101"), + StatusFlags = BacnetBitString.Parse("010") + }) + { + AckRequired = false, + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), + FromState = BacnetEventStates.EVENT_STATE_NORMAL, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1), + MessageText = "Dummy Operation", + NotificationClass = 10, + NotifyType = BacnetNotifyTypes.NOTIFY_EVENT, + Priority = 1, + TimeStamp = new BacnetGenericTime(new DateTime(2018, 2, 22, 16, 14, 15), BacnetTimestampTags.TIME_STAMP_DATETIME), + ProcessIdentifier = 1, + ToState = BacnetEventStates.EVENT_STATE_NORMAL + }; + + // act + client1.SendUnconfirmedEventNotification(Helper.DummyAddress, sentData); + + // assert + Assert.That(receivedData, Is.Not.SameAs(sentData)); + Helper.AssertPropertiesAndFieldsAreEqual(sentData, receivedData); + } + + [Test] + public void should_raise_oneventnotify_when_sending_bufferready_data() + { + // arrange + var (client1, client2) = Helper.CreateConnectedClients(); + StateTransition receivedData = null; + client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; + + var sentData = new StateTransition(new BufferReady + { + BufferProperty = new BacnetDeviceObjectPropertyReference( + new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), + BacnetPropertyIds.PROP_PRESENT_VALUE, + new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1)), + CurrentNotification = 5, + PreviousNotification = 4 + }) + { + AckRequired = false, + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), + FromState = BacnetEventStates.EVENT_STATE_NORMAL, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1), + MessageText = "Dummy Operation", + NotificationClass = 10, + NotifyType = BacnetNotifyTypes.NOTIFY_EVENT, + Priority = 1, + TimeStamp = new BacnetGenericTime(new DateTime(2018, 2, 22, 16, 14, 15), BacnetTimestampTags.TIME_STAMP_DATETIME), + ProcessIdentifier = 1, + ToState = BacnetEventStates.EVENT_STATE_NORMAL + }; + + // act + client1.SendUnconfirmedEventNotification(Helper.DummyAddress, sentData); + + // assert + Assert.That(receivedData, Is.Not.SameAs(sentData)); + Helper.AssertPropertiesAndFieldsAreEqual(sentData, receivedData); + } + + [Test] + public void should_raise_oneventnotify_when_sending_floatinglimit_data() + { + // arrange + var (client1, client2) = Helper.CreateConnectedClients(); + StateTransition receivedData = null; + client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; + + var sentData = new StateTransition(new FloatingLimit() + { + ErrorLimit = (float)12.34, + ReferenceValue = (float)56.78, + SetPointValue = (float)91.011, + StatusFlags = BacnetBitString.Parse("010") + }) + { + AckRequired = false, + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), + FromState = BacnetEventStates.EVENT_STATE_NORMAL, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1), + MessageText = "Dummy Operation", + NotificationClass = 10, + NotifyType = BacnetNotifyTypes.NOTIFY_EVENT, + Priority = 1, + TimeStamp = new BacnetGenericTime(new DateTime(2018, 2, 22, 16, 14, 15), BacnetTimestampTags.TIME_STAMP_DATETIME), + ProcessIdentifier = 1, + ToState = BacnetEventStates.EVENT_STATE_NORMAL + }; + + // act + client1.SendUnconfirmedEventNotification(Helper.DummyAddress, sentData); + + // assert + Assert.That(receivedData, Is.Not.SameAs(sentData)); + Helper.AssertPropertiesAndFieldsAreEqual(sentData, receivedData); + } } } diff --git a/Tests/Helper.cs b/Tests/Helper.cs index 566e28f2..a636bb5c 100644 --- a/Tests/Helper.cs +++ b/Tests/Helper.cs @@ -25,6 +25,14 @@ public static (BacnetClient, BacnetClient) CreateConnectedClients() public static void AssertPropertiesAndFieldsAreEqual(object expected, object actual) { + if (expected == null) + { + Assert.That(actual, Is.Null, "expected == null, checking actual"); + return; + } + + Assert.That(actual, Is.Not.Null, "checking actual"); + var t = expected.GetType(); foreach (var pi in t.GetProperties()) From 47b2b02b96740bf6cb2c0653ecaec5a446a3e5a1 Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Mon, 26 Feb 2018 22:19:38 +0100 Subject: [PATCH 14/31] refactored ChangeOfValue --- .../EventValues/ChangeOfValue.cs | 44 ++++++++++++++++--- Serialize/Services.cs | 33 ++++++-------- .../EventNotificationTests.cs | 29 ++++++------ 3 files changed, 69 insertions(+), 37 deletions(-) diff --git a/Base/EventNotification/EventValues/ChangeOfValue.cs b/Base/EventNotification/EventValues/ChangeOfValue.cs index 0542ec5e..e0da5414 100644 --- a/Base/EventNotification/EventValues/ChangeOfValue.cs +++ b/Base/EventNotification/EventValues/ChangeOfValue.cs @@ -1,12 +1,46 @@ +using System.Diagnostics.CodeAnalysis; + namespace System.IO.BACnet.EventNotification.EventValues { - public class ChangeOfValue : EventValuesBase + public abstract class ChangeOfValue : EventValuesBase { public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_CHANGE_OF_VALUE; - - public BacnetBitString ChangedBits { get; set; } - public float ChangeValue { get; set; } - public BacnetCOVTypes Tag { get; set; } + public BacnetCOVTypes Tag { get; protected set; } public BacnetBitString StatusFlags { get; set; } } + + public class ChangeOfValue : ChangeOfValue + { + public T ChangedValue { get; } + + protected ChangeOfValue(T value, BacnetCOVTypes type) + { + ChangedValue = value; + Tag = type; + } + + public static ChangeOfValue CreateNew(float value) + => new ChangeOfValue(value, BacnetCOVTypes.CHANGE_OF_VALUE_REAL); + + public static ChangeOfValue CreateNew(BacnetBitString value) + => new ChangeOfValue(value, BacnetCOVTypes.CHANGE_OF_VALUE_BITS); + } + + [ExcludeFromCodeCoverage] + public abstract class ChangeOfValueFactory : ChangeOfValue + { + protected ChangeOfValueFactory(object value, BacnetCOVTypes type) : base(value, type) + { + throw new InvalidOperationException("this is a dummy class to avoid static call with generic type parameter"); + } + } + + public static class Extensions + { + public static ChangeOfValue SetStatusFlags(this ChangeOfValue cov, BacnetBitString statusFlags) + { + cov.StatusFlags = statusFlags; + return cov; + } + } } \ No newline at end of file diff --git a/Serialize/Services.cs b/Serialize/Services.cs index ab0a50e7..3e0b22ae 100644 --- a/Serialize/Services.cs +++ b/Serialize/Services.cs @@ -761,7 +761,6 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, var len = 0; uint lenValue; - eventData = new NotificationData(); eventData = default; BacnetNotifyTypes? notifyType; BacnetEventTypes? eventType = default; @@ -959,20 +958,12 @@ private static bool DecodeEventValues(byte[] buffer, int offset, BacnetEventType if (ASN1.decode_is_context_tag(buffer, offset + len, (byte)BacnetCOVTypes.CHANGE_OF_VALUE_BITS)) { len += ASN1.decode_context_bitstring(buffer, offset + len, 0, out var changedBits); - eventValues = new ChangeOfValue - { - ChangedBits = changedBits, - Tag = BacnetCOVTypes.CHANGE_OF_VALUE_BITS - }; + eventValues = ChangeOfValueFactory.CreateNew(changedBits); } else if (ASN1.decode_is_context_tag(buffer, offset + len, (byte)BacnetCOVTypes.CHANGE_OF_VALUE_REAL)) { len += ASN1.decode_context_real(buffer, offset + len, 1, out var changeValue); - eventValues = new ChangeOfValue - { - ChangeValue = changeValue, - Tag = BacnetCOVTypes.CHANGE_OF_VALUE_REAL - }; + eventValues = ChangeOfValueFactory.CreateNew(changeValue); } else { @@ -1140,24 +1131,28 @@ public static void EncodeEventNotifyData(EncodeBuffer buffer, NotificationData d ASN1.encode_closing_tag(buffer, 1); break; - case StateTransition changeOfValue: + case StateTransition changeOfValueTransition when changeOfValueTransition.GetType().GetGenericArguments()[0].BaseType == typeof(ChangeOfValue): ASN1.encode_opening_tag(buffer, 2); ASN1.encode_opening_tag(buffer, 0); - switch (changeOfValue.EventValues.Tag) + BacnetBitString covStatusFlags; + + switch (changeOfValueTransition) { - case BacnetCOVTypes.CHANGE_OF_VALUE_REAL: - ASN1.encode_context_real(buffer, 1, changeOfValue.EventValues.ChangeValue); + case StateTransition> covFLoat: + ASN1.encode_context_real(buffer, 1, covFLoat.EventValues.ChangedValue); + covStatusFlags = covFLoat.EventValues.StatusFlags; break; - case BacnetCOVTypes.CHANGE_OF_VALUE_BITS: - ASN1.encode_context_bitstring(buffer, 0, changeOfValue.EventValues.ChangedBits); + case StateTransition> covBits: + ASN1.encode_context_bitstring(buffer, 0, covBits.EventValues.ChangedValue); + covStatusFlags = covBits.EventValues.StatusFlags; break; default: - throw new ArgumentOutOfRangeException($"Unexpected Tag '{changeOfValue.EventValues.Tag}'"); + throw new ArgumentOutOfRangeException($"Unexpected Type '{changeOfValueTransition.GetType()}'"); } ASN1.encode_closing_tag(buffer, 0); - ASN1.encode_context_bitstring(buffer, 1, changeOfValue.EventValues.StatusFlags); + ASN1.encode_context_bitstring(buffer, 1, covStatusFlags); ASN1.encode_closing_tag(buffer, 2); break; diff --git a/Tests/Base/EventNotification/EventNotificationTests.cs b/Tests/Base/EventNotification/EventNotificationTests.cs index 00f63f88..4d901d1c 100644 --- a/Tests/Base/EventNotification/EventNotificationTests.cs +++ b/Tests/Base/EventNotification/EventNotificationTests.cs @@ -13,7 +13,6 @@ public class EventNotificationTests [TestCase(typeof(StateTransition))] [TestCase(typeof(StateTransition))] [TestCase(typeof(StateTransition))] - [TestCase(typeof(StateTransition))] [TestCase(typeof(StateTransition))] [TestCase(typeof(NotificationData))] public void should_override_tostring(Type type) @@ -27,6 +26,14 @@ public void should_override_tostring(Type type) Assert.That(instance.ToString(), Is.Not.EqualTo(type.ToString())); } + [Test] + public void should_override_tostring_in_changeofvalue_float() + { + var instance = new StateTransition>(ChangeOfValueFactory.CreateNew((float)123.456)); + + Assert.That(instance.ToString(), Is.Not.EqualTo(instance.GetType().ToString())); + } + [Test] public void should_raise_oneventnotify_when_sending_changeoflifesafety_data() { @@ -220,12 +227,10 @@ public void should_raise_oneventnotify_when_sending_changeofvalue_data_real() StateTransition receivedData = null; client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; - var sentData = new StateTransition(new ChangeOfValue() - { - Tag = BacnetCOVTypes.CHANGE_OF_VALUE_REAL, - ChangeValue = (float)123.456, - StatusFlags = BacnetBitString.Parse("010") - }) + var sentData = new StateTransition>( + ChangeOfValueFactory + .CreateNew((float)123.456) + .SetStatusFlags(BacnetBitString.Parse("010"))) { AckRequired = false, EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), @@ -256,12 +261,10 @@ public void should_raise_oneventnotify_when_sending_changeofvalue_data_bits() StateTransition receivedData = null; client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; - var sentData = new StateTransition(new ChangeOfValue() - { - Tag = BacnetCOVTypes.CHANGE_OF_VALUE_BITS, - ChangedBits = BacnetBitString.Parse("101"), - StatusFlags = BacnetBitString.Parse("010") - }) + var sentData = new StateTransition>( + ChangeOfValueFactory + .CreateNew(BacnetBitString.Parse("101")) + .SetStatusFlags(BacnetBitString.Parse("010"))) { AckRequired = false, EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), From 926c1223289a892e641cdbbf7d7fcef94a12837e Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Tue, 27 Feb 2018 14:00:55 +0100 Subject: [PATCH 15/31] Refactored ChangeOfState --- BACnet.csproj | 3 +- BACnetClient.cs | 2 +- Base/BacnetPropertyState.cs | 51 ----- Base/BacnetScale.cs | 2 +- Base/Enums/EnumUtils.cs | 11 +- .../EventValues/ChangeOfState.cs | 70 ++++++- .../EventValues/ChangeOfValue.cs | 15 +- Base/IHasStatusFlags.cs | 18 ++ Helpers/FactoryHelper.cs | 18 ++ Serialize/ASN1.cs | 179 +++++++++++------- Serialize/Services.cs | 48 +++-- .../EventNotificationTests.cs | 108 ++++++++--- 12 files changed, 340 insertions(+), 185 deletions(-) delete mode 100644 Base/BacnetPropertyState.cs create mode 100644 Base/IHasStatusFlags.cs create mode 100644 Helpers/FactoryHelper.cs diff --git a/BACnet.csproj b/BACnet.csproj index 1d16323c..7aade2d9 100644 --- a/BACnet.csproj +++ b/BACnet.csproj @@ -81,6 +81,7 @@ + @@ -108,7 +109,6 @@ - @@ -139,6 +139,7 @@ + diff --git a/BACnetClient.cs b/BACnetClient.cs index 24761d4e..9b6cf09d 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -1398,7 +1398,7 @@ public byte[] EndRawEncodedDecodedPropertyConfirmedRequest(BacnetAsyncResult req len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 1) throw new Exception("Failed to decode"); - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out _); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint _); var outBuffer = new byte[buffer.Length - len]; Array.Copy(buffer, len, outBuffer, 0, outBuffer.Length); diff --git a/Base/BacnetPropertyState.cs b/Base/BacnetPropertyState.cs deleted file mode 100644 index e7a7da3f..00000000 --- a/Base/BacnetPropertyState.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.IO.BACnet.Base; - -namespace System.IO.BACnet -{ - public struct BacnetPropertyState - { - public enum BacnetPropertyStateTypes - { - BOOLEAN_VALUE, - BINARY_VALUE, - EVENT_TYPE, - POLARITY, - PROGRAM_CHANGE, - PROGRAM_STATE, - REASON_FOR_HALT, - RELIABILITY, - STATE, - SYSTEM_STATUS, - UNITS, - UNSIGNED_VALUE, - LIFE_SAFETY_MODE, - LIFE_SAFETY_STATE - } - - public struct State - { - public bool boolean_value; - public BacnetBinaryPv binaryValue; - public BacnetEventTypes eventType; - public BacnetPolarity polarity; - public BacnetProgramRequest programChange; - public BacnetProgramState programState; - public BacnetProgramError programError; - public BacnetReliability reliability; - public BacnetEventStates state; - public BacnetDeviceStatus systemStatus; - public BacnetUnitsId units; - public uint unsignedValue; - public BacnetLifeSafetyModes lifeSafetyMode; - public BacnetLifeSafetyStates lifeSafetyState; - } - - public BacnetPropertyStateTypes tag; - public State state; - - public override string ToString() - { - return $"{tag}:{state}"; - } - } -} diff --git a/Base/BacnetScale.cs b/Base/BacnetScale.cs index 54335d63..fdd33b13 100644 --- a/Base/BacnetScale.cs +++ b/Base/BacnetScale.cs @@ -51,7 +51,7 @@ public int Decode(byte[] buffer, int offset, uint count) else if (tagNumber == (byte)ValueType.INTEGER) { len += tagLen; - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out var oval); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint oval); value = oval; } diff --git a/Base/Enums/EnumUtils.cs b/Base/Enums/EnumUtils.cs index 472a6bf2..1139e64d 100644 --- a/Base/Enums/EnumUtils.cs +++ b/Base/Enums/EnumUtils.cs @@ -10,9 +10,18 @@ namespace System.IO.BACnet { public abstract class EnumClassUtils where TClass : class { + public static TEnum DecodeEnumerated(byte[] buffer, int offset, uint lenValue, out int sectionLength) where TEnum : struct, TClass + { + sectionLength = ASN1.decode_unsigned(buffer, offset, lenValue, out uint rawValue); + if(sectionLength == -1) + throw new InvalidOperationException($"{nameof(ASN1.decode_unsigned)} returned -1"); + + return (TEnum)(dynamic)rawValue; + } + public static int DecodeEnumerated(byte[] buffer, int offset, uint lenValue, out TEnum value) where TEnum: struct, TClass { - var len = ASN1.decode_unsigned(buffer, offset, lenValue, out var rawValue); + var len = ASN1.decode_unsigned(buffer, offset, lenValue, out uint rawValue); value = (TEnum)(dynamic)rawValue; return len; } diff --git a/Base/EventNotification/EventValues/ChangeOfState.cs b/Base/EventNotification/EventValues/ChangeOfState.cs index f6f96577..09b7b36e 100644 --- a/Base/EventNotification/EventValues/ChangeOfState.cs +++ b/Base/EventNotification/EventValues/ChangeOfState.cs @@ -1,10 +1,76 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO.BACnet.Base; +using System.IO.BACnet.Helpers; +using System.Reflection; + namespace System.IO.BACnet.EventNotification.EventValues { - public class ChangeOfState : EventValuesBase + public abstract class ChangeOfState : EventValuesBase, IHasStatusFlags { public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_CHANGE_OF_STATE; - public BacnetPropertyState NewState { get; set; } public BacnetBitString StatusFlags { get; set; } } + + public class ChangeOfState : ChangeOfState + { + + public T NewState { get; set; } + + protected ChangeOfState(T value) + { + NewState = value; + } + + public static ChangeOfState Create(bool value) + => new ChangeOfState(value); + + public static ChangeOfState Create(BacnetBinaryPv value) + => new ChangeOfState(value); + + public static ChangeOfState Create(BacnetEventTypes value) + => new ChangeOfState(value); + + public static ChangeOfState Create(BacnetPolarity value) + => new ChangeOfState(value); + + public static ChangeOfState Create(BacnetProgramRequest value) + => new ChangeOfState(value); + + public static ChangeOfState Create(BacnetProgramState value) + => new ChangeOfState(value); + + public static ChangeOfState Create(BacnetProgramError value) + => new ChangeOfState(value); + + public static ChangeOfState Create(BacnetReliability value) + => new ChangeOfState(value); + + public static ChangeOfState Create(BacnetEventStates value) + => new ChangeOfState(value); + + public static ChangeOfState Create(BacnetDeviceStatus value) + => new ChangeOfState(value); + + public static ChangeOfState Create(BacnetUnitsId value) + => new ChangeOfState(value); + + public static ChangeOfState Create(uint value) + => new ChangeOfState(value); + + public static ChangeOfState Create(BacnetLifeSafetyModes value) + => new ChangeOfState(value); + + public static ChangeOfState Create(BacnetLifeSafetyStates value) + => new ChangeOfState(value); + } + + [ExcludeFromCodeCoverage] + public abstract class ChangeOfStateFactory : ChangeOfState + { + protected ChangeOfStateFactory(object value) : base(value) + { + throw new InvalidOperationException("this is a dummy class to avoid static call with generic type parameter"); + } + } } \ No newline at end of file diff --git a/Base/EventNotification/EventValues/ChangeOfValue.cs b/Base/EventNotification/EventValues/ChangeOfValue.cs index e0da5414..9e2c1895 100644 --- a/Base/EventNotification/EventValues/ChangeOfValue.cs +++ b/Base/EventNotification/EventValues/ChangeOfValue.cs @@ -2,7 +2,7 @@ namespace System.IO.BACnet.EventNotification.EventValues { - public abstract class ChangeOfValue : EventValuesBase + public abstract class ChangeOfValue : EventValuesBase, IHasStatusFlags { public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_CHANGE_OF_VALUE; public BacnetCOVTypes Tag { get; protected set; } @@ -19,10 +19,10 @@ protected ChangeOfValue(T value, BacnetCOVTypes type) Tag = type; } - public static ChangeOfValue CreateNew(float value) + public static ChangeOfValue Create(float value) => new ChangeOfValue(value, BacnetCOVTypes.CHANGE_OF_VALUE_REAL); - public static ChangeOfValue CreateNew(BacnetBitString value) + public static ChangeOfValue Create(BacnetBitString value) => new ChangeOfValue(value, BacnetCOVTypes.CHANGE_OF_VALUE_BITS); } @@ -34,13 +34,4 @@ protected ChangeOfValueFactory(object value, BacnetCOVTypes type) : base(value, throw new InvalidOperationException("this is a dummy class to avoid static call with generic type parameter"); } } - - public static class Extensions - { - public static ChangeOfValue SetStatusFlags(this ChangeOfValue cov, BacnetBitString statusFlags) - { - cov.StatusFlags = statusFlags; - return cov; - } - } } \ No newline at end of file diff --git a/Base/IHasStatusFlags.cs b/Base/IHasStatusFlags.cs new file mode 100644 index 00000000..3436fe46 --- /dev/null +++ b/Base/IHasStatusFlags.cs @@ -0,0 +1,18 @@ +using System.IO.BACnet.EventNotification.EventValues; + +namespace System.IO.BACnet +{ + public interface IHasStatusFlags + { + BacnetBitString StatusFlags { get; set; } + } + + public static class Extensions + { + public static T SetStatusFlags(this T obj, BacnetBitString statusFlags) where T:IHasStatusFlags + { + obj.StatusFlags = statusFlags; + return obj; + } + } +} diff --git a/Helpers/FactoryHelper.cs b/Helpers/FactoryHelper.cs new file mode 100644 index 00000000..1a41c4e7 --- /dev/null +++ b/Helpers/FactoryHelper.cs @@ -0,0 +1,18 @@ +using System.Reflection; + +namespace System.IO.BACnet.Helpers +{ + public static class FactoryHelper + { + public static TReturn CreateReflected(object value) where TReturn : class + { + var mi = typeof(TFactory).GetMethod( + "Create", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy, null, new[] { value.GetType() }, null); + + if (mi == null) + throw new InvalidOperationException("factory method not found"); + + return mi.Invoke(null, new[] { value }) as TReturn; + } + } +} diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index ccb9cdd4..e8902d21 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -1,4 +1,7 @@ using System.Collections.Generic; +using System.IO.BACnet.Base; +using System.IO.BACnet.EventNotification; +using System.IO.BACnet.EventNotification.EventValues; using System.Text; namespace System.IO.BACnet.Serialize @@ -362,7 +365,7 @@ public static int DecodeApplicationBitstring(byte[] buffer, int offset, byte tag { var len = decode_tag_number_and_value(buffer, offset, out var decodedTagNumber, out var lenValue); - if(decodedTagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING) + if (decodedTagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING) { value = new BacnetBitString(); return -1; // BACNET_STATUS_ERROR @@ -538,69 +541,83 @@ public static void bacapp_encode_context_device_obj_property_ref(EncodeBuffer bu encode_closing_tag(buffer, tagNumber); } - public static void encode_context_property_state(EncodeBuffer buffer, byte tagNumber, BacnetPropertyState value) + public static void encode_context_property_state(EncodeBuffer buffer, byte tagNumber, StateTransition value, out ChangeOfState changeOfState) { encode_opening_tag(buffer, tagNumber); - switch (value.tag) + switch (value) { - case BacnetPropertyState.BacnetPropertyStateTypes.BOOLEAN_VALUE: - encode_context_boolean(buffer, 0, value.state.boolean_value); + case StateTransition> boolean: + changeOfState = boolean.EventValues; + encode_context_boolean(buffer, 0, boolean.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.BINARY_VALUE: - encode_context_enumerated(buffer, 1, (uint)value.state.binaryValue); + case StateTransition> binary: + changeOfState = binary.EventValues; + encode_context_enumerated(buffer, 1, (uint)binary.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.EVENT_TYPE: - encode_context_enumerated(buffer, 2, (uint)value.state.eventType); + case StateTransition> eventType: + changeOfState = eventType.EventValues; + encode_context_enumerated(buffer, 2, (uint)eventType.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.POLARITY: - encode_context_enumerated(buffer, 3, (uint)value.state.polarity); + case StateTransition> polarity: + changeOfState = polarity.EventValues; + encode_context_enumerated(buffer, 3, (uint)polarity.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.PROGRAM_CHANGE: - encode_context_enumerated(buffer, 4, (uint)value.state.programChange); + case StateTransition> programRequest: + changeOfState = programRequest.EventValues; + encode_context_enumerated(buffer, 4, (uint)programRequest.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.PROGRAM_STATE: - encode_context_enumerated(buffer, 5, (uint)value.state.programState); + case StateTransition> programState: + changeOfState = programState.EventValues; + encode_context_enumerated(buffer, 5, (uint)programState.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.REASON_FOR_HALT: - encode_context_enumerated(buffer, 6, (uint)value.state.programError); + case StateTransition> programError: + changeOfState = programError.EventValues; + encode_context_enumerated(buffer, 6, (uint)programError.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.RELIABILITY: - encode_context_enumerated(buffer, 7, (uint)value.state.reliability); + case StateTransition> reliability: + changeOfState = reliability.EventValues; + encode_context_enumerated(buffer, 7, (uint)reliability.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.STATE: - encode_context_enumerated(buffer, 8, (uint)value.state.state); + case StateTransition> state: + changeOfState = state.EventValues; + encode_context_enumerated(buffer, 8, (uint)state.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.SYSTEM_STATUS: - encode_context_enumerated(buffer, 9, (uint)value.state.systemStatus); + case StateTransition> deviceStatus: + changeOfState = deviceStatus.EventValues; + encode_context_enumerated(buffer, 9, (uint)deviceStatus.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.UNITS: - encode_context_enumerated(buffer, 10, (uint)value.state.units); + case StateTransition> units: + changeOfState = units.EventValues; + encode_context_enumerated(buffer, 10, (uint)units.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.UNSIGNED_VALUE: - encode_context_unsigned(buffer, 11, value.state.unsignedValue); + case StateTransition> unsigned: + changeOfState = unsigned.EventValues; + encode_context_unsigned(buffer, 11, unsigned.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.LIFE_SAFETY_MODE: - encode_context_enumerated(buffer, 12, (uint)value.state.lifeSafetyMode); + case StateTransition> lifeSafetyMode: + changeOfState = lifeSafetyMode.EventValues; + encode_context_enumerated(buffer, 12, (uint)lifeSafetyMode.EventValues.NewState); break; - case BacnetPropertyState.BacnetPropertyStateTypes.LIFE_SAFETY_STATE: - encode_context_enumerated(buffer, 13, (uint)value.state.lifeSafetyState); + case StateTransition> lifeSafetyState: + changeOfState = lifeSafetyState.EventValues; + encode_context_enumerated(buffer, 13, (uint)lifeSafetyState.EventValues.NewState); break; default: - throw new ArgumentOutOfRangeException($"tag {value.tag} is not supported"); + throw new ArgumentOutOfRangeException($"type {value.GetType()} is not supported"); } encode_closing_tag(buffer, tagNumber); } @@ -1120,6 +1137,12 @@ public static int decode_device_obj_property_ref(byte[] buffer, int offset, out return len; } + public static uint decode_unsigned(byte[] buffer, int offset, uint lenValue, out int sectionLength) + { + sectionLength = decode_unsigned(buffer, offset, lenValue, out uint value); + return value; + } + public static int decode_unsigned(byte[] buffer, int offset, uint lenValue, out uint value) { switch (lenValue) @@ -1142,8 +1165,7 @@ public static int decode_unsigned(byte[] buffer, int offset, uint lenValue, out break; default: - value = 0; - break; + throw new ArgumentOutOfRangeException($"Invalid length: {lenValue}"); } return (int)lenValue; @@ -1718,7 +1740,7 @@ public static int bacapp_decode_data(byte[] buffer, int offset, int maxLength, B break; case BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED: - len = decode_unsigned(buffer, offset, lenValueType, out var enumeratedValue); + len = decode_unsigned(buffer, offset, lenValueType, out uint enumeratedValue); value.Value = enumeratedValue; break; @@ -2024,7 +2046,7 @@ public static int bacapp_decode_context_application_data(BacnetAddress address, } else if (tagNumber == 1) // sequence number { - len += decode_unsigned(buffer, offset + len, lenValueType, out var val); + len += decode_unsigned(buffer, offset + len, lenValueType, out uint val); value.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT; value.Value = val; } @@ -2294,7 +2316,7 @@ public static int decode_cov_subscription(byte[] buffer, int offset, int apduLen len += decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) return -1; - len += decode_unsigned(buffer, offset + len, lenValueType, out var tmp); + len += decode_unsigned(buffer, offset + len, lenValueType, out uint tmp); value.Recipient.net = (ushort)tmp; len += decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING) @@ -2365,7 +2387,7 @@ public static int decode_cov_subscription(byte[] buffer, int offset, int apduLen return len; } - public static int decode_context_property_state(byte[] buffer, int offset, byte tagNumber, out BacnetPropertyState value) + public static int decode_context_property_state(byte[] buffer, int offset, byte tagNumber, out object value) { if (!decode_is_opening_tag_number(buffer, offset, tagNumber)) throw new InvalidOperationException($"expected tag {tagNumber} not found"); @@ -2379,48 +2401,77 @@ public static int decode_context_property_state(byte[] buffer, int offset, byte return len + 1; } - public static int decode_property_state(byte[] buffer, int offset, out BacnetPropertyState value) + public static int decode_property_state(byte[] buffer, int offset, out object value) { - value = default; - - var len = decode_tag_number_and_value(buffer, offset, out value.tag, out uint lenValueType); + var len = decode_tag_number_and_value(buffer, offset, out var tag, out var lenValueType); if (len == -1) - throw new InvalidOperationException($"expected tag {value.tag} not found"); + throw new InvalidOperationException("expected tag not found"); var sectionLength = 0; - switch (value.tag) + switch (tag) { - case BacnetPropertyState.BacnetPropertyStateTypes.BOOLEAN_VALUE: + case 0: // bool sectionLength = 1; - value.state.boolean_value = buffer[offset+len] == 1; + value = ChangeOfStateFactory.Create(buffer[offset + len] == 1); break; - case BacnetPropertyState.BacnetPropertyStateTypes.BINARY_VALUE: - sectionLength = EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out value.state.binaryValue); - if (sectionLength == -1) - return -1; + case 1: + value = ChangeOfStateFactory.Create( + EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out sectionLength)); break; - case BacnetPropertyState.BacnetPropertyStateTypes.EVENT_TYPE: - sectionLength = EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out value.state.eventType); - if (sectionLength == -1) - return -1; + case 2: + value = ChangeOfStateFactory.Create( + EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out sectionLength)); + break; + case 3: + value = ChangeOfStateFactory.Create( + EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out sectionLength)); break; - case BacnetPropertyState.BacnetPropertyStateTypes.STATE: - sectionLength = EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out value.state.state); - if (sectionLength == -1) - return -1; + case 4: + value = ChangeOfStateFactory.Create( + EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out sectionLength)); + break; + case 5: + value = ChangeOfStateFactory.Create( + EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out sectionLength)); + break; + case 6: + value = ChangeOfStateFactory.Create( + EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out sectionLength)); + break; + case 7: + value = ChangeOfStateFactory.Create( + EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out sectionLength)); + break; + case 8: + value = ChangeOfStateFactory.Create( + EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out sectionLength)); break; - case BacnetPropertyState.BacnetPropertyStateTypes.UNSIGNED_VALUE: - sectionLength = decode_unsigned(buffer, offset + len, lenValueType, out value.state.unsignedValue); - if (sectionLength == -1) - return -1; + case 9: + value = ChangeOfStateFactory.Create( + EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out sectionLength)); + break; + case 10: + value = ChangeOfStateFactory.Create( + EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out sectionLength)); + break; + case 11: + value = ChangeOfStateFactory.Create(decode_unsigned(buffer, offset + len, lenValueType, out sectionLength)); break; + case 12: + value = ChangeOfStateFactory.Create( + EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out sectionLength)); + break; + case 13: + value = ChangeOfStateFactory.Create( + EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out sectionLength)); + break; default: - throw new ArgumentOutOfRangeException($"tag {value.tag} is not supported"); + throw new ArgumentOutOfRangeException($"tag {tag} is not supported"); } return len + sectionLength; @@ -2449,7 +2500,7 @@ public static int decode_context_real(byte[] buffer, int offset, byte tagNumber, var len = decode_tag_number_and_value(buffer, offset, out _, out _); return len + decode_real(buffer, offset + len, out value); } - + public static int decode_context_unsigned(byte[] buffer, int offset, byte tagNumber, out uint value) { if (!decode_is_context_tag(buffer, offset, tagNumber) || decode_is_closing_tag(buffer, offset)) diff --git a/Serialize/Services.cs b/Serialize/Services.cs index 3e0b22ae..a6f9d476 100644 --- a/Serialize/Services.cs +++ b/Serialize/Services.cs @@ -42,7 +42,7 @@ public static int DecodeIamBroadcast(byte[] buffer, int offset, out uint deviceI apduLen += len; if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) return -1; - len = ASN1.decode_unsigned(buffer, offset + apduLen, lenValue, out var decodedValue); + len = ASN1.decode_unsigned(buffer, offset + apduLen, lenValue, out uint decodedValue); apduLen += len; maxApdu = decodedValue; /* Segmentation - enumerated */ @@ -124,7 +124,7 @@ public static int DecodeWhoIsBroadcast(byte[] buffer, int offset, int apduLen, o return -1; if (apduLen > len) { - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var decodedValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint decodedValue); if (decodedValue <= ASN1.BACNET_MAX_INSTANCE) lowLimit = (int)decodedValue; if (apduLen > len) @@ -771,7 +771,7 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var processIdentifier); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint processIdentifier); decodedNotificationData.Add(e => e.ProcessIdentifier = processIdentifier); } else @@ -815,7 +815,7 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, if (ASN1.decode_is_context_tag(buffer, offset + len, 4)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var notificationClass); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint notificationClass); decodedNotificationData.Add(e => e.NotificationClass = notificationClass); } else @@ -825,7 +825,7 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, if (ASN1.decode_is_context_tag(buffer, offset + len, 5)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var priority); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint priority); if (priority > 0xFF) return -1; decodedNotificationData.Add(e => e.Priority = (byte) priority); } @@ -836,7 +836,7 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, if (ASN1.decode_is_context_tag(buffer, offset + len, 6)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out var eventTypeValue); + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out BacnetEventTypes eventTypeValue); eventType = eventTypeValue; } //else @@ -855,7 +855,7 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, if (ASN1.decode_is_context_tag(buffer, offset + len, 8)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out var notifyTypeValue); + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out BacnetNotifyTypes notifyTypeValue); decodedStateTransition.Add(e => e.NotifyType = notifyTypeValue); notifyType = notifyTypeValue; } @@ -873,7 +873,7 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, /* tag 10 - fromState */ len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var fromstate); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint fromstate); decodedStateTransition.Add(e => e.FromState = (BacnetEventStates) fromstate); break; } @@ -882,7 +882,7 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, if (ASN1.decode_is_context_tag(buffer, offset + len, 11)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var toState); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint toState); decodedNotificationData.Add(e => e.ToState = (BacnetEventStates) toState); } else @@ -943,11 +943,7 @@ private static bool DecodeEventValues(byte[] buffer, int offset, BacnetEventType case BacnetEventTypes.EVENT_CHANGE_OF_STATE: len += ASN1.decode_context_property_state(buffer, offset + len, 0, out var newState); len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var changeOfStateStatusFlags); - eventValues = new ChangeOfState - { - NewState = newState, - StatusFlags = changeOfStateStatusFlags - }; + eventValues = (newState as ChangeOfState).SetStatusFlags(changeOfStateStatusFlags); break; case BacnetEventTypes.EVENT_CHANGE_OF_VALUE: @@ -958,12 +954,12 @@ private static bool DecodeEventValues(byte[] buffer, int offset, BacnetEventType if (ASN1.decode_is_context_tag(buffer, offset + len, (byte)BacnetCOVTypes.CHANGE_OF_VALUE_BITS)) { len += ASN1.decode_context_bitstring(buffer, offset + len, 0, out var changedBits); - eventValues = ChangeOfValueFactory.CreateNew(changedBits); + eventValues = ChangeOfValueFactory.Create(changedBits); } else if (ASN1.decode_is_context_tag(buffer, offset + len, (byte)BacnetCOVTypes.CHANGE_OF_VALUE_REAL)) { len += ASN1.decode_context_real(buffer, offset + len, 1, out var changeValue); - eventValues = ChangeOfValueFactory.CreateNew(changeValue); + eventValues = ChangeOfValueFactory.Create(changeValue); } else { @@ -1124,10 +1120,10 @@ public static void EncodeEventNotifyData(EncodeBuffer buffer, NotificationData d ASN1.encode_closing_tag(buffer, 0); break; - case StateTransition changeOfState: + case StateTransition changeOfStateTransition when changeOfStateTransition.GetType().GetGenericArguments()[0].BaseType == typeof(ChangeOfState): ASN1.encode_opening_tag(buffer, 1); - ASN1.encode_context_property_state(buffer, 0, changeOfState.EventValues.NewState); - ASN1.encode_context_bitstring(buffer, 1, changeOfState.EventValues.StatusFlags); + ASN1.encode_context_property_state(buffer, 0, changeOfStateTransition, out var changeOfState); + ASN1.encode_context_bitstring(buffer, 1, changeOfState.StatusFlags); ASN1.encode_closing_tag(buffer, 1); break; @@ -1630,19 +1626,19 @@ public static uint DecodeReadRangeAcknowledge(byte[] buffer, int offset, int apd len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 1) return 0; - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out _); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint _); /* Tag 2: Optional Array Index or Tag 3: BACnet Result Flags */ len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); if (tagNumber == 2 && len < apduLen) - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out _); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint _); else /* Tag 3: BACnet Result Flags */ len += ASN1.decode_bitstring(buffer, offset + len, 2, out _); /* Tag 4 Item Count */ len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out var itemCount); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint itemCount); if (!ASN1.decode_is_opening_tag(buffer, offset + len)) return 0; @@ -2087,7 +2083,7 @@ public static int DecodeCOVNotifyUnconfirmed(BacnetAddress address, byte[] buffe if (ASN1.decode_is_context_tag(buffer, offset + len, 3)) { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var decodedValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint decodedValue); newEntry.priority = (byte)decodedValue; } else @@ -2159,7 +2155,7 @@ public static int DecodeWriteProperty(BacnetAddress address, byte[] buffer, int if (tagNumber == 4) { len += tagLen; - len = ASN1.decode_unsigned(buffer, offset + len, lenValueType, out var unsignedValue); + len = ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint unsignedValue); if (unsignedValue >= ASN1.BACNET_MIN_PRIORITY && unsignedValue <= ASN1.BACNET_MAX_PRIORITY) value.priority = (byte)unsignedValue; else @@ -2589,7 +2585,7 @@ public static int DecodeLogRecord(byte[] buffer, int offset, int length, int nCu break; case BacnetTrendLogValueType.TL_TYPE_ENUM: - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var eval); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint eval); records[curveNumber].Value = eval; break; @@ -2599,7 +2595,7 @@ public static int DecodeLogRecord(byte[] buffer, int offset, int length, int nCu break; case BacnetTrendLogValueType.TL_TYPE_UNSIGN: - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out var uinval); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint uinval); records[curveNumber].Value = uinval; break; diff --git a/Tests/Base/EventNotification/EventNotificationTests.cs b/Tests/Base/EventNotification/EventNotificationTests.cs index 4d901d1c..f643d46a 100644 --- a/Tests/Base/EventNotification/EventNotificationTests.cs +++ b/Tests/Base/EventNotification/EventNotificationTests.cs @@ -1,5 +1,7 @@ -using System.IO.BACnet.EventNotification; +using System.IO.BACnet.Base; +using System.IO.BACnet.EventNotification; using System.IO.BACnet.EventNotification.EventValues; +using System.IO.BACnet.Helpers; using NUnit.Framework; namespace System.IO.BACnet.Tests.Base.EventNotification @@ -7,7 +9,6 @@ namespace System.IO.BACnet.Tests.Base.EventNotification [TestFixture] public class EventNotificationTests { - [TestCase(typeof(StateTransition))] [TestCase(typeof(StateTransition))] [TestCase(typeof(StateTransition))] [TestCase(typeof(StateTransition))] @@ -29,7 +30,15 @@ public void should_override_tostring(Type type) [Test] public void should_override_tostring_in_changeofvalue_float() { - var instance = new StateTransition>(ChangeOfValueFactory.CreateNew((float)123.456)); + var instance = new StateTransition>(ChangeOfValueFactory.Create((float)123.456)); + + Assert.That(instance.ToString(), Is.Not.EqualTo(instance.GetType().ToString())); + } + + [Test] + public void should_override_tostring_in_changeofvalue_bool() + { + var instance = new StateTransition>(ChangeOfStateFactory.Create(true)); Assert.That(instance.ToString(), Is.Not.EqualTo(instance.GetType().ToString())); } @@ -188,28 +197,75 @@ public void should_raise_oneventnotify_when_sending_changeofstate_data_bool(bool StateTransition receivedData = null; client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; - var sentData = new StateTransition(new ChangeOfState() - { - NewState = new BacnetPropertyState + var sentData = + new StateTransition>(ChangeOfStateFactory.Create(value) + .SetStatusFlags(BacnetBitString.Parse("010"))) { - tag = BacnetPropertyState.BacnetPropertyStateTypes.BOOLEAN_VALUE, - state = new BacnetPropertyState.State() { boolean_value = value } - }, - StatusFlags = BacnetBitString.Parse("010") - }) - { - AckRequired = false, - EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), - FromState = BacnetEventStates.EVENT_STATE_NORMAL, - InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1), - MessageText = "Dummy Operation", - NotificationClass = 10, - NotifyType = BacnetNotifyTypes.NOTIFY_EVENT, - Priority = 1, - TimeStamp = new BacnetGenericTime(new DateTime(2018, 2, 22, 16, 14, 15), BacnetTimestampTags.TIME_STAMP_DATETIME), - ProcessIdentifier = 1, - ToState = BacnetEventStates.EVENT_STATE_NORMAL - }; + AckRequired = false, + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123), + FromState = BacnetEventStates.EVENT_STATE_NORMAL, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1), + MessageText = "Dummy Operation", + NotificationClass = 10, + NotifyType = BacnetNotifyTypes.NOTIFY_EVENT, + Priority = 1, + TimeStamp = new BacnetGenericTime(new DateTime(2018, 2, 22, 16, 14, 15), + BacnetTimestampTags.TIME_STAMP_DATETIME), + ProcessIdentifier = 1, + ToState = BacnetEventStates.EVENT_STATE_NORMAL + }; + + // act + client1.SendUnconfirmedEventNotification(Helper.DummyAddress, sentData); + + // assert + Assert.That(receivedData, Is.Not.SameAs(sentData)); + Helper.AssertPropertiesAndFieldsAreEqual(sentData, receivedData); + } + + [TestCase(default(BacnetBinaryPv))] + [TestCase(default(BacnetEventTypes))] + [TestCase(default(BacnetPolarity))] + [TestCase(default(BacnetProgramRequest))] + [TestCase(default(BacnetProgramState))] + [TestCase(default(BacnetProgramError))] + [TestCase(default(BacnetReliability))] + [TestCase(default(BacnetEventStates))] + [TestCase(default(BacnetDeviceStatus))] + [TestCase(default(BacnetUnitsId))] + [TestCase(default(uint))] + [TestCase(default(BacnetLifeSafetyModes))] + [TestCase(default(BacnetLifeSafetyStates))] + public void should_raise_oneventnotify_when_sending_changeofstate_data(object value) + { + // arrange + var (client1, client2) = Helper.CreateConnectedClients(); + StateTransition receivedData = null; + client2.OnEventNotify += (sender, address, id, data, confirm) => receivedData = data as StateTransition; + + var t = value.GetType(); + var cosType = typeof(ChangeOfState<>).MakeGenericType(t); + var stType = typeof(StateTransition<>).MakeGenericType(cosType); + + var sentData = Activator.CreateInstance(stType, + FactoryHelper.CreateReflected(value).SetStatusFlags( + BacnetBitString.Parse("010"))) + as StateTransition; + + Assert.That(sentData, Is.Not.Null); + + sentData.AckRequired = false; + sentData.EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_ZONE, 123); + sentData.FromState = BacnetEventStates.EVENT_STATE_NORMAL; + sentData.InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 1); + sentData.MessageText = "Dummy Operation"; + sentData.NotificationClass = 10; + sentData.NotifyType = BacnetNotifyTypes.NOTIFY_EVENT; + sentData.Priority = 1; + sentData.TimeStamp = new BacnetGenericTime(new DateTime(2018, 2, 22, 16, 14, 15), + BacnetTimestampTags.TIME_STAMP_DATETIME); + sentData.ProcessIdentifier = 1; + sentData.ToState = BacnetEventStates.EVENT_STATE_NORMAL; // act client1.SendUnconfirmedEventNotification(Helper.DummyAddress, sentData); @@ -229,7 +285,7 @@ public void should_raise_oneventnotify_when_sending_changeofvalue_data_real() var sentData = new StateTransition>( ChangeOfValueFactory - .CreateNew((float)123.456) + .Create((float)123.456) .SetStatusFlags(BacnetBitString.Parse("010"))) { AckRequired = false, @@ -263,7 +319,7 @@ public void should_raise_oneventnotify_when_sending_changeofvalue_data_bits() var sentData = new StateTransition>( ChangeOfValueFactory - .CreateNew(BacnetBitString.Parse("101")) + .Create(BacnetBitString.Parse("101")) .SetStatusFlags(BacnetBitString.Parse("010"))) { AckRequired = false, From 5d1306dfa4d82b1949a27ec421778a598cd0e5b5 Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Tue, 27 Feb 2018 16:11:50 +0100 Subject: [PATCH 16/31] Added ASHRAE Examples F.1.2, F.1.3 and F.1.4 --- Base/BacnetGenericTime.cs | 1 + Base/BacnetPropertyReference.cs | 1 + Base/BacnetPropertyValue.cs | 1 + Base/BacnetValue.cs | 1 + Tests/Serialize/ServicesTests.cs | 254 ++++++++++++++++++++++++++++++- 5 files changed, 251 insertions(+), 7 deletions(-) diff --git a/Base/BacnetGenericTime.cs b/Base/BacnetGenericTime.cs index 322a6dac..0e2b3c68 100644 --- a/Base/BacnetGenericTime.cs +++ b/Base/BacnetGenericTime.cs @@ -1,5 +1,6 @@ namespace System.IO.BACnet { + // TODO you have been flagged for refactoring due to un-C#-iness public struct BacnetGenericTime { public BacnetTimestampTags Tag; diff --git a/Base/BacnetPropertyReference.cs b/Base/BacnetPropertyReference.cs index fb9db11b..91de3b56 100644 --- a/Base/BacnetPropertyReference.cs +++ b/Base/BacnetPropertyReference.cs @@ -1,5 +1,6 @@ namespace System.IO.BACnet { + // TODO you have been flagged for refactoring due to un-C#-iness public struct BacnetPropertyReference { public uint propertyIdentifier; diff --git a/Base/BacnetPropertyValue.cs b/Base/BacnetPropertyValue.cs index 1455e17f..5ed057f1 100644 --- a/Base/BacnetPropertyValue.cs +++ b/Base/BacnetPropertyValue.cs @@ -2,6 +2,7 @@ namespace System.IO.BACnet { + // TODO you have been flagged for refactoring due to un-C#-iness public struct BacnetPropertyValue { public BacnetPropertyReference property; diff --git a/Base/BacnetValue.cs b/Base/BacnetValue.cs index 1be34c2b..151ef311 100644 --- a/Base/BacnetValue.cs +++ b/Base/BacnetValue.cs @@ -2,6 +2,7 @@ namespace System.IO.BACnet { + // TODO you have been flagged for refactoring due to un-C#-iness public struct BacnetValue { public BacnetApplicationTags Tag; diff --git a/Tests/Serialize/ServicesTests.cs b/Tests/Serialize/ServicesTests.cs index c3ad570c..e689ad96 100644 --- a/Tests/Serialize/ServicesTests.cs +++ b/Tests/Serialize/ServicesTests.cs @@ -1,4 +1,7 @@ -using System.IO.BACnet.Serialize; +using System.Collections.Generic; +using System.IO.BACnet.EventNotification; +using System.IO.BACnet.EventNotification.EventValues; +using System.IO.BACnet.Serialize; using NUnit.Framework; namespace System.IO.BACnet.Tests.Serialize @@ -11,8 +14,10 @@ public void should_encode_logrecord_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); - var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, new DateTime(1998,3,23,19,54,27), 0); - var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, new DateTime(1998,3,23,19,56,27), 0); + var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, + new DateTime(1998, 3, 23, 19, 54, 27), 0); + var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, + new DateTime(1998, 3, 23, 19, 56, 27), 0); // example taken from ANNEX F - Examples of APDU Encoding var expectedBytes = new byte[] @@ -37,8 +42,10 @@ public void should_decode_multiple_logrecords() { // arrange var buffer = new EncodeBuffer(); - var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, new DateTime(1998, 3, 23, 19, 54, 27), 0); - var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, new DateTime(1998, 3, 23, 19, 56, 27), 0); + var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, + new DateTime(1998, 3, 23, 19, 54, 27), 0); + var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, + new DateTime(1998, 3, 23, 19, 56, 27), 0); // act Services.EncodeLogRecord(buffer, record1); @@ -49,7 +56,7 @@ public void should_decode_multiple_logrecords() * Debug - write packet to network to analyze in WireShark * - var client = new BacnetClient(new BacnetIpUdpProtocolTransport(48708, true)); + var client = new BacnetClient(new BacnetIpUdpProtocolTransport(47808, true)); client.Start(); client.ReadRangeResponse(new BacnetAddress(BacnetAddressTypes.IP, "192.168.1.99"), 42, null, new BacnetObjectId(BacnetObjectTypes.OBJECT_TRENDLOG, 1), @@ -61,5 +68,238 @@ public void should_decode_multiple_logrecords() Helper.AssertPropertiesAndFieldsAreEqual(record1, decodedRecords[0]); Helper.AssertPropertiesAndFieldsAreEqual(record2, decodedRecords[1]); } + + [Test] + public void should_encode_confirmendcovnotificationrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var data = new[] + { + new BacnetPropertyValue + { + property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE, + ASN1.BACNET_ARRAY_ALL), + value = new List {new BacnetValue((float) 65.0)} + }, + new BacnetPropertyValue + { + property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_STATUS_FLAGS, + ASN1.BACNET_ARRAY_ALL), + value = new List {new BacnetValue(BacnetBitString.Parse("0000"))} + } + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.2 + var expectedBytes = new byte[] + { + 0x00, 0x02, 0x0F, 0x01, + + 0x09, 0x12, 0x1C, 0x02, 0x00, 0x00, 0x04, 0x2C, 0x00, 0x00, 0x00, 0x0A, + 0x39, 0x00, 0x4E, 0x09, 0x55, 0x2E, 0x44, 0x42, 0x82, 0x00, 0x00, 0x2F, + 0x09, 0x6F, 0x2E, 0x82, 0x04, 0x00, 0x2F, 0x4F + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 15); + + Services.EncodeCOVNotifyConfirmed(buffer, 18, 4, + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), 0, data); + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_confirmendcovnotificationrequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.2 + var expectedBytes = new byte[] + { + 0x20, 0x0F, 0x01 + }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, + BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION, 15); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_unconfirmendcovnotificationrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var data = new[] + { + new BacnetPropertyValue + { + property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE, + ASN1.BACNET_ARRAY_ALL), + value = new List {new BacnetValue((float) 65.0)} + }, + new BacnetPropertyValue + { + property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_STATUS_FLAGS, + ASN1.BACNET_ARRAY_ALL), + value = new List {new BacnetValue(BacnetBitString.Parse("0000"))} + } + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.3 + var expectedBytes = new byte[] + { + 0x10, 0x02, + + 0x09, 0x12, 0x1C, 0x02, 0x00, 0x00, 0x04, 0x2C, 0x00, 0x00, 0x00, 0x0A, + 0x39, 0x00, 0x4E, 0x09, 0x55, 0x2E, 0x44, 0x42, 0x82, 0x00, 0x00, 0x2F, + 0x09, 0x6F, 0x2E, 0x82, 0x04, 0x00, 0x2F, 0x4F + }; + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, + BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION); + + Services.EncodeCOVNotifyConfirmed(buffer, 18, 4, + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), 0, data); + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_confirmendeventnotificationrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var data = new StateTransition(new OutOfRange() + { + ExceedingValue = (float) 80.1, + StatusFlags = BacnetBitString.Parse("1000"), + Deadband = (float) 1.0, + ExceededLimit = (float) 80.0 + }) + { + ProcessIdentifier = 1, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 4), + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), + TimeStamp = new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_SEQUENCE, 16), + NotificationClass = 4, + Priority = 100, + NotifyType = BacnetNotifyTypes.NOTIFY_ALARM, + AckRequired = true, + FromState = BacnetEventStates.EVENT_STATE_NORMAL, + ToState = BacnetEventStates.EVENT_STATE_HIGH_LIMIT + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.4 + var expectedBytes = new byte[] + { + 0x00, 0x02, 0x10, 0x02, 0x09, 0x01, 0x1C, 0x02, 0x00, 0x00, 0x04, 0x2C, 0x00, 0x00, 0x00, 0x02, + 0x3E, 0x19, 0x10, 0x3F, 0x49, 0x04, 0x59, 0x64, 0x69, 0x05, 0x89, 0x00, 0x99, 0x01, 0xA9, 0x00, + 0xB9, 0x03, 0xCE, 0x5E, 0x0C, 0x42, 0xA0, 0x33, 0x33, 0x1A, 0x04, 0x80, 0x2C, 0x3F, 0x80, 0x00, + 0x00, 0x3C, 0x42, 0xA0, 0x00, 0x00, 0x5F, 0xCF + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_EVENT_NOTIFICATION, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 16); + + Services.EncodeEventNotifyData(buffer, data); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_confirmendeventnotificationrequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.4 + var expectedBytes = new byte[] + { + 0x20, 0x10, 0x02 + }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, + BacnetConfirmedServices.SERVICE_CONFIRMED_EVENT_NOTIFICATION, 16); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + + } + + [Test] + public void should_encode_acknowledgealarmrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.4 + var expectedBytes = new byte[] + { + 0x00, 0x02, 0x07, 0x00, 0x09, 0x01, 0x1C, 0x00, 0x00, 0x00, 0x02, 0x29, 0x03, + 0x3E, 0x19, 0x10, 0x3F, 0x4C, 0x00, 0x4D, 0x44, 0x4C, 0x5E, 0x2E, 0xA4, 0x5C, + 0x06, 0x15, 0x07 /* instead of FF */, 0xB4, 0x0D, 0x03, 0x29, 0x09, 0x2F, 0x5F + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 7); + + Services.EncodeAlarmAcknowledge(buffer, 1, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), + (uint) BacnetEventStates.EVENT_STATE_HIGH_LIMIT, "MDL", + new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_SEQUENCE, 16), + new BacnetGenericTime(new DateTime(1992, 6, 21, 13, 3, 41).AddMilliseconds(90), + BacnetTimestampTags.TIME_STAMP_DATETIME)); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_acknowledgealarmrequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.4 + var expectedBytes = new byte[] + { + 0x20, 0x07, 0x00 + }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, + BacnetConfirmedServices.SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, 7); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + + } } -} +} \ No newline at end of file From abae73fca6a3d2c38b9b65da58049c383a4a80c4 Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Tue, 27 Feb 2018 17:05:13 +0100 Subject: [PATCH 17/31] Added ASHRAE Examples F.1.5 and F.1.6 --- Tests/Helper.cs | 18 +++++ Tests/Serialize/ServicesTests.cs | 117 +++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/Tests/Helper.cs b/Tests/Helper.cs index a636bb5c..187c6c0d 100644 --- a/Tests/Helper.cs +++ b/Tests/Helper.cs @@ -1,4 +1,7 @@ using System.Collections; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; using NUnit.Framework; namespace System.IO.BACnet.Tests @@ -57,5 +60,20 @@ public static void AssertPropertiesAndFieldsAreEqual(object expected, object act Assert.AreEqual(expectedValue, fi.GetValue(actual), "Field: " + fi.Name); } } + + public static string Doc2Code(string input) + { + var hexCodes = input.Split('\r', '\n') + .Select(line => Regex.Match(line, @"^X'(?[^']+)'")) + .Where(m => m.Success) + .SelectMany(m => m.Groups["hex"].Value).ToArray(); + + var pairs = Enumerable.Range(0, hexCodes.Length) + .GroupBy(x => x / 2) + .Select(x => "0x" + new string(x.Select(y => hexCodes[y]).ToArray())) + .ToArray(); + + return $"var expectedBytes = new byte[] {{{string.Join(", ", pairs)}}};"; + } } } diff --git a/Tests/Serialize/ServicesTests.cs b/Tests/Serialize/ServicesTests.cs index e689ad96..c894a668 100644 --- a/Tests/Serialize/ServicesTests.cs +++ b/Tests/Serialize/ServicesTests.cs @@ -301,5 +301,122 @@ public void should_encode_acknowledgealarmrequest_ack_according_to_ashrae_exampl Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + + [Test] + public void should_encode_unconfirmedeventnotificationrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var data = new StateTransition(new OutOfRange() + { + ExceedingValue = (float)80.1, + StatusFlags = BacnetBitString.Parse("1000"), + Deadband = (float)1.0, + ExceededLimit = (float)80.0 + }) + { + ProcessIdentifier = 1, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 9), + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), + TimeStamp = new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_SEQUENCE, 16), + NotificationClass = 4, + Priority = 100, + NotifyType = BacnetNotifyTypes.NOTIFY_ALARM, + AckRequired = true, + FromState = BacnetEventStates.EVENT_STATE_NORMAL, + ToState = BacnetEventStates.EVENT_STATE_HIGH_LIMIT + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.5 + var expectedBytes = new byte[] + { + 0x10, 0x03, 0x09, 0x01, 0x1C, 0x02, 0x00, 0x00, 0x09, 0x2C, 0x00, 0x00, 0x00, 0x02, 0x3E, 0x19, 0x10, + 0x3F, 0x49, 0x04, 0x59, 0x64, 0x69, 0x05, 0x89, 0x00, 0x99, 0x01, 0xA9, 0x00, 0xB9, 0x03, 0xCE, 0x5E, + 0x0C, 0x42, 0xA0, 0x33, 0x33, 0x1A, 0x04, 0x80, 0x2C, 0x3F, 0x80, 0x00, 0x00, 0x3C, 0x42, 0xA0, 0x00, + 0x00, 0x5F, 0xCF + }; + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, + BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION); + + Services.EncodeEventNotifyData(buffer, data); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_getalarmsummary_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.6 + var expectedBytes = new byte[] + { + 0x00, 0x02, 0x01, 0x03 + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_GET_ALARM_SUMMARY, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 1); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + + } + + [Test] + public void should_encode_getalarmsummary_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.6 + + var expectedBytes = new byte[] + { + 0x30, 0x01, 0x03, 0xC4, 0x00, 0x00, 0x00, 0x02, 0x91, 0x03, 0x82, 0x05, 0x60, 0xC4, 0x00, 0x00, 0x00, + 0x03, 0x91, 0x04, 0x82, 0x05, 0xE0 + }; + + // act + APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, + BacnetConfirmedServices.SERVICE_CONFIRMED_GET_ALARM_SUMMARY, 1); + + Services.EncodeAlarmSummary(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), + BacnetEventStates.EVENT_STATE_HIGH_LIMIT, BacnetBitString.Parse("011")); + + Services.EncodeAlarmSummary(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 3), + BacnetEventStates.EVENT_STATE_LOW_LIMIT, BacnetBitString.Parse("111")); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + + } + + [Test] + public void GenerateCode() + { + Console.WriteLine(Helper.Doc2Code(@" +X'30' PDU Type=3 (BACnet-ComplexACK-PDU, SEG=0, MOR=0) +X'01' Invoke ID=1 +X'03' Service ACK Choice=3 (GetAlarmSummary-ACK) +X'C4' Application Tag 12 (Object Identifier, L=4) (Object Identifier) +X'00000002' Analog Input, Instance Number=2 +X'91' Application Tag 9 (Enumerated, L=1) (Alarm State) +X'03' 3 (HIGH_LIMIT) +X'82' Application Tag 8 (Bit String, L=2) (Acknowledged Transitions) +X'0560' 0,1,1 (FALSE, TRUE, TRUE) +")); + } } } \ No newline at end of file From f26bfacf8a3bc694c46fc0914c63cad50bfc6aa8 Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Tue, 27 Feb 2018 17:59:34 +0100 Subject: [PATCH 18/31] Added ASHRAE Example F.1.8 --- Serialize/ASN1.cs | 22 +++-- Serialize/EncodeBuffer.cs | 3 + Tests/Serialize/ServicesTests.cs | 149 +++++++++++++++++++++++++++++-- 3 files changed, 159 insertions(+), 15 deletions(-) diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index e8902d21..67d80a0b 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -24,6 +24,8 @@ public class ASN1 /// public static Func CustomTagResolver; + private static readonly byte[] AnyDateOrTime = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; + public interface IEncode { void Encode(EncodeBuffer buffer); @@ -706,10 +708,17 @@ public static void encode_closing_tag(EncodeBuffer buffer, byte tagNumber) public static void encode_bacnet_time(EncodeBuffer buffer, DateTime value) { - buffer.Add((byte)value.Hour); - buffer.Add((byte)value.Minute); - buffer.Add((byte)value.Second); - buffer.Add((byte)(value.Millisecond / 10)); + if (value == default) + { + buffer.Add(AnyDateOrTime); + } + else + { + buffer.Add((byte)value.Hour); + buffer.Add((byte)value.Minute); + buffer.Add((byte)value.Second); + buffer.Add((byte)(value.Millisecond / 10)); + } } public static void encode_context_time(EncodeBuffer buffer, byte tagNumber, DateTime value) @@ -722,10 +731,7 @@ public static void encode_bacnet_date(EncodeBuffer buffer, DateTime value) { if (value == new DateTime(1, 1, 1)) // this is the way decode do for 'Date any' = DateTime(0) { - buffer.Add(0xFF); - buffer.Add(0xFF); - buffer.Add(0xFF); - buffer.Add(0xFF); + buffer.Add(AnyDateOrTime); return; } diff --git a/Serialize/EncodeBuffer.cs b/Serialize/EncodeBuffer.cs index 04abb01b..5397d723 100644 --- a/Serialize/EncodeBuffer.cs +++ b/Serialize/EncodeBuffer.cs @@ -70,6 +70,9 @@ public void Add(byte[] buffer, int count) Add(buffer[i]); } + public void Add(byte[] buffer) + => Add(buffer, buffer.Length); + public int GetDiff(EncodeBuffer buffer) { var diff = Math.Abs(buffer.offset - offset); diff --git a/Tests/Serialize/ServicesTests.cs b/Tests/Serialize/ServicesTests.cs index c894a668..9fdb0220 100644 --- a/Tests/Serialize/ServicesTests.cs +++ b/Tests/Serialize/ServicesTests.cs @@ -400,22 +400,157 @@ public void should_encode_getalarmsummary_ack_according_to_ashrae_example() // assert Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_geteventinformation_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.8 + var expectedBytes = new byte[] + { + 0x00 /* docs say 0x02, but that's wrong! */, 0x02, 0x01, 0x1D + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_GET_EVENT_INFORMATION, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 1); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + [Test] + public void should_encode_geteventinformation_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + var data = new[] + { + new BacnetGetEventInformationData() + { + objectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), + eventState = BacnetEventStates.EVENT_STATE_HIGH_LIMIT, + acknowledgedTransitions = BacnetBitString.Parse("011"), + eventTimeStamps = new[] + { + new BacnetGenericTime(new DateTime(1, 1, 1, 15, 35, 00).AddMilliseconds(200), BacnetTimestampTags.TIME_STAMP_TIME), + new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_TIME), + new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_TIME), + }, + notifyType = BacnetNotifyTypes.NOTIFY_ALARM, + eventEnable = BacnetBitString.Parse("111"), + eventPriorities = new uint[] {15, 15, 20} + }, + new BacnetGetEventInformationData() + { + objectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 3), + eventState = BacnetEventStates.EVENT_STATE_NORMAL, + acknowledgedTransitions = BacnetBitString.Parse("110"), + eventTimeStamps = new[] + { + new BacnetGenericTime(new DateTime(1, 1, 1, 15, 40, 00), BacnetTimestampTags.TIME_STAMP_TIME), + new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_TIME), + new BacnetGenericTime(new DateTime(1, 1, 1, 15, 45, 30).AddMilliseconds(300), BacnetTimestampTags.TIME_STAMP_TIME), + }, + notifyType = BacnetNotifyTypes.NOTIFY_ALARM, + eventEnable = BacnetBitString.Parse("111"), + eventPriorities = new uint[] {15, 15, 20} + } + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.8 + var expectedBytes = new byte[] + { + 0x30, 0x01, 0x1D, 0x0E, 0x0C, 0x00, 0x00, 0x00, 0x02, 0x19, 0x03, 0x2A, 0x05, 0x60, 0x3E, 0x0C, 0x0F, + 0x23, 0x00, 0x14, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x49, 0x00, 0x5A, + 0x05, 0xE0, 0x6E, 0x21, 0x0F, 0x21, 0x0F, 0x21, 0x14, 0x6F, 0x0C, 0x00, 0x00, 0x00, 0x03, 0x19, 0x00, + 0x2A, 0x05, 0xC0, 0x3E, 0x0C, 0x0F, 0x28, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C, 0x0F, 0x2D, + 0x1E, 0x1E, 0x3F, 0x49, 0x00, 0x5A, 0x05, 0xE0, 0x6E, 0x21, 0x0F, 0x21, 0x0F, 0x21, 0x14, 0x6F, 0x0F, + 0x19, 0x00 + }; + + // act + APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, + BacnetConfirmedServices.SERVICE_CONFIRMED_GET_EVENT_INFORMATION, 1); + + Services.EncodeGetEventInformationAcknowledge(buffer, data, false); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } [Test] public void GenerateCode() { Console.WriteLine(Helper.Doc2Code(@" -X'30' PDU Type=3 (BACnet-ComplexACK-PDU, SEG=0, MOR=0) -X'01' Invoke ID=1 -X'03' Service ACK Choice=3 (GetAlarmSummary-ACK) -X'C4' Application Tag 12 (Object Identifier, L=4) (Object Identifier) -X'00000002' Analog Input, Instance Number=2 -X'91' Application Tag 9 (Enumerated, L=1) (Alarm State) +X'30' PDU Type = 3 (BACnet-ComplexACK-PDU, SEG=0, MOR=0) +X'01' Invoke ID=01 +X'1D' Service ACK Choice = 29, (GetEventInformation-ACK) +X'0E' PD opening Tag 0 +X'0C' SD context Tag 0 (ObjectIdentifier, L=4) +X'00000002' Analog Input, Instance Number = 2 +X'19' SD context Tag 1 (Enumerated, L=1) X'03' 3 (HIGH_LIMIT) -X'82' Application Tag 8 (Bit String, L=2) (Acknowledged Transitions) +X'2A' SD context Tag 2 (Bit String, L=2) X'0560' 0,1,1 (FALSE, TRUE, TRUE) +X'3E' PD opening Tag 3 +X'0C' SD context Tag 0 (Time L=4) +X'0F230014' Time 15:35:00.20 +X'0C' SD context Tag 0 (Time L=4) +X'FFFFFFFF' Time unspecified +X'0C' SD context Tag 0 (Time L=4) +X'FFFFFFFF' Time unspecified +X'3F' PD closing Tag 3 +X'49' SD context Tag 4 (Enumerated, L=1) +X'00' 0 (ALARM) +X'5A' SD context Tag 5 (Bit String, L=2) +X'05E0' 1,1,1 (TRUE, TRUE, TRUE) +X'6E' PD opening Tag 6 +X'21' Application Tag 2 (Unsigned Integer, L=1) +X'0F' 15 (Priority) +X'21' Application Tag 2 (Unsigned Integer, L=1) +X'0F' 15 (Priority) +X'21' Application Tag 2 (Unsigned Integer, L=1) +X'14' 20 (Priority) +X'6F' PD closing Tag 6 +X'0C' SD context Tag 0 (ObjectIdentifier, L=4) +X'00000003' Analog Input, Instance Number = 3 +X'19' SD context Tag 1 (Enumerated, L=1) +X'00' 0 (NORMAL) +X'2A' SD context Tag 2 (Bit String, L=2) +X'05C0' 1,1,0 (TRUE, TRUE, FALSE) +X'3E' PD opening Tag 3 +X'0C' SD context Tag 0 (Time L=4) +X'0F280000' Time 15:40:00.00 +X'0C' SD context Tag 0 (Time L=4) +X'FFFFFFFF' Time unspecified +X'0C' SD context Tag 0 (Time L=4) +X'0F2D1E1E' 15:45:30.30 +X'3F' PD closing Tag 3 +X'49' SD context Tag 4 (Enumerated, L=1) +X'00' 0 (ALARM) +X'5A' SD context Tag 5 (Bit String, L=2) +X'05E0' 1,1,1 (TRUE, TRUE, TRUE) +X'6E' PD opening Tag 6 +X'21' Application Tag 2 (Unsigned Integer, L=1) +X'0F' 15 (Priority) +X'21' Application Tag 2 (Unsigned Integer, L=1) +X'0F' 15 (Priority) +X'21' Application Tag 2 (Unsigned Integer, L=1) +X'14' 20 (Priority) +X'6F' PD closing Tag 6 +X'0F' PD closing Tag 0 +X'19' SD context Tag 1 (Boolean, L=1) +X'00' FALSE (More Events) ")); } } From d85aa835256ba77bce5a2a767fb385277c513253 Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Wed, 28 Feb 2018 10:45:16 +0100 Subject: [PATCH 19/31] Added ASHRAE Examples F.1.9 to F.1.11 --- Base/BacnetPropertyReference.cs | 1 + Serialize/Services.cs | 1 + .../EventNotificationTests.cs | 12 +- Tests/Serialize/ServicesTests.cs | 240 +++++++++++++----- 4 files changed, 180 insertions(+), 74 deletions(-) diff --git a/Base/BacnetPropertyReference.cs b/Base/BacnetPropertyReference.cs index 91de3b56..67d408bc 100644 --- a/Base/BacnetPropertyReference.cs +++ b/Base/BacnetPropertyReference.cs @@ -6,6 +6,7 @@ public struct BacnetPropertyReference public uint propertyIdentifier; public uint propertyArrayIndex; /* optional */ + // TODO take BacnetPropertyIds, and default to ASN1.BACNET_ARRAY_ALL public BacnetPropertyReference(uint id, uint arrayIndex) { propertyIdentifier = id; diff --git a/Serialize/Services.cs b/Serialize/Services.cs index a6f9d476..2800e215 100644 --- a/Serialize/Services.cs +++ b/Serialize/Services.cs @@ -626,6 +626,7 @@ public static int DecodeSubscribeCOV(byte[] buffer, int offset, int apduLen, out return len; } + // TODO CHECK: rename to EncodeSubscribeCovProperty ? public static void EncodeSubscribeProperty(EncodeBuffer buffer, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, BacnetPropertyReference monitoredProperty, bool covIncrementPresent, float covIncrement) { /* tag 0 - subscriberProcessIdentifier */ diff --git a/Tests/Base/EventNotification/EventNotificationTests.cs b/Tests/Base/EventNotification/EventNotificationTests.cs index f643d46a..55499190 100644 --- a/Tests/Base/EventNotification/EventNotificationTests.cs +++ b/Tests/Base/EventNotification/EventNotificationTests.cs @@ -30,7 +30,7 @@ public void should_override_tostring(Type type) [Test] public void should_override_tostring_in_changeofvalue_float() { - var instance = new StateTransition>(ChangeOfValueFactory.Create((float)123.456)); + var instance = new StateTransition>(ChangeOfValueFactory.Create(123.456f)); Assert.That(instance.ToString(), Is.Not.EqualTo(instance.GetType().ToString())); } @@ -163,7 +163,7 @@ public void should_raise_oneventnotify_when_sending_outofrange_data() { ExceededLimit = float.MinValue, ExceedingValue = float.MaxValue, - Deadband = (float)17.01, + Deadband = 17.01f, StatusFlags = BacnetBitString.Parse("010") }) { @@ -285,7 +285,7 @@ public void should_raise_oneventnotify_when_sending_changeofvalue_data_real() var sentData = new StateTransition>( ChangeOfValueFactory - .Create((float)123.456) + .Create(123.456f) .SetStatusFlags(BacnetBitString.Parse("010"))) { AckRequired = false, @@ -392,9 +392,9 @@ public void should_raise_oneventnotify_when_sending_floatinglimit_data() var sentData = new StateTransition(new FloatingLimit() { - ErrorLimit = (float)12.34, - ReferenceValue = (float)56.78, - SetPointValue = (float)91.011, + ErrorLimit = 12.34f, + ReferenceValue = 56.78f, + SetPointValue = 91.011f, StatusFlags = BacnetBitString.Parse("010") }) { diff --git a/Tests/Serialize/ServicesTests.cs b/Tests/Serialize/ServicesTests.cs index 9fdb0220..00633d87 100644 --- a/Tests/Serialize/ServicesTests.cs +++ b/Tests/Serialize/ServicesTests.cs @@ -80,7 +80,7 @@ public void should_encode_confirmendcovnotificationrequest_according_to_ashrae_e { property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE, ASN1.BACNET_ARRAY_ALL), - value = new List {new BacnetValue((float) 65.0)} + value = new List {new BacnetValue(65.0f)} }, new BacnetPropertyValue { @@ -146,7 +146,7 @@ public void should_encode_unconfirmendcovnotificationrequest_according_to_ashrae { property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE, ASN1.BACNET_ARRAY_ALL), - value = new List {new BacnetValue((float) 65.0)} + value = new List {new BacnetValue(65.0f)} }, new BacnetPropertyValue { @@ -185,10 +185,10 @@ public void should_encode_confirmendeventnotificationrequest_according_to_ashrae var buffer = new EncodeBuffer(); var data = new StateTransition(new OutOfRange() { - ExceedingValue = (float) 80.1, + ExceedingValue = 80.1f, StatusFlags = BacnetBitString.Parse("1000"), - Deadband = (float) 1.0, - ExceededLimit = (float) 80.0 + Deadband = 1.0f, + ExceededLimit = 80.0f }) { ProcessIdentifier = 1, @@ -309,10 +309,10 @@ public void should_encode_unconfirmedeventnotificationrequest_according_to_ashra var buffer = new EncodeBuffer(); var data = new StateTransition(new OutOfRange() { - ExceedingValue = (float)80.1, + ExceedingValue = 80.1f, StatusFlags = BacnetBitString.Parse("1000"), - Deadband = (float)1.0, - ExceededLimit = (float)80.0 + Deadband = 1.0f, + ExceededLimit = 80.0f }) { ProcessIdentifier = 1, @@ -369,7 +369,6 @@ public void should_encode_getalarmsummary_according_to_ashrae_example() // assert Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); - } [Test] @@ -488,69 +487,174 @@ public void should_encode_geteventinformation_ack_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_encode_lifesafetyoperation_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.9 + var expectedBytes = new byte[] + { + 0x00, 0x02, 0x0F, 0x1B, 0x09, 0x12, 0x1C, 0x00, 0x4D, 0x44, 0x4C, 0x29, 0x04, 0x3C, 0x05, 0x40, 0x00, + 0x01 + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 15); + + Services.EncodeLifeSafetyOperation(buffer, 18, "MDL", + (uint) BacnetLifeSafetyOperations.LIFE_SAFETY_OP_RESET, + new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_POINT, 1)); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_lifesafetyoperation_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.9 + var expectedBytes = new byte[] + { + 0x20, 0x0F, 0x1B + }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, + BacnetConfirmedServices.SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION, 15); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_subscribecov_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.10 + var expectedBytes = new byte[] + {0x00, 0x02, 0x0F, 0x05, 0x09, 0x12, 0x1C, 0x00, 0x00, 0x00, 0x0A, 0x29, 0x01, 0x39, 0x00}; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 15); + + Services.EncodeSubscribeCOV(buffer, 18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), + false, true, 0); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_subscribecov_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.10 + var expectedBytes = new byte[] + { + 0x20, 0x0F, 0x05 + }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, + BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, 15); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_subscribecovproperty_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.11 + var expectedBytes = new byte[] + { + 0x00, 0x02, 0x0F, 0x1C, 0x09, 0x12, 0x1C, 0x00, 0x00, 0x00, 0x0A, 0x29, 0x01, 0x39, 0x3C, 0x4E, 0x09, + 0x55, 0x4F, 0x5C, 0x3F, 0x80, 0x00, 0x00 + }; + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 15); + + Services.EncodeSubscribeProperty(buffer, 18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), + false, true, 60, + new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE, ASN1.BACNET_ARRAY_ALL), true, + 1.0f); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_subscribecovproperty_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.1.11 + var expectedBytes = new byte[] + { + 0x20, 0x0F, 0x1C + }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, + BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, 15); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + [Test] public void GenerateCode() { Console.WriteLine(Helper.Doc2Code(@" -X'30' PDU Type = 3 (BACnet-ComplexACK-PDU, SEG=0, MOR=0) -X'01' Invoke ID=01 -X'1D' Service ACK Choice = 29, (GetEventInformation-ACK) -X'0E' PD opening Tag 0 -X'0C' SD context Tag 0 (ObjectIdentifier, L=4) -X'00000002' Analog Input, Instance Number = 2 -X'19' SD context Tag 1 (Enumerated, L=1) -X'03' 3 (HIGH_LIMIT) -X'2A' SD context Tag 2 (Bit String, L=2) -X'0560' 0,1,1 (FALSE, TRUE, TRUE) -X'3E' PD opening Tag 3 -X'0C' SD context Tag 0 (Time L=4) -X'0F230014' Time 15:35:00.20 -X'0C' SD context Tag 0 (Time L=4) -X'FFFFFFFF' Time unspecified -X'0C' SD context Tag 0 (Time L=4) -X'FFFFFFFF' Time unspecified -X'3F' PD closing Tag 3 -X'49' SD context Tag 4 (Enumerated, L=1) -X'00' 0 (ALARM) -X'5A' SD context Tag 5 (Bit String, L=2) -X'05E0' 1,1,1 (TRUE, TRUE, TRUE) -X'6E' PD opening Tag 6 -X'21' Application Tag 2 (Unsigned Integer, L=1) -X'0F' 15 (Priority) -X'21' Application Tag 2 (Unsigned Integer, L=1) -X'0F' 15 (Priority) -X'21' Application Tag 2 (Unsigned Integer, L=1) -X'14' 20 (Priority) -X'6F' PD closing Tag 6 -X'0C' SD context Tag 0 (ObjectIdentifier, L=4) -X'00000003' Analog Input, Instance Number = 3 -X'19' SD context Tag 1 (Enumerated, L=1) -X'00' 0 (NORMAL) -X'2A' SD context Tag 2 (Bit String, L=2) -X'05C0' 1,1,0 (TRUE, TRUE, FALSE) -X'3E' PD opening Tag 3 -X'0C' SD context Tag 0 (Time L=4) -X'0F280000' Time 15:40:00.00 -X'0C' SD context Tag 0 (Time L=4) -X'FFFFFFFF' Time unspecified -X'0C' SD context Tag 0 (Time L=4) -X'0F2D1E1E' 15:45:30.30 -X'3F' PD closing Tag 3 -X'49' SD context Tag 4 (Enumerated, L=1) -X'00' 0 (ALARM) -X'5A' SD context Tag 5 (Bit String, L=2) -X'05E0' 1,1,1 (TRUE, TRUE, TRUE) -X'6E' PD opening Tag 6 -X'21' Application Tag 2 (Unsigned Integer, L=1) -X'0F' 15 (Priority) -X'21' Application Tag 2 (Unsigned Integer, L=1) -X'0F' 15 (Priority) -X'21' Application Tag 2 (Unsigned Integer, L=1) -X'14' 20 (Priority) -X'6F' PD closing Tag 6 -X'0F' PD closing Tag 0 -X'19' SD context Tag 1 (Boolean, L=1) -X'00' FALSE (More Events) +X'00' PDU Type=0 (BACnet-Confirmed-Request-PDU, SEG=0, MOR=0, SA=0) +X'02' Maximum APDU Size Accepted=206 octets +X'0F' Invoke ID=15 +X'1C' Service Choice=28 (SubscribeCOVProperty-Request) +X'09' SD Context Tag 0 (Subscriber Process Identifier, L=1) +X'12' 18 +X'1C' SD Context Tag 1 (Monitored Object Identifier, L=4) +X'0000000A' Analog Input, Instance Number=10 +X'29' SD Context Tag 2 (Issue Confirmed Notifications, L=1) +X'01' 1 (TRUE) +X'39' SD Context Tag 3 (Lifetime, L=1) +X'3C' 60 +X'4E' PD Opening Tag 4 (Monitored Property, L=1) +X'09' SD Context Tag 0 (Property Identifier, L=1) +X'55' 85 (PRESENT_VALUE) +X'4F' PD Closing Tag 4 (Property Identifier) +X'5C' SD Context Tag 5 (COV Increment, L=4) +X'3F800000' 1.0 ")); } } From 80eeef5949bf2d504d19e955ac4b91ffb585c1a9 Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Wed, 28 Feb 2018 12:43:50 +0100 Subject: [PATCH 20/31] Split Serialize.Services into smaller parts --- BACnet.csproj | 5 +- BACnet.csproj.DotSettings | 2 + BACnetClient.cs | 126 +- Serialize/ASN1.cs | 20 + Serialize/Services.cs | 2656 ----------------- Serialize/Services/AlarmAndEventServices.cs | 1020 +++++++ Serialize/Services/FileAccessServices.cs | 322 ++ Serialize/Services/ObjectAccessServices.cs | 976 ++++++ .../RemoteDeviceManagementServices.cs | 333 +++ Tests/BACnet.Tests.csproj | 4 +- Tests/BACnet.Tests.csproj.DotSettings | 2 + .../AlarmAndEventServicesTests.cs} | 84 +- .../Services/ObjectAccessServicesTests.cs | 69 + 13 files changed, 2826 insertions(+), 2793 deletions(-) delete mode 100644 Serialize/Services.cs create mode 100644 Serialize/Services/AlarmAndEventServices.cs create mode 100644 Serialize/Services/FileAccessServices.cs create mode 100644 Serialize/Services/ObjectAccessServices.cs create mode 100644 Serialize/Services/RemoteDeviceManagementServices.cs create mode 100644 Tests/BACnet.Tests.csproj.DotSettings rename Tests/Serialize/{ServicesTests.cs => Services/AlarmAndEventServicesTests.cs} (86%) create mode 100644 Tests/Serialize/Services/ObjectAccessServicesTests.cs diff --git a/BACnet.csproj b/BACnet.csproj index 7aade2d9..5f79e4d5 100644 --- a/BACnet.csproj +++ b/BACnet.csproj @@ -144,6 +144,10 @@ + + + + @@ -168,7 +172,6 @@ - diff --git a/BACnet.csproj.DotSettings b/BACnet.csproj.DotSettings index 39a24956..f6087919 100644 --- a/BACnet.csproj.DotSettings +++ b/BACnet.csproj.DotSettings @@ -1,6 +1,8 @@  True True + True + True True NEVER True diff --git a/BACnetClient.cs b/BACnetClient.cs index 9b6cf09d..950fd0be 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -205,7 +205,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy { int thsRejectReason; - if ((thsRejectReason = Services.DecodeReadProperty(buffer, offset, length, out var objectId, out var property)) >= 0) + if ((thsRejectReason = ObjectAccessServices.DecodeReadProperty(buffer, offset, length, out var objectId, out var property)) >= 0) { OnReadPropertyRequest(this, address, invokeId, objectId, property, maxSegments); } @@ -228,7 +228,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY && OnWritePropertyRequest != null) { - if (Services.DecodeWriteProperty(address, buffer, offset, length, out var objectId, out var value) >= 0) + if (ObjectAccessServices.DecodeWriteProperty(address, buffer, offset, length, out var objectId, out var value) >= 0) OnWritePropertyRequest(this, address, invokeId, objectId, value, maxSegments); else { @@ -239,7 +239,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE && OnReadPropertyMultipleRequest != null) { - if (Services.DecodeReadPropertyMultiple(buffer, offset, length, out var properties) >= 0) + if (ObjectAccessServices.DecodeReadPropertyMultiple(buffer, offset, length, out var properties) >= 0) OnReadPropertyMultipleRequest(this, address, invokeId, properties, maxSegments); else { @@ -249,7 +249,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE && OnWritePropertyMultipleRequest != null) { - if (Services.DecodeWritePropertyMultiple(address, buffer, offset, length, out var objectId, out var values) >= 0) + if (ObjectAccessServices.DecodeWritePropertyMultiple(address, buffer, offset, length, out var objectId, out var values) >= 0) OnWritePropertyMultipleRequest(this, address, invokeId, objectId, values, maxSegments); else { @@ -259,7 +259,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION && OnCOVNotification != null) { - if (Services.DecodeCOVNotifyUnconfirmed(address, buffer, offset, length, out var subscriberProcessIdentifier, out var initiatingDeviceIdentifier, out var monitoredObjectIdentifier, out var timeRemaining, out var values) >= 0) + if (AlarmAndEventServices.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 { @@ -269,7 +269,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE && OnAtomicWriteFileRequest != null) { - if (Services.DecodeAtomicWriteFile(buffer, offset, length, out var isStream, out var objectId, out var position, out var blockCount, out var blocks, out var counts) >= 0) + if (FileAccessServices.DecodeAtomicWriteFile(buffer, offset, length, out var isStream, out var objectId, out var position, out var blockCount, out var blocks, out var counts) >= 0) OnAtomicWriteFileRequest(this, address, invokeId, isStream, objectId, position, blockCount, blocks, counts, maxSegments); else { @@ -279,7 +279,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE && OnAtomicReadFileRequest != null) { - if (Services.DecodeAtomicReadFile(buffer, offset, length, out var isStream, out var objectId, out var position, out var count) >= 0) + if (FileAccessServices.DecodeAtomicReadFile(buffer, offset, length, out var isStream, out var objectId, out var position, out var count) >= 0) OnAtomicReadFileRequest(this, address, invokeId, isStream, objectId, position, count, maxSegments); else { @@ -289,7 +289,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV && OnSubscribeCOV != null) { - if (Services.DecodeSubscribeCOV(buffer, offset, length, out var subscriberProcessIdentifier, out var monitoredObjectIdentifier, out var cancellationRequest, out var issueConfirmedNotifications, out var lifetime) >= 0) + if (AlarmAndEventServices.DecodeSubscribeCOV(buffer, offset, length, out var subscriberProcessIdentifier, out var monitoredObjectIdentifier, out var cancellationRequest, out var issueConfirmedNotifications, out var lifetime) >= 0) OnSubscribeCOV(this, address, invokeId, subscriberProcessIdentifier, monitoredObjectIdentifier, cancellationRequest, issueConfirmedNotifications, lifetime, maxSegments); else { @@ -299,7 +299,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY && OnSubscribeCOVProperty != null) { - if (Services.DecodeSubscribeProperty(buffer, offset, length, out var subscriberProcessIdentifier, out var monitoredObjectIdentifier, out var monitoredProperty, out var cancellationRequest, out var issueConfirmedNotifications, out var lifetime, out var covIncrement) >= 0) + if (AlarmAndEventServices.DecodeSubscribeProperty(buffer, offset, length, out var subscriberProcessIdentifier, out var monitoredObjectIdentifier, out var monitoredProperty, out var cancellationRequest, out var issueConfirmedNotifications, out var lifetime, out var covIncrement) >= 0) OnSubscribeCOVProperty(this, address, invokeId, subscriberProcessIdentifier, monitoredObjectIdentifier, monitoredProperty, cancellationRequest, issueConfirmedNotifications, lifetime, covIncrement, maxSegments); else { @@ -309,7 +309,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL && OnDeviceCommunicationControl != null) { - if (Services.DecodeDeviceCommunicationControl(buffer, offset, length, out var timeDuration, out var enableDisable, out var password) >= 0) + if (RemoteDeviceManagementServices.DecodeDeviceCommunicationControl(buffer, offset, length, out var timeDuration, out var enableDisable, out var password) >= 0) OnDeviceCommunicationControl(this, address, invokeId, timeDuration, enableDisable, password, maxSegments); else { @@ -319,7 +319,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_REINITIALIZE_DEVICE && OnReinitializedDevice != null) { - if (Services.DecodeReinitializeDevice(buffer, offset, length, out var state, out var password) >= 0) + if (RemoteDeviceManagementServices.DecodeReinitializeDevice(buffer, offset, length, out var state, out var password) >= 0) OnReinitializedDevice(this, address, invokeId, state, password, maxSegments); else { @@ -329,7 +329,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_EVENT_NOTIFICATION && OnEventNotify != null) // F. Chaxel { - if (Services.DecodeEventNotifyData(buffer, offset, length, out var eventData) >= 0) + if (AlarmAndEventServices.DecodeEventNotifyData(buffer, offset, length, out var eventData) >= 0) { OnEventNotify(this, address, invokeId, eventData, true); } @@ -341,7 +341,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE && OnReadRange != null) { - if (Services.DecodeReadRange(buffer, offset, length, out var objectId, out var property, out var requestType, out var position, out var time, out var count) >= 0) + if (ObjectAccessServices.DecodeReadRange(buffer, offset, length, out var objectId, out var property, out var requestType, out var position, out var time, out var count) >= 0) OnReadRange(this, address, invokeId, objectId, property, requestType, position, time, count, maxSegments); else { @@ -351,7 +351,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT && OnCreateObjectRequest != null) { - if (Services.DecodeCreateObject(address, buffer, offset, length, out var objectId, out var values) >= 0) + if (ObjectAccessServices.DecodeCreateObject(address, buffer, offset, length, out var objectId, out var values) >= 0) OnCreateObjectRequest(this, address, invokeId, objectId, values, maxSegments); else { @@ -361,7 +361,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT && OnDeleteObjectRequest != null) { - if (Services.DecodeDeleteObject(buffer, offset, length, out var objectId) >= 0) + if (ObjectAccessServices.DecodeDeleteObject(buffer, offset, length, out var objectId) >= 0) OnDeleteObjectRequest(this, address, invokeId, objectId, maxSegments); else { @@ -405,14 +405,14 @@ protected void ProcessUnconfirmedServiceRequest(BacnetAddress address, BacnetPdu OnUnconfirmedServiceRequest?.Invoke(this, address, type, service, buffer, offset, length); if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_I_AM && OnIam != null) { - if (Services.DecodeIamBroadcast(buffer, offset, out var deviceId, out var maxAdpu, out var segmentation, out var vendorId) >= 0) + if (RemoteDeviceManagementServices.DecodeIamBroadcast(buffer, offset, out var deviceId, out var maxAdpu, out var segmentation, out var vendorId) >= 0) OnIam(this, address, deviceId, maxAdpu, segmentation, vendorId); else Log.Warn("Couldn't decode IamBroadcast"); } else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS && OnWhoIs != null) { - if (Services.DecodeWhoIsBroadcast(buffer, offset, length, out var lowLimit, out var highLimit) >= 0) + if (RemoteDeviceManagementServices.DecodeWhoIsBroadcast(buffer, offset, length, out var lowLimit, out var highLimit) >= 0) OnWhoIs(this, address, lowLimit, highLimit); else Log.Warn("Couldn't decode WhoIsBroadcast"); @@ -420,35 +420,35 @@ protected void ProcessUnconfirmedServiceRequest(BacnetAddress address, BacnetPdu // added by thamersalek else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_HAS && OnWhoHas != null) { - if (Services.DecodeWhoHasBroadcast(buffer, offset, length, out var lowLimit, out var highLimit, out var objId, out var objName) >= 0) + if (RemoteDeviceManagementServices.DecodeWhoHasBroadcast(buffer, offset, length, out var lowLimit, out var highLimit, out var objId, out var objName) >= 0) OnWhoHas(this, address, lowLimit, highLimit, objId, objName); else Log.Warn("Couldn't decode WhoHasBroadcast"); } else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION && OnCOVNotification != null) { - if (Services.DecodeCOVNotifyUnconfirmed(address, buffer, offset, length, out var subscriberProcessIdentifier, out var initiatingDeviceIdentifier, out var monitoredObjectIdentifier, out var timeRemaining, out var values) >= 0) + if (AlarmAndEventServices.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"); } else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION && OnTimeSynchronize != null) { - if (Services.DecodeTimeSync(buffer, offset, length, out var dateTime) >= 0) + if (RemoteDeviceManagementServices.DecodeTimeSync(buffer, offset, length, out var dateTime) >= 0) OnTimeSynchronize(this, address, dateTime, false); else Log.Warn("Couldn't decode TimeSynchronize"); } else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION && OnTimeSynchronize != null) { - if (Services.DecodeTimeSync(buffer, offset, length, out var dateTime) >= 0) + if (RemoteDeviceManagementServices.DecodeTimeSync(buffer, offset, length, out var dateTime) >= 0) OnTimeSynchronize(this, address, dateTime, true); else Log.Warn("Couldn't decode TimeSynchronize"); } else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION && OnEventNotify != null) // F. Chaxel { - if (Services.DecodeEventNotifyData(buffer, offset, length, out var eventData) >= 0) + if (AlarmAndEventServices.DecodeEventNotifyData(buffer, offset, length, out var eventData) >= 0) OnEventNotify(this, address, 0, eventData, false); else Log.Warn("Couldn't decode Event/Alarm Notification"); @@ -504,7 +504,7 @@ protected void ProcessError(BacnetAddress address, BacnetPduTypes type, BacnetCo { try { - if (Services.DecodeError(buffer, offset, length, out var errorClass, out var errorCode) < 0) + if (ASN1.DecodeError(buffer, offset, length, out var errorClass, out var errorCode) < 0) Log.Warn("Couldn't decode received Error"); Log.Debug($"Received Error {errorClass} {errorCode}"); @@ -845,7 +845,7 @@ public void RemoteWhoIs(string bbmdIP, int port = DEFAULT_UDP_PORT, int lowLimit var broadcast = Transport.GetBroadcastAddress(); NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, broadcast); APDU.EncodeUnconfirmedServiceRequest(b, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS); - Services.EncodeWhoIsBroadcast(b, lowLimit, highLimit); + RemoteDeviceManagementServices.EncodeWhoIsBroadcast(b, lowLimit, highLimit); var sent = false; @@ -889,7 +889,7 @@ public void WhoIs(int lowLimit = -1, int highLimit = -1, BacnetAddress receiver var b = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, receiver); APDU.EncodeUnconfirmedServiceRequest(b, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS); - Services.EncodeWhoIsBroadcast(b, lowLimit, highLimit); + RemoteDeviceManagementServices.EncodeWhoIsBroadcast(b, lowLimit, highLimit); Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, receiver, false, 0); } @@ -909,7 +909,7 @@ public void Iam(uint deviceId, BacnetSegmentations segmentation = BacnetSegmenta var b = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, receiver); APDU.EncodeUnconfirmedServiceRequest(b, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_I_AM); - Services.EncodeIamBroadcast(b, deviceId, (uint)GetMaxApdu(), segmentation, VendorId); + RemoteDeviceManagementServices.EncodeIamBroadcast(b, deviceId, (uint)GetMaxApdu(), segmentation, VendorId); Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, receiver, false, 0); } @@ -923,7 +923,7 @@ public void IHave(BacnetObjectId deviceId, BacnetObjectId objId, string objName) var broadcast = Transport.GetBroadcastAddress(); NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, broadcast); APDU.EncodeUnconfirmedServiceRequest(b, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_I_HAVE); - Services.EncodeIhaveBroadcast(b, deviceId, objId, objName); + RemoteDeviceManagementServices.EncodeIhaveBroadcast(b, deviceId, objId, objName); Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, broadcast, false, 0); @@ -936,7 +936,7 @@ public void SendUnconfirmedEventNotification(BacnetAddress address, StateTransit var b = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, address); APDU.EncodeUnconfirmedServiceRequest(b, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION); - Services.EncodeEventNotifyData(b, eventData); + AlarmAndEventServices.EncodeEventNotifyData(b, eventData); Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, address, false, 0); } @@ -960,7 +960,7 @@ public void SynchronizeTime(BacnetAddress address, DateTime dateTime) APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, dateTime.Kind == DateTimeKind.Utc ? BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION : BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION); - Services.EncodeTimeSync(buffer, dateTime); + RemoteDeviceManagementServices.EncodeTimeSync(buffer, dateTime); Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, address, false, 0); } @@ -1014,7 +1014,7 @@ public BacnetAsyncResult BeginWriteFileRequest(BacnetAddress address, BacnetObje { Log.Debug("Sending AtomicWriteFileRequest"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, - buffer => Services.EncodeAtomicWriteFile(buffer, true, objectId, position, 1, new[] { fileBuffer }, new[] { count }), waitForTransmit); + buffer => FileAccessServices.EncodeAtomicWriteFile(buffer, true, objectId, position, 1, new[] { fileBuffer }, new[] { count }), waitForTransmit); } public void EndWriteFileRequest(BacnetAsyncResult request, out int position) @@ -1023,7 +1023,7 @@ public void EndWriteFileRequest(BacnetAsyncResult request, out int position) { position = request.GetResult(Timeout, Retries, r => { - if (Services.DecodeAtomicWriteFileAcknowledge(r.Result, 0, r.Result.Length, out _, out var positionValue) < 0) + if (FileAccessServices.DecodeAtomicWriteFileAcknowledge(r.Result, 0, r.Result.Length, out _, out var positionValue) < 0) throw new Exception("Failed to decode AtomicWriteFileAcknowledge"); return positionValue; }); @@ -1034,7 +1034,7 @@ public BacnetAsyncResult BeginReadFileRequest(BacnetAddress address, BacnetObjec { Log.Debug("Sending AtomicReadFileRequest"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, - buffer => Services.EncodeAtomicReadFile(buffer, true, objectId, position, count), waitForTransmit); + buffer => FileAccessServices.EncodeAtomicReadFile(buffer, true, objectId, position, count), waitForTransmit); } public void EndReadFileRequest(BacnetAsyncResult result, out uint count, out int position, out bool endOfFile, out byte[] fileBuffer, out int fileBufferOffset) @@ -1043,7 +1043,7 @@ public void EndReadFileRequest(BacnetAsyncResult result, out uint count, out int { var values = result.GetResult(Timeout, Retries, res => { - var decodedBytesCount = Services.DecodeAtomicReadFileAcknowledge(res.Result, 0, res.Result.Length, out var endOfFileValue, out _, + var decodedBytesCount = FileAccessServices.DecodeAtomicReadFileAcknowledge(res.Result, 0, res.Result.Length, out var endOfFileValue, out _, out var positionValue, out var countValue, out var fileBufferValue, out var fileBufferOffsetValue); if (decodedBytesCount < 0) @@ -1070,7 +1070,7 @@ public BacnetAsyncResult BeginReadRangeRequest(BacnetAddress address, BacnetObje { Log.Debug("Sending ReadRangeRequest"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, - buffer => Services.EncodeReadRange(buffer, objectId, (uint)BacnetPropertyIds.PROP_LOG_BUFFER, ASN1.BACNET_ARRAY_ALL, + buffer => ObjectAccessServices.EncodeReadRange(buffer, objectId, (uint)BacnetPropertyIds.PROP_LOG_BUFFER, ASN1.BACNET_ARRAY_ALL, BacnetReadRangeRequestTypes.RR_BY_POSITION, idxBegin, DateTime.Now, (int)quantity), waitForTransmit); } @@ -1080,7 +1080,7 @@ public void EndReadRangeRequest(BacnetAsyncResult request, out byte[] trendBuffe { var result = request.GetResult(TimeSpan.FromSeconds(40), Retries, r => { - var itemCountValue = Services.DecodeReadRangeAcknowledge(r.Result, 0, r.Result.Length, out var trendBufferValue); + var itemCountValue = ObjectAccessServices.DecodeReadRangeAcknowledge(r.Result, 0, r.Result.Length, out var trendBufferValue); if (itemCountValue == 0) throw new Exception("Failed to decode ReadRangeAcknowledge"); return Tuple.Create(itemCountValue, trendBufferValue); @@ -1107,7 +1107,7 @@ public BacnetAsyncResult BeginSubscribeCOVRequest(BacnetAddress address, BacnetO { Log.Debug($"Sending SubscribeCOVRequest {objectId}"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, - buffer => Services.EncodeSubscribeCOV(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, lifetime), waitForTransmit); + buffer => AlarmAndEventServices.EncodeSubscribeCOV(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, lifetime), waitForTransmit); } public void EndSubscribeCOVRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1122,7 +1122,7 @@ public BacnetAsyncResult BeginSubscribePropertyRequest(BacnetAddress address, Ba { Log.Debug($"Sending SubscribePropertyRequest {objectId}.{monitoredProperty}"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, - buffer => Services.EncodeSubscribeProperty(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, 0, monitoredProperty, false, 0f), waitForTransmit); + buffer => AlarmAndEventServices.EncodeSubscribeProperty(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, 0, monitoredProperty, false, 0f), waitForTransmit); } public void EndSubscribePropertyRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1165,7 +1165,7 @@ public BacnetAsyncResult BeginReadPropertyRequest(BacnetAddress address, BacnetO var propertyIndex = index == ASN1.BACNET_ARRAY_ALL ? "" : $"[{index}]"; Log.Debug($"Sending ReadPropertyRequest {objectId} {propertyId}{propertyIndex}"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, - buffer => Services.EncodeReadProperty(buffer, objectId, (uint)propertyId, index), waitForTransmit); + buffer => ObjectAccessServices.EncodeReadProperty(buffer, objectId, (uint)propertyId, index), waitForTransmit); } public IList EndReadPropertyRequest(BacnetAsyncResult request) @@ -1174,7 +1174,7 @@ public IList EndReadPropertyRequest(BacnetAsyncResult request) { return request.GetResult(Timeout, Retries, r => { - var byteCount = Services.DecodeReadPropertyAcknowledge(r.Address, r.Result, 0, r.Result.Length, + var byteCount = ObjectAccessServices.DecodeReadPropertyAcknowledge(r.Address, r.Result, 0, r.Result.Length, out _, out _, out var valueList); if (byteCount < 0) @@ -1206,7 +1206,7 @@ public BacnetAsyncResult BeginWritePropertyRequest(BacnetAddress address, Bacnet { Log.Debug($"Sending WritePropertyRequest {objectId} {propertyId}"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, - buffer => Services.EncodeWriteProperty(buffer, objectId, (uint)propertyId, ASN1.BACNET_ARRAY_ALL, _writepriority, valueList), waitForTransmit); + buffer => ObjectAccessServices.EncodeWriteProperty(buffer, objectId, (uint)propertyId, ASN1.BACNET_ARRAY_ALL, _writepriority, valueList), waitForTransmit); } public void EndWritePropertyRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1216,7 +1216,7 @@ public BacnetAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress address { Log.Debug($"Sending WritePropertyMultipleRequest {objectId}"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, - buffer => Services.EncodeWritePropertyMultiple(buffer, objectId, valueList), waitForTransmit); + buffer => ObjectAccessServices.EncodeWritePropertyMultiple(buffer, objectId, valueList), waitForTransmit); } public void EndWritePropertyMultipleRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1232,7 +1232,7 @@ public BacnetAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress address var objectIds = string.Join(", ", valueList.Select(v => v.objectIdentifier)); Log.Debug($"Sending WritePropertyMultipleRequest {objectIds}"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, - buffer => Services.EncodeWriteObjectMultiple(buffer, valueList), waitForTransmit); + buffer => ObjectAccessServices.EncodeWriteObjectMultiple(buffer, valueList), waitForTransmit); } public IList ReadPropertyMultipleRequest(BacnetAddress address, BacnetObjectId objectId, IList propertyIdAndIndex) @@ -1271,7 +1271,7 @@ public BacnetAsyncResult BeginReadPropertyMultipleRequest(BacnetAddress address, var objectIds = string.Join(", ", properties.Select(v => v.objectIdentifier)); Log.Debug($"Sending ReadPropertyMultipleRequest {objectIds}"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, - buffer => Services.EncodeReadPropertyMultiple(buffer, properties), waitForTransmit); + buffer => ObjectAccessServices.EncodeReadPropertyMultiple(buffer, properties), waitForTransmit); } public IList EndReadPropertyMultipleRequest(BacnetAsyncResult request) @@ -1281,7 +1281,7 @@ public IList EndReadPropertyMultipleRequest(BacnetAsyncR return request.GetResult(Timeout, Retries, r => { var byteCount = - Services.DecodeReadPropertyMultipleAcknowledge( + ObjectAccessServices.DecodeReadPropertyMultipleAcknowledge( r.Address, r.Result, 0, r.Result.Length, out var values); if (byteCount < 0) @@ -1302,7 +1302,7 @@ public BacnetAsyncResult BeginCreateObjectRequest(BacnetAddress address, BacnetO { Log.Debug("Sending CreateObjectRequest"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT, - buffer => Services.EncodeCreateProperty(buffer, objectId, valueList), waitForTransmit); + buffer => ObjectAccessServices.EncodeCreateObject(buffer, objectId, valueList), waitForTransmit); } public void EndCreateObjectRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1338,14 +1338,14 @@ public BacnetAsyncResult BeginRemoveListElementRequest(BacnetAddress address, Ba { Log.Debug("Sending RemoveListElementRequest"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT, - buffer => Services.EncodeAddListElement(buffer, objectId, reference.propertyIdentifier, reference.propertyArrayIndex, valueList), waitForTransmit); + buffer => ObjectAccessServices.EncodeAddOrRemoveListElement(buffer, objectId, reference.propertyIdentifier, reference.propertyArrayIndex, valueList), waitForTransmit); } public BacnetAsyncResult BeginAddListElementRequest(BacnetAddress address, BacnetObjectId objectId, BacnetPropertyReference reference, IList valueList, bool waitForTransmit = false) { Log.Debug($"Sending AddListElementRequest {objectId} {(BacnetPropertyIds)reference.propertyIdentifier}"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_ADD_LIST_ELEMENT, - buffer => Services.EncodeAddListElement(buffer, objectId, reference.propertyIdentifier, reference.propertyArrayIndex, valueList), waitForTransmit); + buffer => ObjectAccessServices.EncodeAddOrRemoveListElement(buffer, objectId, reference.propertyIdentifier, reference.propertyArrayIndex, valueList), waitForTransmit); } public void EndAddListElementRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1417,7 +1417,7 @@ public BacnetAsyncResult BeginDeviceCommunicationControlRequest(BacnetAddress ad { Log.Debug("Sending DeviceCommunicationControlRequest"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, - buffer => Services.EncodeDeviceCommunicationControl(buffer, timeDuration, enableDisable, password), waitForTransmit); + buffer => RemoteDeviceManagementServices.EncodeDeviceCommunicationControl(buffer, timeDuration, enableDisable, password), waitForTransmit); } public void EndDeviceCommunicationControlRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1460,7 +1460,7 @@ public BacnetAsyncResult BeginGetEventsRequest(BacnetAddress address, BacnetObje { Log.Debug("Sending Events request"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_EVENT_INFORMATION, - buffer => Services.EncodeGetEventInformation(buffer, lastEventObjectId), waitForTransmit); + buffer => AlarmAndEventServices.EncodeGetEventInformation(buffer, lastEventObjectId), waitForTransmit); } public IList EndGetAlarmSummaryRequest(BacnetAsyncResult request) @@ -1470,7 +1470,7 @@ public IList EndGetAlarmSummaryRequest(BacnetAsyncResult return request.GetResult(Timeout, Retries, r => { IList alarms = new List(); - if (Services.DecodeAlarmSummary(r.Result, 0, r.Result.Length, ref alarms) < 0) + if (AlarmAndEventServices.DecodeAlarmSummary(r.Result, 0, r.Result.Length, ref alarms) < 0) throw new Exception("Failed to decode AlarmSummary"); return alarms; }); @@ -1484,7 +1484,7 @@ public IList EndGetEventsRequest(BacnetAsyncResul var result = request.GetResult(Timeout, Retries, r => { IList events = new List(); - if (Services.DecodeEventInformation(r.Result, 0, r.Result.Length, ref events, out var moreEventsValue) < 0) + if (AlarmAndEventServices.DecodeEventInformation(r.Result, 0, r.Result.Length, ref events, out var moreEventsValue) < 0) throw new Exception("Failed to decode Events"); return Tuple.Create(events, moreEventsValue); }); @@ -1505,7 +1505,7 @@ public BacnetAsyncResult BeginAlarmAcknowledgement(BacnetAddress address, Bacnet { Log.Debug("Sending AlarmAcknowledgement"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, - buffer => Services.EncodeAlarmAcknowledge(buffer, 57, objId, (uint)eventState, ackText, evTimeStamp, ackTimeStamp), waitForTransmit); + buffer => AlarmAndEventServices.EncodeAlarmAcknowledge(buffer, 57, objId, (uint)eventState, ackText, evTimeStamp, ackTimeStamp), waitForTransmit); } public void EndAlarmAcknowledgement(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1520,7 +1520,7 @@ public BacnetAsyncResult BeginReinitializeRequest(BacnetAddress address, BacnetR { Log.Debug("Sending ReinitializeRequest"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_REINITIALIZE_DEVICE, - buffer => Services.EncodeReinitializeDevice(buffer, state, password), waitForTransmit); + buffer => RemoteDeviceManagementServices.EncodeReinitializeDevice(buffer, state, password), waitForTransmit); } public void EndReinitializeRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1530,7 +1530,7 @@ public BacnetAsyncResult BeginConfirmedNotify(BacnetAddress address, uint subscr { Log.Debug("Sending Notify (confirmed)"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION, - buffer => Services.EncodeCOVNotifyConfirmed(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, + buffer => AlarmAndEventServices.EncodeCOVNotifyConfirmed(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values), waitForTransmit); } @@ -1545,7 +1545,7 @@ public bool Notify(BacnetAddress address, uint subscriberProcessIdentifier, uint var buffer = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, address.RoutedSource); APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION); - Services.EncodeCOVNotifyUnconfirmed(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values); + AlarmAndEventServices.EncodeCOVNotifyUnconfirmed(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values); // Modif F. Chaxel var sendbytes=Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, address, false, 0); @@ -1569,7 +1569,7 @@ public BacnetAsyncResult BeginLifeSafetyOperationRequest(BacnetAddress address, { Log.Debug($"Sending {ToTitleCase(operation)} {objectId}"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION, - buffer => Services.EncodeLifeSafetyOperation(buffer, processId, requestingSrc, (uint)operation, objectId), waitForTransmit); + buffer => AlarmAndEventServices.EncodeLifeSafetyOperation(buffer, processId, requestingSrc, (uint)operation, objectId), waitForTransmit); } public void EndLifeSafetyOperationRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1786,7 +1786,7 @@ public void ReadPropertyResponse(BacnetAddress address, byte invokeId, Segmentat { SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, b => { - Services.EncodeReadPropertyAcknowledge(b, objectId, property.propertyIdentifier, property.propertyArrayIndex, value); + ObjectAccessServices.EncodeReadPropertyAcknowledge(b, objectId, property.propertyIdentifier, property.propertyArrayIndex, value); }); }); } @@ -1795,7 +1795,7 @@ public void CreateObjectResponse(BacnetAddress address, byte invokeId, Segmentat { SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT, b => { - Services.EncodeCreateObjectAcknowledge(b, objectId); + ObjectAccessServices.EncodeCreateObjectAcknowledge(b, objectId); }); } @@ -1805,7 +1805,7 @@ public void ReadPropertyMultipleResponse(BacnetAddress address, byte invokeId, S { SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, b => { - Services.EncodeReadPropertyMultipleAcknowledge(b, values); + ObjectAccessServices.EncodeReadPropertyMultipleAcknowledge(b, values); }); }); } @@ -1816,7 +1816,7 @@ public void ReadRangeResponse(BacnetAddress address, byte invokeId, Segmentation { SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, b => { - Services.EncodeReadRangeAcknowledge(b, objectId, property.propertyIdentifier, property.propertyArrayIndex, BacnetBitString.ConvertFromInt((uint)status), itemCount, applicationData, requestType, firstSequenceNo); + ObjectAccessServices.EncodeReadRangeAcknowledge(b, objectId, property.propertyIdentifier, property.propertyArrayIndex, BacnetBitString.ConvertFromInt((uint)status), itemCount, applicationData, requestType, firstSequenceNo); }); }); } @@ -1827,7 +1827,7 @@ public void ReadFileResponse(BacnetAddress address, byte invokeId, Segmentation { SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, b => { - Services.EncodeAtomicReadFileAcknowledge(b, true, endOfFile, position, 1, new[] { fileBuffer }, new[] { (int)count }); + FileAccessServices.EncodeAtomicReadFileAcknowledge(b, true, endOfFile, position, 1, new[] { fileBuffer }, new[] { (int)count }); }); }); } @@ -1836,7 +1836,7 @@ public void WriteFileResponse(BacnetAddress address, byte invokeId, Segmentation { SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, b => { - Services.EncodeAtomicWriteFileAcknowledge(b, true, position); + FileAccessServices.EncodeAtomicWriteFileAcknowledge(b, true, position); }); } @@ -1846,7 +1846,7 @@ public void ErrorResponse(BacnetAddress address, BacnetConfirmedServices service var buffer = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, address.RoutedSource); APDU.EncodeError(buffer, BacnetPduTypes.PDU_TYPE_ERROR, service, invokeId); - Services.EncodeError(buffer, errorClass, errorCode); + ASN1.EncodeError(buffer, errorClass, errorCode); Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, address, false, 0); } diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index 67d80a0b..be3f7f23 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -2517,5 +2517,25 @@ public static int decode_context_unsigned(byte[] buffer, int offset, byte tagNum var len = decode_tag_number_and_value(buffer, offset, out _, out var lenValue); return len + decode_unsigned(buffer, offset + len, lenValue, out value); } + + public static void EncodeError(EncodeBuffer buffer, BacnetErrorClasses errorClass, BacnetErrorCodes errorCode) + { + encode_application_enumerated(buffer, (uint)errorClass); + encode_application_enumerated(buffer, (uint)errorCode); + } + + public static int DecodeError(byte[] buffer, int offset, int length, out BacnetErrorClasses errorClass, out BacnetErrorCodes errorCode) + { + var orgOffset = offset; + + offset += decode_tag_number_and_value(buffer, offset, out _, out var lenValueType); + /* FIXME: we could validate that the tag is enumerated... */ + offset += EnumUtils.DecodeEnumerated(buffer, offset, lenValueType, out errorClass); + offset += decode_tag_number_and_value(buffer, offset, out _, out lenValueType); + /* FIXME: we could validate that the tag is enumerated... */ + offset += EnumUtils.DecodeEnumerated(buffer, offset, lenValueType, out errorCode); + + return offset - orgOffset; + } } } diff --git a/Serialize/Services.cs b/Serialize/Services.cs deleted file mode 100644 index 2800e215..00000000 --- a/Serialize/Services.cs +++ /dev/null @@ -1,2656 +0,0 @@ -using System.Collections.Generic; -using System.IO.BACnet.EventNotification; -using System.IO.BACnet.EventNotification.EventValues; -using System.Linq; - -namespace System.IO.BACnet.Serialize -{ - public class Services - { - public static void EncodeIamBroadcast(EncodeBuffer buffer, uint deviceId, uint maxApdu, BacnetSegmentations segmentation, ushort vendorId) - { - ASN1.encode_application_object_id(buffer, BacnetObjectTypes.OBJECT_DEVICE, deviceId); - ASN1.encode_application_unsigned(buffer, maxApdu); - ASN1.encode_application_enumerated(buffer, (uint)segmentation); - ASN1.encode_application_unsigned(buffer, vendorId); - } - - public static int DecodeIamBroadcast(byte[] buffer, int offset, out uint deviceId, out uint maxApdu, out BacnetSegmentations segmentation, out ushort vendorId) - { - var apduLen = 0; - var orgOffset = offset; - - deviceId = 0; - maxApdu = 0; - segmentation = BacnetSegmentations.SEGMENTATION_NONE; - vendorId = 0; - - /* OBJECT ID - object id */ - var len = ASN1.decode_tag_number_and_value(buffer, offset + apduLen, out var tagNumber, out var lenValue); - apduLen += len; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID) - return -1; - len = ASN1.decode_object_id(buffer, offset + apduLen, out BacnetObjectTypes type, out var instance); - apduLen += len; - var objectId = new BacnetObjectId(type, instance); - if (objectId.Type != BacnetObjectTypes.OBJECT_DEVICE) - return -1; - deviceId = objectId.Instance; - /* MAX APDU - unsigned */ - len = - ASN1.decode_tag_number_and_value(buffer, offset + apduLen, out tagNumber, out lenValue); - apduLen += len; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) - return -1; - len = ASN1.decode_unsigned(buffer, offset + apduLen, lenValue, out uint decodedValue); - apduLen += len; - maxApdu = decodedValue; - /* Segmentation - enumerated */ - len = - ASN1.decode_tag_number_and_value(buffer, offset + apduLen, out tagNumber, out lenValue); - apduLen += len; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED) - return -1; - len = ASN1.decode_unsigned(buffer, offset + apduLen, lenValue, out decodedValue); - apduLen += len; - if (decodedValue > (uint)BacnetSegmentations.SEGMENTATION_NONE) - return -1; - segmentation = (BacnetSegmentations)decodedValue; - /* Vendor ID - unsigned16 */ - len = - ASN1.decode_tag_number_and_value(buffer, offset + apduLen, out tagNumber, out lenValue); - apduLen += len; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) - return -1; - ASN1.decode_unsigned(buffer, offset + apduLen, lenValue, out decodedValue); - if (decodedValue > 0xFFFF) - return -1; - vendorId = (ushort)decodedValue; - - return offset - orgOffset; - } - - public static void EncodeIhaveBroadcast(EncodeBuffer buffer, BacnetObjectId deviceId, BacnetObjectId objectId, string objectName) - { - /* deviceIdentifier */ - ASN1.encode_application_object_id(buffer, deviceId.Type, deviceId.Instance); - /* objectIdentifier */ - ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); - /* objectName */ - ASN1.encode_application_character_string(buffer, objectName); - } - - public static void EncodeWhoHasBroadcast(EncodeBuffer buffer, int lowLimit, int highLimit, BacnetObjectId objectId, string objectName) - { - /* optional limits - must be used as a pair */ - if (lowLimit >= 0 && lowLimit <= ASN1.BACNET_MAX_INSTANCE && highLimit >= 0 && highLimit <= ASN1.BACNET_MAX_INSTANCE) - { - ASN1.encode_context_unsigned(buffer, 0, (uint)lowLimit); - ASN1.encode_context_unsigned(buffer, 1, (uint)highLimit); - } - if (!string.IsNullOrEmpty(objectName)) - { - ASN1.encode_context_character_string(buffer, 3, objectName); - } - else - { - ASN1.encode_context_object_id(buffer, 2, objectId.Type, objectId.Instance); - } - } - - public static void EncodeWhoIsBroadcast(EncodeBuffer buffer, int lowLimit, int highLimit) - { - /* optional limits - must be used as a pair */ - if (lowLimit >= 0 && lowLimit <= ASN1.BACNET_MAX_INSTANCE && - highLimit >= 0 && highLimit <= ASN1.BACNET_MAX_INSTANCE) - { - ASN1.encode_context_unsigned(buffer, 0, (uint)lowLimit); - ASN1.encode_context_unsigned(buffer, 1, (uint)highLimit); - } - } - - public static int DecodeWhoIsBroadcast(byte[] buffer, int offset, int apduLen, out int lowLimit, out int highLimit) - { - var len = 0; - - lowLimit = -1; - highLimit = -1; - - if (apduLen <= 0) return len; - - /* optional limits - must be used as a pair */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); - if (tagNumber != 0) - return -1; - if (apduLen > len) - { - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint decodedValue); - if (decodedValue <= ASN1.BACNET_MAX_INSTANCE) - lowLimit = (int)decodedValue; - if (apduLen > len) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - if (tagNumber != 1) - return -1; - if (apduLen > len) - { - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out decodedValue); - if (decodedValue <= ASN1.BACNET_MAX_INSTANCE) - highLimit = (int)decodedValue; - } - else - return -1; - } - else - return -1; - } - else - return -1; - - return len; - } - - // Added by thamersalek - public static int DecodeWhoHasBroadcast(byte[] buffer, int offset, int apduLen, out int lowLimit, out int highLimit, out BacnetObjectId objId, out string objName) - { - var len = 0; - uint decodedValue; - - objName = null; - objId = new BacnetObjectId(BacnetObjectTypes.OBJECT_BINARY_OUTPUT, 0x3FFFFF); - lowLimit = -1; - highLimit = -1; - - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); - - if (tagNumber == 0) - { - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out decodedValue); - if (decodedValue <= ASN1.BACNET_MAX_INSTANCE) - lowLimit = (int)decodedValue; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - } - - if (tagNumber == 1) - { - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out decodedValue); - if (decodedValue <= ASN1.BACNET_MAX_INSTANCE) - highLimit = (int)decodedValue; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - } - - if (tagNumber == 2) - { - len += ASN1.decode_object_id(buffer, offset + len, out ushort objType, out var objInst); - objId = new BacnetObjectId((BacnetObjectTypes)objType, objInst); - } - - if (tagNumber == 3) - len += ASN1.decode_character_string(buffer, offset + len, apduLen - (offset + len), lenValue, out objName); - - return len; - } - - public static void EncodeAlarmAcknowledge(EncodeBuffer buffer, uint ackProcessIdentifier, BacnetObjectId eventObjectIdentifier, uint eventStateAcked, string ackSource, BacnetGenericTime eventTimeStamp, BacnetGenericTime ackTimeStamp) - { - ASN1.encode_context_unsigned(buffer, 0, ackProcessIdentifier); - ASN1.encode_context_object_id(buffer, 1, eventObjectIdentifier.Type, eventObjectIdentifier.Instance); - ASN1.encode_context_enumerated(buffer, 2, eventStateAcked); - ASN1.bacapp_encode_context_timestamp(buffer, 3, eventTimeStamp); - ASN1.encode_context_character_string(buffer, 4, ackSource); - ASN1.bacapp_encode_context_timestamp(buffer, 5, ackTimeStamp); - } - - public static void EncodeAtomicReadFile(EncodeBuffer buffer, bool isStream, BacnetObjectId objectId, int position, uint count) - { - ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); - var tagNumber = (byte)(isStream ? 0 : 1); - ASN1.encode_opening_tag(buffer, tagNumber); - ASN1.encode_application_signed(buffer, position); - ASN1.encode_application_unsigned(buffer, count); - ASN1.encode_closing_tag(buffer, tagNumber); - } - - public static int DecodeAtomicReadFile(byte[] buffer, int offset, int apduLen, out bool isStream, out BacnetObjectId objectId, out int position, out uint count) - { - objectId = default(BacnetObjectId); - - var len = 0; - int tagLen; - - isStream = true; - position = -1; - count = 0; - - len = ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID) - return -1; - - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - objectId = new BacnetObjectId(type, instance); - - if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 0)) - { - /* a tag number is not extended so only one octet */ - len++; - /* fileStartPosition */ - tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += tagLen; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) - return -1; - len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); - /* requestedOctetCount */ - tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += tagLen; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) - return -1; - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out count); - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 0)) - return -1; - /* a tag number is not extended so only one octet */ - len++; - } - else if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 1)) - { - isStream = false; - /* a tag number is not extended so only one octet */ - len++; - /* fileStartRecord */ - tagLen = - ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, - out lenValueType); - len += tagLen; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) - return -1; - len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); - /* RecordCount */ - tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += tagLen; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) - return -1; - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out count); - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) - return -1; - /* a tag number is not extended so only one octet */ - len++; - } - else - return -1; - - return len; - } - - public static void EncodeAtomicReadFileAcknowledge(EncodeBuffer buffer, bool isStream, bool endOfFile, int position, uint blockCount, byte[][] blocks, int[] counts) - { - ASN1.encode_application_boolean(buffer, endOfFile); - var tagNumber = (byte)(isStream ? 0 : 1); - ASN1.encode_opening_tag(buffer, tagNumber); - ASN1.encode_application_signed(buffer, position); - - if (isStream) - { - ASN1.encode_application_octet_string(buffer, blocks[0], 0, counts[0]); - } - else - { - ASN1.encode_application_unsigned(buffer, blockCount); - for (var i = 0; i < blockCount; i++) - ASN1.encode_application_octet_string(buffer, blocks[i], 0, counts[i]); - } - - ASN1.encode_closing_tag(buffer, tagNumber); - } - - public static void EncodeAtomicWriteFile(EncodeBuffer buffer, bool isStream, BacnetObjectId objectId, int position, uint blockCount, byte[][] blocks, int[] counts) - { - ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); - var tagNumber = (byte)(isStream ? 0 : 1); - - ASN1.encode_opening_tag(buffer, tagNumber); - ASN1.encode_application_signed(buffer, position); - - if (isStream) - { - ASN1.encode_application_octet_string(buffer, blocks[0], 0, counts[0]); - } - else - { - ASN1.encode_application_unsigned(buffer, blockCount); - for (var i = 0; i < blockCount; i++) - ASN1.encode_application_octet_string(buffer, blocks[i], 0, counts[i]); - } - - ASN1.encode_closing_tag(buffer, tagNumber); - } - - public static int DecodeAtomicWriteFile(byte[] buffer, int offset, int apduLen, out bool isStream, out BacnetObjectId objectId, out int position, out uint blockCount, out byte[][] blocks, out int[] counts) - { - var len = 0; - int tagLen; - - objectId = default(BacnetObjectId); - isStream = true; - position = -1; - blockCount = 0; - blocks = null; - counts = null; - - len = ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID) - return -1; - - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - objectId = new BacnetObjectId(type, instance); - - if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 0)) - { - /* a tag number of 2 is not extended so only one octet */ - len++; - /* fileStartPosition */ - tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += tagLen; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) - return -1; - len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); - /* fileData */ - tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += tagLen; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING) - return -1; - blockCount = 1; - blocks = new byte[1][]; - blocks[0] = new byte[lenValueType]; - counts = new[] { (int)lenValueType }; - len += ASN1.decode_octet_string(buffer, offset + len, apduLen, blocks[0], 0, lenValueType); - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 0)) - return -1; - /* a tag number is not extended so only one octet */ - len++; - } - else if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 1)) - { - isStream = false; - /* a tag number is not extended so only one octet */ - len++; - /* fileStartRecord */ - tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += tagLen; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) - return -1; - len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); - /* returnedRecordCount */ - tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += tagLen; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) - return -1; - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out blockCount); - /* fileData */ - blocks = new byte[blockCount][]; - counts = new int[blockCount]; - for (var i = 0; i < blockCount; i++) - { - tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += tagLen; - blocks[i] = new byte[lenValueType]; - counts[i] = (int)lenValueType; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING) - return -1; - len += ASN1.decode_octet_string(buffer, offset + len, apduLen, blocks[i], 0, lenValueType); - } - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) - return -1; - /* a tag number is not extended so only one octet */ - len++; - } - else - return -1; - - return len; - } - - // by Christopher Günter - public static void EncodeCreateProperty(EncodeBuffer buffer, BacnetObjectId objectId, ICollection valueList) - { - /* Tag 1: sequence of WriteAccessSpecification */ - ASN1.encode_opening_tag(buffer, 0); - ASN1.encode_context_object_id(buffer, 1, objectId.Type, objectId.Instance); - ASN1.encode_closing_tag(buffer, 0); - - if (valueList == null) - return; - - ASN1.encode_opening_tag(buffer, 1); - - foreach (var pValue in valueList) - { - ASN1.encode_context_enumerated(buffer, 0, pValue.property.propertyIdentifier); - - if (pValue.property.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) - ASN1.encode_context_unsigned(buffer, 1, pValue.property.propertyArrayIndex); - - ASN1.encode_opening_tag(buffer, 2); - foreach (var value in pValue.value) - { - ASN1.bacapp_encode_application_data(buffer, value); - } - ASN1.encode_closing_tag(buffer, 2); - - if (pValue.priority != ASN1.BACNET_NO_PRIORITY) - ASN1.encode_context_unsigned(buffer, 3, pValue.priority); - } - - ASN1.encode_closing_tag(buffer, 1); - } - - public static void EncodeAddListElement(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, IList valueList) - { - ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); - ASN1.encode_context_enumerated(buffer, 1, propertyId); - - if (arrayIndex != ASN1.BACNET_ARRAY_ALL) - { - ASN1.encode_context_unsigned(buffer, 2, arrayIndex); - } - - ASN1.encode_opening_tag(buffer, 3); - foreach (var value in valueList) - { - ASN1.bacapp_encode_application_data(buffer, value); - } - - ASN1.encode_closing_tag(buffer, 3); - } - - public static void EncodeAtomicWriteFileAcknowledge(EncodeBuffer buffer, bool isStream, int position) - { - ASN1.encode_context_signed(buffer, (byte)(isStream ? 0 : 1), position); - } - - public static void EncodeCOVNotifyConfirmed(EncodeBuffer buffer, uint subscriberProcessIdentifier, uint initiatingDeviceIdentifier, BacnetObjectId monitoredObjectIdentifier, uint timeRemaining, IEnumerable values) - { - /* tag 0 - subscriberProcessIdentifier */ - ASN1.encode_context_unsigned(buffer, 0, subscriberProcessIdentifier); - /* tag 1 - initiatingDeviceIdentifier */ - ASN1.encode_context_object_id(buffer, 1, BacnetObjectTypes.OBJECT_DEVICE, initiatingDeviceIdentifier); - /* tag 2 - monitoredObjectIdentifier */ - ASN1.encode_context_object_id(buffer, 2, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); - /* tag 3 - timeRemaining */ - ASN1.encode_context_unsigned(buffer, 3, timeRemaining); - /* tag 4 - listOfValues */ - ASN1.encode_opening_tag(buffer, 4); - foreach (var value in values) - { - /* tag 0 - propertyIdentifier */ - ASN1.encode_context_enumerated(buffer, 0, value.property.propertyIdentifier); - /* tag 1 - propertyArrayIndex OPTIONAL */ - if (value.property.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) - { - ASN1.encode_context_unsigned(buffer, 1, value.property.propertyArrayIndex); - } - /* tag 2 - value */ - /* abstract syntax gets enclosed in a context tag */ - ASN1.encode_opening_tag(buffer, 2); - foreach (var v in value.value) - { - ASN1.bacapp_encode_application_data(buffer, v); - } - ASN1.encode_closing_tag(buffer, 2); - /* tag 3 - priority OPTIONAL */ - if (value.priority != ASN1.BACNET_NO_PRIORITY) - { - ASN1.encode_context_unsigned(buffer, 3, value.priority); - } - /* is there another one to encode? */ - /* FIXME: check to see if there is room in the APDU */ - } - ASN1.encode_closing_tag(buffer, 4); - } - - public static void EncodeCOVNotifyUnconfirmed(EncodeBuffer buffer, uint subscriberProcessIdentifier, uint initiatingDeviceIdentifier, BacnetObjectId monitoredObjectIdentifier, uint timeRemaining, IEnumerable values) - { - /* tag 0 - subscriberProcessIdentifier */ - ASN1.encode_context_unsigned(buffer, 0, subscriberProcessIdentifier); - /* tag 1 - initiatingDeviceIdentifier */ - ASN1.encode_context_object_id(buffer, 1, BacnetObjectTypes.OBJECT_DEVICE, initiatingDeviceIdentifier); - /* tag 2 - monitoredObjectIdentifier */ - ASN1.encode_context_object_id(buffer, 2, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); - /* tag 3 - timeRemaining */ - ASN1.encode_context_unsigned(buffer, 3, timeRemaining); - /* tag 4 - listOfValues */ - ASN1.encode_opening_tag(buffer, 4); - foreach (var value in values) - { - /* tag 0 - propertyIdentifier */ - ASN1.encode_context_enumerated(buffer, 0, value.property.propertyIdentifier); - /* tag 1 - propertyArrayIndex OPTIONAL */ - if (value.property.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) - { - ASN1.encode_context_unsigned(buffer, 1, value.property.propertyArrayIndex); - } - /* tag 2 - value */ - /* abstract syntax gets enclosed in a context tag */ - ASN1.encode_opening_tag(buffer, 2); - foreach (var v in value.value) - { - ASN1.bacapp_encode_application_data(buffer, v); - } - ASN1.encode_closing_tag(buffer, 2); - /* tag 3 - priority OPTIONAL */ - if (value.priority != ASN1.BACNET_NO_PRIORITY) - { - ASN1.encode_context_unsigned(buffer, 3, value.priority); - } - /* is there another one to encode? */ - /* FIXME: check to see if there is room in the APDU */ - } - ASN1.encode_closing_tag(buffer, 4); - } - - public static void EncodeSubscribeCOV(EncodeBuffer buffer, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime) - { - /* tag 0 - subscriberProcessIdentifier */ - ASN1.encode_context_unsigned(buffer, 0, subscriberProcessIdentifier); - /* tag 1 - monitoredObjectIdentifier */ - ASN1.encode_context_object_id(buffer, 1, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); - /* - If both the 'Issue Confirmed Notifications' and - 'Lifetime' parameters are absent, then this shall - indicate a cancellation request. - */ - if (cancellationRequest) - return; - /* tag 2 - issueConfirmedNotifications */ - ASN1.encode_context_boolean(buffer, 2, issueConfirmedNotifications); - /* tag 3 - lifetime */ - ASN1.encode_context_unsigned(buffer, 3, lifetime); - } - - public static int DecodeSubscribeCOV(byte[] buffer, int offset, int apduLen, out uint subscriberProcessIdentifier, out BacnetObjectId monitoredObjectIdentifier, out bool cancellationRequest, out bool issueConfirmedNotifications, out uint lifetime) - { - var len = 0; - uint lenValue; - - subscriberProcessIdentifier = 0; - monitoredObjectIdentifier = default(BacnetObjectId); - cancellationRequest = false; - issueConfirmedNotifications = false; - lifetime = 0; - - /* tag 0 - subscriberProcessIdentifier */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out subscriberProcessIdentifier); - } - else - return -1; - /* tag 1 - monitoredObjectIdentifier */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - monitoredObjectIdentifier = new BacnetObjectId(type, instance); - } - else - return -1; - /* optional parameters - if missing, means cancellation */ - if (len < apduLen) - { - /* tag 2 - issueConfirmedNotifications - optional */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 2)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - issueConfirmedNotifications = buffer[offset + len] > 0; - len += (int)lenValue; - } - else - { - cancellationRequest = true; - } - /* tag 3 - lifetime - optional */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 3)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out lifetime); - } - else - { - lifetime = 0; - } - } - else - { - cancellationRequest = true; - } - - return len; - } - - // TODO CHECK: rename to EncodeSubscribeCovProperty ? - public static void EncodeSubscribeProperty(EncodeBuffer buffer, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, BacnetPropertyReference monitoredProperty, bool covIncrementPresent, float covIncrement) - { - /* tag 0 - subscriberProcessIdentifier */ - ASN1.encode_context_unsigned(buffer, 0, subscriberProcessIdentifier); - /* tag 1 - monitoredObjectIdentifier */ - ASN1.encode_context_object_id(buffer, 1, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); - if (!cancellationRequest) - { - /* tag 2 - issueConfirmedNotifications */ - ASN1.encode_context_boolean(buffer, 2, issueConfirmedNotifications); - /* tag 3 - lifetime */ - ASN1.encode_context_unsigned(buffer, 3, lifetime); - } - /* tag 4 - monitoredPropertyIdentifier */ - ASN1.encode_opening_tag(buffer, 4); - ASN1.encode_context_enumerated(buffer, 0, monitoredProperty.propertyIdentifier); - if (monitoredProperty.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) - { - ASN1.encode_context_unsigned(buffer, 1, monitoredProperty.propertyArrayIndex); - } - ASN1.encode_closing_tag(buffer, 4); - - /* tag 5 - covIncrement */ - if (covIncrementPresent) - ASN1.encode_context_real(buffer, 5, covIncrement); - } - - public static int DecodeSubscribeProperty(byte[] buffer, int offset, int apduLen, out uint subscriberProcessIdentifier, out BacnetObjectId monitoredObjectIdentifier, out BacnetPropertyReference monitoredProperty, out bool cancellationRequest, out bool issueConfirmedNotifications, out uint lifetime, out float covIncrement) - { - var len = 0; - uint lenValue; - uint decodedValue; - - subscriberProcessIdentifier = 0; - monitoredObjectIdentifier = default(BacnetObjectId); - cancellationRequest = false; - issueConfirmedNotifications = false; - lifetime = 0; - covIncrement = 0; - monitoredProperty = new BacnetPropertyReference(); - - /* tag 0 - subscriberProcessIdentifier */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out subscriberProcessIdentifier); - } - else - return -1; - - /* tag 1 - monitoredObjectIdentifier */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - monitoredObjectIdentifier = new BacnetObjectId(type, instance); - } - else - return -1; - - /* tag 2 - issueConfirmedNotifications - optional */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 2)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - issueConfirmedNotifications = buffer[offset + len] > 0; - len++; - } - else - { - cancellationRequest = true; - } - - /* tag 3 - lifetime - optional */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 3)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out lifetime); - } - else - { - lifetime = 0; - } - - /* tag 4 - monitoredPropertyIdentifier */ - if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 4)) - return -1; - - /* a tag number of 4 is not extended so only one octet */ - len++; - /* the propertyIdentifier is tag 0 */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out monitoredProperty.propertyIdentifier); - } - else - return -1; - - /* the optional array index is tag 1 */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out decodedValue); - monitoredProperty.propertyArrayIndex = decodedValue; - } - else - { - monitoredProperty.propertyArrayIndex = ASN1.BACNET_ARRAY_ALL; - } - - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 4)) - return -1; - - /* a tag number of 4 is not extended so only one octet */ - len++; - /* tag 5 - covIncrement - optional */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 5)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_real(buffer, offset + len, out covIncrement); - } - else - { - covIncrement = 0; - } - - return len; - } - - // F Chaxel - public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, out NotificationData eventData) - { - var len = 0; - uint lenValue; - - eventData = default; - BacnetNotifyTypes? notifyType; - BacnetEventTypes? eventType = default; - var decodedNotificationData = new List>(); - var decodedStateTransition = new List>(); - - /* tag 0 - processIdentifier */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint processIdentifier); - decodedNotificationData.Add(e => e.ProcessIdentifier = processIdentifier); - } - else - return -1; - - /* tag 1 - initiatingObjectIdentifier */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - decodedNotificationData.Add(e => e.InitiatingObjectIdentifier = new BacnetObjectId(type, instance)); - } - else - return -1; - - /* tag 2 - eventObjectIdentifier */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 2)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - decodedNotificationData.Add(e => e.EventObjectIdentifier = new BacnetObjectId(type, instance)); - } - else - return -1; - - /* tag 3 - timeStamp */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 3)) - { - len += 2; // opening Tag 3 then 2 - len += ASN1.decode_application_date(buffer, offset + len, out var date); - len += ASN1.decode_application_time(buffer, offset + len, out var time); - decodedNotificationData.Add(e => e.TimeStamp = new BacnetGenericTime(new DateTime( - date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond), - BacnetTimestampTags.TIME_STAMP_DATETIME)); - len += 2; // closing tag 2 then 3 - } - else - return -1; - - /* tag 4 - noticicationClass */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 4)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint notificationClass); - decodedNotificationData.Add(e => e.NotificationClass = notificationClass); - } - else - return -1; - - /* tag 5 - priority */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 5)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint priority); - if (priority > 0xFF) return -1; - decodedNotificationData.Add(e => e.Priority = (byte) priority); - } - else - return -1; - - /* tag 6 - eventType */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 6)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out BacnetEventTypes eventTypeValue); - eventType = eventTypeValue; - } - //else - // return -1; - // shouldn't be present in ack transitions (according to the spec), but still is with some hardware - - /* optional tag 7 - messageText : never tested */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 7)) - { - // max_lenght 20000 sound like a joke - len += ASN1.decode_context_character_string(buffer, offset + len, 20000, 7, out var messageText); - decodedNotificationData.Add(e => e.MessageText = messageText); - } - - /* tag 8 - notifyType */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 8)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out BacnetNotifyTypes notifyTypeValue); - decodedStateTransition.Add(e => e.NotifyType = notifyTypeValue); - notifyType = notifyTypeValue; - } - else - return -1; - - switch (notifyType) - { - case BacnetNotifyTypes.NOTIFY_ALARM: - case BacnetNotifyTypes.NOTIFY_EVENT: - /* tag 9 - ackRequired */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned8(buffer, offset + len, out var val); - decodedStateTransition.Add(e => e.AckRequired = Convert.ToBoolean(val)); - - /* tag 10 - fromState */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint fromstate); - decodedStateTransition.Add(e => e.FromState = (BacnetEventStates) fromstate); - break; - } - - /* tag 11 - toState */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 11)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint toState); - decodedNotificationData.Add(e => e.ToState = (BacnetEventStates) toState); - } - else - return -1; - - /* tag 12 - event values */ - switch (notifyType) - { - case BacnetNotifyTypes.NOTIFY_ALARM when eventType.HasValue: - case BacnetNotifyTypes.NOTIFY_EVENT when eventType.HasValue: - if (!DecodeEventValues(buffer, offset, eventType.Value, ref len, out var eventValues)) - return -1; - - var targetType = typeof(StateTransition<>).MakeGenericType(eventValues.GetType()); - eventData = Activator.CreateInstance(targetType, eventValues) as StateTransition; - break; - } - - eventData = eventData ?? (decodedStateTransition.Any() - ? new StateTransition(eventType ?? throw new ArgumentNullException($"Need eventType to initialize StateTransition")) - : new NotificationData()); - - if (eventData is StateTransition stateTransition) - foreach (var setValue in decodedStateTransition) - setValue(stateTransition); - - foreach (var setValue in decodedNotificationData) - setValue(eventData); - - return len; - } - - private static bool DecodeEventValues(byte[] buffer, int offset, BacnetEventTypes eventType, ref int len, - out EventValuesBase eventValues) - { - eventValues = default; - - if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 12)) - return false; - - len++; - if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, (byte)eventType)) - return false; - - len++; - switch (eventType) - { - case BacnetEventTypes.EVENT_CHANGE_OF_BITSTRING: - len += ASN1.decode_context_bitstring(buffer, offset + len, 0, out var referencedBitString); - len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var changeOfBitStringStatusFlags); - eventValues = new ChangeOfBitString - { - ReferencedBitString = referencedBitString, - StatusFlags = changeOfBitStringStatusFlags - }; - break; - - case BacnetEventTypes.EVENT_CHANGE_OF_STATE: - len += ASN1.decode_context_property_state(buffer, offset + len, 0, out var newState); - len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var changeOfStateStatusFlags); - eventValues = (newState as ChangeOfState).SetStatusFlags(changeOfStateStatusFlags); - break; - - case BacnetEventTypes.EVENT_CHANGE_OF_VALUE: - if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 0)) - return false; - - len++; - if (ASN1.decode_is_context_tag(buffer, offset + len, (byte)BacnetCOVTypes.CHANGE_OF_VALUE_BITS)) - { - len += ASN1.decode_context_bitstring(buffer, offset + len, 0, out var changedBits); - eventValues = ChangeOfValueFactory.Create(changedBits); - } - else if (ASN1.decode_is_context_tag(buffer, offset + len, (byte)BacnetCOVTypes.CHANGE_OF_VALUE_REAL)) - { - len += ASN1.decode_context_real(buffer, offset + len, 1, out var changeValue); - eventValues = ChangeOfValueFactory.Create(changeValue); - } - else - { - return false; - } - - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 0)) - return false; - - len++; - len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var changeOfValueStatusFlags); - ((ChangeOfValue)eventValues).StatusFlags = changeOfValueStatusFlags; - break; - - case BacnetEventTypes.EVENT_FLOATING_LIMIT: - len += ASN1.decode_context_real(buffer, offset + len, 0, out var referenceValue); - len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var floatingLimitStatusFlags); - len += ASN1.decode_context_real(buffer, offset + len, 2, out var setPointValue); - len += ASN1.decode_context_real(buffer, offset + len, 3, out var errorLimit); - eventValues = new FloatingLimit - { - ReferenceValue = referenceValue, - StatusFlags = floatingLimitStatusFlags, - SetPointValue = setPointValue, - ErrorLimit = errorLimit - }; - break; - - case BacnetEventTypes.EVENT_OUT_OF_RANGE: - len += ASN1.decode_context_real(buffer, offset + len, 0, out var outOfRangeExceedingValue); - len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var outOfRangeStatusFlags); - len += ASN1.decode_context_real(buffer, offset + len, 2, out var deadband); - len += ASN1.decode_context_real(buffer, offset + len, 3, out var outOfRangeExceededLimit); - eventValues = new OutOfRange - { - ExceedingValue = outOfRangeExceedingValue, - StatusFlags = outOfRangeStatusFlags, - Deadband = deadband, - ExceededLimit = outOfRangeExceededLimit - }; - break; - - case BacnetEventTypes.EVENT_CHANGE_OF_LIFE_SAFETY: - len += EnumUtils.DecodeContextEnumerated(buffer, offset + len, 0, out BacnetLifeSafetyStates lifeSafetyNewState); - len += EnumUtils.DecodeContextEnumerated(buffer, offset + len, 1, out BacnetLifeSafetyModes lifeSafetyNewMode); - len += ASN1.decode_context_bitstring(buffer, offset + len, 2, out var lifeSafetyStatusFlags); - len += EnumUtils.DecodeContextEnumerated(buffer, offset + len, 3, out BacnetLifeSafetyOperations operationExpected); - eventValues = new ChangeOfLifeSafety - { - NewState = lifeSafetyNewState, - NewMode = lifeSafetyNewMode, - StatusFlags = lifeSafetyStatusFlags, - OperationExpected = operationExpected - }; - break; - - case BacnetEventTypes.EVENT_BUFFER_READY: - len += ASN1.decode_context_device_obj_property_ref(buffer, offset + len, 0, out var bufferProperty); - len += ASN1.decode_context_unsigned(buffer, offset + len, 1, out var previousNotification); - len += ASN1.decode_context_unsigned(buffer, offset + len, 2, out var currentNotification); - eventValues = new BufferReady - { - BufferProperty = bufferProperty, - CurrentNotification = currentNotification, - PreviousNotification = previousNotification - }; - break; - - case BacnetEventTypes.EVENT_UNSIGNED_RANGE: - len += ASN1.decode_context_unsigned(buffer, offset + len, 0, out var unsignedRangeExceedingValue); - len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var unsignedRangeStatusFlags); - len += ASN1.decode_context_unsigned(buffer, offset + len, 2, out var unsignedRangeExceededLimit); - eventValues = new UnsignedRange - { - ExceedingValue = unsignedRangeExceedingValue, - StatusFlags = unsignedRangeStatusFlags, - ExceededLimit = unsignedRangeExceededLimit - }; - break; - - default: - throw new ArgumentOutOfRangeException($"Event-Type {eventType} is not supported"); - } - - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, (byte)eventType)) - return false; - - len++; - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 12)) - return false; - - len++; - return eventValues != null; - } - - public static void EncodeEventNotifyData(EncodeBuffer buffer, NotificationData data) - { - /* tag 0 - processIdentifier */ - ASN1.encode_context_unsigned(buffer, 0, data.ProcessIdentifier); - /* tag 1 - initiatingObjectIdentifier */ - ASN1.encode_context_object_id( - buffer, 1, data.InitiatingObjectIdentifier.Type, data.InitiatingObjectIdentifier.Instance); - - /* tag 2 - eventObjectIdentifier */ - ASN1.encode_context_object_id( - buffer, 2, data.EventObjectIdentifier.Type, data.EventObjectIdentifier.Instance); - - /* tag 3 - timeStamp */ - ASN1.bacapp_encode_context_timestamp(buffer, 3, data.TimeStamp); - - /* tag 4 - noticicationClass */ - ASN1.encode_context_unsigned(buffer, 4, data.NotificationClass); - - /* tag 5 - priority */ - ASN1.encode_context_unsigned(buffer, 5, data.Priority); - - var stateTransition = data as StateTransition; - - if (stateTransition != null) - { - /* tag 6 - eventType */ - ASN1.encode_context_enumerated(buffer, 6, (uint) stateTransition.EventType); - } - - /* tag 7 - messageText */ - if (!string.IsNullOrEmpty(data.MessageText)) - ASN1.encode_context_character_string(buffer, 7, data.MessageText); - - /* tag 8 - notifyType */ - ASN1.encode_context_enumerated(buffer, 8, (uint) data.NotifyType); - - switch (stateTransition?.NotifyType) - { - case BacnetNotifyTypes.NOTIFY_ALARM: - case BacnetNotifyTypes.NOTIFY_EVENT: - /* tag 9 - ackRequired */ - ASN1.encode_context_boolean(buffer, 9, stateTransition.AckRequired); - - /* tag 10 - fromState */ - ASN1.encode_context_enumerated(buffer, 10, (uint) stateTransition.FromState); - break; - } - - /* tag 11 - toState */ - ASN1.encode_context_enumerated(buffer, 11, (uint) data.ToState); - - if(stateTransition == null || !stateTransition.GetType().IsGenericType) - return; // there are no EventValues if we're not processing a StateTransition - - ASN1.encode_opening_tag(buffer, 12); - - switch (stateTransition) - { - case StateTransition changeOfBitString: - ASN1.encode_opening_tag(buffer, 0); - ASN1.encode_context_bitstring(buffer, 0, changeOfBitString.EventValues.ReferencedBitString); - ASN1.encode_context_bitstring(buffer, 1, changeOfBitString.EventValues.StatusFlags); - ASN1.encode_closing_tag(buffer, 0); - break; - - case StateTransition changeOfStateTransition when changeOfStateTransition.GetType().GetGenericArguments()[0].BaseType == typeof(ChangeOfState): - ASN1.encode_opening_tag(buffer, 1); - ASN1.encode_context_property_state(buffer, 0, changeOfStateTransition, out var changeOfState); - ASN1.encode_context_bitstring(buffer, 1, changeOfState.StatusFlags); - ASN1.encode_closing_tag(buffer, 1); - break; - - case StateTransition changeOfValueTransition when changeOfValueTransition.GetType().GetGenericArguments()[0].BaseType == typeof(ChangeOfValue): - ASN1.encode_opening_tag(buffer, 2); - ASN1.encode_opening_tag(buffer, 0); - - BacnetBitString covStatusFlags; - - switch (changeOfValueTransition) - { - case StateTransition> covFLoat: - ASN1.encode_context_real(buffer, 1, covFLoat.EventValues.ChangedValue); - covStatusFlags = covFLoat.EventValues.StatusFlags; - break; - case StateTransition> covBits: - ASN1.encode_context_bitstring(buffer, 0, covBits.EventValues.ChangedValue); - covStatusFlags = covBits.EventValues.StatusFlags; - break; - default: - throw new ArgumentOutOfRangeException($"Unexpected Type '{changeOfValueTransition.GetType()}'"); - } - - ASN1.encode_closing_tag(buffer, 0); - ASN1.encode_context_bitstring(buffer, 1, covStatusFlags); - ASN1.encode_closing_tag(buffer, 2); - break; - - case StateTransition floatingLimit: - ASN1.encode_opening_tag(buffer, 4); - ASN1.encode_context_real(buffer, 0, floatingLimit.EventValues.ReferenceValue); - ASN1.encode_context_bitstring(buffer, 1, floatingLimit.EventValues.StatusFlags); - ASN1.encode_context_real(buffer, 2, floatingLimit.EventValues.SetPointValue); - ASN1.encode_context_real(buffer, 3, floatingLimit.EventValues.ErrorLimit); - ASN1.encode_closing_tag(buffer, 4); - break; - - case StateTransition outOfRange: - ASN1.encode_opening_tag(buffer, 5); - ASN1.encode_context_real(buffer, 0, outOfRange.EventValues.ExceedingValue); - ASN1.encode_context_bitstring(buffer, 1, outOfRange.EventValues.StatusFlags); - ASN1.encode_context_real(buffer, 2, outOfRange.EventValues.Deadband); - ASN1.encode_context_real(buffer, 3, outOfRange.EventValues.ExceededLimit); - ASN1.encode_closing_tag(buffer, 5); - break; - - case StateTransition changeOfLifeSafety: - ASN1.encode_opening_tag(buffer, 8); - ASN1.encode_context_enumerated(buffer, 0, (uint) changeOfLifeSafety.EventValues.NewState); - ASN1.encode_context_enumerated(buffer, 1, (uint) changeOfLifeSafety.EventValues.NewMode); - ASN1.encode_context_bitstring(buffer, 2, changeOfLifeSafety.EventValues.StatusFlags); - ASN1.encode_context_enumerated(buffer, 3, (uint) changeOfLifeSafety.EventValues.OperationExpected); - ASN1.encode_closing_tag(buffer, 8); - break; - - case StateTransition bufferReady: - ASN1.encode_opening_tag(buffer, 10); - ASN1.bacapp_encode_context_device_obj_property_ref(buffer, 0, bufferReady.EventValues.BufferProperty); - ASN1.encode_context_unsigned(buffer, 1, bufferReady.EventValues.PreviousNotification); - ASN1.encode_context_unsigned(buffer, 2, bufferReady.EventValues.CurrentNotification); - ASN1.encode_closing_tag(buffer, 10); - break; - - case StateTransition unsignedRange: - ASN1.encode_opening_tag(buffer, 11); - ASN1.encode_context_unsigned(buffer, 0, unsignedRange.EventValues.ExceedingValue); - ASN1.encode_context_bitstring(buffer, 1, unsignedRange.EventValues.StatusFlags); - ASN1.encode_context_unsigned(buffer, 2, unsignedRange.EventValues.ExceededLimit); - ASN1.encode_closing_tag(buffer, 11); - break; - - default: - var eventValuesType = stateTransition.GetType().GetGenericArguments().First(); - throw new NotImplementedException($"EventValues of type {eventValuesType} is not implemented"); - } - - ASN1.encode_closing_tag(buffer, 12); - } - - public static void EncodeAlarmSummary(EncodeBuffer buffer, BacnetObjectId objectIdentifier, BacnetEventStates alarmState, BacnetBitString acknowledgedTransitions) - { - /* tag 0 - Object Identifier */ - ASN1.encode_application_object_id(buffer, objectIdentifier.Type, objectIdentifier.Instance); - /* tag 1 - Alarm State */ - ASN1.encode_application_enumerated(buffer, (uint)alarmState); - /* tag 2 - Acknowledged Transitions */ - ASN1.encode_application_bitstring(buffer, acknowledgedTransitions); - } - - public static int DecodeEventInformation(byte[] buffer, int offset, int apduLen, ref IList events, out bool moreEvent) - { - var len = 1; // tag 0 - - while (apduLen - 3 - len > 0) - { - var value = new BacnetGetEventInformationData(); - - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - value.objectIdentifier = new BacnetObjectId(type, instance); - - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out value.eventState); - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.acknowledgedTransitions); - - len++; // opening Tag 3 - value.eventTimeStamps = new BacnetGenericTime[3]; - - for (var i = 0; i < 3; i++) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); // opening tag - - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_NULL) - { - len += ASN1.decode_application_date(buffer, offset + len, out var date); - len += ASN1.decode_application_time(buffer, offset + len, out var time); - var timestamp = date.Date + time.TimeOfDay; - value.eventTimeStamps[i] = new BacnetGenericTime(timestamp, BacnetTimestampTags.TIME_STAMP_DATETIME); - len++; // closing tag - } - else - len += (int)lenValue; - } - - len++; // closing Tag 3 - - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out value.notifyType); - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.eventEnable); - - len++; // opening tag 6; - value.eventPriorities = new uint[3]; - for (var i = 0; i < 3; i++) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out value.eventPriorities[i]); - } - len++; // closing Tag 6 - - events.Add(value); - } - - moreEvent = buffer[apduLen - 1] == 1; - return len; - } - - public static int DecodeAlarmSummary(byte[] buffer, int offset, int apduLen, ref IList alarms) - { - var len = 0; - - while (apduLen - 3 - len > 0) - { - var value = new BacnetAlarmSummaryData(); - - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - value.objectIdentifier = new BacnetObjectId(type, instance); - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out value.alarmState); - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.acknowledgedTransitions); - - alarms.Add(value); - } - - return len; - } - - public static void EncodeGetEventInformation(EncodeBuffer buffer, BacnetObjectId? lastReceivedObjectIdentifier) - { - /* encode optional parameter */ - if (lastReceivedObjectIdentifier != null) - ASN1.encode_context_object_id(buffer, 0, lastReceivedObjectIdentifier.Value.Type, lastReceivedObjectIdentifier.Value.Instance); - } - - public static void EncodeGetEventInformationAcknowledge(EncodeBuffer buffer, BacnetGetEventInformationData[] events, bool moreEvents) - { - /* service ack follows */ - /* Tag 0: listOfEventSummaries */ - ASN1.encode_opening_tag(buffer, 0); - foreach (var eventData in events) - { - /* Tag 0: objectIdentifier */ - ASN1.encode_context_object_id(buffer, 0, eventData.objectIdentifier.Type, eventData.objectIdentifier.Instance); - /* Tag 1: eventState */ - ASN1.encode_context_enumerated(buffer, 1, (uint)eventData.eventState); - /* Tag 2: acknowledgedTransitions */ - ASN1.encode_context_bitstring(buffer, 2, eventData.acknowledgedTransitions); - /* Tag 3: eventTimeStamps */ - ASN1.encode_opening_tag(buffer, 3); - for (var i = 0; i < 3; i++) - { - ASN1.bacapp_encode_timestamp(buffer, eventData.eventTimeStamps[i]); - } - ASN1.encode_closing_tag(buffer, 3); - /* Tag 4: notifyType */ - ASN1.encode_context_enumerated(buffer, 4, (uint)eventData.notifyType); - /* Tag 5: eventEnable */ - ASN1.encode_context_bitstring(buffer, 5, eventData.eventEnable); - /* Tag 6: eventPriorities */ - ASN1.encode_opening_tag(buffer, 6); - for (var i = 0; i < 3; i++) - { - ASN1.encode_application_unsigned(buffer, eventData.eventPriorities[i]); - } - ASN1.encode_closing_tag(buffer, 6); - } - ASN1.encode_closing_tag(buffer, 0); - ASN1.encode_context_boolean(buffer, 1, moreEvents); - } - - public static void EncodeLifeSafetyOperation(EncodeBuffer buffer, uint processId, string requestingSrc, uint operation, BacnetObjectId targetObject) - { - /* tag 0 - requestingProcessId */ - ASN1.encode_context_unsigned(buffer, 0, processId); - /* tag 1 - requestingSource */ - ASN1.encode_context_character_string(buffer, 1, requestingSrc); - /* Operation */ - ASN1.encode_context_enumerated(buffer, 2, operation); - /* Object ID */ - ASN1.encode_context_object_id(buffer, 3, targetObject.Type, targetObject.Instance); - } - - public static void EncodePrivateTransferConfirmed(EncodeBuffer buffer, uint vendorID, uint serviceNumber, byte[] data) - { - ASN1.encode_context_unsigned(buffer, 0, vendorID); - ASN1.encode_context_unsigned(buffer, 1, serviceNumber); - ASN1.encode_opening_tag(buffer, 2); - buffer.Add(data, data.Length); - ASN1.encode_closing_tag(buffer, 2); - } - - public static void EncodePrivateTransferUnconfirmed(EncodeBuffer buffer, uint vendorID, uint serviceNumber, byte[] data) - { - ASN1.encode_context_unsigned(buffer, 0, vendorID); - ASN1.encode_context_unsigned(buffer, 1, serviceNumber); - ASN1.encode_opening_tag(buffer, 2); - buffer.Add(data, data.Length); - ASN1.encode_closing_tag(buffer, 2); - } - - public static void EncodePrivateTransferAcknowledge(EncodeBuffer buffer, uint vendorID, uint serviceNumber, byte[] data) - { - ASN1.encode_context_unsigned(buffer, 0, vendorID); - ASN1.encode_context_unsigned(buffer, 1, serviceNumber); - ASN1.encode_opening_tag(buffer, 2); - buffer.Add(data, data.Length); - ASN1.encode_closing_tag(buffer, 2); - } - - public static void EncodeDeviceCommunicationControl(EncodeBuffer buffer, uint timeDuration, uint enableDisable, string password) - { - /* optional timeDuration */ - if (timeDuration > 0) - ASN1.encode_context_unsigned(buffer, 0, timeDuration); - - /* enable disable */ - ASN1.encode_context_enumerated(buffer, 1, enableDisable); - - /* optional password */ - if (!string.IsNullOrEmpty(password)) - { - /* FIXME: must be at least 1 character, limited to 20 characters */ - ASN1.encode_context_character_string(buffer, 2, password); - } - } - - public static int DecodeDeviceCommunicationControl(byte[] buffer, int offset, int apduLen, out uint timeDuration, out uint enableDisable, out string password) - { - var len = 0; - uint lenValueType; - - timeDuration = 0; - enableDisable = 0; - password = ""; - - /* Tag 0: timeDuration, in minutes --optional-- - * But if not included, take it as indefinite, - * which we return as "very large" */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValueType); - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out timeDuration); - } - - /* Tag 1: enable_disable */ - if (!ASN1.decode_is_context_tag(buffer, offset + len, 1)) - return -1; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValueType); - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out enableDisable); - - /* Tag 2: password --optional-- */ - if (len < apduLen) - { - if (!ASN1.decode_is_context_tag(buffer, offset + len, 2)) - return -1; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValueType); - len += ASN1.decode_character_string(buffer, offset + len, apduLen - (offset + len), lenValueType, out password); - } - - return len; - } - - public static void EncodeReinitializeDevice(EncodeBuffer buffer, BacnetReinitializedStates state, string password) - { - ASN1.encode_context_enumerated(buffer, 0, (uint)state); - - /* optional password */ - if (!string.IsNullOrEmpty(password)) - { - /* FIXME: must be at least 1 character, limited to 20 characters */ - ASN1.encode_context_character_string(buffer, 1, password); - } - } - - public static int DecodeReinitializeDevice(byte[] buffer, int offset, int apduLen, out BacnetReinitializedStates state, out string password) - { - var len = 0; - - state = BacnetReinitializedStates.BACNET_REINIT_IDLE; - password = ""; - - /* Tag 0: reinitializedStateOfDevice */ - if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) - return -1; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out var lenValueType); - len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out state); - /* Tag 1: password - optional */ - if (len < apduLen) - { - if (!ASN1.decode_is_context_tag(buffer, offset + len, 1)) - return -1; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValueType); - len += ASN1.decode_character_string(buffer, offset + len, apduLen - (offset + len), lenValueType, out password); - } - - return len; - } - - public static void EncodeReadRange(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, BacnetReadRangeRequestTypes requestType, uint position, DateTime time, int count) - { - ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); - ASN1.encode_context_enumerated(buffer, 1, propertyId); - - /* optional array index */ - if (arrayIndex != ASN1.BACNET_ARRAY_ALL) - { - ASN1.encode_context_unsigned(buffer, 2, arrayIndex); - } - - /* Build the appropriate (optional) range parameter based on the request type */ - switch (requestType) - { - case BacnetReadRangeRequestTypes.RR_BY_POSITION: - ASN1.encode_opening_tag(buffer, 3); - ASN1.encode_application_unsigned(buffer, position); - ASN1.encode_application_signed(buffer, count); - ASN1.encode_closing_tag(buffer, 3); - break; - - case BacnetReadRangeRequestTypes.RR_BY_SEQUENCE: - ASN1.encode_opening_tag(buffer, 6); - ASN1.encode_application_unsigned(buffer, position); - ASN1.encode_application_signed(buffer, count); - ASN1.encode_closing_tag(buffer, 6); - break; - - case BacnetReadRangeRequestTypes.RR_BY_TIME: - ASN1.encode_opening_tag(buffer, 7); - ASN1.encode_application_date(buffer, time); - ASN1.encode_application_time(buffer, time); - ASN1.encode_application_signed(buffer, count); - ASN1.encode_closing_tag(buffer, 7); - break; - - case BacnetReadRangeRequestTypes.RR_READ_ALL: /* to attempt a read of the whole array or list, omit the range parameter */ - break; - } - } - - public static int DecodeReadRange(byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyReference property, out BacnetReadRangeRequestTypes requestType, out uint position, out DateTime time, out int count) - { - var len = 0; - - objectId = default(BacnetObjectId); - property = new BacnetPropertyReference(); - requestType = BacnetReadRangeRequestTypes.RR_READ_ALL; - position = 0; - time = new DateTime(1, 1, 1); - count = -1; - - /* Tag 0: Object ID */ - if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) - return -1; - len++; - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - objectId = new BacnetObjectId(type, instance); - /* Tag 1: Property ID */ - len += - ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); - if (tagNumber != 1) - return -1; - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyIdentifier); - - /* Tag 2: Optional Array Index */ - if (len < apduLen && ASN1.decode_is_context_tag(buffer, offset + len, 0)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyArrayIndex); - } - else - property.propertyArrayIndex = ASN1.BACNET_ARRAY_ALL; - - /* optional request type */ - if (len < apduLen) - { - len += ASN1.decode_tag_number(buffer, offset + len, out tagNumber); //opening tag - switch (tagNumber) - { - case 3: - requestType = BacnetReadRangeRequestTypes.RR_BY_POSITION; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out position); - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += ASN1.decode_signed(buffer, offset + len, lenValueType, out count); - break; - - case 6: - requestType = BacnetReadRangeRequestTypes.RR_BY_SEQUENCE; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out position); - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += ASN1.decode_signed(buffer, offset + len, lenValueType, out count); - break; - - case 7: - requestType = BacnetReadRangeRequestTypes.RR_BY_TIME; - len += ASN1.decode_application_date(buffer, offset + len, out var date); - len += ASN1.decode_application_time(buffer, offset + len, out time); - time = new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond); - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += ASN1.decode_signed(buffer, offset + len, lenValueType, out count); - break; - - default: - return -1; //don't know this type yet - } - len += ASN1.decode_tag_number(buffer, offset + len, out tagNumber); //closing tag - } - return len; - } - - public static void EncodeReadRangeAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, BacnetBitString resultFlags, uint itemCount, byte[] applicationData, BacnetReadRangeRequestTypes requestType, uint firstSequence) - { - /* service ack follows */ - ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); - ASN1.encode_context_enumerated(buffer, 1, propertyId); - /* context 2 array index is optional */ - if (arrayIndex != ASN1.BACNET_ARRAY_ALL) - { - ASN1.encode_context_unsigned(buffer, 2, arrayIndex); - } - /* Context 3 BACnet Result Flags */ - ASN1.encode_context_bitstring(buffer, 3, resultFlags); - /* Context 4 Item Count */ - ASN1.encode_context_unsigned(buffer, 4, itemCount); - /* Context 5 Property list - reading the standard it looks like an empty list still - * requires an opening and closing tag as the tagged parameter is not optional - */ - ASN1.encode_opening_tag(buffer, 5); - if (itemCount != 0) - { - buffer.Add(applicationData, applicationData.Length); - } - ASN1.encode_closing_tag(buffer, 5); - - if (itemCount != 0 && requestType != BacnetReadRangeRequestTypes.RR_BY_POSITION && requestType != BacnetReadRangeRequestTypes.RR_READ_ALL) - { - /* Context 6 Sequence number of first item */ - ASN1.encode_context_unsigned(buffer, 6, firstSequence); - } - } - - // FC - public static uint DecodeReadRangeAcknowledge(byte[] buffer, int offset, int apduLen, out byte[] rangeBuffer) - { - var len = 0; - rangeBuffer = null; - - /* Tag 0: Object ID */ - if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) - return 0; - - len++; - len += ASN1.decode_object_id(buffer, offset + len, out ushort _, out _); - - /* Tag 1: Property ID */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); - if (tagNumber != 1) - return 0; - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint _); - - /* Tag 2: Optional Array Index or Tag 3: BACnet Result Flags */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - if (tagNumber == 2 && len < apduLen) - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint _); - else - /* Tag 3: BACnet Result Flags */ - len += ASN1.decode_bitstring(buffer, offset + len, 2, out _); - - /* Tag 4 Item Count */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint itemCount); - - if (!ASN1.decode_is_opening_tag(buffer, offset + len)) - return 0; - len += 1; - - rangeBuffer = new byte[buffer.Length - offset - len - 1]; - - Array.Copy(buffer, offset + len, rangeBuffer, 0, rangeBuffer.Length); - - return itemCount; - } - - public static void EncodeReadProperty(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) - { - if ((int)objectId.Type <= ASN1.BACNET_MAX_OBJECT) - { - /* check bounds so that we could create malformed - messages for testing */ - ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); - } - if (propertyId <= (uint)BacnetPropertyIds.MAX_BACNET_PROPERTY_ID) - { - /* check bounds so that we could create malformed - messages for testing */ - ASN1.encode_context_enumerated(buffer, 1, propertyId); - } - /* optional array index */ - if (arrayIndex != ASN1.BACNET_ARRAY_ALL) - { - ASN1.encode_context_unsigned(buffer, 2, arrayIndex); - } - } - - public static int DecodeAtomicWriteFileAcknowledge(byte[] buffer, int offset, int apduLen, out bool isStream, out int position) - { - var len = 0; - - isStream = false; - position = 0; - - len = ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); - switch (tagNumber) - { - case 0: - isStream = true; - len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); - break; - - case 1: - len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); - break; - - default: - return -1; - } - - return len; - } - - public static int DecodeAtomicReadFileAcknowledge(byte[] buffer, int offset, int apduLen, out bool endOfFile, out bool isStream, out int position, out uint count, out byte[] targetBuffer, out int targetOffset) - { - var len = 0; - - endOfFile = false; - isStream = false; - position = -1; - count = 0; - targetBuffer = null; - targetOffset = -1; - - len = ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_BOOLEAN) - return -1; - - endOfFile = lenValueType > 0; - if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 0)) - { - isStream = true; - /* a tag number is not extended so only one octet */ - len++; - /* fileStartPosition */ - var tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += tagLen; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) - return -1; - len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); - /* fileData */ - tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - len += tagLen; - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING) - return -1; - //len += ASN1.decode_octet_string(buffer, offset + len, buffer.Length, target_buffer, target_offset, len_value_type); - targetBuffer = buffer; - targetOffset = offset + len; - count = lenValueType; - len += (int)count; - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 0)) - return -1; - /* a tag number is not extended so only one octet */ - len++; - } - else if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 1)) - { - throw new NotImplementedException("Non stream File transfers are not supported"); - ///* a tag number is not extended so only one octet */ - //len++; - ///* fileStartRecord */ - //tag_len = ASN1.decode_tag_number_and_value(buffer, offset + len, out tag_number, out len_value_type); - //len += tag_len; - //if (tag_number != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) - // return -1; - //len += ASN1.decode_signed(buffer, offset + len, len_value_type, out position); - ///* returnedRecordCount */ - //tag_len = ASN1.decode_tag_number_and_value(buffer, offset + len, out tag_number, out len_value_type); - //len += tag_len; - //if (tag_number != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) - // return -1; - //len += ASN1.decode_unsigned(buffer, offset + len, len_value_type, out count); - //for (i = 0; i < count; i++) - //{ - // /* fileData */ - // tag_len = ASN1.decode_tag_number_and_value(buffer, offset + len, out tag_number, out len_value_type); - // len += tag_len; - // if (tag_number != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING) - // return -1; - // len += ASN1.decode_octet_string(buffer, offset + len, buffer.Length, target_buffer, target_offset, len_value_type); - //} - //if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) - // return -1; - ///* a tag number is not extended so only one octet */ - //len++; - } - else - return -1; - - return len; - } - - public static int DecodeReadProperty(byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyReference property) - { - var len = 0; - - objectId = default(BacnetObjectId); - property = new BacnetPropertyReference(); - - // must have at least 2 tags , otherwise return reject code: Missing required parameter - if (apduLen < 7) - return -1; - - /* Tag 0: Object ID */ - if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) - return -2; - - len++; - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - objectId = new BacnetObjectId(type, instance); - - /* Tag 1: Property ID */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); - if (tagNumber != 1) - return -2; - - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyIdentifier); - - /* Tag 2: Optional Array Index */ - if (len < apduLen) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - if (tagNumber == 2 && len < apduLen) - { - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyArrayIndex); - } - else - return -2; - } - else - property.propertyArrayIndex = ASN1.BACNET_ARRAY_ALL; - - if (len < apduLen) - /* If something left over now, we have an invalid request */ - return -3; - - return len; - } - - public static void EncodeReadPropertyAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, IEnumerable valueList) - { - /* service ack follows */ - ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); - ASN1.encode_context_enumerated(buffer, 1, propertyId); - /* context 2 array index is optional */ - if (arrayIndex != ASN1.BACNET_ARRAY_ALL) - { - ASN1.encode_context_unsigned(buffer, 2, arrayIndex); - } - - /* Value */ - ASN1.encode_opening_tag(buffer, 3); - foreach (BacnetValue value in valueList) - { - ASN1.bacapp_encode_application_data(buffer, value); - } - ASN1.encode_closing_tag(buffer, 3); - } - - public static int DecodeReadPropertyAcknowledge(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyReference property, out IList valueList) - { - objectId = default(BacnetObjectId); - property = new BacnetPropertyReference(); - valueList = new List(); - - /* FIXME: check apduLen against the len during decode */ - /* Tag 0: Object ID */ - if (!ASN1.decode_is_context_tag(buffer, offset, 0)) - return -1; - var len = 1; - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - objectId = new BacnetObjectId(type, instance); - /* Tag 1: Property ID */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); - if (tagNumber != 1) - return -1; - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyIdentifier); - /* Tag 2: Optional Array Index */ - var tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - if (tagNumber == 2) - { - len += tagLen; - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyArrayIndex); - } - else - property.propertyArrayIndex = ASN1.BACNET_ARRAY_ALL; - - /* Tag 3: opening context tag */ - if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 3)) - { - /* a tag number of 3 is not extended so only one octet */ - len++; - - while (apduLen - len > 1) - { - tagLen = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, objectId.Type, (BacnetPropertyIds)property.propertyIdentifier, out var value); - if (tagLen < 0) return -1; - len += tagLen; - valueList.Add(value); - } - } - else - return -1; - - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 3)) - return -1; - len++; - - return len; - } - - public static void EncodeReadPropertyMultiple(EncodeBuffer buffer, IList properties) - { - foreach (var value in properties) - ASN1.encode_read_access_specification(buffer, value); - } - - public static void EncodeReadPropertyMultiple(EncodeBuffer buffer, BacnetObjectId objectId, IList properties) - { - EncodeReadPropertyMultiple(buffer, new[] { new BacnetReadAccessSpecification(objectId, properties) }); - } - - public static int DecodeReadPropertyMultiple(byte[] buffer, int offset, int apduLen, out IList properties) - { - var len = 0; - - var values = new List(); - properties = null; - - while (apduLen - len > 0) - { - var tmp = ASN1.decode_read_access_specification(buffer, offset + len, apduLen - len, out var value); - if (tmp < 0) return -1; - len += tmp; - values.Add(value); - } - - properties = values; - return len; - } - - public static void EncodeReadPropertyMultipleAcknowledge(EncodeBuffer buffer, IList values) - { - foreach (var value in values) - ASN1.encode_read_access_result(buffer, value); - } - - public static int DecodeReadPropertyMultipleAcknowledge(BacnetAddress address, byte[] buffer, int offset, int apduLen, out IList values) - { - var len = 0; - - var result = new List(); - - while (apduLen - len > 0) - { - var tmp = ASN1.decode_read_access_result(address, buffer, offset + len, apduLen - len, out var value); - if (tmp < 0) - { - values = null; - return -1; - } - len += tmp; - result.Add(value); - } - - values = result; - return len; - } - - public static void EncodeWriteProperty(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, uint priority, IEnumerable valueList) - { - ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); - ASN1.encode_context_enumerated(buffer, 1, propertyId); - - /* optional array index; ALL is -1 which is assumed when missing */ - if (arrayIndex != ASN1.BACNET_ARRAY_ALL) - { - ASN1.encode_context_unsigned(buffer, 2, arrayIndex); - } - - /* propertyValue */ - ASN1.encode_opening_tag(buffer, 3); - foreach (var value in valueList) - { - ASN1.bacapp_encode_application_data(buffer, value); - } - ASN1.encode_closing_tag(buffer, 3); - - /* optional priority - 0 if not set, 1..16 if set */ - if (priority != ASN1.BACNET_NO_PRIORITY) - { - ASN1.encode_context_unsigned(buffer, 4, priority); - } - } - - public static int DecodeCOVNotifyUnconfirmed(BacnetAddress address, byte[] buffer, int offset, int apduLen, out uint subscriberProcessIdentifier, out BacnetObjectId initiatingDeviceIdentifier, out BacnetObjectId monitoredObjectIdentifier, out uint timeRemaining, out ICollection values) - { - var len = 0; - uint lenValue; - - subscriberProcessIdentifier = 0; - initiatingDeviceIdentifier = default(BacnetObjectId); - monitoredObjectIdentifier = default(BacnetObjectId); - timeRemaining = 0; - values = null; - - /* tag 0 - subscriberProcessIdentifier */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out subscriberProcessIdentifier); - } - else - return -1; - - /* tag 1 - initiatingDeviceIdentifier */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - initiatingDeviceIdentifier = new BacnetObjectId(type, instance); - } - else - return -1; - - /* tag 2 - monitoredObjectIdentifier */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 2)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - monitoredObjectIdentifier = new BacnetObjectId(type, instance); - } - else - return -1; - - /* tag 3 - timeRemaining */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 3)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out timeRemaining); - } - else - return -1; - - /* tag 4: opening context tag - listOfValues */ - if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 4)) - return -1; - - /* a tag number of 4 is not extended so only one octet */ - len++; - var _values = new LinkedList(); - while (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 4)) - { - var newEntry = new BacnetPropertyValue(); - - /* tag 0 - propertyIdentifier */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out newEntry.property.propertyIdentifier); - } - else - return -1; - - /* tag 1 - propertyArrayIndex OPTIONAL */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out newEntry.property.propertyArrayIndex); - } - else - newEntry.property.propertyArrayIndex = ASN1.BACNET_ARRAY_ALL; - - /* tag 2: opening context tag - value */ - if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 2)) - return -1; - - /* a tag number of 2 is not extended so only one octet */ - len++; - var bValues = new List(); - while (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 2)) - { - var tmp = ASN1.bacapp_decode_application_data( - address, buffer, offset + len, apduLen + offset, monitoredObjectIdentifier.Type, - (BacnetPropertyIds) newEntry.property.propertyIdentifier, out var bValue); - - if (tmp < 0) return -1; - len += tmp; - bValues.Add(bValue); - } - newEntry.value = bValues; - - /* a tag number of 2 is not extended so only one octet */ - len++; - /* tag 3 - priority OPTIONAL */ - if (ASN1.decode_is_context_tag(buffer, offset + len, 3)) - { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint decodedValue); - newEntry.priority = (byte)decodedValue; - } - else - newEntry.priority = (byte)ASN1.BACNET_NO_PRIORITY; - - _values.AddLast(newEntry); - } - - values = _values; - return len; - } - - public static int DecodeWriteProperty(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyValue value) - { - var len = 0; - - objectId = default(BacnetObjectId); - value = new BacnetPropertyValue(); - - /* Tag 0: Object ID */ - if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) - return -1; - len++; - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - objectId = new BacnetObjectId(type, instance); - /* Tag 1: Property ID */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); - if (tagNumber != 1) - return -1; - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out value.property.propertyIdentifier); - /* Tag 2: Optional Array Index */ - /* note: decode without incrementing len so we can check for opening tag */ - var tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - if (tagNumber == 2) - { - len += tagLen; - len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out value.property.propertyArrayIndex); - } - else - value.property.propertyArrayIndex = ASN1.BACNET_ARRAY_ALL; - /* Tag 3: opening context tag */ - if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 3)) - return -1; - len++; - - //data - var valueList = new List(); - while (apduLen - len > 1 && !ASN1.decode_is_closing_tag_number(buffer, offset + len, 3)) - { - var l = ASN1.bacapp_decode_application_data( - address, buffer, offset + len, apduLen + offset, objectId.Type, - (BacnetPropertyIds) value.property.propertyIdentifier, out var bValue); - - if (l <= 0) return -1; - len += l; - valueList.Add(bValue); - } - value.value = valueList; - - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 3)) - return -2; - /* a tag number of 3 is not extended so only one octet */ - len++; - /* Tag 4: optional Priority - assumed MAX if not explicitly set */ - value.priority = (byte)ASN1.BACNET_MAX_PRIORITY; - if (len < apduLen) - { - tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); - if (tagNumber == 4) - { - len += tagLen; - len = ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint unsignedValue); - if (unsignedValue >= ASN1.BACNET_MIN_PRIORITY && unsignedValue <= ASN1.BACNET_MAX_PRIORITY) - value.priority = (byte)unsignedValue; - else - return -1; - } - } - - return len; - } - - public static void EncodeWritePropertyMultiple(EncodeBuffer buffer, BacnetObjectId objectId, ICollection valueList) - { - ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); - /* Tag 1: sequence of WriteAccessSpecification */ - ASN1.encode_opening_tag(buffer, 1); - - foreach (var pValue in valueList) - { - /* Tag 0: Property */ - ASN1.encode_context_enumerated(buffer, 0, pValue.property.propertyIdentifier); - - /* Tag 1: array index */ - if (pValue.property.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) - ASN1.encode_context_unsigned(buffer, 1, pValue.property.propertyArrayIndex); - - /* Tag 2: Value */ - ASN1.encode_opening_tag(buffer, 2); - foreach (var value in pValue.value) - { - ASN1.bacapp_encode_application_data(buffer, value); - } - ASN1.encode_closing_tag(buffer, 2); - - /* Tag 3: Priority */ - if (pValue.priority != ASN1.BACNET_NO_PRIORITY) - ASN1.encode_context_unsigned(buffer, 3, pValue.priority); - } - - ASN1.encode_closing_tag(buffer, 1); - } - - public static void EncodeWriteObjectMultiple(EncodeBuffer buffer, ICollection valueList) - { - foreach (var value in valueList) - EncodeWritePropertyMultiple(buffer, value.objectIdentifier, value.values); - } - - // By C. Gunter - // quite the same as DecodeWritePropertyMultiple - public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out ICollection valuesRefs) - { - var len = 0; - - objectId = default(BacnetObjectId); - valuesRefs = null; - - //object id - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); - - if (tagNumber == 0 && apduLen > len) - { - apduLen -= len; - if (apduLen >= 4) - { - len += ASN1.decode_context_object_id(buffer, offset + len, 1, out var typenr, out var instance); - objectId = new BacnetObjectId((BacnetObjectTypes) typenr, instance); - } - else - return -1; - } - else - return -1; - if (ASN1.decode_is_closing_tag(buffer, offset + len)) - len++; - //end objectid - - // No initial values ? - if (buffer.Length == offset + len) - return len; - - /* Tag 1: sequence of WriteAccessSpecification */ - if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 1)) - return -1; - len++; - - var _values = new LinkedList(); - while (apduLen - len > 1) - { - var newEntry = new BacnetPropertyValue(); - - /* tag 0 - Property Identifier */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - uint propertyId; - if (tagNumber == 0) - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out propertyId); - else - return -1; - - /* tag 1 - Property Array Index - optional */ - var ulVal = ASN1.BACNET_ARRAY_ALL; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - if (tagNumber == 1) - { - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out ulVal); - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - } - newEntry.property = new BacnetPropertyReference(propertyId, ulVal); - - /* tag 2 - Property Value */ - if (tagNumber == 2 && ASN1.decode_is_opening_tag(buffer, offset + len - 1)) - { - var values = new List(); - while (!ASN1.decode_is_closing_tag(buffer, offset + len)) - { - var l = ASN1.bacapp_decode_application_data( - address, buffer, offset + len, apduLen + offset, objectId.Type, - (BacnetPropertyIds) propertyId, out var value); - - if (l <= 0) return -1; - len += l; - values.Add(value); - } - len++; - newEntry.value = values; - } - else - return -1; - - _values.AddLast(newEntry); - } - - /* Closing tag 1 - List of Properties */ - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) - return -1; - len++; - - valuesRefs = _values; - - return len; - } - - public static int DecodeDeleteObject(byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId) - { - objectId = default(BacnetObjectId); - - ASN1.decode_tag_number_and_value(buffer, offset, out var tagNumber, out _); - - if (tagNumber != 12) - return -1; - - var len = 1; - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - objectId = new BacnetObjectId(type, instance); - - if (len == apduLen) //check if packet was correct! - return len; - - return -1; - } - - public static void EncodeCreateObjectAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId) - { - ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); - } - - public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out ICollection valuesRefs) - { - var len = 0; - objectId = default(BacnetObjectId); - valuesRefs = null; - - /* Context tag 0 - Object ID */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); - if (tagNumber == 0 && apduLen > len) - { - apduLen -= len; - if (apduLen >= 4) - { - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - objectId = new BacnetObjectId(type, instance); - } - else - return -1; - } - else - return -1; - - /* Tag 1: sequence of WriteAccessSpecification */ - if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 1)) - return -1; - len++; - - var linkedList = new LinkedList(); - while (apduLen - len > 1) - { - var newEntry = new BacnetPropertyValue(); - - /* tag 0 - Property Identifier */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - uint propertyId; - if (tagNumber == 0) - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out propertyId); - else - return -1; - - /* tag 1 - Property Array Index - optional */ - var ulVal = ASN1.BACNET_ARRAY_ALL; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - if (tagNumber == 1) - { - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out ulVal); - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - } - newEntry.property = new BacnetPropertyReference(propertyId, ulVal); - - /* tag 2 - Property Value */ - if (tagNumber == 2 && ASN1.decode_is_opening_tag(buffer, offset + len - 1)) - { - var list = new List(); - while (!ASN1.decode_is_closing_tag(buffer, offset + len)) - { - var l = ASN1.bacapp_decode_application_data( - address, buffer, offset + len, apduLen + offset, objectId.Type, - (BacnetPropertyIds) propertyId, out var value); - - if (l <= 0) return -1; - len += l; - list.Add(value); - } - len++; - newEntry.value = list; - } - else - return -1; - - /* tag 3 - Priority - optional */ - ulVal = ASN1.BACNET_NO_PRIORITY; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - if (tagNumber == 3) - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out ulVal); - else - len--; - newEntry.priority = (byte)ulVal; - - linkedList.AddLast(newEntry); - } - - /* Closing tag 1 - List of Properties */ - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) - return -1; - len++; - - valuesRefs = linkedList; - - return len; - } - - public static void EncodeTimeSync(EncodeBuffer buffer, DateTime time) - { - ASN1.encode_application_date(buffer, time); - ASN1.encode_application_time(buffer, time); - } - - public static int DecodeTimeSync(byte[] buffer, int offset, int length, out DateTime dateTime) - { - var len = 0; - dateTime = new DateTime(1, 1, 1); - - /* date */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out _); - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_DATE) - return -1; - len += ASN1.decode_date(buffer, offset + len, out var date); - /* time */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out _); - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_TIME) - return -1; - len += ASN1.decode_bacnet_time(buffer, offset + len, out var time); - - //merge - dateTime = new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond); - - return len; - } - - public static void EncodeError(EncodeBuffer buffer, BacnetErrorClasses errorClass, BacnetErrorCodes errorCode) - { - ASN1.encode_application_enumerated(buffer, (uint)errorClass); - ASN1.encode_application_enumerated(buffer, (uint)errorCode); - } - - public static int DecodeError(byte[] buffer, int offset, int length, out BacnetErrorClasses errorClass, out BacnetErrorCodes errorCode) - { - var orgOffset = offset; - - offset += ASN1.decode_tag_number_and_value(buffer, offset, out _, out var lenValueType); - /* FIXME: we could validate that the tag is enumerated... */ - offset += EnumUtils.DecodeEnumerated(buffer, offset, lenValueType, out errorClass); - offset += ASN1.decode_tag_number_and_value(buffer, offset, out _, out lenValueType); - /* FIXME: we could validate that the tag is enumerated... */ - offset += EnumUtils.DecodeEnumerated(buffer, offset, lenValueType, out errorCode); - - return offset - orgOffset; - } - - public static void EncodeLogRecord(EncodeBuffer buffer, BacnetLogRecord record) - { - /* Tag 0: timestamp */ - ASN1.encode_opening_tag(buffer, 0); - ASN1.encode_application_date(buffer, record.timestamp); - ASN1.encode_application_time(buffer, record.timestamp); - ASN1.encode_closing_tag(buffer, 0); - - /* Tag 1: logDatum */ - if (record.type != BacnetTrendLogValueType.TL_TYPE_NULL) - { - if (record.type == BacnetTrendLogValueType.TL_TYPE_ERROR) - { - ASN1.encode_opening_tag(buffer, 1); - ASN1.encode_opening_tag(buffer, 8); - var err = record.GetValue(); - EncodeError(buffer, err.error_class, err.error_code); - ASN1.encode_closing_tag(buffer, 8); - ASN1.encode_closing_tag(buffer, 1); - return; - } - - ASN1.encode_opening_tag(buffer, 1); - var tmp1 = new EncodeBuffer(); - switch (record.type) - { - case BacnetTrendLogValueType.TL_TYPE_ANY: - throw new NotImplementedException(); - case BacnetTrendLogValueType.TL_TYPE_BITS: - ASN1.encode_bitstring(tmp1, record.GetValue()); - break; - - case BacnetTrendLogValueType.TL_TYPE_BOOL: - tmp1.Add(record.GetValue() ? (byte)1 : (byte)0); - break; - - case BacnetTrendLogValueType.TL_TYPE_DELTA: - ASN1.encode_bacnet_real(tmp1, record.GetValue()); - break; - - case BacnetTrendLogValueType.TL_TYPE_ENUM: - ASN1.encode_application_enumerated(tmp1, record.GetValue()); - break; - - case BacnetTrendLogValueType.TL_TYPE_REAL: - ASN1.encode_bacnet_real(tmp1, record.GetValue()); - break; - - case BacnetTrendLogValueType.TL_TYPE_SIGN: - ASN1.encode_bacnet_signed(tmp1, record.GetValue()); - break; - - case BacnetTrendLogValueType.TL_TYPE_STATUS: - ASN1.encode_bitstring(tmp1, record.GetValue()); - break; - - case BacnetTrendLogValueType.TL_TYPE_UNSIGN: - ASN1.encode_bacnet_unsigned(tmp1, record.GetValue()); - break; - } - ASN1.encode_tag(buffer, (byte)record.type, true, (uint)tmp1.offset); - buffer.Add(tmp1.buffer, tmp1.offset); - ASN1.encode_closing_tag(buffer, 1); - } - - /* Tag 2: status */ - var recordStatusFlags = BacnetBitString.ConvertFromInt((uint)record.statusFlags, 4); - if (recordStatusFlags.BitsUsed > 0) - { - ASN1.encode_tag(buffer, 2, true, 2); - ASN1.encode_bitstring(buffer, recordStatusFlags); - } - } - - public static int DecodeLogRecord(byte[] buffer, int offset, int length, int nCurves, - out BacnetLogRecord[] records) - { - var len = 0; - records = new BacnetLogRecord[nCurves]; - - for (var curveNumber = 0; curveNumber < nCurves; curveNumber++) - { - len += ASN1.decode_tag_number(buffer, offset + len, out var tagNumber); - if (tagNumber != 0) return -1; - - // Date and Time in Tag 0 - len += ASN1.decode_application_date(buffer, offset + len, out var date); - len += ASN1.decode_application_time(buffer, offset + len, out var time); - - var dt = new DateTime( - date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond); - - if (!ASN1.decode_is_closing_tag(buffer, offset + len)) return -1; - len++; - - // Value or error in Tag 1 - len += ASN1.decode_tag_number(buffer, offset + len, out tagNumber); - if (tagNumber != 1) return -1; - - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var contextTagType, out var lenValue); - records[curveNumber] = new BacnetLogRecord - { - timestamp = dt, - type = (BacnetTrendLogValueType) contextTagType - }; - - switch ((BacnetTrendLogValueType) contextTagType) - { - case BacnetTrendLogValueType.TL_TYPE_STATUS: - len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out var sval); - records[curveNumber].Value = sval; - break; - - case BacnetTrendLogValueType.TL_TYPE_BOOL: - records[curveNumber].Value = buffer[offset + len] > 0; - len++; - break; - - case BacnetTrendLogValueType.TL_TYPE_REAL: - len += ASN1.decode_real(buffer, offset + len, out var rval); - records[curveNumber].Value = rval; - break; - - case BacnetTrendLogValueType.TL_TYPE_ENUM: - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint eval); - records[curveNumber].Value = eval; - break; - - case BacnetTrendLogValueType.TL_TYPE_SIGN: - len += ASN1.decode_signed(buffer, offset + len, lenValue, out var ival); - records[curveNumber].Value = ival; - break; - - case BacnetTrendLogValueType.TL_TYPE_UNSIGN: - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint uinval); - records[curveNumber].Value = uinval; - break; - - case BacnetTrendLogValueType.TL_TYPE_ERROR: - len += DecodeError(buffer, offset + len, length, out var errclass, out var errcode); - records[curveNumber].Value = new BacnetError(errclass, errcode); - len++; // Closing Tag 8 - break; - - case BacnetTrendLogValueType.TL_TYPE_NULL: - len++; - records[curveNumber].Value = null; - break; - // Time change (Automatic or Synch time) Delta in seconds - case BacnetTrendLogValueType.TL_TYPE_DELTA: - len += ASN1.decode_real(buffer, offset + len, out var dval); - records[curveNumber].Value = dval; - break; - // No way to handle these data types, sure it's the end of this download ! - case BacnetTrendLogValueType.TL_TYPE_ANY: - throw new NotImplementedException(); - case BacnetTrendLogValueType.TL_TYPE_BITS: - len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out var bval); - records[curveNumber].Value = bval; - break; - - default: - return 0; - } - - - if (!ASN1.decode_is_closing_tag(buffer, offset + len)) - return -1; - - len++; - - if (len >= length) - return len; - - ASN1.decode_tag_number(buffer, offset + len, out tagNumber); - - // Optional Tag 2 - if (tagNumber != 2) - return len; - - len++; - len += ASN1.decode_bitstring(buffer, offset + len, 2, out var statusFlagsBits); - - //set status to all returns - var statusFlags = (BacnetStatusFlags) statusFlagsBits.ConvertToInt(); - records[curveNumber].statusFlags = statusFlags; - } - - return len; - } - } -} diff --git a/Serialize/Services/AlarmAndEventServices.cs b/Serialize/Services/AlarmAndEventServices.cs new file mode 100644 index 00000000..e62656a3 --- /dev/null +++ b/Serialize/Services/AlarmAndEventServices.cs @@ -0,0 +1,1020 @@ +using System.Collections.Generic; +using System.IO.BACnet.EventNotification; +using System.IO.BACnet.EventNotification.EventValues; +using System.Linq; + +namespace System.IO.BACnet.Serialize +{ + public static class AlarmAndEventServices + { + public static void EncodeAlarmAcknowledge(EncodeBuffer buffer, uint ackProcessIdentifier, BacnetObjectId eventObjectIdentifier, uint eventStateAcked, string ackSource, BacnetGenericTime eventTimeStamp, BacnetGenericTime ackTimeStamp) + { + ASN1.encode_context_unsigned(buffer, 0, ackProcessIdentifier); + ASN1.encode_context_object_id(buffer, 1, eventObjectIdentifier.Type, eventObjectIdentifier.Instance); + ASN1.encode_context_enumerated(buffer, 2, eventStateAcked); + ASN1.bacapp_encode_context_timestamp(buffer, 3, eventTimeStamp); + ASN1.encode_context_character_string(buffer, 4, ackSource); + ASN1.bacapp_encode_context_timestamp(buffer, 5, ackTimeStamp); + } + + public static void EncodeCOVNotifyConfirmed(EncodeBuffer buffer, uint subscriberProcessIdentifier, uint initiatingDeviceIdentifier, BacnetObjectId monitoredObjectIdentifier, uint timeRemaining, IEnumerable values) + { + /* tag 0 - subscriberProcessIdentifier */ + ASN1.encode_context_unsigned(buffer, 0, subscriberProcessIdentifier); + /* tag 1 - initiatingDeviceIdentifier */ + ASN1.encode_context_object_id(buffer, 1, BacnetObjectTypes.OBJECT_DEVICE, initiatingDeviceIdentifier); + /* tag 2 - monitoredObjectIdentifier */ + ASN1.encode_context_object_id(buffer, 2, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); + /* tag 3 - timeRemaining */ + ASN1.encode_context_unsigned(buffer, 3, timeRemaining); + /* tag 4 - listOfValues */ + ASN1.encode_opening_tag(buffer, 4); + foreach (var value in values) + { + /* tag 0 - propertyIdentifier */ + ASN1.encode_context_enumerated(buffer, 0, value.property.propertyIdentifier); + /* tag 1 - propertyArrayIndex OPTIONAL */ + if (value.property.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) + { + ASN1.encode_context_unsigned(buffer, 1, value.property.propertyArrayIndex); + } + /* tag 2 - value */ + /* abstract syntax gets enclosed in a context tag */ + ASN1.encode_opening_tag(buffer, 2); + foreach (var v in value.value) + { + ASN1.bacapp_encode_application_data(buffer, v); + } + ASN1.encode_closing_tag(buffer, 2); + /* tag 3 - priority OPTIONAL */ + if (value.priority != ASN1.BACNET_NO_PRIORITY) + { + ASN1.encode_context_unsigned(buffer, 3, value.priority); + } + /* is there another one to encode? */ + /* FIXME: check to see if there is room in the APDU */ + } + ASN1.encode_closing_tag(buffer, 4); + } + + public static void EncodeCOVNotifyUnconfirmed(EncodeBuffer buffer, uint subscriberProcessIdentifier, uint initiatingDeviceIdentifier, BacnetObjectId monitoredObjectIdentifier, uint timeRemaining, IEnumerable values) + { + /* tag 0 - subscriberProcessIdentifier */ + ASN1.encode_context_unsigned(buffer, 0, subscriberProcessIdentifier); + /* tag 1 - initiatingDeviceIdentifier */ + ASN1.encode_context_object_id(buffer, 1, BacnetObjectTypes.OBJECT_DEVICE, initiatingDeviceIdentifier); + /* tag 2 - monitoredObjectIdentifier */ + ASN1.encode_context_object_id(buffer, 2, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); + /* tag 3 - timeRemaining */ + ASN1.encode_context_unsigned(buffer, 3, timeRemaining); + /* tag 4 - listOfValues */ + ASN1.encode_opening_tag(buffer, 4); + foreach (var value in values) + { + /* tag 0 - propertyIdentifier */ + ASN1.encode_context_enumerated(buffer, 0, value.property.propertyIdentifier); + /* tag 1 - propertyArrayIndex OPTIONAL */ + if (value.property.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) + { + ASN1.encode_context_unsigned(buffer, 1, value.property.propertyArrayIndex); + } + /* tag 2 - value */ + /* abstract syntax gets enclosed in a context tag */ + ASN1.encode_opening_tag(buffer, 2); + foreach (var v in value.value) + { + ASN1.bacapp_encode_application_data(buffer, v); + } + ASN1.encode_closing_tag(buffer, 2); + /* tag 3 - priority OPTIONAL */ + if (value.priority != ASN1.BACNET_NO_PRIORITY) + { + ASN1.encode_context_unsigned(buffer, 3, value.priority); + } + /* is there another one to encode? */ + /* FIXME: check to see if there is room in the APDU */ + } + ASN1.encode_closing_tag(buffer, 4); + } + + public static int DecodeCOVNotifyUnconfirmed(BacnetAddress address, byte[] buffer, int offset, int apduLen, out uint subscriberProcessIdentifier, out BacnetObjectId initiatingDeviceIdentifier, out BacnetObjectId monitoredObjectIdentifier, out uint timeRemaining, out ICollection values) + { + var len = 0; + uint lenValue; + + subscriberProcessIdentifier = 0; + initiatingDeviceIdentifier = default(BacnetObjectId); + monitoredObjectIdentifier = default(BacnetObjectId); + timeRemaining = 0; + values = null; + + /* tag 0 - subscriberProcessIdentifier */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out subscriberProcessIdentifier); + } + else + return -1; + + /* tag 1 - initiatingDeviceIdentifier */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + initiatingDeviceIdentifier = new BacnetObjectId(type, instance); + } + else + return -1; + + /* tag 2 - monitoredObjectIdentifier */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 2)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + monitoredObjectIdentifier = new BacnetObjectId(type, instance); + } + else + return -1; + + /* tag 3 - timeRemaining */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 3)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out timeRemaining); + } + else + return -1; + + /* tag 4: opening context tag - listOfValues */ + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 4)) + return -1; + + /* a tag number of 4 is not extended so only one octet */ + len++; + var _values = new LinkedList(); + while (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 4)) + { + var newEntry = new BacnetPropertyValue(); + + /* tag 0 - propertyIdentifier */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out newEntry.property.propertyIdentifier); + } + else + return -1; + + /* tag 1 - propertyArrayIndex OPTIONAL */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out newEntry.property.propertyArrayIndex); + } + else + newEntry.property.propertyArrayIndex = ASN1.BACNET_ARRAY_ALL; + + /* tag 2: opening context tag - value */ + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 2)) + return -1; + + /* a tag number of 2 is not extended so only one octet */ + len++; + var bValues = new List(); + while (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 2)) + { + var tmp = ASN1.bacapp_decode_application_data( + address, buffer, offset + len, apduLen + offset, monitoredObjectIdentifier.Type, + (BacnetPropertyIds) newEntry.property.propertyIdentifier, out var bValue); + + if (tmp < 0) return -1; + len += tmp; + bValues.Add(bValue); + } + newEntry.value = bValues; + + /* a tag number of 2 is not extended so only one octet */ + len++; + /* tag 3 - priority OPTIONAL */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 3)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint decodedValue); + newEntry.priority = (byte)decodedValue; + } + else + newEntry.priority = (byte)ASN1.BACNET_NO_PRIORITY; + + _values.AddLast(newEntry); + } + + values = _values; + return len; + } + + public static void EncodeEventNotifyData(EncodeBuffer buffer, NotificationData data) + { + /* tag 0 - processIdentifier */ + ASN1.encode_context_unsigned(buffer, 0, data.ProcessIdentifier); + /* tag 1 - initiatingObjectIdentifier */ + ASN1.encode_context_object_id( + buffer, 1, data.InitiatingObjectIdentifier.Type, data.InitiatingObjectIdentifier.Instance); + + /* tag 2 - eventObjectIdentifier */ + ASN1.encode_context_object_id( + buffer, 2, data.EventObjectIdentifier.Type, data.EventObjectIdentifier.Instance); + + /* tag 3 - timeStamp */ + ASN1.bacapp_encode_context_timestamp(buffer, 3, data.TimeStamp); + + /* tag 4 - noticicationClass */ + ASN1.encode_context_unsigned(buffer, 4, data.NotificationClass); + + /* tag 5 - priority */ + ASN1.encode_context_unsigned(buffer, 5, data.Priority); + + var stateTransition = data as StateTransition; + + if (stateTransition != null) + { + /* tag 6 - eventType */ + ASN1.encode_context_enumerated(buffer, 6, (uint)stateTransition.EventType); + } + + /* tag 7 - messageText */ + if (!String.IsNullOrEmpty(data.MessageText)) + ASN1.encode_context_character_string(buffer, 7, data.MessageText); + + /* tag 8 - notifyType */ + ASN1.encode_context_enumerated(buffer, 8, (uint)data.NotifyType); + + switch (stateTransition?.NotifyType) + { + case BacnetNotifyTypes.NOTIFY_ALARM: + case BacnetNotifyTypes.NOTIFY_EVENT: + /* tag 9 - ackRequired */ + ASN1.encode_context_boolean(buffer, 9, stateTransition.AckRequired); + + /* tag 10 - fromState */ + ASN1.encode_context_enumerated(buffer, 10, (uint)stateTransition.FromState); + break; + } + + /* tag 11 - toState */ + ASN1.encode_context_enumerated(buffer, 11, (uint)data.ToState); + + if (stateTransition == null || !stateTransition.GetType().IsGenericType) + return; // there are no EventValues if we're not processing a StateTransition + + ASN1.encode_opening_tag(buffer, 12); + + switch (stateTransition) + { + case StateTransition changeOfBitString: + ASN1.encode_opening_tag(buffer, 0); + ASN1.encode_context_bitstring(buffer, 0, changeOfBitString.EventValues.ReferencedBitString); + ASN1.encode_context_bitstring(buffer, 1, changeOfBitString.EventValues.StatusFlags); + ASN1.encode_closing_tag(buffer, 0); + break; + + case StateTransition changeOfStateTransition when changeOfStateTransition.GetType().GetGenericArguments()[0].BaseType == typeof(ChangeOfState): + ASN1.encode_opening_tag(buffer, 1); + ASN1.encode_context_property_state(buffer, 0, changeOfStateTransition, out var changeOfState); + ASN1.encode_context_bitstring(buffer, 1, changeOfState.StatusFlags); + ASN1.encode_closing_tag(buffer, 1); + break; + + case StateTransition changeOfValueTransition when changeOfValueTransition.GetType().GetGenericArguments()[0].BaseType == typeof(ChangeOfValue): + ASN1.encode_opening_tag(buffer, 2); + ASN1.encode_opening_tag(buffer, 0); + + BacnetBitString covStatusFlags; + + switch (changeOfValueTransition) + { + case StateTransition> covFLoat: + ASN1.encode_context_real(buffer, 1, covFLoat.EventValues.ChangedValue); + covStatusFlags = covFLoat.EventValues.StatusFlags; + break; + case StateTransition> covBits: + ASN1.encode_context_bitstring(buffer, 0, covBits.EventValues.ChangedValue); + covStatusFlags = covBits.EventValues.StatusFlags; + break; + default: + throw new ArgumentOutOfRangeException($"Unexpected Type '{changeOfValueTransition.GetType()}'"); + } + + ASN1.encode_closing_tag(buffer, 0); + ASN1.encode_context_bitstring(buffer, 1, covStatusFlags); + ASN1.encode_closing_tag(buffer, 2); + break; + + case StateTransition floatingLimit: + ASN1.encode_opening_tag(buffer, 4); + ASN1.encode_context_real(buffer, 0, floatingLimit.EventValues.ReferenceValue); + ASN1.encode_context_bitstring(buffer, 1, floatingLimit.EventValues.StatusFlags); + ASN1.encode_context_real(buffer, 2, floatingLimit.EventValues.SetPointValue); + ASN1.encode_context_real(buffer, 3, floatingLimit.EventValues.ErrorLimit); + ASN1.encode_closing_tag(buffer, 4); + break; + + case StateTransition outOfRange: + ASN1.encode_opening_tag(buffer, 5); + ASN1.encode_context_real(buffer, 0, outOfRange.EventValues.ExceedingValue); + ASN1.encode_context_bitstring(buffer, 1, outOfRange.EventValues.StatusFlags); + ASN1.encode_context_real(buffer, 2, outOfRange.EventValues.Deadband); + ASN1.encode_context_real(buffer, 3, outOfRange.EventValues.ExceededLimit); + ASN1.encode_closing_tag(buffer, 5); + break; + + case StateTransition changeOfLifeSafety: + ASN1.encode_opening_tag(buffer, 8); + ASN1.encode_context_enumerated(buffer, 0, (uint)changeOfLifeSafety.EventValues.NewState); + ASN1.encode_context_enumerated(buffer, 1, (uint)changeOfLifeSafety.EventValues.NewMode); + ASN1.encode_context_bitstring(buffer, 2, changeOfLifeSafety.EventValues.StatusFlags); + ASN1.encode_context_enumerated(buffer, 3, (uint)changeOfLifeSafety.EventValues.OperationExpected); + ASN1.encode_closing_tag(buffer, 8); + break; + + case StateTransition bufferReady: + ASN1.encode_opening_tag(buffer, 10); + ASN1.bacapp_encode_context_device_obj_property_ref(buffer, 0, bufferReady.EventValues.BufferProperty); + ASN1.encode_context_unsigned(buffer, 1, bufferReady.EventValues.PreviousNotification); + ASN1.encode_context_unsigned(buffer, 2, bufferReady.EventValues.CurrentNotification); + ASN1.encode_closing_tag(buffer, 10); + break; + + case StateTransition unsignedRange: + ASN1.encode_opening_tag(buffer, 11); + ASN1.encode_context_unsigned(buffer, 0, unsignedRange.EventValues.ExceedingValue); + ASN1.encode_context_bitstring(buffer, 1, unsignedRange.EventValues.StatusFlags); + ASN1.encode_context_unsigned(buffer, 2, unsignedRange.EventValues.ExceededLimit); + ASN1.encode_closing_tag(buffer, 11); + break; + + default: + var eventValuesType = stateTransition.GetType().GetGenericArguments().First(); + throw new NotImplementedException($"EventValues of type {eventValuesType} is not implemented"); + } + + ASN1.encode_closing_tag(buffer, 12); + } + + public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, out NotificationData eventData) + { + var len = 0; + uint lenValue; + + eventData = default; + BacnetNotifyTypes? notifyType; + BacnetEventTypes? eventType = default; + var decodedNotificationData = new List>(); + var decodedStateTransition = new List>(); + + /* tag 0 - processIdentifier */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint processIdentifier); + decodedNotificationData.Add(e => e.ProcessIdentifier = processIdentifier); + } + else + return -1; + + /* tag 1 - initiatingObjectIdentifier */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + decodedNotificationData.Add(e => e.InitiatingObjectIdentifier = new BacnetObjectId(type, instance)); + } + else + return -1; + + /* tag 2 - eventObjectIdentifier */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 2)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + decodedNotificationData.Add(e => e.EventObjectIdentifier = new BacnetObjectId(type, instance)); + } + else + return -1; + + /* tag 3 - timeStamp */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 3)) + { + len += 2; // opening Tag 3 then 2 + len += ASN1.decode_application_date(buffer, offset + len, out var date); + len += ASN1.decode_application_time(buffer, offset + len, out var time); + decodedNotificationData.Add(e => e.TimeStamp = new BacnetGenericTime(new DateTime( + date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond), + BacnetTimestampTags.TIME_STAMP_DATETIME)); + len += 2; // closing tag 2 then 3 + } + else + return -1; + + /* tag 4 - noticicationClass */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 4)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint notificationClass); + decodedNotificationData.Add(e => e.NotificationClass = notificationClass); + } + else + return -1; + + /* tag 5 - priority */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 5)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint priority); + if (priority > 0xFF) return -1; + decodedNotificationData.Add(e => e.Priority = (byte) priority); + } + else + return -1; + + /* tag 6 - eventType */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 6)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out BacnetEventTypes eventTypeValue); + eventType = eventTypeValue; + } + //else + // return -1; + // shouldn't be present in ack transitions (according to the spec), but still is with some hardware + + /* optional tag 7 - messageText : never tested */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 7)) + { + // max_lenght 20000 sound like a joke + len += ASN1.decode_context_character_string(buffer, offset + len, 20000, 7, out var messageText); + decodedNotificationData.Add(e => e.MessageText = messageText); + } + + /* tag 8 - notifyType */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 8)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out BacnetNotifyTypes notifyTypeValue); + decodedStateTransition.Add(e => e.NotifyType = notifyTypeValue); + notifyType = notifyTypeValue; + } + else + return -1; + + switch (notifyType) + { + case BacnetNotifyTypes.NOTIFY_ALARM: + case BacnetNotifyTypes.NOTIFY_EVENT: + /* tag 9 - ackRequired */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned8(buffer, offset + len, out var val); + decodedStateTransition.Add(e => e.AckRequired = Convert.ToBoolean(val)); + + /* tag 10 - fromState */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint fromstate); + decodedStateTransition.Add(e => e.FromState = (BacnetEventStates) fromstate); + break; + } + + /* tag 11 - toState */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 11)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint toState); + decodedNotificationData.Add(e => e.ToState = (BacnetEventStates) toState); + } + else + return -1; + + /* tag 12 - event values */ + switch (notifyType) + { + case BacnetNotifyTypes.NOTIFY_ALARM when eventType.HasValue: + case BacnetNotifyTypes.NOTIFY_EVENT when eventType.HasValue: + if (!DecodeEventValues(buffer, offset, eventType.Value, ref len, out var eventValues)) + return -1; + + var targetType = typeof(StateTransition<>).MakeGenericType(eventValues.GetType()); + eventData = Activator.CreateInstance(targetType, eventValues) as StateTransition; + break; + } + + eventData = eventData ?? (decodedStateTransition.Any() + ? new StateTransition(eventType ?? throw new ArgumentNullException($"Need eventType to initialize StateTransition")) + : new NotificationData()); + + if (eventData is StateTransition stateTransition) + foreach (var setValue in decodedStateTransition) + setValue(stateTransition); + + foreach (var setValue in decodedNotificationData) + setValue(eventData); + + return len; + } + + private static bool DecodeEventValues(byte[] buffer, int offset, BacnetEventTypes eventType, ref int len, + out EventValuesBase eventValues) + { + eventValues = default; + + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 12)) + return false; + + len++; + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, (byte)eventType)) + return false; + + len++; + switch (eventType) + { + case BacnetEventTypes.EVENT_CHANGE_OF_BITSTRING: + len += ASN1.decode_context_bitstring(buffer, offset + len, 0, out var referencedBitString); + len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var changeOfBitStringStatusFlags); + eventValues = new ChangeOfBitString + { + ReferencedBitString = referencedBitString, + StatusFlags = changeOfBitStringStatusFlags + }; + break; + + case BacnetEventTypes.EVENT_CHANGE_OF_STATE: + len += ASN1.decode_context_property_state(buffer, offset + len, 0, out var newState); + len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var changeOfStateStatusFlags); + eventValues = (newState as ChangeOfState).SetStatusFlags(changeOfStateStatusFlags); + break; + + case BacnetEventTypes.EVENT_CHANGE_OF_VALUE: + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 0)) + return false; + + len++; + if (ASN1.decode_is_context_tag(buffer, offset + len, (byte)BacnetCOVTypes.CHANGE_OF_VALUE_BITS)) + { + len += ASN1.decode_context_bitstring(buffer, offset + len, 0, out var changedBits); + eventValues = ChangeOfValueFactory.Create(changedBits); + } + else if (ASN1.decode_is_context_tag(buffer, offset + len, (byte)BacnetCOVTypes.CHANGE_OF_VALUE_REAL)) + { + len += ASN1.decode_context_real(buffer, offset + len, 1, out var changeValue); + eventValues = ChangeOfValueFactory.Create(changeValue); + } + else + { + return false; + } + + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 0)) + return false; + + len++; + len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var changeOfValueStatusFlags); + ((ChangeOfValue)eventValues).StatusFlags = changeOfValueStatusFlags; + break; + + case BacnetEventTypes.EVENT_FLOATING_LIMIT: + len += ASN1.decode_context_real(buffer, offset + len, 0, out var referenceValue); + len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var floatingLimitStatusFlags); + len += ASN1.decode_context_real(buffer, offset + len, 2, out var setPointValue); + len += ASN1.decode_context_real(buffer, offset + len, 3, out var errorLimit); + eventValues = new FloatingLimit + { + ReferenceValue = referenceValue, + StatusFlags = floatingLimitStatusFlags, + SetPointValue = setPointValue, + ErrorLimit = errorLimit + }; + break; + + case BacnetEventTypes.EVENT_OUT_OF_RANGE: + len += ASN1.decode_context_real(buffer, offset + len, 0, out var outOfRangeExceedingValue); + len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var outOfRangeStatusFlags); + len += ASN1.decode_context_real(buffer, offset + len, 2, out var deadband); + len += ASN1.decode_context_real(buffer, offset + len, 3, out var outOfRangeExceededLimit); + eventValues = new OutOfRange + { + ExceedingValue = outOfRangeExceedingValue, + StatusFlags = outOfRangeStatusFlags, + Deadband = deadband, + ExceededLimit = outOfRangeExceededLimit + }; + break; + + case BacnetEventTypes.EVENT_CHANGE_OF_LIFE_SAFETY: + len += EnumUtils.DecodeContextEnumerated(buffer, offset + len, 0, out BacnetLifeSafetyStates lifeSafetyNewState); + len += EnumUtils.DecodeContextEnumerated(buffer, offset + len, 1, out BacnetLifeSafetyModes lifeSafetyNewMode); + len += ASN1.decode_context_bitstring(buffer, offset + len, 2, out var lifeSafetyStatusFlags); + len += EnumUtils.DecodeContextEnumerated(buffer, offset + len, 3, out BacnetLifeSafetyOperations operationExpected); + eventValues = new ChangeOfLifeSafety + { + NewState = lifeSafetyNewState, + NewMode = lifeSafetyNewMode, + StatusFlags = lifeSafetyStatusFlags, + OperationExpected = operationExpected + }; + break; + + case BacnetEventTypes.EVENT_BUFFER_READY: + len += ASN1.decode_context_device_obj_property_ref(buffer, offset + len, 0, out var bufferProperty); + len += ASN1.decode_context_unsigned(buffer, offset + len, 1, out var previousNotification); + len += ASN1.decode_context_unsigned(buffer, offset + len, 2, out var currentNotification); + eventValues = new BufferReady + { + BufferProperty = bufferProperty, + CurrentNotification = currentNotification, + PreviousNotification = previousNotification + }; + break; + + case BacnetEventTypes.EVENT_UNSIGNED_RANGE: + len += ASN1.decode_context_unsigned(buffer, offset + len, 0, out var unsignedRangeExceedingValue); + len += ASN1.decode_context_bitstring(buffer, offset + len, 1, out var unsignedRangeStatusFlags); + len += ASN1.decode_context_unsigned(buffer, offset + len, 2, out var unsignedRangeExceededLimit); + eventValues = new UnsignedRange + { + ExceedingValue = unsignedRangeExceedingValue, + StatusFlags = unsignedRangeStatusFlags, + ExceededLimit = unsignedRangeExceededLimit + }; + break; + + default: + throw new ArgumentOutOfRangeException($"Event-Type {eventType} is not supported"); + } + + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, (byte)eventType)) + return false; + + len++; + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 12)) + return false; + + len++; + return eventValues != null; + } + + public static void EncodeAlarmSummary(EncodeBuffer buffer, BacnetObjectId objectIdentifier, BacnetEventStates alarmState, BacnetBitString acknowledgedTransitions) + { + /* tag 0 - Object Identifier */ + ASN1.encode_application_object_id(buffer, objectIdentifier.Type, objectIdentifier.Instance); + /* tag 1 - Alarm State */ + ASN1.encode_application_enumerated(buffer, (uint)alarmState); + /* tag 2 - Acknowledged Transitions */ + ASN1.encode_application_bitstring(buffer, acknowledgedTransitions); + } + + public static int DecodeAlarmSummary(byte[] buffer, int offset, int apduLen, ref IList alarms) + { + var len = 0; + + while (apduLen - 3 - len > 0) + { + var value = new BacnetAlarmSummaryData(); + + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + value.objectIdentifier = new BacnetObjectId(type, instance); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out value.alarmState); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.acknowledgedTransitions); + + alarms.Add(value); + } + + return len; + } + + public static void EncodeGetEventInformation(EncodeBuffer buffer, BacnetObjectId? lastReceivedObjectIdentifier) + { + /* encode optional parameter */ + if (lastReceivedObjectIdentifier != null) + ASN1.encode_context_object_id(buffer, 0, lastReceivedObjectIdentifier.Value.Type, lastReceivedObjectIdentifier.Value.Instance); + } + + public static void EncodeGetEventInformationAcknowledge(EncodeBuffer buffer, BacnetGetEventInformationData[] events, bool moreEvents) + { + /* service ack follows */ + /* Tag 0: listOfEventSummaries */ + ASN1.encode_opening_tag(buffer, 0); + foreach (var eventData in events) + { + /* Tag 0: objectIdentifier */ + ASN1.encode_context_object_id(buffer, 0, eventData.objectIdentifier.Type, eventData.objectIdentifier.Instance); + /* Tag 1: eventState */ + ASN1.encode_context_enumerated(buffer, 1, (uint)eventData.eventState); + /* Tag 2: acknowledgedTransitions */ + ASN1.encode_context_bitstring(buffer, 2, eventData.acknowledgedTransitions); + /* Tag 3: eventTimeStamps */ + ASN1.encode_opening_tag(buffer, 3); + for (var i = 0; i < 3; i++) + { + ASN1.bacapp_encode_timestamp(buffer, eventData.eventTimeStamps[i]); + } + ASN1.encode_closing_tag(buffer, 3); + /* Tag 4: notifyType */ + ASN1.encode_context_enumerated(buffer, 4, (uint)eventData.notifyType); + /* Tag 5: eventEnable */ + ASN1.encode_context_bitstring(buffer, 5, eventData.eventEnable); + /* Tag 6: eventPriorities */ + ASN1.encode_opening_tag(buffer, 6); + for (var i = 0; i < 3; i++) + { + ASN1.encode_application_unsigned(buffer, eventData.eventPriorities[i]); + } + ASN1.encode_closing_tag(buffer, 6); + } + ASN1.encode_closing_tag(buffer, 0); + ASN1.encode_context_boolean(buffer, 1, moreEvents); + } + + public static int DecodeEventInformation(byte[] buffer, int offset, int apduLen, ref IList events, out bool moreEvent) + { + var len = 1; // tag 0 + + while (apduLen - 3 - len > 0) + { + var value = new BacnetGetEventInformationData(); + + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + value.objectIdentifier = new BacnetObjectId(type, instance); + + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out value.eventState); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.acknowledgedTransitions); + + len++; // opening Tag 3 + value.eventTimeStamps = new BacnetGenericTime[3]; + + for (var i = 0; i < 3; i++) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); // opening tag + + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_NULL) + { + len += ASN1.decode_application_date(buffer, offset + len, out var date); + len += ASN1.decode_application_time(buffer, offset + len, out var time); + var timestamp = date.Date + time.TimeOfDay; + value.eventTimeStamps[i] = new BacnetGenericTime(timestamp, BacnetTimestampTags.TIME_STAMP_DATETIME); + len++; // closing tag + } + else + len += (int)lenValue; + } + + len++; // closing Tag 3 + + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out value.notifyType); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.eventEnable); + + len++; // opening tag 6; + value.eventPriorities = new uint[3]; + for (var i = 0; i < 3; i++) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out value.eventPriorities[i]); + } + len++; // closing Tag 6 + + events.Add(value); + } + + moreEvent = buffer[apduLen - 1] == 1; + return len; + } + + public static void EncodeLifeSafetyOperation(EncodeBuffer buffer, uint processId, string requestingSrc, uint operation, BacnetObjectId targetObject) + { + /* tag 0 - requestingProcessId */ + ASN1.encode_context_unsigned(buffer, 0, processId); + /* tag 1 - requestingSource */ + ASN1.encode_context_character_string(buffer, 1, requestingSrc); + /* Operation */ + ASN1.encode_context_enumerated(buffer, 2, operation); + /* Object ID */ + ASN1.encode_context_object_id(buffer, 3, targetObject.Type, targetObject.Instance); + } + + public static void EncodeSubscribeCOV(EncodeBuffer buffer, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime) + { + /* tag 0 - subscriberProcessIdentifier */ + ASN1.encode_context_unsigned(buffer, 0, subscriberProcessIdentifier); + /* tag 1 - monitoredObjectIdentifier */ + ASN1.encode_context_object_id(buffer, 1, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); + /* + If both the 'Issue Confirmed Notifications' and + 'Lifetime' parameters are absent, then this shall + indicate a cancellation request. + */ + if (cancellationRequest) + return; + /* tag 2 - issueConfirmedNotifications */ + ASN1.encode_context_boolean(buffer, 2, issueConfirmedNotifications); + /* tag 3 - lifetime */ + ASN1.encode_context_unsigned(buffer, 3, lifetime); + } + + public static int DecodeSubscribeCOV(byte[] buffer, int offset, int apduLen, out uint subscriberProcessIdentifier, out BacnetObjectId monitoredObjectIdentifier, out bool cancellationRequest, out bool issueConfirmedNotifications, out uint lifetime) + { + var len = 0; + uint lenValue; + + subscriberProcessIdentifier = 0; + monitoredObjectIdentifier = default(BacnetObjectId); + cancellationRequest = false; + issueConfirmedNotifications = false; + lifetime = 0; + + /* tag 0 - subscriberProcessIdentifier */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out subscriberProcessIdentifier); + } + else + return -1; + /* tag 1 - monitoredObjectIdentifier */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + monitoredObjectIdentifier = new BacnetObjectId(type, instance); + } + else + return -1; + /* optional parameters - if missing, means cancellation */ + if (len < apduLen) + { + /* tag 2 - issueConfirmedNotifications - optional */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 2)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + issueConfirmedNotifications = buffer[offset + len] > 0; + len += (int)lenValue; + } + else + { + cancellationRequest = true; + } + /* tag 3 - lifetime - optional */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 3)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out lifetime); + } + else + { + lifetime = 0; + } + } + else + { + cancellationRequest = true; + } + + return len; + } + + // TODO CHECK: rename to EncodeSubscribeCovProperty ? + public static void EncodeSubscribeProperty(EncodeBuffer buffer, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier, bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, BacnetPropertyReference monitoredProperty, bool covIncrementPresent, float covIncrement) + { + /* tag 0 - subscriberProcessIdentifier */ + ASN1.encode_context_unsigned(buffer, 0, subscriberProcessIdentifier); + /* tag 1 - monitoredObjectIdentifier */ + ASN1.encode_context_object_id(buffer, 1, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); + if (!cancellationRequest) + { + /* tag 2 - issueConfirmedNotifications */ + ASN1.encode_context_boolean(buffer, 2, issueConfirmedNotifications); + /* tag 3 - lifetime */ + ASN1.encode_context_unsigned(buffer, 3, lifetime); + } + /* tag 4 - monitoredPropertyIdentifier */ + ASN1.encode_opening_tag(buffer, 4); + ASN1.encode_context_enumerated(buffer, 0, monitoredProperty.propertyIdentifier); + if (monitoredProperty.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) + { + ASN1.encode_context_unsigned(buffer, 1, monitoredProperty.propertyArrayIndex); + } + ASN1.encode_closing_tag(buffer, 4); + + /* tag 5 - covIncrement */ + if (covIncrementPresent) + ASN1.encode_context_real(buffer, 5, covIncrement); + } + + // TODO CHECK: rename to DecodeSubscribeCovProperty ? + public static int DecodeSubscribeProperty(byte[] buffer, int offset, int apduLen, out uint subscriberProcessIdentifier, out BacnetObjectId monitoredObjectIdentifier, out BacnetPropertyReference monitoredProperty, out bool cancellationRequest, out bool issueConfirmedNotifications, out uint lifetime, out float covIncrement) + { + var len = 0; + uint lenValue; + uint decodedValue; + + subscriberProcessIdentifier = 0; + monitoredObjectIdentifier = default(BacnetObjectId); + cancellationRequest = false; + issueConfirmedNotifications = false; + lifetime = 0; + covIncrement = 0; + monitoredProperty = new BacnetPropertyReference(); + + /* tag 0 - subscriberProcessIdentifier */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out subscriberProcessIdentifier); + } + else + return -1; + + /* tag 1 - monitoredObjectIdentifier */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + monitoredObjectIdentifier = new BacnetObjectId(type, instance); + } + else + return -1; + + /* tag 2 - issueConfirmedNotifications - optional */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 2)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + issueConfirmedNotifications = buffer[offset + len] > 0; + len++; + } + else + { + cancellationRequest = true; + } + + /* tag 3 - lifetime - optional */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 3)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out lifetime); + } + else + { + lifetime = 0; + } + + /* tag 4 - monitoredPropertyIdentifier */ + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 4)) + return -1; + + /* a tag number of 4 is not extended so only one octet */ + len++; + /* the propertyIdentifier is tag 0 */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out monitoredProperty.propertyIdentifier); + } + else + return -1; + + /* the optional array index is tag 1 */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 1)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out decodedValue); + monitoredProperty.propertyArrayIndex = decodedValue; + } + else + { + monitoredProperty.propertyArrayIndex = ASN1.BACNET_ARRAY_ALL; + } + + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 4)) + return -1; + + /* a tag number of 4 is not extended so only one octet */ + len++; + /* tag 5 - covIncrement - optional */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 5)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValue); + len += ASN1.decode_real(buffer, offset + len, out covIncrement); + } + else + { + covIncrement = 0; + } + + return len; + } + } +} diff --git a/Serialize/Services/FileAccessServices.cs b/Serialize/Services/FileAccessServices.cs new file mode 100644 index 00000000..f7a6dc71 --- /dev/null +++ b/Serialize/Services/FileAccessServices.cs @@ -0,0 +1,322 @@ +namespace System.IO.BACnet.Serialize +{ + public static class FileAccessServices + { + public static void EncodeAtomicReadFile(EncodeBuffer buffer, bool isStream, BacnetObjectId objectId, int position, uint count) + { + ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); + var tagNumber = (byte)(isStream ? 0 : 1); + ASN1.encode_opening_tag(buffer, tagNumber); + ASN1.encode_application_signed(buffer, position); + ASN1.encode_application_unsigned(buffer, count); + ASN1.encode_closing_tag(buffer, tagNumber); + } + + public static int DecodeAtomicReadFile(byte[] buffer, int offset, int apduLen, out bool isStream, out BacnetObjectId objectId, out int position, out uint count) + { + objectId = default(BacnetObjectId); + + var len = 0; + int tagLen; + + isStream = true; + position = -1; + count = 0; + + len = ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID) + return -1; + + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); + + if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 0)) + { + /* a tag number is not extended so only one octet */ + len++; + /* fileStartPosition */ + tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += tagLen; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) + return -1; + len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); + /* requestedOctetCount */ + tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += tagLen; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) + return -1; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out count); + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 0)) + return -1; + /* a tag number is not extended so only one octet */ + len++; + } + else if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 1)) + { + isStream = false; + /* a tag number is not extended so only one octet */ + len++; + /* fileStartRecord */ + tagLen = + ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, + out lenValueType); + len += tagLen; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) + return -1; + len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); + /* RecordCount */ + tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += tagLen; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) + return -1; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out count); + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) + return -1; + /* a tag number is not extended so only one octet */ + len++; + } + else + return -1; + + return len; + } + + public static void EncodeAtomicWriteFile(EncodeBuffer buffer, bool isStream, BacnetObjectId objectId, int position, uint blockCount, byte[][] blocks, int[] counts) + { + ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); + var tagNumber = (byte)(isStream ? 0 : 1); + + ASN1.encode_opening_tag(buffer, tagNumber); + ASN1.encode_application_signed(buffer, position); + + if (isStream) + { + ASN1.encode_application_octet_string(buffer, blocks[0], 0, counts[0]); + } + else + { + ASN1.encode_application_unsigned(buffer, blockCount); + for (var i = 0; i < blockCount; i++) + ASN1.encode_application_octet_string(buffer, blocks[i], 0, counts[i]); + } + + ASN1.encode_closing_tag(buffer, tagNumber); + } + + public static int DecodeAtomicWriteFile(byte[] buffer, int offset, int apduLen, out bool isStream, out BacnetObjectId objectId, out int position, out uint blockCount, out byte[][] blocks, out int[] counts) + { + var len = 0; + int tagLen; + + objectId = default(BacnetObjectId); + isStream = true; + position = -1; + blockCount = 0; + blocks = null; + counts = null; + + len = ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID) + return -1; + + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); + + if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 0)) + { + /* a tag number of 2 is not extended so only one octet */ + len++; + /* fileStartPosition */ + tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += tagLen; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) + return -1; + len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); + /* fileData */ + tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += tagLen; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING) + return -1; + blockCount = 1; + blocks = new byte[1][]; + blocks[0] = new byte[lenValueType]; + counts = new[] { (int)lenValueType }; + len += ASN1.decode_octet_string(buffer, offset + len, apduLen, blocks[0], 0, lenValueType); + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 0)) + return -1; + /* a tag number is not extended so only one octet */ + len++; + } + else if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 1)) + { + isStream = false; + /* a tag number is not extended so only one octet */ + len++; + /* fileStartRecord */ + tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += tagLen; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) + return -1; + len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); + /* returnedRecordCount */ + tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += tagLen; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) + return -1; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out blockCount); + /* fileData */ + blocks = new byte[blockCount][]; + counts = new int[blockCount]; + for (var i = 0; i < blockCount; i++) + { + tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += tagLen; + blocks[i] = new byte[lenValueType]; + counts[i] = (int)lenValueType; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING) + return -1; + len += ASN1.decode_octet_string(buffer, offset + len, apduLen, blocks[i], 0, lenValueType); + } + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) + return -1; + /* a tag number is not extended so only one octet */ + len++; + } + else + return -1; + + return len; + } + + public static void EncodeAtomicReadFileAcknowledge(EncodeBuffer buffer, bool isStream, bool endOfFile, int position, uint blockCount, byte[][] blocks, int[] counts) + { + ASN1.encode_application_boolean(buffer, endOfFile); + var tagNumber = (byte)(isStream ? 0 : 1); + ASN1.encode_opening_tag(buffer, tagNumber); + ASN1.encode_application_signed(buffer, position); + + if (isStream) + { + ASN1.encode_application_octet_string(buffer, blocks[0], 0, counts[0]); + } + else + { + ASN1.encode_application_unsigned(buffer, blockCount); + for (var i = 0; i < blockCount; i++) + ASN1.encode_application_octet_string(buffer, blocks[i], 0, counts[i]); + } + + ASN1.encode_closing_tag(buffer, tagNumber); + } + + public static int DecodeAtomicReadFileAcknowledge(byte[] buffer, int offset, int apduLen, out bool endOfFile, out bool isStream, out int position, out uint count, out byte[] targetBuffer, out int targetOffset) + { + var len = 0; + + endOfFile = false; + isStream = false; + position = -1; + count = 0; + targetBuffer = null; + targetOffset = -1; + + len = ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_BOOLEAN) + return -1; + + endOfFile = lenValueType > 0; + if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 0)) + { + isStream = true; + /* a tag number is not extended so only one octet */ + len++; + /* fileStartPosition */ + var tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += tagLen; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) + return -1; + len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); + /* fileData */ + tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += tagLen; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING) + return -1; + //len += ASN1.decode_octet_string(buffer, offset + len, buffer.Length, target_buffer, target_offset, len_value_type); + targetBuffer = buffer; + targetOffset = offset + len; + count = lenValueType; + len += (int)count; + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 0)) + return -1; + /* a tag number is not extended so only one octet */ + len++; + } + else if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 1)) + { + throw new NotImplementedException("Non stream File transfers are not supported"); + ///* a tag number is not extended so only one octet */ + //len++; + ///* fileStartRecord */ + //tag_len = ASN1.decode_tag_number_and_value(buffer, offset + len, out tag_number, out len_value_type); + //len += tag_len; + //if (tag_number != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) + // return -1; + //len += ASN1.decode_signed(buffer, offset + len, len_value_type, out position); + ///* returnedRecordCount */ + //tag_len = ASN1.decode_tag_number_and_value(buffer, offset + len, out tag_number, out len_value_type); + //len += tag_len; + //if (tag_number != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) + // return -1; + //len += ASN1.decode_unsigned(buffer, offset + len, len_value_type, out count); + //for (i = 0; i < count; i++) + //{ + // /* fileData */ + // tag_len = ASN1.decode_tag_number_and_value(buffer, offset + len, out tag_number, out len_value_type); + // len += tag_len; + // if (tag_number != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OCTET_STRING) + // return -1; + // len += ASN1.decode_octet_string(buffer, offset + len, buffer.Length, target_buffer, target_offset, len_value_type); + //} + //if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) + // return -1; + ///* a tag number is not extended so only one octet */ + //len++; + } + else + return -1; + + return len; + } + + public static void EncodeAtomicWriteFileAcknowledge(EncodeBuffer buffer, bool isStream, int position) + { + ASN1.encode_context_signed(buffer, (byte)(isStream ? 0 : 1), position); + } + + public static int DecodeAtomicWriteFileAcknowledge(byte[] buffer, int offset, int apduLen, out bool isStream, out int position) + { + var len = 0; + + isStream = false; + position = 0; + + len = ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); + switch (tagNumber) + { + case 0: + isStream = true; + len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); + break; + + case 1: + len += ASN1.decode_signed(buffer, offset + len, lenValueType, out position); + break; + + default: + return -1; + } + + return len; + } + } +} diff --git a/Serialize/Services/ObjectAccessServices.cs b/Serialize/Services/ObjectAccessServices.cs new file mode 100644 index 00000000..bd1f6708 --- /dev/null +++ b/Serialize/Services/ObjectAccessServices.cs @@ -0,0 +1,976 @@ +using System.Collections.Generic; + +namespace System.IO.BACnet.Serialize +{ + public static class ObjectAccessServices + { + public static void EncodeAddOrRemoveListElement(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, IList valueList) + { + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); + ASN1.encode_context_enumerated(buffer, 1, propertyId); + + if (arrayIndex != ASN1.BACNET_ARRAY_ALL) + { + ASN1.encode_context_unsigned(buffer, 2, arrayIndex); + } + + ASN1.encode_opening_tag(buffer, 3); + foreach (var value in valueList) + { + ASN1.bacapp_encode_application_data(buffer, value); + } + + ASN1.encode_closing_tag(buffer, 3); + } + + // by Christopher Günter + public static void EncodeCreateObject(EncodeBuffer buffer, BacnetObjectId objectId, ICollection valueList) + { + /* Tag 1: sequence of WriteAccessSpecification */ + ASN1.encode_opening_tag(buffer, 0); + ASN1.encode_context_object_id(buffer, 1, objectId.Type, objectId.Instance); + ASN1.encode_closing_tag(buffer, 0); + + if (valueList == null) + return; + + ASN1.encode_opening_tag(buffer, 1); + + foreach (var pValue in valueList) + { + ASN1.encode_context_enumerated(buffer, 0, pValue.property.propertyIdentifier); + + if (pValue.property.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) + ASN1.encode_context_unsigned(buffer, 1, pValue.property.propertyArrayIndex); + + ASN1.encode_opening_tag(buffer, 2); + foreach (var value in pValue.value) + { + ASN1.bacapp_encode_application_data(buffer, value); + } + ASN1.encode_closing_tag(buffer, 2); + + if (pValue.priority != ASN1.BACNET_NO_PRIORITY) + ASN1.encode_context_unsigned(buffer, 3, pValue.priority); + } + + ASN1.encode_closing_tag(buffer, 1); + } + + // By C. Gunter + // quite the same as DecodeWritePropertyMultiple + public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out ICollection valuesRefs) + { + var len = 0; + + objectId = default(BacnetObjectId); + valuesRefs = null; + + //object id + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); + + if (tagNumber == 0 && apduLen > len) + { + apduLen -= len; + if (apduLen >= 4) + { + len += ASN1.decode_context_object_id(buffer, offset + len, 1, out var typenr, out var instance); + objectId = new BacnetObjectId((BacnetObjectTypes) typenr, instance); + } + else + return -1; + } + else + return -1; + if (ASN1.decode_is_closing_tag(buffer, offset + len)) + len++; + //end objectid + + // No initial values ? + if (buffer.Length == offset + len) + return len; + + /* Tag 1: sequence of WriteAccessSpecification */ + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 1)) + return -1; + len++; + + var _values = new LinkedList(); + while (apduLen - len > 1) + { + var newEntry = new BacnetPropertyValue(); + + /* tag 0 - Property Identifier */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + uint propertyId; + if (tagNumber == 0) + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out propertyId); + else + return -1; + + /* tag 1 - Property Array Index - optional */ + var ulVal = ASN1.BACNET_ARRAY_ALL; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + if (tagNumber == 1) + { + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out ulVal); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + } + newEntry.property = new BacnetPropertyReference(propertyId, ulVal); + + /* tag 2 - Property Value */ + if (tagNumber == 2 && ASN1.decode_is_opening_tag(buffer, offset + len - 1)) + { + var values = new List(); + while (!ASN1.decode_is_closing_tag(buffer, offset + len)) + { + var l = ASN1.bacapp_decode_application_data( + address, buffer, offset + len, apduLen + offset, objectId.Type, + (BacnetPropertyIds) propertyId, out var value); + + if (l <= 0) return -1; + len += l; + values.Add(value); + } + len++; + newEntry.value = values; + } + else + return -1; + + _values.AddLast(newEntry); + } + + /* Closing tag 1 - List of Properties */ + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) + return -1; + len++; + + valuesRefs = _values; + + return len; + } + + public static void EncodeCreateObjectAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId) + { + ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); + } + + public static int DecodeDeleteObject(byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId) + { + objectId = default(BacnetObjectId); + + ASN1.decode_tag_number_and_value(buffer, offset, out var tagNumber, out _); + + if (tagNumber != 12) + return -1; + + var len = 1; + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); + + if (len == apduLen) //check if packet was correct! + return len; + + return -1; + } + + public static void EncodeReadProperty(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) + { + if ((int)objectId.Type <= ASN1.BACNET_MAX_OBJECT) + { + /* check bounds so that we could create malformed + messages for testing */ + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); + } + if (propertyId <= (uint)BacnetPropertyIds.MAX_BACNET_PROPERTY_ID) + { + /* check bounds so that we could create malformed + messages for testing */ + ASN1.encode_context_enumerated(buffer, 1, propertyId); + } + /* optional array index */ + if (arrayIndex != ASN1.BACNET_ARRAY_ALL) + { + ASN1.encode_context_unsigned(buffer, 2, arrayIndex); + } + } + + public static int DecodeReadProperty(byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyReference property) + { + var len = 0; + + objectId = default(BacnetObjectId); + property = new BacnetPropertyReference(); + + // must have at least 2 tags , otherwise return reject code: Missing required parameter + if (apduLen < 7) + return -1; + + /* Tag 0: Object ID */ + if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) + return -2; + + len++; + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); + + /* Tag 1: Property ID */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); + if (tagNumber != 1) + return -2; + + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyIdentifier); + + /* Tag 2: Optional Array Index */ + if (len < apduLen) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + if (tagNumber == 2 && len < apduLen) + { + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyArrayIndex); + } + else + return -2; + } + else + property.propertyArrayIndex = ASN1.BACNET_ARRAY_ALL; + + if (len < apduLen) + /* If something left over now, we have an invalid request */ + return -3; + + return len; + } + + public static void EncodeReadPropertyAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, IEnumerable valueList) + { + /* service ack follows */ + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); + ASN1.encode_context_enumerated(buffer, 1, propertyId); + /* context 2 array index is optional */ + if (arrayIndex != ASN1.BACNET_ARRAY_ALL) + { + ASN1.encode_context_unsigned(buffer, 2, arrayIndex); + } + + /* Value */ + ASN1.encode_opening_tag(buffer, 3); + foreach (BacnetValue value in valueList) + { + ASN1.bacapp_encode_application_data(buffer, value); + } + ASN1.encode_closing_tag(buffer, 3); + } + + public static int DecodeReadPropertyAcknowledge(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyReference property, out IList valueList) + { + objectId = default(BacnetObjectId); + property = new BacnetPropertyReference(); + valueList = new List(); + + /* FIXME: check apduLen against the len during decode */ + /* Tag 0: Object ID */ + if (!ASN1.decode_is_context_tag(buffer, offset, 0)) + return -1; + var len = 1; + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); + /* Tag 1: Property ID */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); + if (tagNumber != 1) + return -1; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyIdentifier); + /* Tag 2: Optional Array Index */ + var tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + if (tagNumber == 2) + { + len += tagLen; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyArrayIndex); + } + else + property.propertyArrayIndex = ASN1.BACNET_ARRAY_ALL; + + /* Tag 3: opening context tag */ + if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 3)) + { + /* a tag number of 3 is not extended so only one octet */ + len++; + + while (apduLen - len > 1) + { + tagLen = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, objectId.Type, (BacnetPropertyIds)property.propertyIdentifier, out var value); + if (tagLen < 0) return -1; + len += tagLen; + valueList.Add(value); + } + } + else + return -1; + + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 3)) + return -1; + len++; + + return len; + } + + public static void EncodeReadPropertyMultiple(EncodeBuffer buffer, IList properties) + { + foreach (var value in properties) + ASN1.encode_read_access_specification(buffer, value); + } + + public static void EncodeReadPropertyMultiple(EncodeBuffer buffer, BacnetObjectId objectId, IList properties) + { + EncodeReadPropertyMultiple(buffer, new[] { new BacnetReadAccessSpecification(objectId, properties) }); + } + + public static int DecodeReadPropertyMultiple(byte[] buffer, int offset, int apduLen, out IList properties) + { + var len = 0; + + var values = new List(); + properties = null; + + while (apduLen - len > 0) + { + var tmp = ASN1.decode_read_access_specification(buffer, offset + len, apduLen - len, out var value); + if (tmp < 0) return -1; + len += tmp; + values.Add(value); + } + + properties = values; + return len; + } + + public static void EncodeReadPropertyMultipleAcknowledge(EncodeBuffer buffer, IList values) + { + foreach (var value in values) + ASN1.encode_read_access_result(buffer, value); + } + + public static int DecodeReadPropertyMultipleAcknowledge(BacnetAddress address, byte[] buffer, int offset, int apduLen, out IList values) + { + var len = 0; + + var result = new List(); + + while (apduLen - len > 0) + { + var tmp = ASN1.decode_read_access_result(address, buffer, offset + len, apduLen - len, out var value); + if (tmp < 0) + { + values = null; + return -1; + } + len += tmp; + result.Add(value); + } + + values = result; + return len; + } + + public static void EncodeReadRange(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, BacnetReadRangeRequestTypes requestType, uint position, DateTime time, int count) + { + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); + ASN1.encode_context_enumerated(buffer, 1, propertyId); + + /* optional array index */ + if (arrayIndex != ASN1.BACNET_ARRAY_ALL) + { + ASN1.encode_context_unsigned(buffer, 2, arrayIndex); + } + + /* Build the appropriate (optional) range parameter based on the request type */ + switch (requestType) + { + case BacnetReadRangeRequestTypes.RR_BY_POSITION: + ASN1.encode_opening_tag(buffer, 3); + ASN1.encode_application_unsigned(buffer, position); + ASN1.encode_application_signed(buffer, count); + ASN1.encode_closing_tag(buffer, 3); + break; + + case BacnetReadRangeRequestTypes.RR_BY_SEQUENCE: + ASN1.encode_opening_tag(buffer, 6); + ASN1.encode_application_unsigned(buffer, position); + ASN1.encode_application_signed(buffer, count); + ASN1.encode_closing_tag(buffer, 6); + break; + + case BacnetReadRangeRequestTypes.RR_BY_TIME: + ASN1.encode_opening_tag(buffer, 7); + ASN1.encode_application_date(buffer, time); + ASN1.encode_application_time(buffer, time); + ASN1.encode_application_signed(buffer, count); + ASN1.encode_closing_tag(buffer, 7); + break; + + case BacnetReadRangeRequestTypes.RR_READ_ALL: /* to attempt a read of the whole array or list, omit the range parameter */ + break; + } + } + + public static int DecodeReadRange(byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyReference property, out BacnetReadRangeRequestTypes requestType, out uint position, out DateTime time, out int count) + { + var len = 0; + + objectId = default(BacnetObjectId); + property = new BacnetPropertyReference(); + requestType = BacnetReadRangeRequestTypes.RR_READ_ALL; + position = 0; + time = new DateTime(1, 1, 1); + count = -1; + + /* Tag 0: Object ID */ + if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) + return -1; + len++; + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); + /* Tag 1: Property ID */ + len += + ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); + if (tagNumber != 1) + return -1; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyIdentifier); + + /* Tag 2: Optional Array Index */ + if (len < apduLen && ASN1.decode_is_context_tag(buffer, offset + len, 0)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyArrayIndex); + } + else + property.propertyArrayIndex = ASN1.BACNET_ARRAY_ALL; + + /* optional request type */ + if (len < apduLen) + { + len += ASN1.decode_tag_number(buffer, offset + len, out tagNumber); //opening tag + switch (tagNumber) + { + case 3: + requestType = BacnetReadRangeRequestTypes.RR_BY_POSITION; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out position); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += ASN1.decode_signed(buffer, offset + len, lenValueType, out count); + break; + + case 6: + requestType = BacnetReadRangeRequestTypes.RR_BY_SEQUENCE; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out position); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += ASN1.decode_signed(buffer, offset + len, lenValueType, out count); + break; + + case 7: + requestType = BacnetReadRangeRequestTypes.RR_BY_TIME; + len += ASN1.decode_application_date(buffer, offset + len, out var date); + len += ASN1.decode_application_time(buffer, offset + len, out time); + time = new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += ASN1.decode_signed(buffer, offset + len, lenValueType, out count); + break; + + default: + return -1; //don't know this type yet + } + len += ASN1.decode_tag_number(buffer, offset + len, out tagNumber); //closing tag + } + return len; + } + + public static void EncodeReadRangeAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, BacnetBitString resultFlags, uint itemCount, byte[] applicationData, BacnetReadRangeRequestTypes requestType, uint firstSequence) + { + /* service ack follows */ + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); + ASN1.encode_context_enumerated(buffer, 1, propertyId); + /* context 2 array index is optional */ + if (arrayIndex != ASN1.BACNET_ARRAY_ALL) + { + ASN1.encode_context_unsigned(buffer, 2, arrayIndex); + } + /* Context 3 BACnet Result Flags */ + ASN1.encode_context_bitstring(buffer, 3, resultFlags); + /* Context 4 Item Count */ + ASN1.encode_context_unsigned(buffer, 4, itemCount); + /* Context 5 Property list - reading the standard it looks like an empty list still + * requires an opening and closing tag as the tagged parameter is not optional + */ + ASN1.encode_opening_tag(buffer, 5); + if (itemCount != 0) + { + buffer.Add(applicationData, applicationData.Length); + } + ASN1.encode_closing_tag(buffer, 5); + + if (itemCount != 0 && requestType != BacnetReadRangeRequestTypes.RR_BY_POSITION && requestType != BacnetReadRangeRequestTypes.RR_READ_ALL) + { + /* Context 6 Sequence number of first item */ + ASN1.encode_context_unsigned(buffer, 6, firstSequence); + } + } + + public static uint DecodeReadRangeAcknowledge(byte[] buffer, int offset, int apduLen, out byte[] rangeBuffer) + { + var len = 0; + rangeBuffer = null; + + /* Tag 0: Object ID */ + if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) + return 0; + + len++; + len += ASN1.decode_object_id(buffer, offset + len, out ushort _, out _); + + /* Tag 1: Property ID */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); + if (tagNumber != 1) + return 0; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint _); + + /* Tag 2: Optional Array Index or Tag 3: BACnet Result Flags */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + if (tagNumber == 2 && len < apduLen) + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint _); + else + /* Tag 3: BACnet Result Flags */ + len += ASN1.decode_bitstring(buffer, offset + len, 2, out _); + + /* Tag 4 Item Count */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint itemCount); + + if (!ASN1.decode_is_opening_tag(buffer, offset + len)) + return 0; + len += 1; + + rangeBuffer = new byte[buffer.Length - offset - len - 1]; + + Array.Copy(buffer, offset + len, rangeBuffer, 0, rangeBuffer.Length); + + return itemCount; + } + + public static void EncodeLogRecord(EncodeBuffer buffer, BacnetLogRecord record) + { + /* Tag 0: timestamp */ + ASN1.encode_opening_tag(buffer, 0); + ASN1.encode_application_date(buffer, record.timestamp); + ASN1.encode_application_time(buffer, record.timestamp); + ASN1.encode_closing_tag(buffer, 0); + + /* Tag 1: logDatum */ + if (record.type != BacnetTrendLogValueType.TL_TYPE_NULL) + { + if (record.type == BacnetTrendLogValueType.TL_TYPE_ERROR) + { + ASN1.encode_opening_tag(buffer, 1); + ASN1.encode_opening_tag(buffer, 8); + var err = record.GetValue(); + ASN1.EncodeError(buffer, err.error_class, err.error_code); + ASN1.encode_closing_tag(buffer, 8); + ASN1.encode_closing_tag(buffer, 1); + return; + } + + ASN1.encode_opening_tag(buffer, 1); + var tmp1 = new EncodeBuffer(); + switch (record.type) + { + case BacnetTrendLogValueType.TL_TYPE_ANY: + throw new NotImplementedException(); + case BacnetTrendLogValueType.TL_TYPE_BITS: + ASN1.encode_bitstring(tmp1, record.GetValue()); + break; + + case BacnetTrendLogValueType.TL_TYPE_BOOL: + tmp1.Add(record.GetValue() ? (byte)1 : (byte)0); + break; + + case BacnetTrendLogValueType.TL_TYPE_DELTA: + ASN1.encode_bacnet_real(tmp1, record.GetValue()); + break; + + case BacnetTrendLogValueType.TL_TYPE_ENUM: + ASN1.encode_application_enumerated(tmp1, record.GetValue()); + break; + + case BacnetTrendLogValueType.TL_TYPE_REAL: + ASN1.encode_bacnet_real(tmp1, record.GetValue()); + break; + + case BacnetTrendLogValueType.TL_TYPE_SIGN: + ASN1.encode_bacnet_signed(tmp1, record.GetValue()); + break; + + case BacnetTrendLogValueType.TL_TYPE_STATUS: + ASN1.encode_bitstring(tmp1, record.GetValue()); + break; + + case BacnetTrendLogValueType.TL_TYPE_UNSIGN: + ASN1.encode_bacnet_unsigned(tmp1, record.GetValue()); + break; + } + ASN1.encode_tag(buffer, (byte)record.type, true, (uint)tmp1.offset); + buffer.Add(tmp1.buffer, tmp1.offset); + ASN1.encode_closing_tag(buffer, 1); + } + + /* Tag 2: status */ + var recordStatusFlags = BacnetBitString.ConvertFromInt((uint)record.statusFlags, 4); + if (recordStatusFlags.BitsUsed > 0) + { + ASN1.encode_tag(buffer, 2, true, 2); + ASN1.encode_bitstring(buffer, recordStatusFlags); + } + } + + public static int DecodeLogRecord(byte[] buffer, int offset, int length, int nCurves, out BacnetLogRecord[] records) + { + var len = 0; + records = new BacnetLogRecord[nCurves]; + + for (var curveNumber = 0; curveNumber < nCurves; curveNumber++) + { + len += ASN1.decode_tag_number(buffer, offset + len, out var tagNumber); + if (tagNumber != 0) return -1; + + // Date and Time in Tag 0 + len += ASN1.decode_application_date(buffer, offset + len, out var date); + len += ASN1.decode_application_time(buffer, offset + len, out var time); + + var dt = new DateTime( + date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond); + + if (!ASN1.decode_is_closing_tag(buffer, offset + len)) return -1; + len++; + + // Value or error in Tag 1 + len += ASN1.decode_tag_number(buffer, offset + len, out tagNumber); + if (tagNumber != 1) return -1; + + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var contextTagType, out var lenValue); + records[curveNumber] = new BacnetLogRecord + { + timestamp = dt, + type = (BacnetTrendLogValueType)contextTagType + }; + + switch ((BacnetTrendLogValueType)contextTagType) + { + case BacnetTrendLogValueType.TL_TYPE_STATUS: + len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out var sval); + records[curveNumber].Value = sval; + break; + + case BacnetTrendLogValueType.TL_TYPE_BOOL: + records[curveNumber].Value = buffer[offset + len] > 0; + len++; + break; + + case BacnetTrendLogValueType.TL_TYPE_REAL: + len += ASN1.decode_real(buffer, offset + len, out var rval); + records[curveNumber].Value = rval; + break; + + case BacnetTrendLogValueType.TL_TYPE_ENUM: + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint eval); + records[curveNumber].Value = eval; + break; + + case BacnetTrendLogValueType.TL_TYPE_SIGN: + len += ASN1.decode_signed(buffer, offset + len, lenValue, out var ival); + records[curveNumber].Value = ival; + break; + + case BacnetTrendLogValueType.TL_TYPE_UNSIGN: + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint uinval); + records[curveNumber].Value = uinval; + break; + + case BacnetTrendLogValueType.TL_TYPE_ERROR: + len += ASN1.DecodeError(buffer, offset + len, length, out var errclass, out var errcode); + records[curveNumber].Value = new BacnetError(errclass, errcode); + len++; // Closing Tag 8 + break; + + case BacnetTrendLogValueType.TL_TYPE_NULL: + len++; + records[curveNumber].Value = null; + break; + // Time change (Automatic or Synch time) Delta in seconds + case BacnetTrendLogValueType.TL_TYPE_DELTA: + len += ASN1.decode_real(buffer, offset + len, out var dval); + records[curveNumber].Value = dval; + break; + // No way to handle these data types, sure it's the end of this download ! + case BacnetTrendLogValueType.TL_TYPE_ANY: + throw new NotImplementedException(); + case BacnetTrendLogValueType.TL_TYPE_BITS: + len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out var bval); + records[curveNumber].Value = bval; + break; + + default: + return 0; + } + + + if (!ASN1.decode_is_closing_tag(buffer, offset + len)) + return -1; + + len++; + + if (len >= length) + return len; + + ASN1.decode_tag_number(buffer, offset + len, out tagNumber); + + // Optional Tag 2 + if (tagNumber != 2) + return len; + + len++; + len += ASN1.decode_bitstring(buffer, offset + len, 2, out var statusFlagsBits); + + //set status to all returns + var statusFlags = (BacnetStatusFlags)statusFlagsBits.ConvertToInt(); + records[curveNumber].statusFlags = statusFlags; + } + + return len; + } + + public static void EncodeWriteProperty(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, uint priority, IEnumerable valueList) + { + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); + ASN1.encode_context_enumerated(buffer, 1, propertyId); + + /* optional array index; ALL is -1 which is assumed when missing */ + if (arrayIndex != ASN1.BACNET_ARRAY_ALL) + { + ASN1.encode_context_unsigned(buffer, 2, arrayIndex); + } + + /* propertyValue */ + ASN1.encode_opening_tag(buffer, 3); + foreach (var value in valueList) + { + ASN1.bacapp_encode_application_data(buffer, value); + } + ASN1.encode_closing_tag(buffer, 3); + + /* optional priority - 0 if not set, 1..16 if set */ + if (priority != ASN1.BACNET_NO_PRIORITY) + { + ASN1.encode_context_unsigned(buffer, 4, priority); + } + } + + public static int DecodeWriteProperty(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out BacnetPropertyValue value) + { + var len = 0; + + objectId = default(BacnetObjectId); + value = new BacnetPropertyValue(); + + /* Tag 0: Object ID */ + if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) + return -1; + len++; + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); + /* Tag 1: Property ID */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); + if (tagNumber != 1) + return -1; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out value.property.propertyIdentifier); + /* Tag 2: Optional Array Index */ + /* note: decode without incrementing len so we can check for opening tag */ + var tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + if (tagNumber == 2) + { + len += tagLen; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out value.property.propertyArrayIndex); + } + else + value.property.propertyArrayIndex = ASN1.BACNET_ARRAY_ALL; + /* Tag 3: opening context tag */ + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 3)) + return -1; + len++; + + //data + var valueList = new List(); + while (apduLen - len > 1 && !ASN1.decode_is_closing_tag_number(buffer, offset + len, 3)) + { + var l = ASN1.bacapp_decode_application_data( + address, buffer, offset + len, apduLen + offset, objectId.Type, + (BacnetPropertyIds) value.property.propertyIdentifier, out var bValue); + + if (l <= 0) return -1; + len += l; + valueList.Add(bValue); + } + value.value = valueList; + + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 3)) + return -2; + /* a tag number of 3 is not extended so only one octet */ + len++; + /* Tag 4: optional Priority - assumed MAX if not explicitly set */ + value.priority = (byte)ASN1.BACNET_MAX_PRIORITY; + if (len < apduLen) + { + tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); + if (tagNumber == 4) + { + len += tagLen; + len = ASN1.decode_unsigned(buffer, offset + len, lenValueType, out uint unsignedValue); + if (unsignedValue >= ASN1.BACNET_MIN_PRIORITY && unsignedValue <= ASN1.BACNET_MAX_PRIORITY) + value.priority = (byte)unsignedValue; + else + return -1; + } + } + + return len; + } + + public static void EncodeWritePropertyMultiple(EncodeBuffer buffer, BacnetObjectId objectId, ICollection valueList) + { + ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); + /* Tag 1: sequence of WriteAccessSpecification */ + ASN1.encode_opening_tag(buffer, 1); + + foreach (var pValue in valueList) + { + /* Tag 0: Property */ + ASN1.encode_context_enumerated(buffer, 0, pValue.property.propertyIdentifier); + + /* Tag 1: array index */ + if (pValue.property.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) + ASN1.encode_context_unsigned(buffer, 1, pValue.property.propertyArrayIndex); + + /* Tag 2: Value */ + ASN1.encode_opening_tag(buffer, 2); + foreach (var value in pValue.value) + { + ASN1.bacapp_encode_application_data(buffer, value); + } + ASN1.encode_closing_tag(buffer, 2); + + /* Tag 3: Priority */ + if (pValue.priority != ASN1.BACNET_NO_PRIORITY) + ASN1.encode_context_unsigned(buffer, 3, pValue.priority); + } + + ASN1.encode_closing_tag(buffer, 1); + } + + public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out ICollection valuesRefs) + { + var len = 0; + objectId = default(BacnetObjectId); + valuesRefs = null; + + /* Context tag 0 - Object ID */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); + if (tagNumber == 0 && apduLen > len) + { + apduLen -= len; + if (apduLen >= 4) + { + len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); + } + else + return -1; + } + else + return -1; + + /* Tag 1: sequence of WriteAccessSpecification */ + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 1)) + return -1; + len++; + + var linkedList = new LinkedList(); + while (apduLen - len > 1) + { + var newEntry = new BacnetPropertyValue(); + + /* tag 0 - Property Identifier */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + uint propertyId; + if (tagNumber == 0) + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out propertyId); + else + return -1; + + /* tag 1 - Property Array Index - optional */ + var ulVal = ASN1.BACNET_ARRAY_ALL; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + if (tagNumber == 1) + { + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out ulVal); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + } + newEntry.property = new BacnetPropertyReference(propertyId, ulVal); + + /* tag 2 - Property Value */ + if (tagNumber == 2 && ASN1.decode_is_opening_tag(buffer, offset + len - 1)) + { + var list = new List(); + while (!ASN1.decode_is_closing_tag(buffer, offset + len)) + { + var l = ASN1.bacapp_decode_application_data( + address, buffer, offset + len, apduLen + offset, objectId.Type, + (BacnetPropertyIds) propertyId, out var value); + + if (l <= 0) return -1; + len += l; + list.Add(value); + } + len++; + newEntry.value = list; + } + else + return -1; + + /* tag 3 - Priority - optional */ + ulVal = ASN1.BACNET_NO_PRIORITY; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + if (tagNumber == 3) + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out ulVal); + else + len--; + newEntry.priority = (byte)ulVal; + + linkedList.AddLast(newEntry); + } + + /* Closing tag 1 - List of Properties */ + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) + return -1; + len++; + + valuesRefs = linkedList; + + return len; + } + + public static void EncodeWriteObjectMultiple(EncodeBuffer buffer, ICollection valueList) + { + foreach (var value in valueList) + ObjectAccessServices.EncodeWritePropertyMultiple(buffer, value.objectIdentifier, value.values); + } + } +} diff --git a/Serialize/Services/RemoteDeviceManagementServices.cs b/Serialize/Services/RemoteDeviceManagementServices.cs new file mode 100644 index 00000000..3b287150 --- /dev/null +++ b/Serialize/Services/RemoteDeviceManagementServices.cs @@ -0,0 +1,333 @@ +namespace System.IO.BACnet.Serialize +{ + public static class RemoteDeviceManagementServices + { + public static void EncodeDeviceCommunicationControl(EncodeBuffer buffer, uint timeDuration, uint enableDisable, string password) + { + /* optional timeDuration */ + if (timeDuration > 0) + ASN1.encode_context_unsigned(buffer, 0, timeDuration); + + /* enable disable */ + ASN1.encode_context_enumerated(buffer, 1, enableDisable); + + /* optional password */ + if (!String.IsNullOrEmpty(password)) + { + /* FIXME: must be at least 1 character, limited to 20 characters */ + ASN1.encode_context_character_string(buffer, 2, password); + } + } + + public static int DecodeDeviceCommunicationControl(byte[] buffer, int offset, int apduLen, out uint timeDuration, out uint enableDisable, out string password) + { + var len = 0; + uint lenValueType; + + timeDuration = 0; + enableDisable = 0; + password = ""; + + /* Tag 0: timeDuration, in minutes --optional-- + * But if not included, take it as indefinite, + * which we return as "very large" */ + if (ASN1.decode_is_context_tag(buffer, offset + len, 0)) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValueType); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out timeDuration); + } + + /* Tag 1: enable_disable */ + if (!ASN1.decode_is_context_tag(buffer, offset + len, 1)) + return -1; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValueType); + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out enableDisable); + + /* Tag 2: password --optional-- */ + if (len < apduLen) + { + if (!ASN1.decode_is_context_tag(buffer, offset + len, 2)) + return -1; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValueType); + len += ASN1.decode_character_string(buffer, offset + len, apduLen - (offset + len), lenValueType, out password); + } + + return len; + } + + public static void EncodePrivateTransferConfirmed(EncodeBuffer buffer, uint vendorID, uint serviceNumber, byte[] data) + { + ASN1.encode_context_unsigned(buffer, 0, vendorID); + ASN1.encode_context_unsigned(buffer, 1, serviceNumber); + ASN1.encode_opening_tag(buffer, 2); + buffer.Add(data, data.Length); + ASN1.encode_closing_tag(buffer, 2); + } + + public static void EncodePrivateTransferUnconfirmed(EncodeBuffer buffer, uint vendorID, uint serviceNumber, byte[] data) + { + ASN1.encode_context_unsigned(buffer, 0, vendorID); + ASN1.encode_context_unsigned(buffer, 1, serviceNumber); + ASN1.encode_opening_tag(buffer, 2); + buffer.Add(data, data.Length); + ASN1.encode_closing_tag(buffer, 2); + } + + public static void EncodePrivateTransferAcknowledge(EncodeBuffer buffer, uint vendorID, uint serviceNumber, byte[] data) + { + ASN1.encode_context_unsigned(buffer, 0, vendorID); + ASN1.encode_context_unsigned(buffer, 1, serviceNumber); + ASN1.encode_opening_tag(buffer, 2); + buffer.Add(data, data.Length); + ASN1.encode_closing_tag(buffer, 2); + } + + public static void EncodeReinitializeDevice(EncodeBuffer buffer, BacnetReinitializedStates state, string password) + { + ASN1.encode_context_enumerated(buffer, 0, (uint)state); + + /* optional password */ + if (!String.IsNullOrEmpty(password)) + { + /* FIXME: must be at least 1 character, limited to 20 characters */ + ASN1.encode_context_character_string(buffer, 1, password); + } + } + + public static int DecodeReinitializeDevice(byte[] buffer, int offset, int apduLen, out BacnetReinitializedStates state, out string password) + { + var len = 0; + + state = BacnetReinitializedStates.BACNET_REINIT_IDLE; + password = ""; + + /* Tag 0: reinitializedStateOfDevice */ + if (!ASN1.decode_is_context_tag(buffer, offset + len, 0)) + return -1; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out var lenValueType); + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValueType, out state); + /* Tag 1: password - optional */ + if (len < apduLen) + { + if (!ASN1.decode_is_context_tag(buffer, offset + len, 1)) + return -1; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out _, out lenValueType); + len += ASN1.decode_character_string(buffer, offset + len, apduLen - (offset + len), lenValueType, out password); + } + + return len; + } + + public static void EncodeTimeSync(EncodeBuffer buffer, DateTime time) + { + ASN1.encode_application_date(buffer, time); + ASN1.encode_application_time(buffer, time); + } + + public static int DecodeTimeSync(byte[] buffer, int offset, int length, out DateTime dateTime) + { + var len = 0; + dateTime = new DateTime(1, 1, 1); + + /* date */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out _); + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_DATE) + return -1; + len += ASN1.decode_date(buffer, offset + len, out var date); + /* time */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out _); + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_TIME) + return -1; + len += ASN1.decode_bacnet_time(buffer, offset + len, out var time); + + //merge + dateTime = new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond); + + return len; + } + + public static void EncodeWhoHasBroadcast(EncodeBuffer buffer, int lowLimit, int highLimit, BacnetObjectId objectId, string objectName) + { + /* optional limits - must be used as a pair */ + if (lowLimit >= 0 && lowLimit <= ASN1.BACNET_MAX_INSTANCE && highLimit >= 0 && highLimit <= ASN1.BACNET_MAX_INSTANCE) + { + ASN1.encode_context_unsigned(buffer, 0, (uint)lowLimit); + ASN1.encode_context_unsigned(buffer, 1, (uint)highLimit); + } + if (!String.IsNullOrEmpty(objectName)) + { + ASN1.encode_context_character_string(buffer, 3, objectName); + } + else + { + ASN1.encode_context_object_id(buffer, 2, objectId.Type, objectId.Instance); + } + } + + // Added by thamersalek + public static int DecodeWhoHasBroadcast(byte[] buffer, int offset, int apduLen, out int lowLimit, out int highLimit, out BacnetObjectId objId, out string objName) + { + var len = 0; + uint decodedValue; + + objName = null; + objId = new BacnetObjectId(BacnetObjectTypes.OBJECT_BINARY_OUTPUT, 0x3FFFFF); + lowLimit = -1; + highLimit = -1; + + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); + + if (tagNumber == 0) + { + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out decodedValue); + if (decodedValue <= ASN1.BACNET_MAX_INSTANCE) + lowLimit = (int)decodedValue; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + } + + if (tagNumber == 1) + { + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out decodedValue); + if (decodedValue <= ASN1.BACNET_MAX_INSTANCE) + highLimit = (int)decodedValue; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + } + + if (tagNumber == 2) + { + len += ASN1.decode_object_id(buffer, offset + len, out ushort objType, out var objInst); + objId = new BacnetObjectId((BacnetObjectTypes)objType, objInst); + } + + if (tagNumber == 3) + len += ASN1.decode_character_string(buffer, offset + len, apduLen - (offset + len), lenValue, out objName); + + return len; + } + + public static void EncodeIhaveBroadcast(EncodeBuffer buffer, BacnetObjectId deviceId, BacnetObjectId objectId, string objectName) + { + /* deviceIdentifier */ + ASN1.encode_application_object_id(buffer, deviceId.Type, deviceId.Instance); + /* objectIdentifier */ + ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); + /* objectName */ + ASN1.encode_application_character_string(buffer, objectName); + } + + public static void EncodeWhoIsBroadcast(EncodeBuffer buffer, int lowLimit, int highLimit) + { + /* optional limits - must be used as a pair */ + if (lowLimit >= 0 && lowLimit <= ASN1.BACNET_MAX_INSTANCE && + highLimit >= 0 && highLimit <= ASN1.BACNET_MAX_INSTANCE) + { + ASN1.encode_context_unsigned(buffer, 0, (uint)lowLimit); + ASN1.encode_context_unsigned(buffer, 1, (uint)highLimit); + } + } + + public static int DecodeWhoIsBroadcast(byte[] buffer, int offset, int apduLen, out int lowLimit, out int highLimit) + { + var len = 0; + + lowLimit = -1; + highLimit = -1; + + if (apduLen <= 0) return len; + + /* optional limits - must be used as a pair */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); + if (tagNumber != 0) + return -1; + if (apduLen > len) + { + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint decodedValue); + if (decodedValue <= ASN1.BACNET_MAX_INSTANCE) + lowLimit = (int)decodedValue; + if (apduLen > len) + { + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + if (tagNumber != 1) + return -1; + if (apduLen > len) + { + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out decodedValue); + if (decodedValue <= ASN1.BACNET_MAX_INSTANCE) + highLimit = (int)decodedValue; + } + else + return -1; + } + else + return -1; + } + else + return -1; + + return len; + } + + public static void EncodeIamBroadcast(EncodeBuffer buffer, uint deviceId, uint maxApdu, BacnetSegmentations segmentation, ushort vendorId) + { + ASN1.encode_application_object_id(buffer, BacnetObjectTypes.OBJECT_DEVICE, deviceId); + ASN1.encode_application_unsigned(buffer, maxApdu); + ASN1.encode_application_enumerated(buffer, (uint)segmentation); + ASN1.encode_application_unsigned(buffer, vendorId); + } + + public static int DecodeIamBroadcast(byte[] buffer, int offset, out uint deviceId, out uint maxApdu, out BacnetSegmentations segmentation, out ushort vendorId) + { + var apduLen = 0; + var orgOffset = offset; + + deviceId = 0; + maxApdu = 0; + segmentation = BacnetSegmentations.SEGMENTATION_NONE; + vendorId = 0; + + /* OBJECT ID - object id */ + var len = ASN1.decode_tag_number_and_value(buffer, offset + apduLen, out var tagNumber, out var lenValue); + apduLen += len; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID) + return -1; + len = ASN1.decode_object_id(buffer, offset + apduLen, out BacnetObjectTypes type, out var instance); + apduLen += len; + var objectId = new BacnetObjectId(type, instance); + if (objectId.Type != BacnetObjectTypes.OBJECT_DEVICE) + return -1; + deviceId = objectId.Instance; + /* MAX APDU - unsigned */ + len = + ASN1.decode_tag_number_and_value(buffer, offset + apduLen, out tagNumber, out lenValue); + apduLen += len; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) + return -1; + len = ASN1.decode_unsigned(buffer, offset + apduLen, lenValue, out uint decodedValue); + apduLen += len; + maxApdu = decodedValue; + /* Segmentation - enumerated */ + len = + ASN1.decode_tag_number_and_value(buffer, offset + apduLen, out tagNumber, out lenValue); + apduLen += len; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED) + return -1; + len = ASN1.decode_unsigned(buffer, offset + apduLen, lenValue, out decodedValue); + apduLen += len; + if (decodedValue > (uint)BacnetSegmentations.SEGMENTATION_NONE) + return -1; + segmentation = (BacnetSegmentations)decodedValue; + /* Vendor ID - unsigned16 */ + len = + ASN1.decode_tag_number_and_value(buffer, offset + apduLen, out tagNumber, out lenValue); + apduLen += len; + if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) + return -1; + ASN1.decode_unsigned(buffer, offset + apduLen, lenValue, out decodedValue); + if (decodedValue > 0xFFFF) + return -1; + vendorId = (ushort)decodedValue; + + return offset - orgOffset; + } + } +} diff --git a/Tests/BACnet.Tests.csproj b/Tests/BACnet.Tests.csproj index c194ae93..6c3cd8a4 100644 --- a/Tests/BACnet.Tests.csproj +++ b/Tests/BACnet.Tests.csproj @@ -48,8 +48,9 @@ - + + @@ -60,5 +61,6 @@ BACnet + \ No newline at end of file diff --git a/Tests/BACnet.Tests.csproj.DotSettings b/Tests/BACnet.Tests.csproj.DotSettings new file mode 100644 index 00000000..a198f2d2 --- /dev/null +++ b/Tests/BACnet.Tests.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Tests/Serialize/ServicesTests.cs b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs similarity index 86% rename from Tests/Serialize/ServicesTests.cs rename to Tests/Serialize/Services/AlarmAndEventServicesTests.cs index 00633d87..08e5b162 100644 --- a/Tests/Serialize/ServicesTests.cs +++ b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs @@ -7,68 +7,8 @@ namespace System.IO.BACnet.Tests.Serialize { [TestFixture] - public class ServicesTests + public class AlarmAndEventServicesTests { - [Test] - public void should_encode_logrecord_according_to_ashrae_example() - { - // arrange - var buffer = new EncodeBuffer(); - var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, - new DateTime(1998, 3, 23, 19, 54, 27), 0); - var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, - new DateTime(1998, 3, 23, 19, 56, 27), 0); - - // example taken from ANNEX F - Examples of APDU Encoding - var expectedBytes = new byte[] - { - 0x0E, 0xA4, 0x62, 0x03, 0x17, 0x01, 0xB4, 0x13, 0x36, 0x1B, 0x00, 0x0F, - 0x1E, 0x2C, 0x41, 0x90, 0x00, 0x00, 0x1F, 0x2A, 0x04, 0x00, 0x0E, 0xA4, - 0x62, 0x03, 0x17, 0x01, 0xB4, 0x13, 0x38, 0x1B, 0x00, 0x0F, 0x1E, 0x2C, - 0x41, 0x90, 0xCC, 0xCD, 0x1F, 0x2A, 0x04, 0x00 - }; - - // act - Services.EncodeLogRecord(buffer, record1); - Services.EncodeLogRecord(buffer, record2); - var encodedBytes = buffer.ToArray(); - - // assert - Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); - } - - [Test] - public void should_decode_multiple_logrecords() - { - // arrange - var buffer = new EncodeBuffer(); - var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, - new DateTime(1998, 3, 23, 19, 54, 27), 0); - var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, - new DateTime(1998, 3, 23, 19, 56, 27), 0); - - // act - Services.EncodeLogRecord(buffer, record1); - Services.EncodeLogRecord(buffer, record2); - Services.DecodeLogRecord(buffer.buffer, 0, buffer.GetLength(), 2, out var decodedRecords); - - /* - * Debug - write packet to network to analyze in WireShark - * - - var client = new BacnetClient(new BacnetIpUdpProtocolTransport(47808, true)); - client.Start(); - client.ReadRangeResponse(new BacnetAddress(BacnetAddressTypes.IP, "192.168.1.99"), 42, null, - new BacnetObjectId(BacnetObjectTypes.OBJECT_TRENDLOG, 1), - new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_LOG_BUFFER, ASN1.BACNET_ARRAY_ALL), - BacnetResultFlags.LAST_ITEM, 2, buffer.ToArray(), BacnetReadRangeRequestTypes.RR_BY_TIME, 79201); - */ - - // assert - Helper.AssertPropertiesAndFieldsAreEqual(record1, decodedRecords[0]); - Helper.AssertPropertiesAndFieldsAreEqual(record2, decodedRecords[1]); - } - [Test] public void should_encode_confirmendcovnotificationrequest_according_to_ashrae_example() { @@ -105,7 +45,7 @@ public void should_encode_confirmendcovnotificationrequest_according_to_ashrae_e BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 15); - Services.EncodeCOVNotifyConfirmed(buffer, 18, 4, + AlarmAndEventServices.EncodeCOVNotifyConfirmed(buffer, 18, 4, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), 0, data); var encodedBytes = buffer.ToArray(); @@ -170,7 +110,7 @@ public void should_encode_unconfirmendcovnotificationrequest_according_to_ashrae APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION); - Services.EncodeCOVNotifyConfirmed(buffer, 18, 4, + AlarmAndEventServices.EncodeCOVNotifyConfirmed(buffer, 18, 4, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), 0, data); var encodedBytes = buffer.ToArray(); @@ -217,7 +157,7 @@ public void should_encode_confirmendeventnotificationrequest_according_to_ashrae BacnetConfirmedServices.SERVICE_CONFIRMED_EVENT_NOTIFICATION, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 16); - Services.EncodeEventNotifyData(buffer, data); + AlarmAndEventServices.EncodeEventNotifyData(buffer, data); var encodedBytes = buffer.ToArray(); @@ -267,7 +207,7 @@ public void should_encode_acknowledgealarmrequest_according_to_ashrae_example() BacnetConfirmedServices.SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 7); - Services.EncodeAlarmAcknowledge(buffer, 1, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), + AlarmAndEventServices.EncodeAlarmAcknowledge(buffer, 1, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), (uint) BacnetEventStates.EVENT_STATE_HIGH_LIMIT, "MDL", new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_SEQUENCE, 16), new BacnetGenericTime(new DateTime(1992, 6, 21, 13, 3, 41).AddMilliseconds(90), @@ -340,7 +280,7 @@ public void should_encode_unconfirmedeventnotificationrequest_according_to_ashra APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION); - Services.EncodeEventNotifyData(buffer, data); + AlarmAndEventServices.EncodeEventNotifyData(buffer, data); var encodedBytes = buffer.ToArray(); @@ -389,10 +329,10 @@ public void should_encode_getalarmsummary_ack_according_to_ashrae_example() APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_ALARM_SUMMARY, 1); - Services.EncodeAlarmSummary(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), + AlarmAndEventServices.EncodeAlarmSummary(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), BacnetEventStates.EVENT_STATE_HIGH_LIMIT, BacnetBitString.Parse("011")); - Services.EncodeAlarmSummary(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 3), + AlarmAndEventServices.EncodeAlarmSummary(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 3), BacnetEventStates.EVENT_STATE_LOW_LIMIT, BacnetBitString.Parse("111")); var encodedBytes = buffer.ToArray(); @@ -479,7 +419,7 @@ public void should_encode_geteventinformation_ack_according_to_ashrae_example() APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_EVENT_INFORMATION, 1); - Services.EncodeGetEventInformationAcknowledge(buffer, data, false); + AlarmAndEventServices.EncodeGetEventInformationAcknowledge(buffer, data, false); var encodedBytes = buffer.ToArray(); @@ -505,7 +445,7 @@ public void should_encode_lifesafetyoperation_according_to_ashrae_example() BacnetConfirmedServices.SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 15); - Services.EncodeLifeSafetyOperation(buffer, 18, "MDL", + AlarmAndEventServices.EncodeLifeSafetyOperation(buffer, 18, "MDL", (uint) BacnetLifeSafetyOperations.LIFE_SAFETY_OP_RESET, new BacnetObjectId(BacnetObjectTypes.OBJECT_LIFE_SAFETY_POINT, 1)); @@ -552,7 +492,7 @@ public void should_encode_subscribecov_according_to_ashrae_example() BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 15); - Services.EncodeSubscribeCOV(buffer, 18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), + AlarmAndEventServices.EncodeSubscribeCOV(buffer, 18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), false, true, 0); var encodedBytes = buffer.ToArray(); @@ -600,7 +540,7 @@ public void should_encode_subscribecovproperty_according_to_ashrae_example() BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 15); - Services.EncodeSubscribeProperty(buffer, 18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), + AlarmAndEventServices.EncodeSubscribeProperty(buffer, 18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), false, true, 60, new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE, ASN1.BACNET_ARRAY_ALL), true, 1.0f); diff --git a/Tests/Serialize/Services/ObjectAccessServicesTests.cs b/Tests/Serialize/Services/ObjectAccessServicesTests.cs new file mode 100644 index 00000000..c3fb60e4 --- /dev/null +++ b/Tests/Serialize/Services/ObjectAccessServicesTests.cs @@ -0,0 +1,69 @@ +using System.IO.BACnet.Serialize; +using NUnit.Framework; + +namespace System.IO.BACnet.Tests.Serialize +{ + [TestFixture] + class ObjectAccessServicesTests + { + [Test] + public void should_encode_logrecord_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, + new DateTime(1998, 3, 23, 19, 54, 27), 0); + var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, + new DateTime(1998, 3, 23, 19, 56, 27), 0); + + // example taken from ANNEX F - Examples of APDU Encoding + var expectedBytes = new byte[] + { + 0x0E, 0xA4, 0x62, 0x03, 0x17, 0x01, 0xB4, 0x13, 0x36, 0x1B, 0x00, 0x0F, + 0x1E, 0x2C, 0x41, 0x90, 0x00, 0x00, 0x1F, 0x2A, 0x04, 0x00, 0x0E, 0xA4, + 0x62, 0x03, 0x17, 0x01, 0xB4, 0x13, 0x38, 0x1B, 0x00, 0x0F, 0x1E, 0x2C, + 0x41, 0x90, 0xCC, 0xCD, 0x1F, 0x2A, 0x04, 0x00 + }; + + // act + ObjectAccessServices.EncodeLogRecord(buffer, record1); + ObjectAccessServices.EncodeLogRecord(buffer, record2); + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_decode_multiple_logrecords() + { + // arrange + var buffer = new EncodeBuffer(); + var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, + new DateTime(1998, 3, 23, 19, 54, 27), 0); + var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, + new DateTime(1998, 3, 23, 19, 56, 27), 0); + + // act + ObjectAccessServices.EncodeLogRecord(buffer, record1); + ObjectAccessServices.EncodeLogRecord(buffer, record2); + ObjectAccessServices.DecodeLogRecord(buffer.buffer, 0, buffer.GetLength(), 2, out var decodedRecords); + + /* + * Debug - write packet to network to analyze in WireShark + * + + var client = new BacnetClient(new BacnetIpUdpProtocolTransport(47808, true)); + client.Start(); + client.ReadRangeResponse(new BacnetAddress(BacnetAddressTypes.IP, "192.168.1.99"), 42, null, + new BacnetObjectId(BacnetObjectTypes.OBJECT_TRENDLOG, 1), + new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_LOG_BUFFER, ASN1.BACNET_ARRAY_ALL), + BacnetResultFlags.LAST_ITEM, 2, buffer.ToArray(), BacnetReadRangeRequestTypes.RR_BY_TIME, 79201); + */ + + // assert + Helper.AssertPropertiesAndFieldsAreEqual(record1, decodedRecords[0]); + Helper.AssertPropertiesAndFieldsAreEqual(record2, decodedRecords[1]); + } + } +} From 54abdb4eef34f919916b7eade2829dc773e76964 Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Wed, 28 Feb 2018 13:19:12 +0100 Subject: [PATCH 21/31] Added ASHRAE Examples F.2.1 and F.2.2 --- Tests/BACnet.Tests.csproj | 1 + .../Services/AlarmAndEventServicesTests.cs | 27 +- .../Services/FileAccessServicesTests.cs | 241 ++++++++++++++++++ 3 files changed, 243 insertions(+), 26 deletions(-) create mode 100644 Tests/Serialize/Services/FileAccessServicesTests.cs diff --git a/Tests/BACnet.Tests.csproj b/Tests/BACnet.Tests.csproj index 6c3cd8a4..df42a179 100644 --- a/Tests/BACnet.Tests.csproj +++ b/Tests/BACnet.Tests.csproj @@ -50,6 +50,7 @@ + diff --git a/Tests/Serialize/Services/AlarmAndEventServicesTests.cs b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs index 08e5b162..659c4690 100644 --- a/Tests/Serialize/Services/AlarmAndEventServicesTests.cs +++ b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs @@ -571,31 +571,6 @@ public void should_encode_subscribecovproperty_ack_according_to_ashrae_example() // assert Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); - } - - [Test] - public void GenerateCode() - { - Console.WriteLine(Helper.Doc2Code(@" -X'00' PDU Type=0 (BACnet-Confirmed-Request-PDU, SEG=0, MOR=0, SA=0) -X'02' Maximum APDU Size Accepted=206 octets -X'0F' Invoke ID=15 -X'1C' Service Choice=28 (SubscribeCOVProperty-Request) -X'09' SD Context Tag 0 (Subscriber Process Identifier, L=1) -X'12' 18 -X'1C' SD Context Tag 1 (Monitored Object Identifier, L=4) -X'0000000A' Analog Input, Instance Number=10 -X'29' SD Context Tag 2 (Issue Confirmed Notifications, L=1) -X'01' 1 (TRUE) -X'39' SD Context Tag 3 (Lifetime, L=1) -X'3C' 60 -X'4E' PD Opening Tag 4 (Monitored Property, L=1) -X'09' SD Context Tag 0 (Property Identifier, L=1) -X'55' 85 (PRESENT_VALUE) -X'4F' PD Closing Tag 4 (Property Identifier) -X'5C' SD Context Tag 5 (COV Increment, L=4) -X'3F800000' 1.0 -")); - } + } } } \ No newline at end of file diff --git a/Tests/Serialize/Services/FileAccessServicesTests.cs b/Tests/Serialize/Services/FileAccessServicesTests.cs new file mode 100644 index 00000000..ef51583d --- /dev/null +++ b/Tests/Serialize/Services/FileAccessServicesTests.cs @@ -0,0 +1,241 @@ +using System.IO.BACnet.Serialize; +using System.Linq; +using System.Text; +using NUnit.Framework; + +namespace System.IO.BACnet.Tests.Serialize +{ + [TestFixture] + class FileAccessServicesTests + { + [Test] + public void should_encode_atomicreadfilerequest_data_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.2.1 + var expectedBytes = new byte[] + {0x00, 0x02, 0x00, 0x06, 0xC4, 0x02, 0x80, 0x00, 0x01, 0x0E, 0x31, 0x00, 0x21, 0x1B, 0x0F}; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 0); + + FileAccessServices.EncodeAtomicReadFile(buffer, true, new BacnetObjectId(BacnetObjectTypes.OBJECT_FILE, 1), + 0, 27); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_atomicreadfilerequest_data_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var data = new[] + { + Encoding.ASCII.GetBytes("Chiller01 On-Time=4.3 Hours") + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.2.1 + var expectedBytes = new byte[] + { + 0x30, 0x00, 0x06, 0x10, 0x0E, 0x31, 0x00, 0x65, 0x1B, 0x43, 0x68, 0x69, 0x6C, 0x6C, 0x65, 0x72, 0x30, + 0x31, 0x20, 0x4F, 0x6E, 0x2D, 0x54, 0x69, 0x6D, 0x65, 0x3D, 0x34, 0x2E, 0x33, 0x20, 0x48, 0x6F, 0x75, + 0x72, 0x73, 0x0F + }; + + // act + APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, + BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, 0); + + FileAccessServices.EncodeAtomicReadFileAcknowledge(buffer, true, false, 0, 1, data, + data.Select(arr => arr.Length).ToArray()); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_atomicreadfilerequest_record_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.2.1 + var expectedBytes = new byte[] + {0x00, 0x02, 0x12, 0x06, 0xC4, 0x02, 0x80, 0x00, 0x02, 0x1E, 0x31, 0x0E, 0x21, 0x03, 0x1F}; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 18); + + FileAccessServices.EncodeAtomicReadFile(buffer, false, new BacnetObjectId(BacnetObjectTypes.OBJECT_FILE, 2), + 14, 3); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_atomicreadfilerequest_record_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var data = new[] + { + Encoding.ASCII.GetBytes("12:00,45.6"), + Encoding.ASCII.GetBytes("12:15,44.8") + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.2.1 + var expectedBytes = new byte[] + { + 0x30, 0x12, 0x06, 0x11, 0x1E, 0x31, 0x0E, 0x21, 0x02, 0x65, 0x0A, 0x31, 0x32, 0x3A, 0x30, 0x30, 0x2C, + 0x34, 0x35, 0x2E, 0x36, 0x65, 0x0A, 0x31, 0x32, 0x3A, 0x31, 0x35, 0x2C, 0x34, 0x34, 0x2E, 0x38, 0x1F + }; + + // act + APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, + BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, 18); + + FileAccessServices.EncodeAtomicReadFileAcknowledge(buffer, false, true, 14, 2, data, + data.Select(arr => arr.Length).ToArray()); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_atomicwritefilerequest_data_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var data = new[] + { + Encoding.ASCII.GetBytes("Chiller01 On-Time=4.3 Hours") + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.2.2 + var expectedBytes = new byte[] + { + 0x00, 0x02, 0x55, 0x07, 0xC4, 0x02, 0x80, 0x00, 0x01, 0x0E, 0x31, 0x1E, 0x65, 0x1B, 0x43, 0x68, 0x69, + 0x6C, 0x6C, 0x65, 0x72, 0x30, 0x31, 0x20, 0x4F, 0x6E, 0x2D, 0x54, 0x69, 0x6D, 0x65, 0x3D, 0x34, 0x2E, + 0x33, 0x20, 0x48, 0x6F, 0x75, 0x72, 0x73, 0x0F + }; + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 85); + + FileAccessServices.EncodeAtomicWriteFile(buffer, true, new BacnetObjectId(BacnetObjectTypes.OBJECT_FILE, 1), + 30, 1, data, data.Select(arr => arr.Length).ToArray()); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_atomicwritefilerequest_data_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.2.2 + var expectedBytes = new byte[] {0x30, 0x55, 0x07, 0x09, 0x1E}; + + // act + APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, + BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, 85); + + FileAccessServices.EncodeAtomicWriteFileAcknowledge(buffer, true, 30); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_atomicwritefilerequest_record_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var data = new[] + { + Encoding.ASCII.GetBytes("12:00,45.6"), + Encoding.ASCII.GetBytes("12:15,44.8") + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.2.2 + var expectedBytes = new byte[] + { + 0x00, 0x02, 0x55, 0x07, 0xC4, 0x02, 0x80, 0x00, 0x02, 0x1E, 0x31, 0xFF, 0x21, 0x02, 0x65, 0x0A, 0x31, + 0x32, 0x3A, 0x30, 0x30, 0x2C, 0x34, 0x35, 0x2E, 0x36, 0x65, 0x0A, 0x31, 0x32, 0x3A, 0x31, 0x35, 0x2C, + 0x34, 0x34, 0x2E, 0x38, 0x1F + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 85); + + FileAccessServices.EncodeAtomicWriteFile(buffer, false, + new BacnetObjectId(BacnetObjectTypes.OBJECT_FILE, 2), -1, 2, data, + data.Select(arr => arr.Length).ToArray()); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_atomicwritefilerequest_record_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.2.2 + var expectedBytes = new byte[] {0x30, 0x55, 0x07, 0x19, 0x0E}; + + // act + APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, + BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, 85); + + FileAccessServices.EncodeAtomicWriteFileAcknowledge(buffer, false, 14); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void GenerateCode() + { + Console.WriteLine(Helper.Doc2Code(@" +X'30' PDU Type=3 (BACnet-ComplexACK-PDU, SEG=0, MOR=0) +X'55' Invoke ID=85 +X'07' Service ACK Choice=7 (AtomicWriteFile-ACK) +X'19' SD Context Tag 1 (File Start Record, L=1) +X'0E' 14 +")); + } + + } +} From 92ed10a51176363a4329e0515f32d5bae487540f Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Wed, 28 Feb 2018 15:23:26 +0100 Subject: [PATCH 22/31] Added ASHRAE Examples F.3.1 to F.3.8 --- BACnet.csproj | 1 + BACnetClient.cs | 18 +- Base/BacnetObjectPropertyReference.cs | 17 + Base/BacnetPropertyReference.cs | 12 +- Serialize/APDU.cs | 12 +- Serialize/ASN1.cs | 60 +- Serialize/Services/ObjectAccessServices.cs | 54 +- Tests/BACnet.Tests.csproj | 1 + .../BacnetObjectPropertyReferenceTests.cs | 16 + .../Services/AlarmAndEventServicesTests.cs | 38 +- .../Services/FileAccessServicesTests.cs | 22 +- .../Services/ObjectAccessServicesTests.cs | 553 +++++++++++++++++- 12 files changed, 719 insertions(+), 85 deletions(-) create mode 100644 Base/BacnetObjectPropertyReference.cs create mode 100644 Tests/Base/BacnetObjectPropertyReferenceTests.cs diff --git a/BACnet.csproj b/BACnet.csproj index 5f79e4d5..841e3dcb 100644 --- a/BACnet.csproj +++ b/BACnet.csproj @@ -62,6 +62,7 @@ + diff --git a/BACnetClient.cs b/BACnetClient.cs index 950fd0be..bcc41dee 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -637,7 +637,7 @@ private void PerformDefaultSegmentHandling(BacnetAddress address, BacnetPduTypes if (confirmedServiceRequest) APDU.EncodeConfirmedServiceRequest(encodedBuffer, type, service, maxSegments, maxAdpu, invokeId); else - APDU.EncodeComplexAck(encodedBuffer, type, service, invokeId); + APDU.EncodeComplexAck(encodedBuffer, service, invokeId, type); segments.Add(Tuple.Create(sequenceNumber, copy)); // doesn't include BVLC or NPDU } @@ -1070,8 +1070,8 @@ public BacnetAsyncResult BeginReadRangeRequest(BacnetAddress address, BacnetObje { Log.Debug("Sending ReadRangeRequest"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, - buffer => ObjectAccessServices.EncodeReadRange(buffer, objectId, (uint)BacnetPropertyIds.PROP_LOG_BUFFER, ASN1.BACNET_ARRAY_ALL, - BacnetReadRangeRequestTypes.RR_BY_POSITION, idxBegin, DateTime.Now, (int)quantity), waitForTransmit); + buffer => ObjectAccessServices.EncodeReadRange(buffer, objectId, BacnetPropertyIds.PROP_LOG_BUFFER, + BacnetReadRangeRequestTypes.RR_BY_POSITION, idxBegin, DateTime.Now, (int)quantity, ASN1.BACNET_ARRAY_ALL), waitForTransmit); } public void EndReadRangeRequest(BacnetAsyncResult request, out byte[] trendBuffer, out uint itemCount) @@ -1165,7 +1165,7 @@ public BacnetAsyncResult BeginReadPropertyRequest(BacnetAddress address, BacnetO var propertyIndex = index == ASN1.BACNET_ARRAY_ALL ? "" : $"[{index}]"; Log.Debug($"Sending ReadPropertyRequest {objectId} {propertyId}{propertyIndex}"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, - buffer => ObjectAccessServices.EncodeReadProperty(buffer, objectId, (uint)propertyId, index), waitForTransmit); + buffer => ObjectAccessServices.EncodeReadProperty(buffer, objectId, propertyId, index), waitForTransmit); } public IList EndReadPropertyRequest(BacnetAsyncResult request) @@ -1317,7 +1317,7 @@ public BacnetAsyncResult BeginDeleteObjectRequest(BacnetAddress address, BacnetO { Log.Debug("Sending DeleteObjectRequest"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT, - buffer => ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance), waitForTransmit); + buffer => ObjectAccessServices.EncodeDeleteObject(buffer, objectId), waitForTransmit); } public void EndDeleteObjectRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1650,7 +1650,7 @@ private EncodeBuffer EncodeSegmentHeader(BacnetAddress address, byte invokeId, S //set segments limits buffer.max_offset = buffer.offset + GetMaxApdu(); - var apduHeader = APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK | (isSegmented ? BacnetPduTypes.SEGMENTED_MESSAGE | BacnetPduTypes.SERVER : 0) | (moreFollows ? BacnetPduTypes.MORE_FOLLOWS : 0), service, invokeId, segmentation?.sequence_number ?? 0, segmentation?.window_size ?? 0); + var apduHeader = APDU.EncodeComplexAck(buffer, service, invokeId, type: BacnetPduTypes.PDU_TYPE_COMPLEX_ACK | (isSegmented ? BacnetPduTypes.SEGMENTED_MESSAGE | BacnetPduTypes.SERVER : 0) | (moreFollows ? BacnetPduTypes.MORE_FOLLOWS : 0), sequenceNumber: segmentation?.sequence_number ?? 0, proposedWindowNumber: segmentation?.window_size ?? 0); buffer.min_limit = (GetMaxApdu() - apduHeader) * (segmentation?.sequence_number ?? 0); return buffer; @@ -1786,7 +1786,7 @@ public void ReadPropertyResponse(BacnetAddress address, byte invokeId, Segmentat { SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, b => { - ObjectAccessServices.EncodeReadPropertyAcknowledge(b, objectId, property.propertyIdentifier, property.propertyArrayIndex, value); + ObjectAccessServices.EncodeReadPropertyAcknowledge(b, objectId, (BacnetPropertyIds) property.propertyIdentifier, value, property.propertyArrayIndex); }); }); } @@ -1816,7 +1816,7 @@ public void ReadRangeResponse(BacnetAddress address, byte invokeId, Segmentation { SendComplexAck(address, invokeId, segmentation, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, b => { - ObjectAccessServices.EncodeReadRangeAcknowledge(b, objectId, property.propertyIdentifier, property.propertyArrayIndex, BacnetBitString.ConvertFromInt((uint)status), itemCount, applicationData, requestType, firstSequenceNo); + ObjectAccessServices.EncodeReadRangeAcknowledge(b, objectId, (BacnetPropertyIds) property.propertyIdentifier, BacnetBitString.ConvertFromInt((uint)status), itemCount, applicationData, requestType, firstSequenceNo, property.propertyArrayIndex); }); }); } @@ -1855,7 +1855,7 @@ public void SimpleAckResponse(BacnetAddress address, BacnetConfirmedServices ser Log.Debug($"Sending SimpleAckResponse for {service}"); var buffer = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, address.RoutedSource); - APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, service, invokeId); + APDU.EncodeSimpleAck(buffer, service, invokeId); Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, address, false, 0); } diff --git a/Base/BacnetObjectPropertyReference.cs b/Base/BacnetObjectPropertyReference.cs new file mode 100644 index 00000000..d2ecf0e1 --- /dev/null +++ b/Base/BacnetObjectPropertyReference.cs @@ -0,0 +1,17 @@ +namespace System.IO.BACnet +{ + public class BacnetObjectPropertyReference + { + public BacnetObjectId ObjectId { get; } + public BacnetPropertyReference[] PropertyReferences { get; } + + public BacnetObjectPropertyReference(BacnetObjectId objectId, params BacnetPropertyReference[] propertyReferences) + { + if((propertyReferences?.Length ?? 0) == 0) + throw new ArgumentOutOfRangeException($"{nameof(propertyReferences)} count must be > 0"); + + ObjectId = objectId; + PropertyReferences = propertyReferences; + } + } +} diff --git a/Base/BacnetPropertyReference.cs b/Base/BacnetPropertyReference.cs index 67d408bc..b5f7a538 100644 --- a/Base/BacnetPropertyReference.cs +++ b/Base/BacnetPropertyReference.cs @@ -1,4 +1,6 @@ -namespace System.IO.BACnet +using System.IO.BACnet.Serialize; + +namespace System.IO.BACnet { // TODO you have been flagged for refactoring due to un-C#-iness public struct BacnetPropertyReference @@ -6,8 +8,12 @@ public struct BacnetPropertyReference public uint propertyIdentifier; public uint propertyArrayIndex; /* optional */ - // TODO take BacnetPropertyIds, and default to ASN1.BACNET_ARRAY_ALL - public BacnetPropertyReference(uint id, uint arrayIndex) + public BacnetPropertyReference(BacnetPropertyIds id, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) + : this((uint)id, arrayIndex) + { + } + + public BacnetPropertyReference(uint id, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) { propertyIdentifier = id; propertyArrayIndex = arrayIndex; diff --git a/Serialize/APDU.cs b/Serialize/APDU.cs index 48c3a229..5dda4042 100644 --- a/Serialize/APDU.cs +++ b/Serialize/APDU.cs @@ -83,7 +83,8 @@ public static int DecodeUnconfirmedServiceRequest(byte[] buffer, int offset, out return offset - orgOffset; } - public static void EncodeSimpleAck(EncodeBuffer buffer, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId) + public static void EncodeSimpleAck(EncodeBuffer buffer, BacnetConfirmedServices service, byte invokeId, + BacnetPduTypes type = BacnetPduTypes.PDU_TYPE_SIMPLE_ACK) // TODO can this be something else?? { buffer.buffer[buffer.offset++] = (byte)type; buffer.buffer[buffer.offset++] = invokeId; @@ -101,10 +102,12 @@ public static int DecodeSimpleAck(byte[] buffer, int offset, out BacnetPduTypes return offset - orgOffset; } - public static int EncodeComplexAck(EncodeBuffer buffer, BacnetPduTypes type, BacnetConfirmedServices service, byte invokeId, byte sequenceNumber = 0, byte proposedWindowNumber = 0) + public static int EncodeComplexAck(EncodeBuffer buffer, BacnetConfirmedServices service, byte invokeId, + BacnetPduTypes type = BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, byte sequenceNumber = 0, + byte proposedWindowNumber = 0) { var len = 3; - buffer.buffer[buffer.offset++] = (byte)type; + buffer.buffer[buffer.offset++] = (byte) type; buffer.buffer[buffer.offset++] = invokeId; if ((type & BacnetPduTypes.SEGMENTED_MESSAGE) > 0) { @@ -112,7 +115,8 @@ public static int EncodeComplexAck(EncodeBuffer buffer, BacnetPduTypes type, Bac buffer.buffer[buffer.offset++] = proposedWindowNumber; len += 2; } - buffer.buffer[buffer.offset++] = (byte)service; + + buffer.buffer[buffer.offset++] = (byte) service; return len; } diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index be3f7f23..1279e81b 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -24,8 +24,20 @@ public class ASN1 /// public static Func CustomTagResolver; - private static readonly byte[] AnyDateOrTime = new byte[] {0xFF, 0xFF, 0xFF, 0xFF}; - + private static readonly byte[] AnyDateOrTime = {0xFF, 0xFF, 0xFF, 0xFF}; + + /* + * TODO: I'm sorry, but I am going to paint a big, fat, "kill me" on you, IEncode / IDecode. + * + * There is this little thing known as "separation of concerns", and I don't think a class that + * holds data should be concernd with how (or even if) it gets serialized - that would make it + * very hard to have different encoders/decoders. We won't have different encoders because BACnet + * is a standard and there is only one way to encode/decode? Well, yes, except for application + * specifics and / or buggy hardware. Idealy, I'd be able to use a specific en/decoder per device + * I'm talking with, which inherits from a standard bacnet-decoder and can be extended / overridden. + * + * Also: cyclic dependencies. Bad thing. + */ public interface IEncode { void Encode(EncodeBuffer buffer); @@ -190,6 +202,7 @@ public static void encode_context_character_string(EncodeBuffer buffer, byte tag buffer.Add(tmp.buffer, tmp.offset); } + // TODO: should't this be the same as encode_context_unsigned --> kill this, keep unsigned public static void encode_context_enumerated(EncodeBuffer buffer, byte tagNumber, uint value) { int len; /* return value */ @@ -461,6 +474,10 @@ public static void bacapp_encode_application_data(EncodeBuffer buffer, BacnetVal ((BacnetObjectId)value.Value).Instance); break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE: + EncodeApplicationObjectPropertyReference(buffer, (BacnetObjectPropertyReference) value.Value); + break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_COV_SUBSCRIPTION: encode_cov_subscription(buffer, (BacnetCOVSubscription)value.Value); //is this the right way to do it, I wonder? @@ -521,6 +538,45 @@ public static void bacapp_encode_application_data(EncodeBuffer buffer, BacnetVal } } + // TODO why is this called ...Application... ? + private static void EncodeApplicationObjectPropertyReference(EncodeBuffer buffer, BacnetObjectPropertyReference value) + { + encode_context_object_id(buffer, 0, value.ObjectId.Type, value.ObjectId.Instance); + + /* + * TODO this is just a guess + * I'm implementing this for a LIST_OF_GROUP_MEMBERS, where I need a list of PropRefs per object + */ + if (value.PropertyReferences.Length < 2) // can't be 0, but just in case I'd rather have you throw below + { + EncodePropertyReference(buffer, value.PropertyReferences[0], 1); + return; + } + + encode_opening_tag(buffer, 1); + + foreach (var propRef in value.PropertyReferences) + EncodePropertyReference(buffer, propRef, 0); + + encode_closing_tag(buffer, 1); + } + + public static void EncodePropertyReference(EncodeBuffer buffer, BacnetPropertyReference value, byte? contextTag=null) + { + if(contextTag.HasValue) + encode_context_unsigned(buffer, contextTag.Value, value.propertyIdentifier); + else + encode_unsigned32(buffer, value.propertyIdentifier); + + if (value.propertyArrayIndex == BACNET_ARRAY_ALL) + return; + + if(contextTag.HasValue) + encode_context_unsigned(buffer, (byte) (contextTag.Value+1), value.propertyArrayIndex); + else + encode_unsigned32(buffer, value.propertyArrayIndex); + } + public static void bacapp_encode_device_obj_property_ref(EncodeBuffer buffer, BacnetDeviceObjectPropertyReference value) { encode_context_object_id(buffer, 0, value.objectIdentifier.Type, value.objectIdentifier.Instance); diff --git a/Serialize/Services/ObjectAccessServices.cs b/Serialize/Services/ObjectAccessServices.cs index bd1f6708..c5911b88 100644 --- a/Serialize/Services/ObjectAccessServices.cs +++ b/Serialize/Services/ObjectAccessServices.cs @@ -23,6 +23,15 @@ public static void EncodeAddOrRemoveListElement(EncodeBuffer buffer, BacnetObjec ASN1.encode_closing_tag(buffer, 3); } + public static void EncodeCreateObject(EncodeBuffer buffer, BacnetObjectTypes objectType, ICollection valueList) + { + ASN1.encode_opening_tag(buffer, 0); + ASN1.encode_context_unsigned(buffer, 0, (uint)objectType); + ASN1.encode_closing_tag(buffer, 0); + + EncodeCreateObjectInternal(buffer, valueList); + } + // by Christopher Günter public static void EncodeCreateObject(EncodeBuffer buffer, BacnetObjectId objectId, ICollection valueList) { @@ -31,7 +40,12 @@ public static void EncodeCreateObject(EncodeBuffer buffer, BacnetObjectId object ASN1.encode_context_object_id(buffer, 1, objectId.Type, objectId.Instance); ASN1.encode_closing_tag(buffer, 0); - if (valueList == null) + EncodeCreateObjectInternal(buffer, valueList); + } + + private static void EncodeCreateObjectInternal(EncodeBuffer buffer, ICollection valueList) + { + if (valueList == null || valueList.Count == 0) return; ASN1.encode_opening_tag(buffer, 1); @@ -48,6 +62,7 @@ public static void EncodeCreateObject(EncodeBuffer buffer, BacnetObjectId object { ASN1.bacapp_encode_application_data(buffer, value); } + ASN1.encode_closing_tag(buffer, 2); if (pValue.priority != ASN1.BACNET_NO_PRIORITY) @@ -95,7 +110,7 @@ public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int o return -1; len++; - var _values = new LinkedList(); + var linkedPropertyValues = new LinkedList(); while (apduLen - len > 1) { var newEntry = new BacnetPropertyValue(); @@ -138,7 +153,7 @@ public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int o else return -1; - _values.AddLast(newEntry); + linkedPropertyValues.AddLast(newEntry); } /* Closing tag 1 - List of Properties */ @@ -146,7 +161,7 @@ public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int o return -1; len++; - valuesRefs = _values; + valuesRefs = linkedPropertyValues; return len; } @@ -156,9 +171,14 @@ public static void EncodeCreateObjectAcknowledge(EncodeBuffer buffer, BacnetObje ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); } + public static void EncodeDeleteObject(EncodeBuffer buffer, BacnetObjectId objectId) + { + ASN1.encode_application_object_id(buffer, objectId.Type, objectId.Instance); + } + public static int DecodeDeleteObject(byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId) { - objectId = default(BacnetObjectId); + objectId = default; ASN1.decode_tag_number_and_value(buffer, offset, out var tagNumber, out _); @@ -175,7 +195,7 @@ public static int DecodeDeleteObject(byte[] buffer, int offset, int apduLen, out return -1; } - public static void EncodeReadProperty(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) + public static void EncodeReadProperty(EncodeBuffer buffer, BacnetObjectId objectId, BacnetPropertyIds propertyId, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) { if ((int)objectId.Type <= ASN1.BACNET_MAX_OBJECT) { @@ -183,11 +203,11 @@ public static void EncodeReadProperty(EncodeBuffer buffer, BacnetObjectId object messages for testing */ ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); } - if (propertyId <= (uint)BacnetPropertyIds.MAX_BACNET_PROPERTY_ID) + if (propertyId <= BacnetPropertyIds.MAX_BACNET_PROPERTY_ID) { /* check bounds so that we could create malformed messages for testing */ - ASN1.encode_context_enumerated(buffer, 1, propertyId); + ASN1.encode_context_enumerated(buffer, 1, (uint)propertyId); } /* optional array index */ if (arrayIndex != ASN1.BACNET_ARRAY_ALL) @@ -243,11 +263,12 @@ public static int DecodeReadProperty(byte[] buffer, int offset, int apduLen, out return len; } - public static void EncodeReadPropertyAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, IEnumerable valueList) + public static void EncodeReadPropertyAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId, + BacnetPropertyIds propertyId, IEnumerable valueList, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) { /* service ack follows */ ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); - ASN1.encode_context_enumerated(buffer, 1, propertyId); + ASN1.encode_context_unsigned(buffer, 1, (uint)propertyId); /* context 2 array index is optional */ if (arrayIndex != ASN1.BACNET_ARRAY_ALL) { @@ -373,10 +394,11 @@ public static int DecodeReadPropertyMultipleAcknowledge(BacnetAddress address, b return len; } - public static void EncodeReadRange(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, BacnetReadRangeRequestTypes requestType, uint position, DateTime time, int count) + public static void EncodeReadRange(EncodeBuffer buffer, BacnetObjectId objectId, BacnetPropertyIds propertyId, + BacnetReadRangeRequestTypes requestType, uint position, DateTime time, int count, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) { ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); - ASN1.encode_context_enumerated(buffer, 1, propertyId); + ASN1.encode_context_unsigned(buffer, 1, (uint)propertyId); /* optional array index */ if (arrayIndex != ASN1.BACNET_ARRAY_ALL) @@ -486,11 +508,13 @@ public static int DecodeReadRange(byte[] buffer, int offset, int apduLen, out Ba return len; } - public static void EncodeReadRangeAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, BacnetBitString resultFlags, uint itemCount, byte[] applicationData, BacnetReadRangeRequestTypes requestType, uint firstSequence) + public static void EncodeReadRangeAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId, + BacnetPropertyIds propertyId, BacnetBitString resultFlags, uint itemCount, byte[] applicationData, + BacnetReadRangeRequestTypes requestType, uint firstSequence, uint arrayIndex = ASN1.BACNET_ARRAY_ALL) { /* service ack follows */ ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); - ASN1.encode_context_enumerated(buffer, 1, propertyId); + ASN1.encode_context_unsigned(buffer, 1, (uint)propertyId); /* context 2 array index is optional */ if (arrayIndex != ASN1.BACNET_ARRAY_ALL) { @@ -970,7 +994,7 @@ public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buff public static void EncodeWriteObjectMultiple(EncodeBuffer buffer, ICollection valueList) { foreach (var value in valueList) - ObjectAccessServices.EncodeWritePropertyMultiple(buffer, value.objectIdentifier, value.values); + EncodeWritePropertyMultiple(buffer, value.objectIdentifier, value.values); } } } diff --git a/Tests/BACnet.Tests.csproj b/Tests/BACnet.Tests.csproj index df42a179..2d3112d0 100644 --- a/Tests/BACnet.Tests.csproj +++ b/Tests/BACnet.Tests.csproj @@ -44,6 +44,7 @@ + diff --git a/Tests/Base/BacnetObjectPropertyReferenceTests.cs b/Tests/Base/BacnetObjectPropertyReferenceTests.cs new file mode 100644 index 00000000..40b93dee --- /dev/null +++ b/Tests/Base/BacnetObjectPropertyReferenceTests.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; + +namespace System.IO.BACnet.Tests.Base +{ + [TestFixture] + public class BacnetObjectPropertyReferenceTests + { + [Test] + public void should_throw_argumentoutofrangeexception_when_no_propertyreferences() + { + Assert.That( + () => new BacnetObjectPropertyReference(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 1)), + Throws.InstanceOf()); + } + } +} diff --git a/Tests/Serialize/Services/AlarmAndEventServicesTests.cs b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs index 659c4690..532e16b1 100644 --- a/Tests/Serialize/Services/AlarmAndEventServicesTests.cs +++ b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs @@ -18,14 +18,12 @@ public void should_encode_confirmendcovnotificationrequest_according_to_ashrae_e { new BacnetPropertyValue { - property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE, - ASN1.BACNET_ARRAY_ALL), + property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE), value = new List {new BacnetValue(65.0f)} }, new BacnetPropertyValue { - property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_STATUS_FLAGS, - ASN1.BACNET_ARRAY_ALL), + property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_STATUS_FLAGS), value = new List {new BacnetValue(BacnetBitString.Parse("0000"))} } }; @@ -66,8 +64,7 @@ public void should_encode_confirmendcovnotificationrequest_ack_according_to_ashr }; // act - APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, - BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION, 15); + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION, 15); var encodedBytes = buffer.ToArray(); @@ -84,14 +81,12 @@ public void should_encode_unconfirmendcovnotificationrequest_according_to_ashrae { new BacnetPropertyValue { - property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE, - ASN1.BACNET_ARRAY_ALL), + property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE), value = new List {new BacnetValue(65.0f)} }, new BacnetPropertyValue { - property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_STATUS_FLAGS, - ASN1.BACNET_ARRAY_ALL), + property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_STATUS_FLAGS), value = new List {new BacnetValue(BacnetBitString.Parse("0000"))} } }; @@ -178,8 +173,7 @@ public void should_encode_confirmendeventnotificationrequest_ack_according_to_as }; // act - APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, - BacnetConfirmedServices.SERVICE_CONFIRMED_EVENT_NOTIFICATION, 16); + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_EVENT_NOTIFICATION, 16); var encodedBytes = buffer.ToArray(); @@ -232,8 +226,7 @@ public void should_encode_acknowledgealarmrequest_ack_according_to_ashrae_exampl }; // act - APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, - BacnetConfirmedServices.SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, 7); + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, 7); var encodedBytes = buffer.ToArray(); @@ -326,8 +319,7 @@ public void should_encode_getalarmsummary_ack_according_to_ashrae_example() }; // act - APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, - BacnetConfirmedServices.SERVICE_CONFIRMED_GET_ALARM_SUMMARY, 1); + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_ALARM_SUMMARY, 1); AlarmAndEventServices.EncodeAlarmSummary(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), BacnetEventStates.EVENT_STATE_HIGH_LIMIT, BacnetBitString.Parse("011")); @@ -416,8 +408,7 @@ public void should_encode_geteventinformation_ack_according_to_ashrae_example() }; // act - APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, - BacnetConfirmedServices.SERVICE_CONFIRMED_GET_EVENT_INFORMATION, 1); + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_EVENT_INFORMATION, 1); AlarmAndEventServices.EncodeGetEventInformationAcknowledge(buffer, data, false); @@ -468,8 +459,7 @@ public void should_encode_lifesafetyoperation_ack_according_to_ashrae_example() }; // act - APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, - BacnetConfirmedServices.SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION, 15); + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION, 15); var encodedBytes = buffer.ToArray(); @@ -514,8 +504,7 @@ public void should_encode_subscribecov_ack_according_to_ashrae_example() }; // act - APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, - BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, 15); + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, 15); var encodedBytes = buffer.ToArray(); @@ -542,7 +531,7 @@ public void should_encode_subscribecovproperty_according_to_ashrae_example() AlarmAndEventServices.EncodeSubscribeProperty(buffer, 18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), false, true, 60, - new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE, ASN1.BACNET_ARRAY_ALL), true, + new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE), true, 1.0f); var encodedBytes = buffer.ToArray(); @@ -564,8 +553,7 @@ public void should_encode_subscribecovproperty_ack_according_to_ashrae_example() }; // act - APDU.EncodeSimpleAck(buffer, BacnetPduTypes.PDU_TYPE_SIMPLE_ACK, - BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, 15); + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, 15); var encodedBytes = buffer.ToArray(); diff --git a/Tests/Serialize/Services/FileAccessServicesTests.cs b/Tests/Serialize/Services/FileAccessServicesTests.cs index ef51583d..efde0d61 100644 --- a/Tests/Serialize/Services/FileAccessServicesTests.cs +++ b/Tests/Serialize/Services/FileAccessServicesTests.cs @@ -51,8 +51,7 @@ public void should_encode_atomicreadfilerequest_data_ack_according_to_ashrae_exa }; // act - APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, - BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, 0); + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, 0); FileAccessServices.EncodeAtomicReadFileAcknowledge(buffer, true, false, 0, 1, data, data.Select(arr => arr.Length).ToArray()); @@ -106,7 +105,7 @@ public void should_encode_atomicreadfilerequest_record_ack_according_to_ashrae_e }; // act - APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, 18); FileAccessServices.EncodeAtomicReadFileAcknowledge(buffer, false, true, 14, 2, data, @@ -159,7 +158,7 @@ public void should_encode_atomicwritefilerequest_data_ack_according_to_ashrae_ex var expectedBytes = new byte[] {0x30, 0x55, 0x07, 0x09, 0x1E}; // act - APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, 85); FileAccessServices.EncodeAtomicWriteFileAcknowledge(buffer, true, 30); @@ -214,7 +213,7 @@ public void should_encode_atomicwritefilerequest_record_ack_according_to_ashrae_ var expectedBytes = new byte[] {0x30, 0x55, 0x07, 0x19, 0x0E}; // act - APDU.EncodeComplexAck(buffer, BacnetPduTypes.PDU_TYPE_COMPLEX_ACK, + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, 85); FileAccessServices.EncodeAtomicWriteFileAcknowledge(buffer, false, 14); @@ -224,18 +223,5 @@ public void should_encode_atomicwritefilerequest_record_ack_according_to_ashrae_ // assert Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } - - [Test] - public void GenerateCode() - { - Console.WriteLine(Helper.Doc2Code(@" -X'30' PDU Type=3 (BACnet-ComplexACK-PDU, SEG=0, MOR=0) -X'55' Invoke ID=85 -X'07' Service ACK Choice=7 (AtomicWriteFile-ACK) -X'19' SD Context Tag 1 (File Start Record, L=1) -X'0E' 14 -")); - } - } } diff --git a/Tests/Serialize/Services/ObjectAccessServicesTests.cs b/Tests/Serialize/Services/ObjectAccessServicesTests.cs index c3fb60e4..4239fd7b 100644 --- a/Tests/Serialize/Services/ObjectAccessServicesTests.cs +++ b/Tests/Serialize/Services/ObjectAccessServicesTests.cs @@ -1,4 +1,7 @@ -using System.IO.BACnet.Serialize; +using System.Collections.Generic; +using System.IO.BACnet.Serialize; +using System.Linq; +using System.Text; using NUnit.Framework; namespace System.IO.BACnet.Tests.Serialize @@ -7,27 +10,536 @@ namespace System.IO.BACnet.Tests.Serialize class ObjectAccessServicesTests { [Test] - public void should_encode_logrecord_according_to_ashrae_example() + public void should_encode_addlistelementrequest_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); + + var data = new List + { + new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE, + new BacnetObjectPropertyReference(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 15), + new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), + new BacnetPropertyReference(BacnetPropertyIds.PROP_RELIABILITY))) + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.1 + var expectedBytes = new byte[] + { + 0x00, 0x02, 0x01, 0x08, 0x0C, 0x02, 0xC0, 0x00, 0x03, 0x19, 0x35, 0x3E, 0x0C, 0x00, 0x00, 0x00, 0x0F, + 0x1E, 0x09, 0x55, 0x09, 0x67, 0x1F, 0x3F + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_ADD_LIST_ELEMENT, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 1); + + ObjectAccessServices.EncodeAddOrRemoveListElement(buffer, + new BacnetObjectId(BacnetObjectTypes.OBJECT_GROUP, 3), + (uint) BacnetPropertyIds.PROP_LIST_OF_GROUP_MEMBERS, ASN1.BACNET_ARRAY_ALL, data); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_addlistelementrequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.1 + var expectedBytes = new byte[] { 0x20, 0x01, 0x08 }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ADD_LIST_ELEMENT, 1); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_removelistelementrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + var data = new List + { + new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE, + new BacnetObjectPropertyReference(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 12), + new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), + new BacnetPropertyReference(BacnetPropertyIds.PROP_RELIABILITY), + new BacnetPropertyReference(BacnetPropertyIds.PROP_DESCRIPTION))), + + new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE, + new BacnetObjectPropertyReference(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 13), + new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), + new BacnetPropertyReference(BacnetPropertyIds.PROP_RELIABILITY), + new BacnetPropertyReference(BacnetPropertyIds.PROP_DESCRIPTION))) + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.2 + var expectedBytes = new byte[] + { + 0x00, 0x02, 0x34, 0x09, 0x0C, 0x02, 0xC0, 0x00, 0x03, 0x19, 0x35, 0x3E, 0x0C, 0x00, 0x00, 0x00, 0x0C, + 0x1E, 0x09, 0x55, 0x09, 0x67, 0x09, 0x1C, 0x1F, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x1E, 0x09, 0x55, 0x09, + 0x67, 0x09, 0x1C, 0x1F, 0x3F + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 52); + + ObjectAccessServices.EncodeAddOrRemoveListElement(buffer, + new BacnetObjectId(BacnetObjectTypes.OBJECT_GROUP, 3), + (uint)BacnetPropertyIds.PROP_LIST_OF_GROUP_MEMBERS, ASN1.BACNET_ARRAY_ALL, data); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_removelistelementrequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.2 + var expectedBytes = new byte[] { 0x20, 0x34, 0x09 }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT, 52); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_createobjectrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + var data = new List + { + new BacnetPropertyValue + { + property = new BacnetPropertyReference(BacnetPropertyIds.PROP_OBJECT_NAME), + value = new List {new BacnetValue("Trend 1")} + }, + new BacnetPropertyValue + { + property = new BacnetPropertyReference(BacnetPropertyIds.PROP_FILE_ACCESS_METHOD), + value = new List {new BacnetValue(BacnetFileAccessMethod.RECORD_ACCESS)} + } + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.3 + var expectedBytes = new byte[] + { + 0x00, 0x04, 0x56, 0x0A, 0x0E, 0x09, 0x0A, 0x0F, 0x1E, 0x09, 0x4D, 0x2E, 0x75, 0x08, 0x00, 0x54, 0x72, + 0x65, 0x6E, 0x64, 0x20, 0x31, 0x2F, 0x09, 0x29, 0x2E, 0x91, 0x00, 0x2F, 0x1F + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU1024, 86); + + ObjectAccessServices.EncodeCreateObject(buffer, BacnetObjectTypes.OBJECT_FILE, data); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_createobjectrequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.3 + var expectedBytes = new byte[] { 0x30, 0x56, 0x0A, 0xC4, 0x02, 0x80, 0x00, 0x0D }; + + // act + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT, 86); + + ObjectAccessServices.EncodeCreateObjectAcknowledge(buffer, + new BacnetObjectId(BacnetObjectTypes.OBJECT_FILE, 13)); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_deleteobjectrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.4 + var expectedBytes = new byte[] {0x00, 0x04, 0x57, 0x0B, 0xC4, 0x02, 0xC0, 0x00, 0x06}; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU1024, 87); + + ObjectAccessServices.EncodeDeleteObject(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_GROUP, 6)); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_deleteobjectrequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.4 + var expectedBytes = new byte[] { 0x20, 0x57, 0x0B }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT, 87); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_deleteobjectrequest_fail_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.4 + var expectedBytes = new byte[] {0x50, 0x58, 0x0B, 0x91, 0x01, 0x91, 0x17}; + + // act + APDU.EncodeError(buffer, BacnetPduTypes.PDU_TYPE_ERROR, + BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT, 88); + ASN1.EncodeError(buffer, BacnetErrorClasses.ERROR_CLASS_OBJECT, + BacnetErrorCodes.ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_readpropertyrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.5 + var expectedBytes = new byte[] {0x00, 0x00, 0x01, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x05, 0x19, 0x55}; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU50, 1); + + ObjectAccessServices.EncodeReadProperty(buffer, + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 5), BacnetPropertyIds.PROP_PRESENT_VALUE); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_readpropertyrequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + var data = new List + { + new BacnetValue(72.3f) + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.5 + var expectedBytes = new byte[] + {0x30, 0x01, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x05, 0x19, 0x55, 0x3E, 0x44, 0x42, 0x90, 0x99, 0x9A, 0x3F}; + + // act + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, 1); + ObjectAccessServices.EncodeReadPropertyAcknowledge(buffer, + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 5), BacnetPropertyIds.PROP_PRESENT_VALUE, + data); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_readpropertymultiplerequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + var data = new List + { + new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), + new BacnetPropertyReference(BacnetPropertyIds.PROP_RELIABILITY) + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.7 + var expectedBytes = new byte[] + {0x00, 0x04, 0xF1, 0x0E, 0x0C, 0x00, 0x00, 0x00, 0x10, 0x1E, 0x09, 0x55, 0x09, 0x67, 0x1F}; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU1024, 241); + + ObjectAccessServices.EncodeReadPropertyMultiple(buffer, + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 16), data); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_readpropertymultiplerequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + var data = new List + { + new BacnetReadAccessResult(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 16), + new List + { + new BacnetPropertyValue + { + property = new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), + value = new List {new BacnetValue(72.3f)} + }, + new BacnetPropertyValue + { + property = new BacnetPropertyReference(BacnetPropertyIds.PROP_RELIABILITY), + value = new List + { + new BacnetValue(BacnetReliability.RELIABILITY_NO_FAULT_DETECTED) + } + } + }) + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.7 + var expectedBytes = new byte[] + { + 0x30, 0xF1, 0x0E, 0x0C, 0x00, 0x00, 0x00, 0x10, 0x1E, 0x29, 0x55, 0x4E, 0x44, 0x42, 0x90, 0x99, 0x9A, + 0x4F, 0x29, 0x67, 0x4E, 0x91, 0x00, 0x4F, 0x1F + }; + + // act + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, 241); + ObjectAccessServices.EncodeReadPropertyMultipleAcknowledge(buffer, data); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_readpropertymultiplerequest_formultipleobjects_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + var data = new List + { + new BacnetReadAccessSpecification(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 33), + new List + { + new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE) + }), + + new BacnetReadAccessSpecification(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 50), + new List + { + new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE) + }), + + new BacnetReadAccessSpecification(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 35), + new List + { + new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE) + }), + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.7 + var expectedBytes = new byte[] + { + 0x00, 0x04, 0x02, 0x0E, 0x0C, 0x00, 0x00, 0x00, 0x21, 0x1E, 0x09, 0x55, 0x1F, 0x0C, 0x00, 0x00, 0x00, + 0x32, 0x1E, 0x09, 0x55, 0x1F, 0x0C, 0x00, 0x00, 0x00, 0x23, 0x1E, 0x09, 0x55, 0x1F + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU1024, 2); + + ObjectAccessServices.EncodeReadPropertyMultiple(buffer, data); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_readpropertymultiplerequest_formultipleobjects_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + var data = new List + { + new BacnetReadAccessResult(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 33), + new List + { + new BacnetPropertyValue + { + property = new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), + value = new List {new BacnetValue(42.3f)} + } + }), + + new BacnetReadAccessResult(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 50), + new List + { + new BacnetPropertyValue + { + property = new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), + value = new List + { + new BacnetValue(new BacnetError(BacnetErrorClasses.ERROR_CLASS_OBJECT, + BacnetErrorCodes.ERROR_CODE_UNKNOWN_OBJECT)) + } + } + }), + + new BacnetReadAccessResult(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 35), + new List + { + new BacnetPropertyValue + { + property = new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), + value = new List {new BacnetValue(435.7f)} + } + }) + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.7 + var expectedBytes = new byte[] + { + 0x30, 0x02, 0x0E, 0x0C, 0x00, 0x00, 0x00, 0x21, 0x1E, 0x29, 0x55, 0x4E, 0x44, 0x42, 0x29, 0x33, 0x33, + 0x4F, 0x1F, 0x0C, 0x00, 0x00, 0x00, 0x32, 0x1E, 0x29, 0x55, 0x5E, 0x91, 0x01, 0x91, 0x1F, 0x5F, 0x1F, + 0x0C, 0x00, 0x00, 0x00, 0x23, 0x1E, 0x29, 0x55, 0x4E, 0x44, 0x43, 0xD9, 0xD9, 0x9A, 0x4F, 0x1F + }; + + // act + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, 2); + ObjectAccessServices.EncodeReadPropertyMultipleAcknowledge(buffer, data); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_readrangerequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.8 + var expectedBytes = new byte[] + { + 0x02, 0x02, 0x01, 0x1A, 0x0C, 0x05, 0x00, 0x00, 0x01, 0x19, 0x83, 0x7E, 0xA4, 0x62, 0x03, 0x17, 0x01, + 0xB4, 0x13, 0x34, 0x22, 0x00, 0x31, 0x04, 0x7F + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, + BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST | BacnetPduTypes.SEGMENTED_RESPONSE_ACCEPTED, + BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 1); + + ObjectAccessServices.EncodeReadRange(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_TRENDLOG, 1), + BacnetPropertyIds.PROP_LOG_BUFFER, BacnetReadRangeRequestTypes.RR_BY_TIME, 0, + new DateTime(1998, 3, 23, 19, 52, 34), 4); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_readrangerequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, new DateTime(1998, 3, 23, 19, 54, 27), 0); var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, new DateTime(1998, 3, 23, 19, 56, 27), 0); - // example taken from ANNEX F - Examples of APDU Encoding + // example taken from ANNEX F - Examples of APDU Encoding - F.3.8 var expectedBytes = new byte[] { - 0x0E, 0xA4, 0x62, 0x03, 0x17, 0x01, 0xB4, 0x13, 0x36, 0x1B, 0x00, 0x0F, - 0x1E, 0x2C, 0x41, 0x90, 0x00, 0x00, 0x1F, 0x2A, 0x04, 0x00, 0x0E, 0xA4, - 0x62, 0x03, 0x17, 0x01, 0xB4, 0x13, 0x38, 0x1B, 0x00, 0x0F, 0x1E, 0x2C, - 0x41, 0x90, 0xCC, 0xCD, 0x1F, 0x2A, 0x04, 0x00 + 0x30, 0x01, 0x1A, 0x0C, 0x05, 0x00, 0x00, 0x01, 0x19, 0x83, 0x3A, 0x05, 0xC0, 0x49, 0x02, 0x5E, 0x0E, + 0xA4, 0x62, 0x03, 0x17, 0x01, 0xB4, 0x13, 0x36, 0x1B, 0x00, 0x0F, 0x1E, 0x2C, 0x41, 0x90, 0x00, 0x00, + 0x1F, 0x2A, 0x04, 0x00, 0x0E, 0xA4, 0x62, 0x03, 0x17, 0x01, 0xB4, 0x13, 0x38, 0x1B, 0x00, 0x0F, 0x1E, + 0x2C, 0x41, 0x90, 0xCC, 0xCD, 0x1F, 0x2A, 0x04, 0x00, 0x5F, 0x6B, 0x01, 0x35, 0x61 }; // act - ObjectAccessServices.EncodeLogRecord(buffer, record1); - ObjectAccessServices.EncodeLogRecord(buffer, record2); + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, 1); + + var applicationData = new EncodeBuffer(); + ObjectAccessServices.EncodeLogRecord(applicationData, record1); + ObjectAccessServices.EncodeLogRecord(applicationData, record2); + + ObjectAccessServices.EncodeReadRangeAcknowledge(buffer, + new BacnetObjectId(BacnetObjectTypes.OBJECT_TRENDLOG, 1), BacnetPropertyIds.PROP_LOG_BUFFER, + BacnetBitString.Parse("110"), 2, applicationData.ToArray(), BacnetReadRangeRequestTypes.RR_BY_SEQUENCE, + 79201); + var encodedBytes = buffer.ToArray(); // assert @@ -65,5 +577,28 @@ public void should_decode_multiple_logrecords() Helper.AssertPropertiesAndFieldsAreEqual(record1, decodedRecords[0]); Helper.AssertPropertiesAndFieldsAreEqual(record2, decodedRecords[1]); } + + [Test] + public void GenerateCode() + { + Console.WriteLine(Helper.Doc2Code(@" +X'02' PDU Type = 0 (BACnet-Confirmed-Request-PDU, SEG=0, MOR=0, SA=1) +X'02' Maximum APDU Size Accepted = 206 octets +X'01' Invoke ID = 1 +X'1A' Service Choice = (26), (ReadRange-Request) +X'0C' SD Context Tag 0 (Object Identifier, L=4) +X'05000001' Trend Log, Instance Number = 1 +X'19' SD Context Tag 1 (Property Identifier, L=1) +X'83' 131 (LOG_BUFFER) +X'7E' PD Opening Tag 7 (By Time) +X'A4' Application Tag 10 (Date, L=4) +X'62031701' March 23, 1998 (Day Of Week Monday) +X'B4' Application Tag 11, (Time, L=4) +X'13342200' 19:52:34.0 +X'31' Application Tag 1 (Signed Integer, L=1) +X'04' 4 (Count) +X'7F' PD Closing Tag 7 (By Time) +")); + } } } From 8d959c06653af328b3bc835b10dfaded9c570f7d Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Thu, 1 Mar 2018 10:34:14 +0100 Subject: [PATCH 23/31] Started to move test-data to its own class so that it can be shared --- BACnet.sln.DotSettings | 1 + Tests/BACnet.Tests.csproj | 1 + .../Services/ObjectAccessServicesTests.cs | 35 ++++++++----------- Tests/TestData/ASHRAE.cs | 21 +++++++++++ 4 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 Tests/TestData/ASHRAE.cs diff --git a/BACnet.sln.DotSettings b/BACnet.sln.DotSettings index 2d443df8..a1ca10f8 100644 --- a/BACnet.sln.DotSettings +++ b/BACnet.sln.DotSettings @@ -1,5 +1,6 @@  APDU + ASHRAE ASN BBMD BVLC diff --git a/Tests/BACnet.Tests.csproj b/Tests/BACnet.Tests.csproj index 2d3112d0..bfc74d09 100644 --- a/Tests/BACnet.Tests.csproj +++ b/Tests/BACnet.Tests.csproj @@ -53,6 +53,7 @@ + diff --git a/Tests/Serialize/Services/ObjectAccessServicesTests.cs b/Tests/Serialize/Services/ObjectAccessServicesTests.cs index 4239fd7b..e5a8df6d 100644 --- a/Tests/Serialize/Services/ObjectAccessServicesTests.cs +++ b/Tests/Serialize/Services/ObjectAccessServicesTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO.BACnet.Serialize; +using System.IO.BACnet.Tests.TestData; using System.Linq; using System.Text; using NUnit.Framework; @@ -513,11 +514,7 @@ public void should_encode_readrangerequest_ack_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); - - var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, - new DateTime(1998, 3, 23, 19, 54, 27), 0); - var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, - new DateTime(1998, 3, 23, 19, 56, 27), 0); + var data = ASHRAE.F_3_8(); // example taken from ANNEX F - Examples of APDU Encoding - F.3.8 var expectedBytes = new byte[] @@ -532,13 +529,11 @@ public void should_encode_readrangerequest_ack_according_to_ashrae_example() APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, 1); var applicationData = new EncodeBuffer(); - ObjectAccessServices.EncodeLogRecord(applicationData, record1); - ObjectAccessServices.EncodeLogRecord(applicationData, record2); + ObjectAccessServices.EncodeLogRecord(applicationData, data.Record1); + ObjectAccessServices.EncodeLogRecord(applicationData, data.Record2); - ObjectAccessServices.EncodeReadRangeAcknowledge(buffer, - new BacnetObjectId(BacnetObjectTypes.OBJECT_TRENDLOG, 1), BacnetPropertyIds.PROP_LOG_BUFFER, - BacnetBitString.Parse("110"), 2, applicationData.ToArray(), BacnetReadRangeRequestTypes.RR_BY_SEQUENCE, - 79201); + ObjectAccessServices.EncodeReadRangeAcknowledge(buffer, data.ObjectId, data.PropertyId, data.Flags, + data.ItemCount, applicationData.ToArray(), data.RequestType, data.FirstSequence); var encodedBytes = buffer.ToArray(); @@ -551,15 +546,12 @@ public void should_decode_multiple_logrecords() { // arrange var buffer = new EncodeBuffer(); - var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, - new DateTime(1998, 3, 23, 19, 54, 27), 0); - var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, - new DateTime(1998, 3, 23, 19, 56, 27), 0); + var data = ASHRAE.F_3_8(); // act - ObjectAccessServices.EncodeLogRecord(buffer, record1); - ObjectAccessServices.EncodeLogRecord(buffer, record2); - ObjectAccessServices.DecodeLogRecord(buffer.buffer, 0, buffer.GetLength(), 2, out var decodedRecords); + ObjectAccessServices.EncodeLogRecord(buffer, data.Record1); + ObjectAccessServices.EncodeLogRecord(buffer, data.Record2); + ObjectAccessServices.DecodeLogRecord(buffer.buffer, 0, buffer.GetLength(), (int)data.ItemCount, out var decodedRecords); /* * Debug - write packet to network to analyze in WireShark @@ -574,8 +566,11 @@ public void should_decode_multiple_logrecords() */ // assert - Helper.AssertPropertiesAndFieldsAreEqual(record1, decodedRecords[0]); - Helper.AssertPropertiesAndFieldsAreEqual(record2, decodedRecords[1]); + Assert.That(decodedRecords.Length, Is.EqualTo(2)); + Assert.That(data.Record1, Is.Not.SameAs(decodedRecords[0])); + Assert.That(data.Record2, Is.Not.SameAs(decodedRecords[1])); + Helper.AssertPropertiesAndFieldsAreEqual(data.Record1, decodedRecords[0]); + Helper.AssertPropertiesAndFieldsAreEqual(data.Record2, decodedRecords[1]); } [Test] diff --git a/Tests/TestData/ASHRAE.cs b/Tests/TestData/ASHRAE.cs new file mode 100644 index 00000000..9e703227 --- /dev/null +++ b/Tests/TestData/ASHRAE.cs @@ -0,0 +1,21 @@ +using System.IO.BACnet.Serialize; + +namespace System.IO.BACnet.Tests.TestData +{ + public static class ASHRAE + { + public static (BacnetLogRecord Record1, BacnetLogRecord Record2, BacnetObjectId ObjectId, BacnetPropertyIds + PropertyId, BacnetBitString Flags, uint ItemCount, BacnetReadRangeRequestTypes RequestType, uint FirstSequence + ) F_3_8() + { + var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, + new DateTime(1998, 3, 23, 19, 54, 27), 0); + var record2 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.1, + new DateTime(1998, 3, 23, 19, 56, 27), 0); + + return (record1, record2, new BacnetObjectId(BacnetObjectTypes.OBJECT_TRENDLOG, 1), + BacnetPropertyIds.PROP_LOG_BUFFER, BacnetBitString.Parse("110"), 2, + BacnetReadRangeRequestTypes.RR_BY_SEQUENCE, 79201); + } + } +} From 52669df16da3ec01b83cd9716b0c159cf4482e4a Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Thu, 1 Mar 2018 12:57:11 +0100 Subject: [PATCH 24/31] Added ASHRAE Examples F.3.9 and F.3.10 --- BACnet.csproj | 1 + BACnetClient.cs | 6 +- Base/BacnetWriteAccessSpecification.cs | 34 ++++ Serialize/APDU.cs | 13 +- Serialize/ASN1.cs | 3 + Serialize/Services/ObjectAccessServices.cs | 72 ++++--- Tests/Helper.cs | 3 + .../Services/AlarmAndEventServicesTests.cs | 16 +- .../Services/FileAccessServicesTests.cs | 8 +- .../Services/ObjectAccessServicesTests.cs | 184 +++++++++++++++--- 10 files changed, 268 insertions(+), 72 deletions(-) create mode 100644 Base/BacnetWriteAccessSpecification.cs diff --git a/BACnet.csproj b/BACnet.csproj index 841e3dcb..31c254eb 100644 --- a/BACnet.csproj +++ b/BACnet.csproj @@ -79,6 +79,7 @@ + diff --git a/BACnetClient.cs b/BACnetClient.cs index bcc41dee..58b4d4d0 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -635,7 +635,7 @@ private void PerformDefaultSegmentHandling(BacnetAddress address, BacnetPduTypes var encodedBuffer = new EncodeBuffer(copy, 0); if (confirmedServiceRequest) - APDU.EncodeConfirmedServiceRequest(encodedBuffer, type, service, maxSegments, maxAdpu, invokeId); + APDU.EncodeConfirmedServiceRequest(encodedBuffer, service, maxSegments, maxAdpu, invokeId, type: type); else APDU.EncodeComplexAck(encodedBuffer, service, invokeId, type); @@ -1206,7 +1206,7 @@ public BacnetAsyncResult BeginWritePropertyRequest(BacnetAddress address, Bacnet { Log.Debug($"Sending WritePropertyRequest {objectId} {propertyId}"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, - buffer => ObjectAccessServices.EncodeWriteProperty(buffer, objectId, (uint)propertyId, ASN1.BACNET_ARRAY_ALL, _writepriority, valueList), waitForTransmit); + buffer => ObjectAccessServices.EncodeWriteProperty(buffer, objectId, propertyId, valueList, _writepriority), waitForTransmit); } public void EndWritePropertyRequest(BacnetAsyncResult request) => EndConfirmedServiceRequest(request); @@ -1896,7 +1896,7 @@ public BacnetAsyncResult BeginConfirmedServiceRequest(BacnetAddress address, Bac var function = BacnetNpduControls.PriorityNormalMessage | BacnetNpduControls.ExpectingReply; NPDU.Encode(buffer, function, address.RoutedSource); - APDU.EncodeConfirmedServiceRequest(buffer, PduConfirmedServiceRequest(), service, MaxSegments, Transport.MaxAdpuLength, invokeId); + APDU.EncodeConfirmedServiceRequest(buffer, service, MaxSegments, Transport.MaxAdpuLength, invokeId, type:PduConfirmedServiceRequest()); encode?.Invoke(buffer); var transmitLength = buffer.offset - Transport.HeaderLength; diff --git a/Base/BacnetWriteAccessSpecification.cs b/Base/BacnetWriteAccessSpecification.cs new file mode 100644 index 00000000..9189e1c0 --- /dev/null +++ b/Base/BacnetWriteAccessSpecification.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace System.IO.BACnet +{ + public class BacnetWriteAccessSpecification + { + public BacnetObjectId ObjectId { get; } + public ReadOnlyCollection Properties { get; } + + public BacnetWriteAccessSpecification(BacnetObjectId objectId, IEnumerable properties) + { + ObjectId = objectId; + Properties = properties.ToList().AsReadOnly(); + } + + public class Property + { + public BacnetPropertyIds Id { get; } + public uint? ArrayIndex { get; } + public BacnetValue Value { get; } + public uint? Priority { get; } + + public Property(BacnetPropertyIds id, BacnetValue value, uint? arrayIndex = null, uint? priority = null) + { + Id = id; + ArrayIndex = arrayIndex; + Value = value; + Priority = priority; + } + } + } +} diff --git a/Serialize/APDU.cs b/Serialize/APDU.cs index 5dda4042..fe1214b4 100644 --- a/Serialize/APDU.cs +++ b/Serialize/APDU.cs @@ -30,11 +30,13 @@ public static int GetDecodedInvokeId(byte[] buffer, int offset) } } - public static void EncodeConfirmedServiceRequest(EncodeBuffer buffer, BacnetPduTypes type, BacnetConfirmedServices service, BacnetMaxSegments maxSegments, - BacnetMaxAdpu maxAdpu, byte invokeId, byte sequenceNumber = 0, byte proposedWindowSize = 0) + public static void EncodeConfirmedServiceRequest(EncodeBuffer buffer, BacnetConfirmedServices service, + BacnetMaxSegments maxSegments, BacnetMaxAdpu maxAdpu, byte invokeId, + BacnetPduTypes type = BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, byte sequenceNumber = 0, + byte proposedWindowSize = 0) { - buffer.buffer[buffer.offset++] = (byte)type; - buffer.buffer[buffer.offset++] = (byte)((byte)maxSegments | (byte)maxAdpu); + buffer.buffer[buffer.offset++] = (byte) type; + buffer.buffer[buffer.offset++] = (byte) ((byte) maxSegments | (byte) maxAdpu); buffer.buffer[buffer.offset++] = invokeId; if ((type & BacnetPduTypes.SEGMENTED_MESSAGE) > 0) @@ -42,7 +44,8 @@ public static void EncodeConfirmedServiceRequest(EncodeBuffer buffer, BacnetPduT buffer.buffer[buffer.offset++] = sequenceNumber; buffer.buffer[buffer.offset++] = proposedWindowSize; } - buffer.buffer[buffer.offset++] = (byte)service; + + buffer.buffer[buffer.offset++] = (byte) service; } public static int DecodeConfirmedServiceRequest(byte[] buffer, int offset, out BacnetPduTypes type, out BacnetConfirmedServices service, diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index 1279e81b..653c5469 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -732,6 +732,9 @@ public static void encode_context_signed(EncodeBuffer buffer, byte tagNumber, in encode_bacnet_signed(buffer, value); } + public static void encode_context_object_id(EncodeBuffer buffer, byte tagNumber, BacnetObjectId objectId) + => encode_context_object_id(buffer, tagNumber, objectId.Type, objectId.Instance); + public static void encode_context_object_id(EncodeBuffer buffer, byte tagNumber, BacnetObjectTypes objectType, uint instance) { encode_tag(buffer, tagNumber, true, 4); diff --git a/Serialize/Services/ObjectAccessServices.cs b/Serialize/Services/ObjectAccessServices.cs index c5911b88..5a583444 100644 --- a/Serialize/Services/ObjectAccessServices.cs +++ b/Serialize/Services/ObjectAccessServices.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using System.Runtime.Remoting.Messaging; namespace System.IO.BACnet.Serialize { @@ -772,10 +774,12 @@ public static int DecodeLogRecord(byte[] buffer, int offset, int length, int nCu return len; } - public static void EncodeWriteProperty(EncodeBuffer buffer, BacnetObjectId objectId, uint propertyId, uint arrayIndex, uint priority, IEnumerable valueList) + public static void EncodeWriteProperty(EncodeBuffer buffer, BacnetObjectId objectId, + BacnetPropertyIds propertyId, IEnumerable valueList, uint arrayIndex = ASN1.BACNET_ARRAY_ALL, + uint priority=0) { ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); - ASN1.encode_context_enumerated(buffer, 1, propertyId); + ASN1.encode_context_unsigned(buffer, 1, (uint)propertyId); /* optional array index; ALL is -1 which is assumed when missing */ if (arrayIndex != ASN1.BACNET_ARRAY_ALL) @@ -868,35 +872,57 @@ public static int DecodeWriteProperty(BacnetAddress address, byte[] buffer, int return len; } - public static void EncodeWritePropertyMultiple(EncodeBuffer buffer, BacnetObjectId objectId, ICollection valueList) + // TODO check if this still works and / or makes any sense. + public static void EncodeWritePropertyMultiple(EncodeBuffer buffer, BacnetObjectId objectId, + ICollection valueList) + => EncodeWritePropertyMultiple( + buffer, + new BacnetWriteAccessSpecification( + objectId, + valueList.Select( + pv => new BacnetWriteAccessSpecification.Property( + pv.property.GetPropertyId(), pv.value.Single(), // TODO CHECK is someone really passing multiple values here? I don't see that in the spec + pv.property.propertyArrayIndex == ASN1.BACNET_ARRAY_ALL + ? null + : (uint?) pv.property.propertyArrayIndex, + pv.priority == 0 ? null : (uint?) pv.priority)))); + + public static void EncodeWritePropertyMultiple(EncodeBuffer buffer, + params BacnetWriteAccessSpecification[] writeAccessSpec) { - ASN1.encode_context_object_id(buffer, 0, objectId.Type, objectId.Instance); - /* Tag 1: sequence of WriteAccessSpecification */ - ASN1.encode_opening_tag(buffer, 1); - - foreach (var pValue in valueList) + foreach (var objectWithProps in writeAccessSpec) { - /* Tag 0: Property */ - ASN1.encode_context_enumerated(buffer, 0, pValue.property.propertyIdentifier); - /* Tag 1: array index */ - if (pValue.property.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) - ASN1.encode_context_unsigned(buffer, 1, pValue.property.propertyArrayIndex); + ASN1.encode_context_object_id(buffer, 0, objectWithProps.ObjectId); + /* Tag 1: sequence of WriteAccessSpecification */ + ASN1.encode_opening_tag(buffer, 1); - /* Tag 2: Value */ - ASN1.encode_opening_tag(buffer, 2); - foreach (var value in pValue.value) + foreach (var prop in objectWithProps.Properties) { - ASN1.bacapp_encode_application_data(buffer, value); + /* Tag 0: Property */ + ASN1.encode_context_unsigned(buffer, 0, (uint) prop.Id); + + /* Tag 1: array index */ + if (prop.ArrayIndex != null) + ASN1.encode_context_unsigned(buffer, 1, prop.ArrayIndex.Value); + + /* Tag 2: Value */ + ASN1.encode_opening_tag(buffer, 2); + /* + * TODO CHECK: removed the loop, because I don't think we can have a list of values here. + * If we can, it should still only be a single BacnetValue which has its own type - e.g. ListOfBacnetValues, + * and the encode-call should handle it. + */ + ASN1.bacapp_encode_application_data(buffer, prop.Value); + ASN1.encode_closing_tag(buffer, 2); + + /* Tag 3: Priority */ + if (prop.Priority != null) + ASN1.encode_context_unsigned(buffer, 3, prop.Priority.Value); } - ASN1.encode_closing_tag(buffer, 2); - /* Tag 3: Priority */ - if (pValue.priority != ASN1.BACNET_NO_PRIORITY) - ASN1.encode_context_unsigned(buffer, 3, pValue.priority); + ASN1.encode_closing_tag(buffer, 1); } - - ASN1.encode_closing_tag(buffer, 1); } public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out ICollection valuesRefs) diff --git a/Tests/Helper.cs b/Tests/Helper.cs index 187c6c0d..b0398904 100644 --- a/Tests/Helper.cs +++ b/Tests/Helper.cs @@ -8,6 +8,9 @@ namespace System.IO.BACnet.Tests { public static class Helper { + public static T[] A(params T[] values) + => values; + public static readonly BacnetAddress DummyAddress = new BacnetAddress(BacnetAddressTypes.None, 0, new byte[] { 42 }); public static (BacnetClient, BacnetClient) CreateConnectedClients() diff --git a/Tests/Serialize/Services/AlarmAndEventServicesTests.cs b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs index 532e16b1..93763d37 100644 --- a/Tests/Serialize/Services/AlarmAndEventServicesTests.cs +++ b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs @@ -39,7 +39,7 @@ public void should_encode_confirmendcovnotificationrequest_according_to_ashrae_e }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 15); @@ -148,7 +148,7 @@ public void should_encode_confirmendeventnotificationrequest_according_to_ashrae }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_EVENT_NOTIFICATION, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 16); @@ -197,7 +197,7 @@ public void should_encode_acknowledgealarmrequest_according_to_ashrae_example() }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 7); @@ -294,7 +294,7 @@ public void should_encode_getalarmsummary_according_to_ashrae_example() }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_ALARM_SUMMARY, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 1); @@ -346,7 +346,7 @@ public void should_encode_geteventinformation_according_to_ashrae_example() }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_EVENT_INFORMATION, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 1); @@ -432,7 +432,7 @@ public void should_encode_lifesafetyoperation_according_to_ashrae_example() }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 15); @@ -478,7 +478,7 @@ public void should_encode_subscribecov_according_to_ashrae_example() {0x00, 0x02, 0x0F, 0x05, 0x09, 0x12, 0x1C, 0x00, 0x00, 0x00, 0x0A, 0x29, 0x01, 0x39, 0x00}; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 15); @@ -525,7 +525,7 @@ public void should_encode_subscribecovproperty_according_to_ashrae_example() 0x55, 0x4F, 0x5C, 0x3F, 0x80, 0x00, 0x00 }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 15); diff --git a/Tests/Serialize/Services/FileAccessServicesTests.cs b/Tests/Serialize/Services/FileAccessServicesTests.cs index efde0d61..41ff45fa 100644 --- a/Tests/Serialize/Services/FileAccessServicesTests.cs +++ b/Tests/Serialize/Services/FileAccessServicesTests.cs @@ -19,7 +19,7 @@ public void should_encode_atomicreadfilerequest_data_according_to_ashrae_example {0x00, 0x02, 0x00, 0x06, 0xC4, 0x02, 0x80, 0x00, 0x01, 0x0E, 0x31, 0x00, 0x21, 0x1B, 0x0F}; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 0); @@ -73,7 +73,7 @@ public void should_encode_atomicreadfilerequest_record_according_to_ashrae_examp {0x00, 0x02, 0x12, 0x06, 0xC4, 0x02, 0x80, 0x00, 0x02, 0x1E, 0x31, 0x0E, 0x21, 0x03, 0x1F}; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 18); @@ -135,7 +135,7 @@ public void should_encode_atomicwritefilerequest_data_according_to_ashrae_exampl 0x33, 0x20, 0x48, 0x6F, 0x75, 0x72, 0x73, 0x0F }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 85); @@ -189,7 +189,7 @@ public void should_encode_atomicwritefilerequest_record_according_to_ashrae_exam }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 85); diff --git a/Tests/Serialize/Services/ObjectAccessServicesTests.cs b/Tests/Serialize/Services/ObjectAccessServicesTests.cs index e5a8df6d..b10f9c0a 100644 --- a/Tests/Serialize/Services/ObjectAccessServicesTests.cs +++ b/Tests/Serialize/Services/ObjectAccessServicesTests.cs @@ -1,9 +1,8 @@ using System.Collections.Generic; using System.IO.BACnet.Serialize; using System.IO.BACnet.Tests.TestData; -using System.Linq; -using System.Text; using NUnit.Framework; +using static System.IO.BACnet.Tests.Helper; namespace System.IO.BACnet.Tests.Serialize { @@ -32,7 +31,7 @@ public void should_encode_addlistelementrequest_according_to_ashrae_example() }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ADD_LIST_ELEMENT, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 1); @@ -94,7 +93,7 @@ public void should_encode_removelistelementrequest_according_to_ashrae_example() }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 52); @@ -154,7 +153,7 @@ public void should_encode_createobjectrequest_according_to_ashrae_example() }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU1024, 86); @@ -197,7 +196,7 @@ public void should_encode_deleteobjectrequest_according_to_ashrae_example() var expectedBytes = new byte[] {0x00, 0x04, 0x57, 0x0B, 0xC4, 0x02, 0xC0, 0x00, 0x06}; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU1024, 87); @@ -258,7 +257,7 @@ public void should_encode_readpropertyrequest_according_to_ashrae_example() var expectedBytes = new byte[] {0x00, 0x00, 0x01, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x05, 0x19, 0x55}; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU50, 1); @@ -315,7 +314,7 @@ public void should_encode_readpropertymultiplerequest_according_to_ashrae_exampl {0x00, 0x04, 0xF1, 0x0E, 0x0C, 0x00, 0x00, 0x00, 0x10, 0x1E, 0x09, 0x55, 0x09, 0x67, 0x1F}; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU1024, 241); @@ -407,7 +406,7 @@ public void should_encode_readpropertymultiplerequest_formultipleobjects_accordi }; // act - APDU.EncodeConfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU1024, 2); @@ -495,9 +494,9 @@ public void should_encode_readrangerequest_according_to_ashrae_example() // act APDU.EncodeConfirmedServiceRequest(buffer, - BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST | BacnetPduTypes.SEGMENTED_RESPONSE_ACCEPTED, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, BacnetMaxSegments.MAX_SEG0, - BacnetMaxAdpu.MAX_APDU206, 1); + BacnetMaxAdpu.MAX_APDU206, 1, + type: BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST | BacnetPduTypes.SEGMENTED_RESPONSE_ACCEPTED); ObjectAccessServices.EncodeReadRange(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_TRENDLOG, 1), BacnetPropertyIds.PROP_LOG_BUFFER, BacnetReadRangeRequestTypes.RR_BY_TIME, 0, @@ -569,30 +568,157 @@ public void should_decode_multiple_logrecords() Assert.That(decodedRecords.Length, Is.EqualTo(2)); Assert.That(data.Record1, Is.Not.SameAs(decodedRecords[0])); Assert.That(data.Record2, Is.Not.SameAs(decodedRecords[1])); - Helper.AssertPropertiesAndFieldsAreEqual(data.Record1, decodedRecords[0]); - Helper.AssertPropertiesAndFieldsAreEqual(data.Record2, decodedRecords[1]); + AssertPropertiesAndFieldsAreEqual(data.Record1, decodedRecords[0]); + AssertPropertiesAndFieldsAreEqual(data.Record2, decodedRecords[1]); + } + + [Test] + public void should_encode_writepropertyrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.9 + var expectedBytes = new byte[] + { + 0x00, 0x04, 0x59, 0x0F, 0x0C, 0x00, 0x80, 0x00, 0x01, 0x19, 0x55, 0x3E, 0x44, 0x43, 0x34, 0x00, 0x00, + 0x3F + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, + BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU1024, 89); + + ObjectAccessServices.EncodeWriteProperty(buffer, + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 1), BacnetPropertyIds.PROP_PRESENT_VALUE, + new List {new BacnetValue(180f)}); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_writepropertyrequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.9 + var expectedBytes = new byte[] { 0x20, 0x59, 0x0F }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, 89); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_writepropertymultiplerequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var data = new[] + { + new BacnetWriteAccessSpecification( + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 5), + A(new BacnetWriteAccessSpecification.Property(BacnetPropertyIds.PROP_PRESENT_VALUE, + new BacnetValue(67f)))), + + new BacnetWriteAccessSpecification( + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 6), + A(new BacnetWriteAccessSpecification.Property(BacnetPropertyIds.PROP_PRESENT_VALUE, + new BacnetValue(67f)))), + + new BacnetWriteAccessSpecification( + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 7), + A(new BacnetWriteAccessSpecification.Property(BacnetPropertyIds.PROP_PRESENT_VALUE, + new BacnetValue(72f)))), + }; + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.10 + var expectedBytes = new byte[] + { + 0x00, 0x04, 0x01, 0x10, 0x0C, 0x00, 0x80, 0x00, 0x05, 0x1E, 0x09, 0x55, 0x2E, 0x44, 0x42, 0x86, 0x00, + 0x00, 0x2F, 0x1F, 0x0C, 0x00, 0x80, 0x00, 0x06, 0x1E, 0x09, 0x55, 0x2E, 0x44, 0x42, 0x86, 0x00, 0x00, + 0x2F, 0x1F, 0x0C, 0x00, 0x80, 0x00, 0x07, 0x1E, 0x09, 0x55, 0x2E, 0x44, 0x42, 0x90, 0x00, 0x00, 0x2F, + 0x1F + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, + BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU1024, 1); + + ObjectAccessServices.EncodeWritePropertyMultiple(buffer, data); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_writepropertymultiplerequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.10 + var expectedBytes = new byte[] { 0x20, 0x01, 0x10 }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, 1); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } [Test] public void GenerateCode() { - Console.WriteLine(Helper.Doc2Code(@" -X'02' PDU Type = 0 (BACnet-Confirmed-Request-PDU, SEG=0, MOR=0, SA=1) -X'02' Maximum APDU Size Accepted = 206 octets -X'01' Invoke ID = 1 -X'1A' Service Choice = (26), (ReadRange-Request) + Console.WriteLine(Doc2Code(@" +F.3.10 Encoding for Example E.3.10 - WritePropertyMultiple Service +X'00' PDU Type=0 (BACnet-Confirmed-Request-PDU, SEG=0, MOR=0, SA=0) +X'04' Maximum APDU Size Accepted=1024 octets +X'01' Invoke ID=1 +X'10' Service Choice=16 (WritePropertyMultiple-Request) +X'0C' SD Context Tag 0 (Object Identifier, L=4) +X'00800005' Analog Value, Instance Number=5 +X'1E' PD Opening Tag 1 (List Of Properties) +X'09' SD Context Tag 0 (Property Identifier, L=1) +X'55' 85 (PRESENT_VALUE) +X'2E' PD Opening Tag 2 (Property Value) +X'44' Application Tag 4 (Real, L=4) +X'42860000' 67.0 +X'2F' PD Closing Tag 2 (Property Value) +X'1F' PD Closing Tag 1 (List Of Properties) +X'0C' SD Context Tag 0 (Object Identifier, L=4) +X'00800006' Analog Value, Instance Number=6 +X'1E' PD Opening Tag 1 (List Of Properties) +X'09' SD Context Tag 0 (Property Identifier, L=1) +X'55' 85 (PRESENT_VALUE) +X'2E' PD Opening Tag 2 (Property Value) +X'44' Application Tag 4 (Real, L=4) +X'42860000' 67.0 +X'2F' PD Closing Tag 2 (Property Value) +X'1F' PD Closing Tag 1 (List Of Properties) X'0C' SD Context Tag 0 (Object Identifier, L=4) -X'05000001' Trend Log, Instance Number = 1 -X'19' SD Context Tag 1 (Property Identifier, L=1) -X'83' 131 (LOG_BUFFER) -X'7E' PD Opening Tag 7 (By Time) -X'A4' Application Tag 10 (Date, L=4) -X'62031701' March 23, 1998 (Day Of Week Monday) -X'B4' Application Tag 11, (Time, L=4) -X'13342200' 19:52:34.0 -X'31' Application Tag 1 (Signed Integer, L=1) -X'04' 4 (Count) -X'7F' PD Closing Tag 7 (By Time) +X'00800007' Analog Value, Instance Number=7 +X'1E' PD Opening Tag 1 (List Of Properties) +X'09' SD Context Tag 0 (Property Identifier, L=1) +X'55' 85 (PRESENT_VALUE) +X'2E' PD Opening Tag 2 (Property Value) +X'44' Application Tag 4 (Real, L=4) +X'42900000' 72.0 +X'2F' PD Closing Tag 2 (Property Value) +X'1F' PD Closing Tag 1 (List Of Properties) +Assuming ")); } } From 42788cb662dc85efd866cfb5c7e7e940f881dbee Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Thu, 1 Mar 2018 16:26:59 +0100 Subject: [PATCH 25/31] Added ASHRAE Examples F.4.1 to F.4.9 --- BACnet.csproj | 1 + BACnetClient.cs | 22 +- Base/Enums/EnableDisable.cs | 9 + Serialize/APDU.cs | 3 +- .../RemoteDeviceManagementServices.cs | 8 +- Tests/BACnet.Tests.csproj | 1 + .../Services/AlarmAndEventServicesTests.cs | 5 +- .../Services/ObjectAccessServicesTests.cs | 43 --- .../RemoteDeviceManagementServicesTests.cs | 365 ++++++++++++++++++ 9 files changed, 397 insertions(+), 60 deletions(-) create mode 100644 Base/Enums/EnableDisable.cs create mode 100644 Tests/Serialize/Services/RemoteDeviceManagementServicesTests.cs diff --git a/BACnet.csproj b/BACnet.csproj index 31c254eb..02c5f53d 100644 --- a/BACnet.csproj +++ b/BACnet.csproj @@ -80,6 +80,7 @@ + diff --git a/BACnetClient.cs b/BACnetClient.cs index 58b4d4d0..c131a2ed 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -844,7 +844,7 @@ public void RemoteWhoIs(string bbmdIP, int port = DEFAULT_UDP_PORT, int lowLimit var b = GetEncodeBuffer(Transport.HeaderLength); var broadcast = Transport.GetBroadcastAddress(); NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, broadcast); - APDU.EncodeUnconfirmedServiceRequest(b, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS); + APDU.EncodeUnconfirmedServiceRequest(b, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS); RemoteDeviceManagementServices.EncodeWhoIsBroadcast(b, lowLimit, highLimit); var sent = false; @@ -888,7 +888,7 @@ public void WhoIs(int lowLimit = -1, int highLimit = -1, BacnetAddress receiver var b = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, receiver); - APDU.EncodeUnconfirmedServiceRequest(b, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS); + APDU.EncodeUnconfirmedServiceRequest(b, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS); RemoteDeviceManagementServices.EncodeWhoIsBroadcast(b, lowLimit, highLimit); Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, receiver, false, 0); @@ -908,7 +908,7 @@ public void Iam(uint deviceId, BacnetSegmentations segmentation = BacnetSegmenta var b = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, receiver); - APDU.EncodeUnconfirmedServiceRequest(b, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_I_AM); + APDU.EncodeUnconfirmedServiceRequest(b, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_I_AM); RemoteDeviceManagementServices.EncodeIamBroadcast(b, deviceId, (uint)GetMaxApdu(), segmentation, VendorId); Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, receiver, false, 0); @@ -922,7 +922,7 @@ public void IHave(BacnetObjectId deviceId, BacnetObjectId objId, string objName) var b = GetEncodeBuffer(Transport.HeaderLength); var broadcast = Transport.GetBroadcastAddress(); NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, broadcast); - APDU.EncodeUnconfirmedServiceRequest(b, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_I_HAVE); + APDU.EncodeUnconfirmedServiceRequest(b, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_I_HAVE); RemoteDeviceManagementServices.EncodeIhaveBroadcast(b, deviceId, objId, objName); Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, broadcast, false, 0); @@ -935,7 +935,7 @@ public void SendUnconfirmedEventNotification(BacnetAddress address, StateTransit var b = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(b, BacnetNpduControls.PriorityNormalMessage, address); - APDU.EncodeUnconfirmedServiceRequest(b, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION); + APDU.EncodeUnconfirmedServiceRequest(b, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION); AlarmAndEventServices.EncodeEventNotifyData(b, eventData); Transport.Send(b.buffer, Transport.HeaderLength, b.offset - Transport.HeaderLength, address, false, 0); } @@ -957,9 +957,9 @@ public void SynchronizeTime(BacnetAddress address, DateTime dateTime) var buffer = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, address); - APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, dateTime.Kind == DateTimeKind.Utc - ? BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION - : BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION); + APDU.EncodeUnconfirmedServiceRequest(buffer, dateTime.Kind == DateTimeKind.Utc + ? BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION + : BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION); RemoteDeviceManagementServices.EncodeTimeSync(buffer, dateTime); Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, address, false, 0); } @@ -1407,13 +1407,13 @@ public byte[] EndRawEncodedDecodedPropertyConfirmedRequest(BacnetAsyncResult req } } - public void DeviceCommunicationControlRequest(BacnetAddress address, uint timeDuration, uint enableDisable, string password) + public void DeviceCommunicationControlRequest(BacnetAddress address, uint timeDuration, EnableDisable enableDisable, string password) { using (var request = BeginDeviceCommunicationControlRequest(address, timeDuration, enableDisable, password, true)) EndDeviceCommunicationControlRequest(request); } - public BacnetAsyncResult BeginDeviceCommunicationControlRequest(BacnetAddress address, uint timeDuration, uint enableDisable, string password, bool waitForTransmit = false) + public BacnetAsyncResult BeginDeviceCommunicationControlRequest(BacnetAddress address, uint timeDuration, EnableDisable enableDisable, string password, bool waitForTransmit = false) { Log.Debug("Sending DeviceCommunicationControlRequest"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, @@ -1544,7 +1544,7 @@ public bool Notify(BacnetAddress address, uint subscriberProcessIdentifier, uint Log.Debug("Sending Notify (unconfirmed)"); var buffer = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, address.RoutedSource); - APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION); + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION); AlarmAndEventServices.EncodeCOVNotifyUnconfirmed(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values); // Modif F. Chaxel diff --git a/Base/Enums/EnableDisable.cs b/Base/Enums/EnableDisable.cs new file mode 100644 index 00000000..d91be2bd --- /dev/null +++ b/Base/Enums/EnableDisable.cs @@ -0,0 +1,9 @@ +namespace System.IO.BACnet +{ + public enum EnableDisable : uint + { + ENABLE = 0, + DISABLE = 1, + DISABLE_INITIATION = 2 + } +} diff --git a/Serialize/APDU.cs b/Serialize/APDU.cs index fe1214b4..056c32f6 100644 --- a/Serialize/APDU.cs +++ b/Serialize/APDU.cs @@ -70,7 +70,8 @@ public static int DecodeConfirmedServiceRequest(byte[] buffer, int offset, out B return offset - orgOffset; } - public static void EncodeUnconfirmedServiceRequest(EncodeBuffer buffer, BacnetPduTypes type, BacnetUnconfirmedServices service) + public static void EncodeUnconfirmedServiceRequest(EncodeBuffer buffer, BacnetUnconfirmedServices service, + BacnetPduTypes type = BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST) { buffer.buffer[buffer.offset++] = (byte)type; buffer.buffer[buffer.offset++] = (byte)service; diff --git a/Serialize/Services/RemoteDeviceManagementServices.cs b/Serialize/Services/RemoteDeviceManagementServices.cs index 3b287150..e77b590a 100644 --- a/Serialize/Services/RemoteDeviceManagementServices.cs +++ b/Serialize/Services/RemoteDeviceManagementServices.cs @@ -2,14 +2,14 @@ { public static class RemoteDeviceManagementServices { - public static void EncodeDeviceCommunicationControl(EncodeBuffer buffer, uint timeDuration, uint enableDisable, string password) + public static void EncodeDeviceCommunicationControl(EncodeBuffer buffer, uint timeDuration, EnableDisable enableDisable, string password) { /* optional timeDuration */ if (timeDuration > 0) ASN1.encode_context_unsigned(buffer, 0, timeDuration); /* enable disable */ - ASN1.encode_context_enumerated(buffer, 1, enableDisable); + ASN1.encode_context_unsigned(buffer, 1, (uint)enableDisable); /* optional password */ if (!String.IsNullOrEmpty(password)) @@ -77,6 +77,10 @@ public static void EncodePrivateTransferAcknowledge(EncodeBuffer buffer, uint ve { ASN1.encode_context_unsigned(buffer, 0, vendorID); ASN1.encode_context_unsigned(buffer, 1, serviceNumber); + + if (data == null || data.Length == 0) + return; // that's ok. + ASN1.encode_opening_tag(buffer, 2); buffer.Add(data, data.Length); ASN1.encode_closing_tag(buffer, 2); diff --git a/Tests/BACnet.Tests.csproj b/Tests/BACnet.Tests.csproj index bfc74d09..e7232fb3 100644 --- a/Tests/BACnet.Tests.csproj +++ b/Tests/BACnet.Tests.csproj @@ -53,6 +53,7 @@ + diff --git a/Tests/Serialize/Services/AlarmAndEventServicesTests.cs b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs index 93763d37..52e643dd 100644 --- a/Tests/Serialize/Services/AlarmAndEventServicesTests.cs +++ b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs @@ -102,8 +102,7 @@ public void should_encode_unconfirmendcovnotificationrequest_according_to_ashrae }; // act - APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, - BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION); + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION); AlarmAndEventServices.EncodeCOVNotifyConfirmed(buffer, 18, 4, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), 0, data); @@ -270,7 +269,7 @@ public void should_encode_unconfirmedeventnotificationrequest_according_to_ashra }; // act - APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetPduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION); AlarmAndEventServices.EncodeEventNotifyData(buffer, data); diff --git a/Tests/Serialize/Services/ObjectAccessServicesTests.cs b/Tests/Serialize/Services/ObjectAccessServicesTests.cs index b10f9c0a..cf350679 100644 --- a/Tests/Serialize/Services/ObjectAccessServicesTests.cs +++ b/Tests/Serialize/Services/ObjectAccessServicesTests.cs @@ -678,48 +678,5 @@ public void should_encode_writepropertymultiplerequest_ack_according_to_ashrae_e // assert Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } - - [Test] - public void GenerateCode() - { - Console.WriteLine(Doc2Code(@" -F.3.10 Encoding for Example E.3.10 - WritePropertyMultiple Service -X'00' PDU Type=0 (BACnet-Confirmed-Request-PDU, SEG=0, MOR=0, SA=0) -X'04' Maximum APDU Size Accepted=1024 octets -X'01' Invoke ID=1 -X'10' Service Choice=16 (WritePropertyMultiple-Request) -X'0C' SD Context Tag 0 (Object Identifier, L=4) -X'00800005' Analog Value, Instance Number=5 -X'1E' PD Opening Tag 1 (List Of Properties) -X'09' SD Context Tag 0 (Property Identifier, L=1) -X'55' 85 (PRESENT_VALUE) -X'2E' PD Opening Tag 2 (Property Value) -X'44' Application Tag 4 (Real, L=4) -X'42860000' 67.0 -X'2F' PD Closing Tag 2 (Property Value) -X'1F' PD Closing Tag 1 (List Of Properties) -X'0C' SD Context Tag 0 (Object Identifier, L=4) -X'00800006' Analog Value, Instance Number=6 -X'1E' PD Opening Tag 1 (List Of Properties) -X'09' SD Context Tag 0 (Property Identifier, L=1) -X'55' 85 (PRESENT_VALUE) -X'2E' PD Opening Tag 2 (Property Value) -X'44' Application Tag 4 (Real, L=4) -X'42860000' 67.0 -X'2F' PD Closing Tag 2 (Property Value) -X'1F' PD Closing Tag 1 (List Of Properties) -X'0C' SD Context Tag 0 (Object Identifier, L=4) -X'00800007' Analog Value, Instance Number=7 -X'1E' PD Opening Tag 1 (List Of Properties) -X'09' SD Context Tag 0 (Property Identifier, L=1) -X'55' 85 (PRESENT_VALUE) -X'2E' PD Opening Tag 2 (Property Value) -X'44' Application Tag 4 (Real, L=4) -X'42900000' 72.0 -X'2F' PD Closing Tag 2 (Property Value) -X'1F' PD Closing Tag 1 (List Of Properties) -Assuming -")); - } } } diff --git a/Tests/Serialize/Services/RemoteDeviceManagementServicesTests.cs b/Tests/Serialize/Services/RemoteDeviceManagementServicesTests.cs new file mode 100644 index 00000000..618a3e72 --- /dev/null +++ b/Tests/Serialize/Services/RemoteDeviceManagementServicesTests.cs @@ -0,0 +1,365 @@ +using System.IO.BACnet.Serialize; +using NUnit.Framework; +using static System.IO.BACnet.Tests.Helper; + +namespace System.IO.BACnet.Tests.Serialize +{ + [TestFixture] + class RemoteDeviceManagementServicesTests + { + [Test] + public void should_encode_devicecommunicationcontrolrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.1 + var expectedBytes = new byte[] + { + 0x00, 0x04, 0x05, 0x11, 0x09, 0x05, 0x19, 0x01, 0x2D, 0x08, 0x00, 0x23, 0x65, 0x67, 0x62, 0x64, 0x66, + 0x21 + }; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, + BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU1024, 5); + + RemoteDeviceManagementServices.EncodeDeviceCommunicationControl(buffer, 5, EnableDisable.DISABLE, "#egbdf!"); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_devicecommunicationcontrolrequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.1 + var expectedBytes = new byte[] { 0x20, 0x05, 0x11 }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, 5); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_confirmedprivatetransferservice_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var dataBuffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.2 + var expectedBytes = new byte[] + { + 0x00, 0x04, 0x55, 0x12, 0x09, 0x19, 0x19, 0x08, 0x2E, 0x44, 0x42, 0x90, 0xCC, 0xCD, 0x62, 0x16, 0x49, + 0x2F + }; + + // act + + ASN1.encode_application_real(dataBuffer, 72.4f); + ASN1.encode_application_octet_string(dataBuffer, new byte[] {0x16, 0x49}, 0, 2); + + APDU.EncodeConfirmedServiceRequest(buffer, + BacnetConfirmedServices.SERVICE_CONFIRMED_PRIVATE_TRANSFER, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU1024, 85); + + RemoteDeviceManagementServices.EncodePrivateTransferConfirmed(buffer, 25, 8, dataBuffer.ToArray()); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_confirmedprivatetransferservice_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.3 + var expectedBytes = new byte[] {0x30, 0x55, 0x12, 0x09, 0x19, 0x19, 0x08}; + + // act + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_PRIVATE_TRANSFER, 85); + RemoteDeviceManagementServices.EncodePrivateTransferAcknowledge(buffer, 25, 8, null); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_unconfirmedprivatetransferservice_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var dataBuffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.3 + var expectedBytes = new byte[] + {0x10, 0x04, 0x09, 0x19, 0x19, 0x08, 0x2E, 0x44, 0x42, 0x90, 0xCC, 0xCD, 0x62, 0x16, 0x49, 0x2F}; + + // act + + ASN1.encode_application_real(dataBuffer, 72.4f); + ASN1.encode_application_octet_string(dataBuffer, new byte[] { 0x16, 0x49 }, 0, 2); + + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_PRIVATE_TRANSFER); + + RemoteDeviceManagementServices.EncodePrivateTransferUnconfirmed(buffer, 25, 8, dataBuffer.ToArray()); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_reinitializedevicerequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.4 + var expectedBytes = new byte[] + {0x00, 0x01, 0x02, 0x14, 0x09, 0x01, 0x1D, 0x09, 0x00, 0x41, 0x62, 0x43, 0x64, 0x45, 0x66, 0x47, 0x68}; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, + BacnetConfirmedServices.SERVICE_CONFIRMED_REINITIALIZE_DEVICE, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU128, 2); + + RemoteDeviceManagementServices.EncodeReinitializeDevice(buffer, + BacnetReinitializedStates.BACNET_REINIT_WARMSTART, "AbCdEfGh"); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_reinitializedevicerequest_ack_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.4 + var expectedBytes = new byte[] { 0x20, 0x02, 0x14 }; + + // act + APDU.EncodeSimpleAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_REINITIALIZE_DEVICE, 2); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_timesynchronizationrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.7 + var expectedBytes = new byte[] {0x10, 0x06, 0xA4, 0x5C, 0x0B, 0x11, 0x02, 0xB4, 0x16, 0x2D, 0x1E, 0x46}; + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION); + + RemoteDeviceManagementServices.EncodeTimeSync(buffer, + new DateTime(1992, 11, 17, 22, 45, 30).AddMilliseconds(700)); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_whohas_via_objectname_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.8 + var expectedBytes = new byte[] {0x10, 0x07, 0x3D, 0x07, 0x00, 0x4F, 0x41, 0x54, 0x65, 0x6D, 0x70}; + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_HAS); + + RemoteDeviceManagementServices.EncodeWhoHasBroadcast(buffer, -1, -1, default(BacnetObjectId), "OATemp"); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_ihave_following_name_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.8 + var expectedBytes = new byte[] + { + 0x10, 0x01, 0xC4, 0x02, 0x00, 0x00, 0x08, 0xC4, 0x00, 0x00, 0x00, 0x03, 0x75, 0x07, 0x00, 0x4F, 0x41, + 0x54, 0x65, 0x6D, 0x70 + }; + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_I_HAVE); + + RemoteDeviceManagementServices.EncodeIhaveBroadcast(buffer, + new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 8), + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 3), "OATemp"); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_whohas_via_id_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.8 + var expectedBytes = new byte[] {0x10, 0x07, 0x2C, 0x00, 0x00, 0x00, 0x03}; + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_HAS); + + RemoteDeviceManagementServices.EncodeWhoHasBroadcast(buffer, -1, -1, + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 3), null); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_whois_via_id_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.9 + var expectedBytes = new byte[] { 0x10, 0x08, 0x09, 0x03, 0x19, 0x03 }; + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS); + + RemoteDeviceManagementServices.EncodeWhoIsBroadcast(buffer, 3, 3); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_iam_following_id_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.9 + var expectedBytes = new byte[] + {0x10, 0x00, 0xC4, 0x02, 0x00, 0x00, 0x03, 0x22, 0x04, 0x00, 0x91, 0x03, 0x21, 0x63}; + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_I_AM); + + RemoteDeviceManagementServices.EncodeIamBroadcast(buffer, 3, 1024, BacnetSegmentations.SEGMENTATION_NONE, + 99); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void should_encode_whois_all_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.9 + var expectedBytes = new byte[] { 0x10, 0x08 }; + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS); + + RemoteDeviceManagementServices.EncodeWhoIsBroadcast(buffer, -1, -1); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + // example taken from ANNEX F - Examples of APDU Encoding - F.4.9 + [TestCase(new byte[] { 0x10, 0x00, 0xC4, 0x02, 0x00, 0x00, 0x01, 0x22, 0x01, 0xE0, 0x91, 0x01, 0x21, 0x63 }, + 1, 480, BacnetSegmentations.SEGMENTATION_TRANSMIT, 99)] + [TestCase(new byte[] { 0x10, 0x00, 0xC4, 0x02, 0x00, 0x00, 0x02, 0x21, 0xCE, 0x91, 0x02, 0x21, 0x21 }, + 2, 206, BacnetSegmentations.SEGMENTATION_RECEIVE, 33)] + [TestCase(new byte[] { 0x10, 0x00, 0xC4, 0x02, 0x00, 0x00, 0x03, 0x22, 0x04, 0x00, 0x91, 0x03, 0x21, 0x63 }, + 3, 1024, BacnetSegmentations.SEGMENTATION_NONE, 99)] + [TestCase(new byte[] { 0x10, 0x00, 0xC4, 0x02, 0x00, 0x00, 0x04, 0x21, 0x80, 0x91, 0x00, 0x21, 0x42 }, + 4, 128, BacnetSegmentations.SEGMENTATION_BOTH, 66)] + public void should_encode_iam_following_all_according_to_ashrae_example(byte[] expectedBytes, int deviceId, + int maxApdu, BacnetSegmentations segmentation, int vendorId) + { + // arrange + var buffer = new EncodeBuffer(); + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_I_AM); + + RemoteDeviceManagementServices.EncodeIamBroadcast(buffer, (uint) deviceId, (uint) maxApdu, segmentation, (ushort) vendorId); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + + [Test] + public void GenerateCode() + { + Console.WriteLine(Doc2Code(@" +X'10' PDU Type=1 (Unconfirmed-Service-Request-PDU) +X'00' Service Choice=0 (I-Am-Request) +X'C4' Application Tag 12 (Object Identifier, L=4) (I-Am Device Identifier) +X'02000004' Device, Instance Number=4 +X'21' Application Tag 2 (Unsigned Integer, L=1) (Max APDU Length Accepted) +X'80' 128 +X'91' Application Tag 9 (Enumerated, L=1) (Segmentation Supported) +X'00' 0 (SEGMENTED_BOTH) +X'21' Application Tag 2 (Unsigned Integer, L=1) (Vendor ID) +X'42' 66 +")); + } + } +} From 6c1a7794f1944e25fde9b722e765b30790b6f201 Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Thu, 1 Mar 2018 18:44:49 +0100 Subject: [PATCH 26/31] added some back2back tests, fixed issues --- BACnetClient.cs | 8 +- Serialize/ASN1.cs | 32 ++++++ Serialize/Services/AlarmAndEventServices.cs | 51 ++------- Tests/Helper.cs | 2 +- .../Services/AlarmAndEventServicesTests.cs | 104 +++++++++--------- Tests/TestData/ASHRAE.cs | 56 +++++++++- 6 files changed, 154 insertions(+), 99 deletions(-) diff --git a/BACnetClient.cs b/BACnetClient.cs index c131a2ed..2457972a 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -259,7 +259,7 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION && OnCOVNotification != null) { - if (AlarmAndEventServices.DecodeCOVNotifyUnconfirmed(address, buffer, offset, length, out var subscriberProcessIdentifier, out var initiatingDeviceIdentifier, out var monitoredObjectIdentifier, out var timeRemaining, out var values) >= 0) + if (AlarmAndEventServices.DecodeCOVNotify(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 { @@ -427,7 +427,7 @@ protected void ProcessUnconfirmedServiceRequest(BacnetAddress address, BacnetPdu } else if (service == BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION && OnCOVNotification != null) { - if (AlarmAndEventServices.DecodeCOVNotifyUnconfirmed(address, buffer, offset, length, out var subscriberProcessIdentifier, out var initiatingDeviceIdentifier, out var monitoredObjectIdentifier, out var timeRemaining, out var values) >= 0) + if (AlarmAndEventServices.DecodeCOVNotify(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"); @@ -1530,7 +1530,7 @@ public BacnetAsyncResult BeginConfirmedNotify(BacnetAddress address, uint subscr { Log.Debug("Sending Notify (confirmed)"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION, - buffer => AlarmAndEventServices.EncodeCOVNotifyConfirmed(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, + buffer => AlarmAndEventServices.EncodeCOVNotify(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values), waitForTransmit); } @@ -1545,7 +1545,7 @@ public bool Notify(BacnetAddress address, uint subscriberProcessIdentifier, uint var buffer = GetEncodeBuffer(Transport.HeaderLength); NPDU.Encode(buffer, BacnetNpduControls.PriorityNormalMessage, address.RoutedSource); APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION); - AlarmAndEventServices.EncodeCOVNotifyUnconfirmed(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values); + AlarmAndEventServices.EncodeCOVNotify(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values); // Modif F. Chaxel var sendbytes=Transport.Send(buffer.buffer, Transport.HeaderLength, buffer.offset - Transport.HeaderLength, address, false, 0); diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index 653c5469..3f14ec5c 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -864,6 +864,38 @@ public static void bacapp_encode_timestamp(EncodeBuffer buffer, BacnetGenericTim } } + public static int bacapp_decode_timestamp(byte[] buffer, int offset, out BacnetGenericTime value) + { + var len = decode_tag_number_and_value(buffer, offset, out var tag, out var lenValue); + + switch ((BacnetTimestampTags)tag) + { + case BacnetTimestampTags.TIME_STAMP_TIME: + len += decode_application_time(buffer, offset + len, out var time); + value = new BacnetGenericTime(time, BacnetTimestampTags.TIME_STAMP_TIME); + break; + + case BacnetTimestampTags.TIME_STAMP_SEQUENCE: + len += decode_unsigned(buffer, offset + len, lenValue, out uint sequence); + value = new BacnetGenericTime(default, BacnetTimestampTags.TIME_STAMP_SEQUENCE, (ushort)sequence); + break; + + case BacnetTimestampTags.TIME_STAMP_DATETIME: + len += decode_application_date(buffer, offset + len, out var date); + len += decode_application_time(buffer, offset + len, out time); + value = new BacnetGenericTime( + new DateTime( + date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond), + BacnetTimestampTags.TIME_STAMP_DATETIME); + ++len; // closing tag + break; + default: + throw new ArgumentOutOfRangeException($"tag {tag} is not supported"); + } + + return len; + } + public static void bacapp_encode_context_timestamp(EncodeBuffer buffer, byte tagNumber, BacnetGenericTime value) { if (value.Tag != BacnetTimestampTags.TIME_STAMP_NONE) diff --git a/Serialize/Services/AlarmAndEventServices.cs b/Serialize/Services/AlarmAndEventServices.cs index e62656a3..a4a4e88c 100644 --- a/Serialize/Services/AlarmAndEventServices.cs +++ b/Serialize/Services/AlarmAndEventServices.cs @@ -17,7 +17,7 @@ public static void EncodeAlarmAcknowledge(EncodeBuffer buffer, uint ackProcessId ASN1.bacapp_encode_context_timestamp(buffer, 5, ackTimeStamp); } - public static void EncodeCOVNotifyConfirmed(EncodeBuffer buffer, uint subscriberProcessIdentifier, uint initiatingDeviceIdentifier, BacnetObjectId monitoredObjectIdentifier, uint timeRemaining, IEnumerable values) + public static void EncodeCOVNotify(EncodeBuffer buffer, uint subscriberProcessIdentifier, uint initiatingDeviceIdentifier, BacnetObjectId monitoredObjectIdentifier, uint timeRemaining, IEnumerable values) { /* tag 0 - subscriberProcessIdentifier */ ASN1.encode_context_unsigned(buffer, 0, subscriberProcessIdentifier); @@ -57,47 +57,7 @@ public static void EncodeCOVNotifyConfirmed(EncodeBuffer buffer, uint subscriber ASN1.encode_closing_tag(buffer, 4); } - public static void EncodeCOVNotifyUnconfirmed(EncodeBuffer buffer, uint subscriberProcessIdentifier, uint initiatingDeviceIdentifier, BacnetObjectId monitoredObjectIdentifier, uint timeRemaining, IEnumerable values) - { - /* tag 0 - subscriberProcessIdentifier */ - ASN1.encode_context_unsigned(buffer, 0, subscriberProcessIdentifier); - /* tag 1 - initiatingDeviceIdentifier */ - ASN1.encode_context_object_id(buffer, 1, BacnetObjectTypes.OBJECT_DEVICE, initiatingDeviceIdentifier); - /* tag 2 - monitoredObjectIdentifier */ - ASN1.encode_context_object_id(buffer, 2, monitoredObjectIdentifier.Type, monitoredObjectIdentifier.Instance); - /* tag 3 - timeRemaining */ - ASN1.encode_context_unsigned(buffer, 3, timeRemaining); - /* tag 4 - listOfValues */ - ASN1.encode_opening_tag(buffer, 4); - foreach (var value in values) - { - /* tag 0 - propertyIdentifier */ - ASN1.encode_context_enumerated(buffer, 0, value.property.propertyIdentifier); - /* tag 1 - propertyArrayIndex OPTIONAL */ - if (value.property.propertyArrayIndex != ASN1.BACNET_ARRAY_ALL) - { - ASN1.encode_context_unsigned(buffer, 1, value.property.propertyArrayIndex); - } - /* tag 2 - value */ - /* abstract syntax gets enclosed in a context tag */ - ASN1.encode_opening_tag(buffer, 2); - foreach (var v in value.value) - { - ASN1.bacapp_encode_application_data(buffer, v); - } - ASN1.encode_closing_tag(buffer, 2); - /* tag 3 - priority OPTIONAL */ - if (value.priority != ASN1.BACNET_NO_PRIORITY) - { - ASN1.encode_context_unsigned(buffer, 3, value.priority); - } - /* is there another one to encode? */ - /* FIXME: check to see if there is room in the APDU */ - } - ASN1.encode_closing_tag(buffer, 4); - } - - public static int DecodeCOVNotifyUnconfirmed(BacnetAddress address, byte[] buffer, int offset, int apduLen, out uint subscriberProcessIdentifier, out BacnetObjectId initiatingDeviceIdentifier, out BacnetObjectId monitoredObjectIdentifier, out uint timeRemaining, out ICollection values) + public static int DecodeCOVNotify(BacnetAddress address, byte[] buffer, int offset, int apduLen, out uint subscriberProcessIdentifier, out BacnetObjectId initiatingDeviceIdentifier, out BacnetObjectId monitoredObjectIdentifier, out uint timeRemaining, out ICollection values) { var len = 0; uint lenValue; @@ -405,13 +365,18 @@ public static int DecodeEventNotifyData(byte[] buffer, int offset, int apduLen, /* tag 3 - timeStamp */ if (ASN1.decode_is_context_tag(buffer, offset + len, 3)) { - len += 2; // opening Tag 3 then 2 + len += 1; // opening Tag 3 + /* len += ASN1.decode_application_date(buffer, offset + len, out var date); len += ASN1.decode_application_time(buffer, offset + len, out var time); decodedNotificationData.Add(e => e.TimeStamp = new BacnetGenericTime(new DateTime( date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond), BacnetTimestampTags.TIME_STAMP_DATETIME)); len += 2; // closing tag 2 then 3 + */ + len += ASN1.bacapp_decode_timestamp(buffer, offset + len, out var genericTime); + decodedNotificationData.Add(e => e.TimeStamp = genericTime); + ++len; // closing tag 3 } else return -1; diff --git a/Tests/Helper.cs b/Tests/Helper.cs index b0398904..f948390e 100644 --- a/Tests/Helper.cs +++ b/Tests/Helper.cs @@ -13,7 +13,7 @@ public static T[] A(params T[] values) public static readonly BacnetAddress DummyAddress = new BacnetAddress(BacnetAddressTypes.None, 0, new byte[] { 42 }); - public static (BacnetClient, BacnetClient) CreateConnectedClients() + public static (BacnetClient Client1, BacnetClient Client2) CreateConnectedClients() { var transport1 = new InMemoryTransport(); var client1 = new BacnetClient(transport1); diff --git a/Tests/Serialize/Services/AlarmAndEventServicesTests.cs b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs index 52e643dd..b72fce78 100644 --- a/Tests/Serialize/Services/AlarmAndEventServicesTests.cs +++ b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs @@ -2,6 +2,8 @@ using System.IO.BACnet.EventNotification; using System.IO.BACnet.EventNotification.EventValues; using System.IO.BACnet.Serialize; +using System.IO.BACnet.Tests.TestData; +using System.Linq; using NUnit.Framework; namespace System.IO.BACnet.Tests.Serialize @@ -14,19 +16,7 @@ public void should_encode_confirmendcovnotificationrequest_according_to_ashrae_e { // arrange var buffer = new EncodeBuffer(); - var data = new[] - { - new BacnetPropertyValue - { - property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE), - value = new List {new BacnetValue(65.0f)} - }, - new BacnetPropertyValue - { - property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_STATUS_FLAGS), - value = new List {new BacnetValue(BacnetBitString.Parse("0000"))} - } - }; + var data = ASHRAE.F_1_2(); // example taken from ANNEX F - Examples of APDU Encoding - F.1.2 var expectedBytes = new byte[] @@ -43,14 +33,40 @@ public void should_encode_confirmendcovnotificationrequest_according_to_ashrae_e BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 15); - AlarmAndEventServices.EncodeCOVNotifyConfirmed(buffer, 18, 4, - new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), 0, data); + AlarmAndEventServices.EncodeCOVNotify(buffer, data.SubscriberProcessIdentifier, + data.InitiatingDeviceIdentifier, data.MonitoredObjectIdentifier, data.TimeRemaining, data.Values); + var encodedBytes = buffer.ToArray(); // assert Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_confirmendcovnotificationrequest_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var data = ASHRAE.F_1_2(); + + // act + AlarmAndEventServices.EncodeCOVNotify(buffer, data.SubscriberProcessIdentifier, + data.InitiatingDeviceIdentifier, data.MonitoredObjectIdentifier, data.TimeRemaining, data.Values); + + var encodedBytes = buffer.ToArray(); + + AlarmAndEventServices.DecodeCOVNotify(Helper.DummyAddress, encodedBytes, 0, encodedBytes.Length, + out var subscriberProcessIdentifier, out var initiatingDeviceIdentifier, + out var monitoredObjectIdentifier, out var timeRemaining, out var values); + + var valuesArray = values.ToArray(); + + // assert + Assert.That(valuesArray.Length, Is.EqualTo(2)); + Helper.AssertPropertiesAndFieldsAreEqual(data.Values[0], valuesArray[0]); + Helper.AssertPropertiesAndFieldsAreEqual(data.Values[1], valuesArray[1]); + } + [Test] public void should_encode_confirmendcovnotificationrequest_ack_according_to_ashrae_example() { @@ -77,19 +93,7 @@ public void should_encode_unconfirmendcovnotificationrequest_according_to_ashrae { // arrange var buffer = new EncodeBuffer(); - var data = new[] - { - new BacnetPropertyValue - { - property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE), - value = new List {new BacnetValue(65.0f)} - }, - new BacnetPropertyValue - { - property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_STATUS_FLAGS), - value = new List {new BacnetValue(BacnetBitString.Parse("0000"))} - } - }; + var data = ASHRAE.F_1_3(); // example taken from ANNEX F - Examples of APDU Encoding - F.1.3 var expectedBytes = new byte[] @@ -104,8 +108,9 @@ public void should_encode_unconfirmendcovnotificationrequest_according_to_ashrae // act APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION); - AlarmAndEventServices.EncodeCOVNotifyConfirmed(buffer, 18, 4, - new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), 0, data); + AlarmAndEventServices.EncodeCOVNotify(buffer, data.SubscriberProcessIdentifier, + data.InitiatingDeviceIdentifier, data.MonitoredObjectIdentifier, data.TimeRemaining, data.Values); + var encodedBytes = buffer.ToArray(); // assert @@ -117,25 +122,7 @@ public void should_encode_confirmendeventnotificationrequest_according_to_ashrae { // arrange var buffer = new EncodeBuffer(); - var data = new StateTransition(new OutOfRange() - { - ExceedingValue = 80.1f, - StatusFlags = BacnetBitString.Parse("1000"), - Deadband = 1.0f, - ExceededLimit = 80.0f - }) - { - ProcessIdentifier = 1, - InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 4), - EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), - TimeStamp = new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_SEQUENCE, 16), - NotificationClass = 4, - Priority = 100, - NotifyType = BacnetNotifyTypes.NOTIFY_ALARM, - AckRequired = true, - FromState = BacnetEventStates.EVENT_STATE_NORMAL, - ToState = BacnetEventStates.EVENT_STATE_HIGH_LIMIT - }; + var data = ASHRAE.F_1_4(); // example taken from ANNEX F - Examples of APDU Encoding - F.1.4 var expectedBytes = new byte[] @@ -159,6 +146,25 @@ public void should_encode_confirmendeventnotificationrequest_according_to_ashrae Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_confirmendeventnotificationrequest_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_1_4(); + + // act + AlarmAndEventServices.EncodeEventNotifyData(buffer, input); + + var encodedBytes = buffer.ToArray(); + + AlarmAndEventServices.DecodeEventNotifyData(encodedBytes, 0, encodedBytes.Length, out var output); + + // assert + Assert.That(output, Is.Not.SameAs(input)); + Helper.AssertPropertiesAndFieldsAreEqual(input, output); + } + [Test] public void should_encode_confirmendeventnotificationrequest_ack_according_to_ashrae_example() { diff --git a/Tests/TestData/ASHRAE.cs b/Tests/TestData/ASHRAE.cs index 9e703227..d96c0a44 100644 --- a/Tests/TestData/ASHRAE.cs +++ b/Tests/TestData/ASHRAE.cs @@ -1,11 +1,63 @@ -using System.IO.BACnet.Serialize; +using System.Collections.Generic; +using System.IO.BACnet.EventNotification; +using System.IO.BACnet.EventNotification.EventValues; +using System.IO.BACnet.Serialize; namespace System.IO.BACnet.Tests.TestData { public static class ASHRAE { + public static (uint SubscriberProcessIdentifier, uint InitiatingDeviceIdentifier, BacnetObjectId + MonitoredObjectIdentifier, uint TimeRemaining, BacnetPropertyValue[] Values) + F_1_2() + { + var data = new[] + { + new BacnetPropertyValue + { + property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE), + value = new List {new BacnetValue(65.0f)} + }, + new BacnetPropertyValue + { + property = new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_STATUS_FLAGS), + value = new List {new BacnetValue(BacnetBitString.Parse("0000"))} + } + }; + + return (18, 4, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), 0, data); + } + + public static (uint SubscriberProcessIdentifier, uint InitiatingDeviceIdentifier, BacnetObjectId + MonitoredObjectIdentifier, uint TimeRemaining, BacnetPropertyValue[] Values) + F_1_3() => F_1_2(); + + public static StateTransition F_1_4() + { + return new StateTransition(new OutOfRange() + { + ExceedingValue = 80.1f, + StatusFlags = BacnetBitString.Parse("1000"), + Deadband = 1.0f, + ExceededLimit = 80.0f + }) + { + ProcessIdentifier = 1, + InitiatingObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, 4), + EventObjectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), + TimeStamp = new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_SEQUENCE, 16), + NotificationClass = 4, + Priority = 100, + NotifyType = BacnetNotifyTypes.NOTIFY_ALARM, + AckRequired = true, + FromState = BacnetEventStates.EVENT_STATE_NORMAL, + ToState = BacnetEventStates.EVENT_STATE_HIGH_LIMIT + }; + } + public static (BacnetLogRecord Record1, BacnetLogRecord Record2, BacnetObjectId ObjectId, BacnetPropertyIds - PropertyId, BacnetBitString Flags, uint ItemCount, BacnetReadRangeRequestTypes RequestType, uint FirstSequence + PropertyId, BacnetBitString Flags, uint ItemCount, BacnetReadRangeRequestTypes RequestType, uint + FirstSequence ) F_3_8() { var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, From 4113a78243e8f1235645de30aba3fa41495159d3 Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Fri, 2 Mar 2018 12:30:57 +0100 Subject: [PATCH 27/31] covered AlarmAndEventServices --- Base/BacnetAlarmSummaryData.cs | 13 +- Serialize/ASN1.cs | 2 +- Serialize/Services/AlarmAndEventServices.cs | 27 +-- .../Services/AlarmAndEventServicesTests.cs | 163 +++++++++++++----- Tests/TestData/ASHRAE.cs | 66 +++++++ 5 files changed, 204 insertions(+), 67 deletions(-) diff --git a/Base/BacnetAlarmSummaryData.cs b/Base/BacnetAlarmSummaryData.cs index 87611bd0..3f68f71a 100644 --- a/Base/BacnetAlarmSummaryData.cs +++ b/Base/BacnetAlarmSummaryData.cs @@ -2,8 +2,15 @@ { public struct BacnetAlarmSummaryData { - public BacnetObjectId objectIdentifier; - public BacnetEventStates alarmState; - public BacnetBitString acknowledgedTransitions; + public BacnetObjectId ObjectIdentifier { get; } + public BacnetEventStates AlarmState { get; } + public BacnetBitString AcknowledgedTransitions { get; } + + public BacnetAlarmSummaryData(BacnetObjectId objectIdentifier, BacnetEventStates alarmState, BacnetBitString acknowledgedTransitions) + { + ObjectIdentifier = objectIdentifier; + AlarmState = alarmState; + AcknowledgedTransitions = acknowledgedTransitions; + } } } \ No newline at end of file diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index 3f14ec5c..e091d4ea 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -871,7 +871,7 @@ public static int bacapp_decode_timestamp(byte[] buffer, int offset, out BacnetG switch ((BacnetTimestampTags)tag) { case BacnetTimestampTags.TIME_STAMP_TIME: - len += decode_application_time(buffer, offset + len, out var time); + len += decode_bacnet_time(buffer, offset + len, out var time); value = new BacnetGenericTime(time, BacnetTimestampTags.TIME_STAMP_TIME); break; diff --git a/Serialize/Services/AlarmAndEventServices.cs b/Serialize/Services/AlarmAndEventServices.cs index a4a4e88c..50bf3d91 100644 --- a/Serialize/Services/AlarmAndEventServices.cs +++ b/Serialize/Services/AlarmAndEventServices.cs @@ -640,16 +640,15 @@ public static int DecodeAlarmSummary(byte[] buffer, int offset, int apduLen, ref var len = 0; while (apduLen - 3 - len > 0) - { - var value = new BacnetAlarmSummaryData(); - + { len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - value.objectIdentifier = new BacnetObjectId(type, instance); len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out value.alarmState); + len += EnumUtils.DecodeEnumerated(buffer, offset + len, lenValue, out BacnetEventStates alarmState); len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out value.acknowledgedTransitions); + len += ASN1.decode_bitstring(buffer, offset + len, lenValue, out var acknowledgedTransitions); + + var value = new BacnetAlarmSummaryData(new BacnetObjectId(type, instance), alarmState, acknowledgedTransitions); alarms.Add(value); } @@ -722,18 +721,8 @@ public static int DecodeEventInformation(byte[] buffer, int offset, int apduLen, for (var i = 0; i < 3; i++) { - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); // opening tag - - if (tagNumber != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_NULL) - { - len += ASN1.decode_application_date(buffer, offset + len, out var date); - len += ASN1.decode_application_time(buffer, offset + len, out var time); - var timestamp = date.Date + time.TimeOfDay; - value.eventTimeStamps[i] = new BacnetGenericTime(timestamp, BacnetTimestampTags.TIME_STAMP_DATETIME); - len++; // closing tag - } - else - len += (int)lenValue; + len += ASN1.bacapp_decode_timestamp(buffer, offset + len, out var timeStamp); + value.eventTimeStamps[i] = timeStamp; } len++; // closing Tag 3 @@ -755,7 +744,7 @@ public static int DecodeEventInformation(byte[] buffer, int offset, int apduLen, events.Add(value); } - moreEvent = buffer[apduLen - 1] == 1; + moreEvent = buffer[offset+len++] == 1; return len; } diff --git a/Tests/Serialize/Services/AlarmAndEventServicesTests.cs b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs index b72fce78..55da106e 100644 --- a/Tests/Serialize/Services/AlarmAndEventServicesTests.cs +++ b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs @@ -63,6 +63,10 @@ public void should_decode_confirmendcovnotificationrequest_after_encode() // assert Assert.That(valuesArray.Length, Is.EqualTo(2)); + Assert.That(subscriberProcessIdentifier, Is.EqualTo(data.SubscriberProcessIdentifier)); + Assert.That(initiatingDeviceIdentifier.Instance, Is.EqualTo(data.InitiatingDeviceIdentifier)); + Assert.That(monitoredObjectIdentifier, Is.EqualTo(data.MonitoredObjectIdentifier)); + Assert.That(timeRemaining, Is.EqualTo(data.TimeRemaining)); Helper.AssertPropertiesAndFieldsAreEqual(data.Values[0], valuesArray[0]); Helper.AssertPropertiesAndFieldsAreEqual(data.Values[1], valuesArray[1]); } @@ -314,6 +318,7 @@ public void should_encode_getalarmsummary_ack_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); + var data = ASHRAE.F_1_6(); // example taken from ANNEX F - Examples of APDU Encoding - F.1.6 @@ -326,11 +331,11 @@ public void should_encode_getalarmsummary_ack_according_to_ashrae_example() // act APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_ALARM_SUMMARY, 1); - AlarmAndEventServices.EncodeAlarmSummary(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), - BacnetEventStates.EVENT_STATE_HIGH_LIMIT, BacnetBitString.Parse("011")); + AlarmAndEventServices.EncodeAlarmSummary(buffer, data[0].ObjectIdentifier, data[0].AlarmState, + data[0].AcknowledgedTransitions); - AlarmAndEventServices.EncodeAlarmSummary(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 3), - BacnetEventStates.EVENT_STATE_LOW_LIMIT, BacnetBitString.Parse("111")); + AlarmAndEventServices.EncodeAlarmSummary(buffer, data[1].ObjectIdentifier, data[1].AlarmState, + data[1].AcknowledgedTransitions); var encodedBytes = buffer.ToArray(); @@ -338,6 +343,32 @@ public void should_encode_getalarmsummary_ack_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_alarmsummary_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_1_6(); + IList output = new List(); + + + // act + AlarmAndEventServices.EncodeAlarmSummary(buffer, input[0].ObjectIdentifier, input[0].AlarmState, + input[0].AcknowledgedTransitions); + + AlarmAndEventServices.EncodeAlarmSummary(buffer, input[1].ObjectIdentifier, input[1].AlarmState, + input[1].AcknowledgedTransitions); + + var encodedBytes = buffer.ToArray(); + + AlarmAndEventServices.DecodeAlarmSummary(encodedBytes, 0, encodedBytes.Length, ref output); + + // assert + Assert.That(output.Count, Is.EqualTo(2)); + Helper.AssertPropertiesAndFieldsAreEqual(input[0], output[0]); + Helper.AssertPropertiesAndFieldsAreEqual(input[1], output[1]); + } + [Test] public void should_encode_geteventinformation_according_to_ashrae_example() { @@ -367,39 +398,7 @@ public void should_encode_geteventinformation_ack_according_to_ashrae_example() // arrange var buffer = new EncodeBuffer(); - var data = new[] - { - new BacnetGetEventInformationData() - { - objectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), - eventState = BacnetEventStates.EVENT_STATE_HIGH_LIMIT, - acknowledgedTransitions = BacnetBitString.Parse("011"), - eventTimeStamps = new[] - { - new BacnetGenericTime(new DateTime(1, 1, 1, 15, 35, 00).AddMilliseconds(200), BacnetTimestampTags.TIME_STAMP_TIME), - new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_TIME), - new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_TIME), - }, - notifyType = BacnetNotifyTypes.NOTIFY_ALARM, - eventEnable = BacnetBitString.Parse("111"), - eventPriorities = new uint[] {15, 15, 20} - }, - new BacnetGetEventInformationData() - { - objectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 3), - eventState = BacnetEventStates.EVENT_STATE_NORMAL, - acknowledgedTransitions = BacnetBitString.Parse("110"), - eventTimeStamps = new[] - { - new BacnetGenericTime(new DateTime(1, 1, 1, 15, 40, 00), BacnetTimestampTags.TIME_STAMP_TIME), - new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_TIME), - new BacnetGenericTime(new DateTime(1, 1, 1, 15, 45, 30).AddMilliseconds(300), BacnetTimestampTags.TIME_STAMP_TIME), - }, - notifyType = BacnetNotifyTypes.NOTIFY_ALARM, - eventEnable = BacnetBitString.Parse("111"), - eventPriorities = new uint[] {15, 15, 20} - } - }; + var data = ASHRAE.F_1_8(); // example taken from ANNEX F - Examples of APDU Encoding - F.1.8 var expectedBytes = new byte[] @@ -415,7 +414,7 @@ public void should_encode_geteventinformation_ack_according_to_ashrae_example() // act APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_EVENT_INFORMATION, 1); - AlarmAndEventServices.EncodeGetEventInformationAcknowledge(buffer, data, false); + AlarmAndEventServices.EncodeGetEventInformationAcknowledge(buffer, data.Data, data.MoreEvents); var encodedBytes = buffer.ToArray(); @@ -423,6 +422,26 @@ public void should_encode_geteventinformation_ack_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_eventinformation_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_1_8(); + IList output = new List(); + + // act + AlarmAndEventServices.EncodeGetEventInformationAcknowledge(buffer, input.Data, input.MoreEvents); + var encodedBytes = buffer.ToArray(); + AlarmAndEventServices.DecodeEventInformation(encodedBytes, 0, encodedBytes.Length, ref output, out var moreEvents); + + // assert + Assert.That(moreEvents, Is.EqualTo(input.MoreEvents)); + Assert.That(output.Count, Is.EqualTo(2)); + Helper.AssertPropertiesAndFieldsAreEqual(input.Data[0], output[0]); + Helper.AssertPropertiesAndFieldsAreEqual(input.Data[1], output[1]); + } + [Test] public void should_encode_lifesafetyoperation_according_to_ashrae_example() { @@ -477,6 +496,7 @@ public void should_encode_subscribecov_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); + var data = ASHRAE.F_1_10(); // example taken from ANNEX F - Examples of APDU Encoding - F.1.10 var expectedBytes = new byte[] @@ -487,8 +507,9 @@ public void should_encode_subscribecov_according_to_ashrae_example() BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 15); - AlarmAndEventServices.EncodeSubscribeCOV(buffer, 18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), - false, true, 0); + AlarmAndEventServices.EncodeSubscribeCOV(buffer, data.SubscriberProcessIdentifier, + data.MonitoredObjectIdentifier, data.CancellationRequest, data.IssueConfirmedNotifications, + data.Lifetime); var encodedBytes = buffer.ToArray(); @@ -496,6 +517,31 @@ public void should_encode_subscribecov_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_subscribecov_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_1_10(); + IList output = new List(); + + // act + AlarmAndEventServices.EncodeSubscribeCOV(buffer, input.SubscriberProcessIdentifier, + input.MonitoredObjectIdentifier, input.CancellationRequest, input.IssueConfirmedNotifications, + input.Lifetime); + var encodedBytes = buffer.ToArray(); + AlarmAndEventServices.DecodeSubscribeCOV(encodedBytes, 0, encodedBytes.Length, + out var subscriberProcessIdentifier, out var monitoredObjectIdentifier, out var cancellationRequest, + out var issueConfirmedNotifications, out var lifetime); + + // assert + Assert.That(subscriberProcessIdentifier, Is.EqualTo(input.SubscriberProcessIdentifier)); + Assert.That(monitoredObjectIdentifier, Is.EqualTo(input.MonitoredObjectIdentifier)); + Assert.That(cancellationRequest, Is.EqualTo(input.CancellationRequest)); + Assert.That(issueConfirmedNotifications, Is.EqualTo(input.IssueConfirmedNotifications)); + Assert.That(lifetime, Is.EqualTo(input.Lifetime)); + } + [Test] public void should_encode_subscribecov_ack_according_to_ashrae_example() { @@ -522,6 +568,7 @@ public void should_encode_subscribecovproperty_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); + var data = ASHRAE.F_1_11(); // example taken from ANNEX F - Examples of APDU Encoding - F.1.11 var expectedBytes = new byte[] @@ -534,10 +581,9 @@ public void should_encode_subscribecovproperty_according_to_ashrae_example() BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU206, 15); - AlarmAndEventServices.EncodeSubscribeProperty(buffer, 18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), - false, true, 60, - new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE), true, - 1.0f); + AlarmAndEventServices.EncodeSubscribeProperty(buffer, data.SubscriberProcessIdentifier, + data.MonitoredObjectIdentifier, data.CancellationRequest, data.IssueConfirmedNotifications, + data.Lifetime, data.MonitoredProperty, data.CovIncrementPresent, data.CovIncrement); var encodedBytes = buffer.ToArray(); @@ -545,6 +591,35 @@ public void should_encode_subscribecovproperty_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_subscribecovproperty_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_1_11(); + + // act + AlarmAndEventServices.EncodeSubscribeProperty(buffer, input.SubscriberProcessIdentifier, + input.MonitoredObjectIdentifier, input.CancellationRequest, input.IssueConfirmedNotifications, + input.Lifetime, input.MonitoredProperty, input.CovIncrementPresent, input.CovIncrement); + + var encodedBytes = buffer.ToArray(); + + AlarmAndEventServices.DecodeSubscribeProperty(encodedBytes, 0, encodedBytes.Length, + out var subscriberProcessIdentifier, out var monitoredObjectIdentifier, out var monitoredProperty, + out var cancellationRequest, out var issueConfirmedNotifications, out var lifetime, + out var covIncrement); + + // assert + Assert.That(subscriberProcessIdentifier, Is.EqualTo(input.SubscriberProcessIdentifier)); + Assert.That(monitoredObjectIdentifier, Is.EqualTo(input.MonitoredObjectIdentifier)); + Assert.That(monitoredProperty, Is.EqualTo(input.MonitoredProperty)); + Assert.That(cancellationRequest, Is.EqualTo(input.CancellationRequest)); + Assert.That(issueConfirmedNotifications, Is.EqualTo(input.IssueConfirmedNotifications)); + Assert.That(lifetime, Is.EqualTo(input.Lifetime)); + Assert.That(covIncrement, Is.EqualTo(input.CovIncrement)); + } + [Test] public void should_encode_subscribecovproperty_ack_according_to_ashrae_example() { diff --git a/Tests/TestData/ASHRAE.cs b/Tests/TestData/ASHRAE.cs index d96c0a44..aa96ad6c 100644 --- a/Tests/TestData/ASHRAE.cs +++ b/Tests/TestData/ASHRAE.cs @@ -55,6 +55,18 @@ public static StateTransition F_1_4() }; } + public static BacnetAlarmSummaryData[] + F_1_6() + { + return new[] + { + new BacnetAlarmSummaryData(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), + BacnetEventStates.EVENT_STATE_HIGH_LIMIT, BacnetBitString.Parse("011")), + new BacnetAlarmSummaryData(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 3), + BacnetEventStates.EVENT_STATE_LOW_LIMIT, BacnetBitString.Parse("111")) + }; + } + public static (BacnetLogRecord Record1, BacnetLogRecord Record2, BacnetObjectId ObjectId, BacnetPropertyIds PropertyId, BacnetBitString Flags, uint ItemCount, BacnetReadRangeRequestTypes RequestType, uint FirstSequence @@ -69,5 +81,59 @@ public static (BacnetLogRecord Record1, BacnetLogRecord Record2, BacnetObjectId BacnetPropertyIds.PROP_LOG_BUFFER, BacnetBitString.Parse("110"), 2, BacnetReadRangeRequestTypes.RR_BY_SEQUENCE, 79201); } + + public static (BacnetGetEventInformationData[] Data, bool MoreEvents) F_1_8() + { + return (new[] + { + new BacnetGetEventInformationData() + { + objectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 2), + eventState = BacnetEventStates.EVENT_STATE_HIGH_LIMIT, + acknowledgedTransitions = BacnetBitString.Parse("011"), + eventTimeStamps = new[] + { + new BacnetGenericTime(new DateTime(1, 1, 1, 15, 35, 00).AddMilliseconds(200), + BacnetTimestampTags.TIME_STAMP_TIME), + new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_TIME), + new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_TIME), + }, + notifyType = BacnetNotifyTypes.NOTIFY_ALARM, + eventEnable = BacnetBitString.Parse("111"), + eventPriorities = new uint[] {15, 15, 20} + }, + new BacnetGetEventInformationData() + { + objectIdentifier = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 3), + eventState = BacnetEventStates.EVENT_STATE_NORMAL, + acknowledgedTransitions = BacnetBitString.Parse("110"), + eventTimeStamps = new[] + { + new BacnetGenericTime(new DateTime(1, 1, 1, 15, 40, 00), BacnetTimestampTags.TIME_STAMP_TIME), + new BacnetGenericTime(default(DateTime), BacnetTimestampTags.TIME_STAMP_TIME), + new BacnetGenericTime(new DateTime(1, 1, 1, 15, 45, 30).AddMilliseconds(300), + BacnetTimestampTags.TIME_STAMP_TIME), + }, + notifyType = BacnetNotifyTypes.NOTIFY_ALARM, + eventEnable = BacnetBitString.Parse("111"), + eventPriorities = new uint[] {15, 15, 20} + } + }, false); + } + + public static (uint SubscriberProcessIdentifier, BacnetObjectId MonitoredObjectIdentifier, bool + CancellationRequest, bool IssueConfirmedNotifications, uint Lifetime) + F_1_10() + { + return (18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), + false, true, 0); + } + + public static (uint SubscriberProcessIdentifier, BacnetObjectId MonitoredObjectIdentifier, bool CancellationRequest, bool IssueConfirmedNotifications, uint Lifetime, BacnetPropertyReference MonitoredProperty, bool CovIncrementPresent, float CovIncrement) + F_1_11() + { + return (18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), false, true, 60, + new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE), true, 1.0f); + } } } From 550819d75c6ca1f1cce92988c5daa0dabc676bd0 Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Fri, 2 Mar 2018 14:01:08 +0100 Subject: [PATCH 28/31] covered FileAccessServices --- Serialize/Services/FileAccessServices.cs | 11 ++- .../Services/FileAccessServicesTests.cs | 96 +++++++++++++++++-- Tests/TestData/ASHRAE.cs | 13 +++ 3 files changed, 109 insertions(+), 11 deletions(-) diff --git a/Serialize/Services/FileAccessServices.cs b/Serialize/Services/FileAccessServices.cs index f7a6dc71..72edd224 100644 --- a/Serialize/Services/FileAccessServices.cs +++ b/Serialize/Services/FileAccessServices.cs @@ -108,7 +108,7 @@ public static int DecodeAtomicWriteFile(byte[] buffer, int offset, int apduLen, var len = 0; int tagLen; - objectId = default(BacnetObjectId); + objectId = default; isStream = true; position = -1; blockCount = 0; @@ -188,6 +188,7 @@ public static int DecodeAtomicWriteFile(byte[] buffer, int offset, int apduLen, return len; } + // TODO: use overloads to get rid of params that don't make sense (stream vs. record) public static void EncodeAtomicReadFileAcknowledge(EncodeBuffer buffer, bool isStream, bool endOfFile, int position, uint blockCount, byte[][] blocks, int[] counts) { ASN1.encode_application_boolean(buffer, endOfFile); @@ -254,15 +255,15 @@ public static int DecodeAtomicReadFileAcknowledge(byte[] buffer, int offset, int else if (ASN1.decode_is_opening_tag_number(buffer, offset + len, 1)) { throw new NotImplementedException("Non stream File transfers are not supported"); - ///* a tag number is not extended so only one octet */ + //* a tag number is not extended so only one octet */ //len++; - ///* fileStartRecord */ + //* fileStartRecord */ //tag_len = ASN1.decode_tag_number_and_value(buffer, offset + len, out tag_number, out len_value_type); //len += tag_len; //if (tag_number != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT) // return -1; //len += ASN1.decode_signed(buffer, offset + len, len_value_type, out position); - ///* returnedRecordCount */ + //* returnedRecordCount */ //tag_len = ASN1.decode_tag_number_and_value(buffer, offset + len, out tag_number, out len_value_type); //len += tag_len; //if (tag_number != (byte)BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT) @@ -279,7 +280,7 @@ public static int DecodeAtomicReadFileAcknowledge(byte[] buffer, int offset, int //} //if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) // return -1; - ///* a tag number is not extended so only one octet */ + //* a tag number is not extended so only one octet */ //len++; } else diff --git a/Tests/Serialize/Services/FileAccessServicesTests.cs b/Tests/Serialize/Services/FileAccessServicesTests.cs index 41ff45fa..4e2cb814 100644 --- a/Tests/Serialize/Services/FileAccessServicesTests.cs +++ b/Tests/Serialize/Services/FileAccessServicesTests.cs @@ -1,4 +1,5 @@ using System.IO.BACnet.Serialize; +using System.IO.BACnet.Tests.TestData; using System.Linq; using System.Text; using NUnit.Framework; @@ -37,10 +38,7 @@ public void should_encode_atomicreadfilerequest_data_ack_according_to_ashrae_exa { // arrange var buffer = new EncodeBuffer(); - var data = new[] - { - Encoding.ASCII.GetBytes("Chiller01 On-Time=4.3 Hours") - }; + var data = ASHRAE.F_2_1(); // example taken from ANNEX F - Examples of APDU Encoding - F.2.1 var expectedBytes = new byte[] @@ -53,8 +51,8 @@ public void should_encode_atomicreadfilerequest_data_ack_according_to_ashrae_exa // act APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, 0); - FileAccessServices.EncodeAtomicReadFileAcknowledge(buffer, true, false, 0, 1, data, - data.Select(arr => arr.Length).ToArray()); + FileAccessServices.EncodeAtomicReadFileAcknowledge(buffer, data.IsStream, data.EndOfFile, data.Position, + data.BlockCount, data.Blocks, data.Counts); var encodedBytes = buffer.ToArray(); @@ -62,6 +60,27 @@ public void should_encode_atomicreadfilerequest_data_ack_according_to_ashrae_exa Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_atomicreadfilerequest_data_ack_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_2_1(); + + // act + FileAccessServices.EncodeAtomicReadFileAcknowledge(buffer, input.IsStream, input.EndOfFile, input.Position, + input.BlockCount, input.Blocks, input.Counts); + FileAccessServices.DecodeAtomicReadFileAcknowledge(buffer.buffer, 0, buffer.GetLength(), out var endOfFile, + out var isStream, out var position, out var count, out var targetBuffer, out var targetOffset); + + // assert + Assert.That(endOfFile, Is.EqualTo(input.EndOfFile)); + Assert.That(isStream, Is.EqualTo(input.IsStream)); + Assert.That(position, Is.EqualTo(input.Position)); + Assert.That(count, Is.EqualTo(input.Counts[0])); + Assert.That(targetBuffer.Skip(targetOffset).Take((int)count), Is.EquivalentTo(input.Blocks[0])); + } + [Test] public void should_encode_atomicreadfilerequest_record_according_to_ashrae_example() { @@ -117,6 +136,25 @@ public void should_encode_atomicreadfilerequest_record_ack_according_to_ashrae_e Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [TestCase(true)] + [TestCase(false)] + public void should_decode_atomicreadfile_after_encode(bool isStream) + { + // arrange + var buffer = new EncodeBuffer(); + var oid = new BacnetObjectId(BacnetObjectTypes.OBJECT_FILE, 42); + + // act + FileAccessServices.EncodeAtomicReadFile(buffer, isStream, oid, 5, 10); + FileAccessServices.DecodeAtomicReadFile(buffer.buffer, 0, buffer.GetLength(), out var wasStream, + out var objectId, out var position, out var count); + + Assert.That(wasStream, Is.EqualTo(isStream)); + Assert.That(objectId, Is.EqualTo(oid)); + Assert.That(position, Is.EqualTo(5)); + Assert.That(count, Is.EqualTo(10)); + } + [Test] public void should_encode_atomicwritefilerequest_data_according_to_ashrae_example() { @@ -223,5 +261,51 @@ public void should_encode_atomicwritefilerequest_record_ack_according_to_ashrae_ // assert Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + + [TestCase(true)] + [TestCase(false)] + public void should_decode_atomicwritefile_after_encode(bool isStream) + { + // arrange + var buffer = new EncodeBuffer(); + var oid = new BacnetObjectId(BacnetObjectTypes.OBJECT_FILE, 42); + var input = new[] + { + new byte[] {0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21}, + new byte[] {0x42} + }; + + // act + FileAccessServices.EncodeAtomicWriteFile(buffer, isStream, oid, 5, (uint)input.Length, input, + input.Select(d => d.Length).ToArray()); + FileAccessServices.DecodeAtomicWriteFile(buffer.buffer, 0, buffer.GetLength(), out var wasStream, + out var objectId, out var position, out var blockCount, out var blocks, out var counts); + + // assert + Assert.That(wasStream, Is.EqualTo(isStream)); + Assert.That(objectId, Is.EqualTo(oid)); + Assert.That(position, Is.EqualTo(5)); + Assert.That(blockCount, Is.EqualTo((uint)(isStream ? 1 : input.Length))); + for (var i = 0; i < (isStream ? 1 : input.Length); i++) + Assert.That(blocks[i], Is.EqualTo(input[i])); + } + + [TestCase(true)] + [TestCase(false)] + public void should_decode_atomicwritefile_ack_after_encode(bool isStream) + { + // arrange + var buffer = new EncodeBuffer(); + const int expectedPosition = 30; + + // act + FileAccessServices.EncodeAtomicWriteFileAcknowledge(buffer, isStream, expectedPosition); + FileAccessServices.DecodeAtomicWriteFileAcknowledge(buffer.buffer, 0, buffer.GetLength(), out var wasStream, + out var position); + + // assert + Assert.That(wasStream, Is.EqualTo(isStream)); + Assert.That(position, Is.EqualTo(expectedPosition)); + } } } diff --git a/Tests/TestData/ASHRAE.cs b/Tests/TestData/ASHRAE.cs index aa96ad6c..3de53def 100644 --- a/Tests/TestData/ASHRAE.cs +++ b/Tests/TestData/ASHRAE.cs @@ -2,6 +2,8 @@ using System.IO.BACnet.EventNotification; using System.IO.BACnet.EventNotification.EventValues; using System.IO.BACnet.Serialize; +using System.Linq; +using System.Text; namespace System.IO.BACnet.Tests.TestData { @@ -135,5 +137,16 @@ public static (uint SubscriberProcessIdentifier, BacnetObjectId MonitoredObjectI return (18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), false, true, 60, new BacnetPropertyReference((uint) BacnetPropertyIds.PROP_PRESENT_VALUE), true, 1.0f); } + + public static (bool IsStream, bool EndOfFile, int Position, uint BlockCount, byte[][] Blocks, int[] Counts) + F_2_1() + { + var data = new[] + { + Encoding.ASCII.GetBytes("Chiller01 On-Time=4.3 Hours") + }; + + return (true, false, 0, 1, data, data.Select(arr => arr.Length).ToArray()); + } } } From 858fa06744e1eb1478e7269bb3ef7678e84e5250 Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Mon, 5 Mar 2018 13:15:13 +0100 Subject: [PATCH 29/31] covered ObjectAccessServices --- BACnetClient.cs | 10 +- Base/BacnetPropertyValue.cs | 26 +- Base/BacnetReadAccessResult.cs | 23 +- Base/BacnetValue.cs | 28 +- Base/BacnetWriteAccessSpecification.cs | 57 +++- Serialize/ASN1.cs | 62 +++- Serialize/Services/ObjectAccessServices.cs | 170 +++++----- .../Services/ObjectAccessServicesTests.cs | 294 +++++++++++++----- Tests/TestData/ASHRAE.cs | 106 ++++++- 9 files changed, 608 insertions(+), 168 deletions(-) diff --git a/BACnetClient.cs b/BACnetClient.cs index 2457972a..711c7020 100644 --- a/BACnetClient.cs +++ b/BACnetClient.cs @@ -162,7 +162,7 @@ public void Start() public event ReadPropertyMultipleRequestHandler OnReadPropertyMultipleRequest; public delegate void WritePropertyRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetObjectId objectId, BacnetPropertyValue value, BacnetMaxSegments maxSegments); public event WritePropertyRequestHandler OnWritePropertyRequest; - public delegate void WritePropertyMultipleRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetObjectId objectId, ICollection values, BacnetMaxSegments maxSegments); + public delegate void WritePropertyMultipleRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, IEnumerable values, BacnetMaxSegments maxSegments); public event WritePropertyMultipleRequestHandler OnWritePropertyMultipleRequest; public delegate void AtomicWriteFileRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, bool isStream, BacnetObjectId objectId, int position, uint blockCount, byte[][] blocks, int[] counts, BacnetMaxSegments maxSegments); public event AtomicWriteFileRequestHandler OnAtomicWriteFileRequest; @@ -180,7 +180,7 @@ public void Start() public event ReinitializedRequestHandler OnReinitializedDevice; public delegate void ReadRangeHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetObjectId objectId, BacnetPropertyReference property, BacnetReadRangeRequestTypes requestType, uint position, DateTime time, int count, BacnetMaxSegments maxSegments); public event ReadRangeHandler OnReadRange; - public delegate void CreateObjectRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetObjectId objectId, ICollection values, BacnetMaxSegments maxSegments); + public delegate void CreateObjectRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, object identifier, ICollection values, BacnetMaxSegments maxSegments); public event CreateObjectRequestHandler OnCreateObjectRequest; public delegate void DeleteObjectRequestHandler(BacnetClient sender, BacnetAddress address, byte invokeId, BacnetObjectId objectId, BacnetMaxSegments maxSegments); public event DeleteObjectRequestHandler OnDeleteObjectRequest; @@ -249,8 +249,8 @@ protected void ProcessConfirmedServiceRequest(BacnetAddress address, BacnetPduTy } else if (service == BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE && OnWritePropertyMultipleRequest != null) { - if (ObjectAccessServices.DecodeWritePropertyMultiple(address, buffer, offset, length, out var objectId, out var values) >= 0) - OnWritePropertyMultipleRequest(this, address, invokeId, objectId, values, maxSegments); + if (ObjectAccessServices.DecodeWritePropertyMultiple(address, buffer, offset, length, out var values) >= 0) + OnWritePropertyMultipleRequest(this, address, invokeId, values, maxSegments); else { ErrorResponse(address, service, invokeId, BacnetErrorClasses.ERROR_CLASS_SERVICES, BacnetErrorCodes.ERROR_CODE_ABORT_OTHER); @@ -1232,7 +1232,7 @@ public BacnetAsyncResult BeginWritePropertyMultipleRequest(BacnetAddress address var objectIds = string.Join(", ", valueList.Select(v => v.objectIdentifier)); Log.Debug($"Sending WritePropertyMultipleRequest {objectIds}"); return BeginConfirmedServiceRequest(address, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, - buffer => ObjectAccessServices.EncodeWriteObjectMultiple(buffer, valueList), waitForTransmit); + buffer => ObjectAccessServices.EncodeWritePropertyMultiple(buffer, valueList), waitForTransmit); } public IList ReadPropertyMultipleRequest(BacnetAddress address, BacnetObjectId objectId, IList propertyIdAndIndex) diff --git a/Base/BacnetPropertyValue.cs b/Base/BacnetPropertyValue.cs index 5ed057f1..06f7ab4c 100644 --- a/Base/BacnetPropertyValue.cs +++ b/Base/BacnetPropertyValue.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; +using System.Linq; namespace System.IO.BACnet { // TODO you have been flagged for refactoring due to un-C#-iness - public struct BacnetPropertyValue + public struct BacnetPropertyValue : IEquatable { public BacnetPropertyReference property; public IList value; @@ -13,5 +14,28 @@ public override string ToString() { return property.ToString(); } + + public bool Equals(BacnetPropertyValue other) + => property.Equals(other.property) + && value.SequenceEqual(other.value) + && priority == other.priority; + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is BacnetPropertyValue propertyValue && Equals(propertyValue); + } + + // TODO BUG FIXME fields used in GetHashCode MUST be immutable or you will break dictionaries & co + public override int GetHashCode() + { + unchecked + { + var hashCode = property.GetHashCode(); + hashCode = (hashCode * 397) ^ (value != null ? value.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ priority.GetHashCode(); + return hashCode; + } + } } } \ No newline at end of file diff --git a/Base/BacnetReadAccessResult.cs b/Base/BacnetReadAccessResult.cs index 11d8decf..b25e59b9 100644 --- a/Base/BacnetReadAccessResult.cs +++ b/Base/BacnetReadAccessResult.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; +using System.Linq; namespace System.IO.BACnet { - public struct BacnetReadAccessResult + public struct BacnetReadAccessResult : IEquatable { public BacnetObjectId objectIdentifier; public IList values; @@ -12,5 +13,25 @@ public BacnetReadAccessResult(BacnetObjectId objectIdentifier, IList { public BacnetApplicationTags Tag; public object Value; @@ -89,5 +90,30 @@ public override string ToString() return tmp.Aggregate(string.Empty, (current, b) => current + b.ToString("X2")); } + + public bool Equals(BacnetValue other) + { + if (Tag != other.Tag) + return false; + + return Value is IEnumerable enumerable && other.Value is IEnumerable otherEnumerable + ? enumerable.Cast().SequenceEqual(otherEnumerable.Cast()) + : Equals(Value, other.Value); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is BacnetValue && Equals((BacnetValue) obj); + } + + // TODO BUG FIXME fields used in GetHashCode MUST be immutable or you will break dictionaries & co + public override int GetHashCode() + { + unchecked + { + return ((int) Tag * 397) ^ (Value != null ? Value.GetHashCode() : 0); + } + } } } \ No newline at end of file diff --git a/Base/BacnetWriteAccessSpecification.cs b/Base/BacnetWriteAccessSpecification.cs index 9189e1c0..84eb7121 100644 --- a/Base/BacnetWriteAccessSpecification.cs +++ b/Base/BacnetWriteAccessSpecification.cs @@ -4,18 +4,44 @@ namespace System.IO.BACnet { - public class BacnetWriteAccessSpecification + public class BacnetWriteAccessSpecification : IEquatable { public BacnetObjectId ObjectId { get; } public ReadOnlyCollection Properties { get; } public BacnetWriteAccessSpecification(BacnetObjectId objectId, IEnumerable properties) { + if(properties == null) + throw new ArgumentNullException(nameof(properties)); + ObjectId = objectId; Properties = properties.ToList().AsReadOnly(); } - public class Property + public bool Equals(BacnetWriteAccessSpecification other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return ObjectId.Equals(other.ObjectId) && Properties.SequenceEqual(other.Properties); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((BacnetWriteAccessSpecification) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (ObjectId.GetHashCode() * 397) ^ Properties.GetHashCode(); + } + } + + public class Property : IEquatable { public BacnetPropertyIds Id { get; } public uint? ArrayIndex { get; } @@ -29,6 +55,33 @@ public Property(BacnetPropertyIds id, BacnetValue value, uint? arrayIndex = null Value = value; Priority = priority; } + + public bool Equals(Property other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && ArrayIndex == other.ArrayIndex && Value.Equals(other.Value) && Priority == other.Priority; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((Property)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (int)Id; + hashCode = (hashCode * 397) ^ ArrayIndex.GetHashCode(); + hashCode = (hashCode * 397) ^ Value.GetHashCode(); + hashCode = (hashCode * 397) ^ Priority.GetHashCode(); + return hashCode; + } + } } } } diff --git a/Serialize/ASN1.cs b/Serialize/ASN1.cs index e091d4ea..584c0dea 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -3,6 +3,7 @@ using System.IO.BACnet.EventNotification; using System.IO.BACnet.EventNotification.EventValues; using System.Text; +using Microsoft.Win32; namespace System.IO.BACnet.Serialize { @@ -1783,8 +1784,11 @@ public static int decode_context_date(byte[] buffer, int offset, byte tagNumber, return len; } - public static int bacapp_decode_data(byte[] buffer, int offset, int maxLength, BacnetApplicationTags tagDataType, uint lenValueType, out BacnetValue value) + public static int bacapp_decode_data(byte[] buffer, int offset, int maxLength, BacnetApplicationTags tagDataType, uint lenValueType, out BacnetValue value, Type enumType = null) { + if (tagDataType == BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED && enumType == null) + throw new ArgumentNullException(nameof(enumType)); + var len = 0; uint uintValue; @@ -1838,7 +1842,8 @@ public static int bacapp_decode_data(byte[] buffer, int offset, int maxLength, B case BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED: len = decode_unsigned(buffer, offset, lenValueType, out uint enumeratedValue); - value.Value = enumeratedValue; + // ReSharper disable once AssignNullToNotNullAttribute - this is checked at the top + value.Value = Enum.ToObject(enumType, enumeratedValue); break; case BacnetApplicationTags.BACNET_APPLICATION_TAG_DATE: @@ -2050,6 +2055,8 @@ public static int bacapp_decode_application_data(BacnetAddress address, byte[] b { var len = 0; + var enumType = GetEnumTypeForPropertyOrNull(propertyId); + value = new BacnetValue(); /* FIXME: use max_apdu_len! */ @@ -2059,7 +2066,7 @@ public static int bacapp_decode_application_data(BacnetAddress address, byte[] b if (tagLen > 0) { len += tagLen; - var decodeLen = bacapp_decode_data(buffer, offset + len, maxOffset, tagNumber, lenValueType, out value); + var decodeLen = bacapp_decode_data(buffer, offset + len, maxOffset, tagNumber, lenValueType, out value, enumType); if (decodeLen < 0) return decodeLen; len += decodeLen; } @@ -2073,6 +2080,19 @@ public static int bacapp_decode_application_data(BacnetAddress address, byte[] b return len; } + private static Type GetEnumTypeForPropertyOrNull(BacnetPropertyIds propertyId) + { + switch (propertyId) + { + case BacnetPropertyIds.PROP_FILE_ACCESS_METHOD: + return typeof(BacnetFileAccessMethod); + case BacnetPropertyIds.PROP_RELIABILITY: + return typeof(BacnetReliability); + default: + return null; + } + } + public static int bacapp_decode_context_application_data(BacnetAddress address, byte[] buffer, int offset, int maxOffset, BacnetObjectTypes objectType, BacnetPropertyIds propertyId, out BacnetValue value) { var len = 0; @@ -2252,6 +2272,13 @@ public static int bacapp_decode_context_application_data(BacnetAddress address, return len; } + public static int decode_object_id(byte[] buffer, int offset, out BacnetObjectId objectId) + { + var len = decode_object_id(buffer, offset, out BacnetObjectTypes type, out var instance); + objectId = new BacnetObjectId(type, instance); + return len; + } + public static int decode_object_id(byte[] buffer, int offset, out BacnetObjectTypes objectType, out uint instance) { var len = decode_unsigned32(buffer, offset, out var value); @@ -2289,6 +2316,17 @@ public static bool decode_is_opening_tag(byte[] buffer, int offset) return (buffer[offset] & 0x07) == 6; } + public static int DecodeExpectedTagNumberAndValue(byte[] buffer, int offset, byte expectedTag, out uint value) + { + var len = decode_tag_number_and_value(buffer, offset, out var tagNumber, out value); + + if(tagNumber != expectedTag) + throw new InvalidOperationException($"expected tag {expectedTag} not found"); + + return len; + } + + public static int decode_tag_number_and_value(byte[] buffer, int offset, out byte tagNumber, out uint value) { var len = decode_tag_number(buffer, offset, out tagNumber); @@ -2628,5 +2666,23 @@ public static int DecodeError(byte[] buffer, int offset, int length, out BacnetE return offset - orgOffset; } + + public static int DecodeOptionalArrayIndex(byte[] buffer, int offset, byte tag, uint defaultValue, + out uint arrayIndex) + { + var len = decode_tag_number_and_value(buffer, offset, out var tagNumber, out var lenValue); + if (tagNumber == tag) + { + len += decode_unsigned(buffer, offset + len, lenValue, out arrayIndex); + len += decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + } + else + { + len--; + arrayIndex = defaultValue; + } + + return len; + } } } diff --git a/Serialize/Services/ObjectAccessServices.cs b/Serialize/Services/ObjectAccessServices.cs index 5a583444..e4aa4b59 100644 --- a/Serialize/Services/ObjectAccessServices.cs +++ b/Serialize/Services/ObjectAccessServices.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Runtime.Remoting.Messaging; namespace System.IO.BACnet.Serialize { @@ -76,33 +75,53 @@ private static void EncodeCreateObjectInternal(EncodeBuffer buffer, ICollection< // By C. Gunter // quite the same as DecodeWritePropertyMultiple - public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out ICollection valuesRefs) + public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int offset, int apduLen, out object identifier, out ICollection valuesRefs) { var len = 0; - objectId = default(BacnetObjectId); + identifier = null; valuesRefs = null; + BacnetObjectTypes? objectType = null; + //object id len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); if (tagNumber == 0 && apduLen > len) { apduLen -= len; - if (apduLen >= 4) + + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var identifierTag, out lenValue); + + switch (identifierTag) { - len += ASN1.decode_context_object_id(buffer, offset + len, 1, out var typenr, out var instance); - objectId = new BacnetObjectId((BacnetObjectTypes) typenr, instance); + case 0: + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint type); + objectType = (BacnetObjectTypes) type; + identifier = objectType.Value; + break; + case 1: + if (apduLen >= 4) + { + len += ASN1.decode_object_id(buffer, offset + len, out var objectId); + objectType = objectId.Type; + identifier = objectId; + } + else + return -1; + + break; } - else - return -1; + } else return -1; + if (ASN1.decode_is_closing_tag(buffer, offset + len)) len++; //end objectid + // No initial values ? if (buffer.Length == offset + len) return len; @@ -142,7 +161,7 @@ public static int DecodeCreateObject(BacnetAddress address, byte[] buffer, int o while (!ASN1.decode_is_closing_tag(buffer, offset + len)) { var l = ASN1.bacapp_decode_application_data( - address, buffer, offset + len, apduLen + offset, objectId.Type, + address, buffer, offset + len, apduLen + offset, objectType.Value, (BacnetPropertyIds) propertyId, out var value); if (l <= 0) return -1; @@ -543,6 +562,15 @@ public static void EncodeReadRangeAcknowledge(EncodeBuffer buffer, BacnetObjectI } } + /* + * TODO FIXME: this implementation is incomplete - and wrong. I'll only patch it up to pass my test-case + * + * It should: + * - return the decoded values + * - further decode the item data (since the type of the data is not known, a hook has to be provided) + * - handle the (optional) sequence-number - which can only be done (cleanly) if we know the byte-count + * of item data; so the hook has to provide that! + */ public static uint DecodeReadRangeAcknowledge(byte[] buffer, int offset, int apduLen, out byte[] rangeBuffer) { var len = 0; @@ -577,7 +605,7 @@ public static uint DecodeReadRangeAcknowledge(byte[] buffer, int offset, int apd return 0; len += 1; - rangeBuffer = new byte[buffer.Length - offset - len - 1]; + rangeBuffer = new byte[apduLen - offset - len - 5]; // TODO FIXME: sequence-number is optional; hard-coding 5 here is wrong Array.Copy(buffer, offset + len, rangeBuffer, 0, rangeBuffer.Length); @@ -925,59 +953,47 @@ public static void EncodeWritePropertyMultiple(EncodeBuffer buffer, } } - public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buffer, int offset, int apduLen, out BacnetObjectId objectId, out ICollection valuesRefs) + public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buffer, int offset, int apduLen, out IEnumerable writeAccessSpec) { var len = 0; - objectId = default(BacnetObjectId); - valuesRefs = null; - /* Context tag 0 - Object ID */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); - if (tagNumber == 0 && apduLen > len) + var retVal = new List(); + + do { - apduLen -= len; - if (apduLen >= 4) - { - len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); - objectId = new BacnetObjectId(type, instance); - } - else - return -1; - } - else - return -1; + /* Context tag 0 - Object ID */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); - /* Tag 1: sequence of WriteAccessSpecification */ - if (!ASN1.decode_is_opening_tag_number(buffer, offset + len, 1)) - return -1; - len++; + if (tagNumber != 0) + throw new InvalidOperationException($"expected object id"); - var linkedList = new LinkedList(); - while (apduLen - len > 1) - { - var newEntry = new BacnetPropertyValue(); + len += ASN1.decode_object_id(buffer, offset + len, out var objectId); + + /* Tag 1: sequence of Properties */ + if (!ASN1.decode_is_opening_tag_number(buffer, offset + len++, 1)) + throw new InvalidOperationException($"expected opening tag"); - /* tag 0 - Property Identifier */ - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - uint propertyId; - if (tagNumber == 0) - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out propertyId); - else - return -1; + var linkedList = new LinkedList(); - /* tag 1 - Property Array Index - optional */ - var ulVal = ASN1.BACNET_ARRAY_ALL; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - if (tagNumber == 1) + while (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) { - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out ulVal); - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - } - newEntry.property = new BacnetPropertyReference(propertyId, ulVal); + var newEntry = new BacnetPropertyValue(); + + /* tag 0 - Property Identifier */ + len += ASN1.DecodeExpectedTagNumberAndValue(buffer, offset + len, 0, out lenValue); + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out uint propertyId); + + /* tag 1 - Property Array Index - optional */ + len += ASN1.DecodeOptionalArrayIndex(buffer, offset + len, 1, ASN1.BACNET_ARRAY_ALL, out var ulVal); + + newEntry.property = new BacnetPropertyReference(propertyId, ulVal); + + /* tag 2 - Property Value */ + len += ASN1.DecodeExpectedTagNumberAndValue(buffer, offset + len, 2, out lenValue); + + if (!ASN1.decode_is_opening_tag(buffer, offset + len - 1)) + throw new InvalidOperationException("expected opening tag"); - /* tag 2 - Property Value */ - if (tagNumber == 2 && ASN1.decode_is_opening_tag(buffer, offset + len - 1)) - { var list = new List(); while (!ASN1.decode_is_closing_tag(buffer, offset + len)) { @@ -985,39 +1001,49 @@ public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buff address, buffer, offset + len, apduLen + offset, objectId.Type, (BacnetPropertyIds) propertyId, out var value); - if (l <= 0) return -1; + if (l <= 0) + throw new InvalidOperationException($"{nameof(ASN1.bacapp_decode_application_data)} returned an error"); + len += l; list.Add(value); } len++; newEntry.value = list; + + /* tag 3 - Priority - optional */ + ulVal = ASN1.BACNET_NO_PRIORITY; + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + if (tagNumber == 3) + len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out ulVal); + else + len--; + + newEntry.priority = (byte)ulVal; + + linkedList.AddLast(newEntry); } - else - return -1; - /* tag 3 - Priority - optional */ - ulVal = ASN1.BACNET_NO_PRIORITY; - len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); - if (tagNumber == 3) - len += ASN1.decode_unsigned(buffer, offset + len, lenValue, out ulVal); - else - len--; - newEntry.priority = (byte)ulVal; + retVal.Add( + new BacnetWriteAccessSpecification( + objectId, + linkedList.Select( + p => new BacnetWriteAccessSpecification.Property( + p.property.GetPropertyId(), p.value[0], + p.property.propertyArrayIndex == ASN1.BACNET_ARRAY_ALL + ? null + : (uint?) p.property.propertyArrayIndex, + p.priority == 0 ? null : (uint?) p.priority)))); + len++; - linkedList.AddLast(newEntry); - } + } while (len < apduLen); - /* Closing tag 1 - List of Properties */ - if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) - return -1; - len++; - valuesRefs = linkedList; + writeAccessSpec = retVal; return len; } - public static void EncodeWriteObjectMultiple(EncodeBuffer buffer, ICollection valueList) + public static void EncodeWritePropertyMultiple(EncodeBuffer buffer, ICollection valueList) { foreach (var value in valueList) EncodeWritePropertyMultiple(buffer, value.objectIdentifier, value.values); diff --git a/Tests/Serialize/Services/ObjectAccessServicesTests.cs b/Tests/Serialize/Services/ObjectAccessServicesTests.cs index cf350679..e0dcc2e7 100644 --- a/Tests/Serialize/Services/ObjectAccessServicesTests.cs +++ b/Tests/Serialize/Services/ObjectAccessServicesTests.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO.BACnet.Serialize; using System.IO.BACnet.Tests.TestData; +using System.Linq; using NUnit.Framework; using static System.IO.BACnet.Tests.Helper; @@ -130,20 +131,7 @@ public void should_encode_createobjectrequest_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); - - var data = new List - { - new BacnetPropertyValue - { - property = new BacnetPropertyReference(BacnetPropertyIds.PROP_OBJECT_NAME), - value = new List {new BacnetValue("Trend 1")} - }, - new BacnetPropertyValue - { - property = new BacnetPropertyReference(BacnetPropertyIds.PROP_FILE_ACCESS_METHOD), - value = new List {new BacnetValue(BacnetFileAccessMethod.RECORD_ACCESS)} - } - }; + var input = ASHRAE.F_3_3(); // example taken from ANNEX F - Examples of APDU Encoding - F.3.3 var expectedBytes = new byte[] @@ -157,12 +145,29 @@ public void should_encode_createobjectrequest_according_to_ashrae_example() BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU1024, 86); - ObjectAccessServices.EncodeCreateObject(buffer, BacnetObjectTypes.OBJECT_FILE, data); + ObjectAccessServices.EncodeCreateObject(buffer, input.ObjectType, input.ValueList); var encodedBytes = buffer.ToArray(); // assert - Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + Assert.That(encodedBytes, Is.EqualTo(expectedBytes)); + } + + [Test] + public void should_decode_createobjectrequest_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_3_3(); + + // act + ObjectAccessServices.EncodeCreateObject(buffer, input.ObjectType, input.ValueList); + ObjectAccessServices.DecodeCreateObject(default(BacnetAddress), buffer.buffer, 0, buffer.GetLength(), + out var objectId, out var valuesRefs); + + // assert + Assert.That(objectId, Is.EqualTo(input.ObjectType)); + Assert.That(valuesRefs, Is.EquivalentTo(input.ValueList)); } [Test] @@ -200,7 +205,7 @@ public void should_encode_deleteobjectrequest_according_to_ashrae_example() BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU1024, 87); - ObjectAccessServices.EncodeDeleteObject(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_GROUP, 6)); + ObjectAccessServices.EncodeDeleteObject(buffer, ASHRAE.F_3_4()); var encodedBytes = buffer.ToArray(); @@ -208,6 +213,20 @@ public void should_encode_deleteobjectrequest_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_deleteobjectrequest_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_3_4(); + + ObjectAccessServices.EncodeDeleteObject(buffer, input); + ObjectAccessServices.DecodeDeleteObject(buffer.buffer, 0, buffer.GetLength(), out var objectId); + + // assert + Assert.That(objectId, Is.EqualTo(input)); + } + [Test] public void should_encode_deleteobjectrequest_ack_according_to_ashrae_example() { @@ -252,6 +271,7 @@ public void should_encode_readpropertyrequest_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); + var data = ASHRAE.F_3_5(); // example taken from ANNEX F - Examples of APDU Encoding - F.3.5 var expectedBytes = new byte[] {0x00, 0x00, 0x01, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x05, 0x19, 0x55}; @@ -261,8 +281,7 @@ public void should_encode_readpropertyrequest_according_to_ashrae_example() BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU50, 1); - ObjectAccessServices.EncodeReadProperty(buffer, - new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 5), BacnetPropertyIds.PROP_PRESENT_VALUE); + ObjectAccessServices.EncodeReadProperty(buffer, data.ObjectId, data.PropertyId, data.ArrayIndex); var encodedBytes = buffer.ToArray(); @@ -270,16 +289,30 @@ public void should_encode_readpropertyrequest_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_readpropertyrequest_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_3_5(); + + ObjectAccessServices.EncodeReadProperty(buffer, input.ObjectId, input.PropertyId, input.ArrayIndex); + ObjectAccessServices.DecodeReadProperty(buffer.buffer, 0, buffer.GetLength(), out var objectId, + out var propertyReference); + + // assert + Assert.That(objectId, Is.EqualTo(input.ObjectId)); + Assert.That(propertyReference.propertyIdentifier, Is.EqualTo((uint)input.PropertyId)); + Assert.That(propertyReference.propertyArrayIndex, Is.EqualTo(input.ArrayIndex)); + } + [Test] public void should_encode_readpropertyrequest_ack_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); - var data = new List - { - new BacnetValue(72.3f) - }; + var data = ASHRAE.F_3_5_Ack(); // example taken from ANNEX F - Examples of APDU Encoding - F.3.5 var expectedBytes = new byte[] @@ -287,9 +320,8 @@ public void should_encode_readpropertyrequest_ack_according_to_ashrae_example() // act APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, 1); - ObjectAccessServices.EncodeReadPropertyAcknowledge(buffer, - new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 5), BacnetPropertyIds.PROP_PRESENT_VALUE, - data); + ObjectAccessServices.EncodeReadPropertyAcknowledge(buffer, data.ObjectId, data.PropertyId, data.ValueList, + data.ArrayIndex); var encodedBytes = buffer.ToArray(); @@ -297,17 +329,35 @@ public void should_encode_readpropertyrequest_ack_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_readpropertyrequest_ack_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + + var input = ASHRAE.F_3_5_Ack(); + + // act + ObjectAccessServices.EncodeReadPropertyAcknowledge(buffer, input.ObjectId, input.PropertyId, + input.ValueList, input.ArrayIndex); + ObjectAccessServices.DecodeReadPropertyAcknowledge(DummyAddress, buffer.buffer, 0, buffer.GetLength(), + out var objectId, out var propertyReference, out var valueList); + + + // assert + Assert.That(objectId, Is.EqualTo(input.ObjectId)); + Assert.That(propertyReference.propertyIdentifier, Is.EqualTo((uint)input.PropertyId)); + Assert.That(propertyReference.propertyArrayIndex, Is.EqualTo(input.ArrayIndex)); + Assert.That(valueList, Is.EquivalentTo(input.ValueList)); + } + [Test] public void should_encode_readpropertymultiplerequest_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); - var data = new List - { - new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), - new BacnetPropertyReference(BacnetPropertyIds.PROP_RELIABILITY) - }; + var data = ASHRAE.F_3_7(); // example taken from ANNEX F - Examples of APDU Encoding - F.3.7 var expectedBytes = new byte[] @@ -318,8 +368,7 @@ public void should_encode_readpropertymultiplerequest_according_to_ashrae_exampl BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU1024, 241); - ObjectAccessServices.EncodeReadPropertyMultiple(buffer, - new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 16), data); + ObjectAccessServices.EncodeReadPropertyMultiple(buffer, data.ObjectId, data.Properties); var encodedBytes = buffer.ToArray(); @@ -327,32 +376,31 @@ public void should_encode_readpropertymultiplerequest_according_to_ashrae_exampl Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_readpropertymultiplerequest_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + + var input = ASHRAE.F_3_7(); + + // act + ObjectAccessServices.EncodeReadPropertyMultiple(buffer, input.ObjectId, input.Properties); + ObjectAccessServices.DecodeReadPropertyMultiple(buffer.buffer, 0, buffer.GetLength(), out var properties); + + // assert + Assert.That(properties.Count, Is.EqualTo(1)); + Assert.That(properties[0].objectIdentifier, Is.EqualTo(input.ObjectId)); + Assert.That(properties[0].propertyReferences, Is.EquivalentTo(input.Properties)); + } + [Test] public void should_encode_readpropertymultiplerequest_ack_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); - var data = new List - { - new BacnetReadAccessResult(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 16), - new List - { - new BacnetPropertyValue - { - property = new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), - value = new List {new BacnetValue(72.3f)} - }, - new BacnetPropertyValue - { - property = new BacnetPropertyReference(BacnetPropertyIds.PROP_RELIABILITY), - value = new List - { - new BacnetValue(BacnetReliability.RELIABILITY_NO_FAULT_DETECTED) - } - } - }) - }; + var data = ASHRAE.F_3_7_Ack(); // example taken from ANNEX F - Examples of APDU Encoding - F.3.7 var expectedBytes = new byte[] @@ -371,6 +419,23 @@ public void should_encode_readpropertymultiplerequest_ack_according_to_ashrae_ex Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_readpropertymultiplerequest_ack_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + + var input = ASHRAE.F_3_7_Ack(); + + // act + ObjectAccessServices.EncodeReadPropertyMultipleAcknowledge(buffer, input); + ObjectAccessServices.DecodeReadPropertyMultipleAcknowledge(DummyAddress, buffer.buffer, 0, + buffer.GetLength(), out var values); + + // assert + Assert.That(values, Is.EquivalentTo(input)); + } + [Test] public void should_encode_readpropertymultiplerequest_formultipleobjects_according_to_ashrae_example() { @@ -432,7 +497,7 @@ public void should_encode_readpropertymultiplerequest_formultipleobjects_ack_acc new BacnetPropertyValue { property = new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), - value = new List {new BacnetValue(42.3f)} + value = new List {new BacnetValue(42.3f)}, } }), @@ -446,7 +511,7 @@ public void should_encode_readpropertymultiplerequest_formultipleobjects_ack_acc { new BacnetValue(new BacnetError(BacnetErrorClasses.ERROR_CLASS_OBJECT, BacnetErrorCodes.ERROR_CODE_UNKNOWN_OBJECT)) - } + }, } }), @@ -456,7 +521,7 @@ public void should_encode_readpropertymultiplerequest_formultipleobjects_ack_acc new BacnetPropertyValue { property = new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), - value = new List {new BacnetValue(435.7f)} + value = new List {new BacnetValue(435.7f)}, } }) }; @@ -484,6 +549,7 @@ public void should_encode_readrangerequest_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); + var data = ASHRAE.F_3_8(); // example taken from ANNEX F - Examples of APDU Encoding - F.3.8 var expectedBytes = new byte[] @@ -498,9 +564,8 @@ public void should_encode_readrangerequest_according_to_ashrae_example() BacnetMaxAdpu.MAX_APDU206, 1, type: BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST | BacnetPduTypes.SEGMENTED_RESPONSE_ACCEPTED); - ObjectAccessServices.EncodeReadRange(buffer, new BacnetObjectId(BacnetObjectTypes.OBJECT_TRENDLOG, 1), - BacnetPropertyIds.PROP_LOG_BUFFER, BacnetReadRangeRequestTypes.RR_BY_TIME, 0, - new DateTime(1998, 3, 23, 19, 52, 34), 4); + ObjectAccessServices.EncodeReadRange(buffer, data.ObjectId, data.PropertyId, data.RequestType, + data.Position, data.Time, data.Count, data.ArrayIndex); var encodedBytes = buffer.ToArray(); @@ -508,12 +573,35 @@ public void should_encode_readrangerequest_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_readrangerequest_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_3_8(); + + // act + ObjectAccessServices.EncodeReadRange(buffer, input.ObjectId, input.PropertyId, input.RequestType, + input.Position, input.Time, input.Count, input.ArrayIndex); + ObjectAccessServices.DecodeReadRange(buffer.buffer, 0, buffer.GetLength(), out var objectId, + out var property, out var requestType, out var position, out var time, out var count); + + // assert + Assert.That(objectId, Is.EqualTo(input.ObjectId)); + Assert.That(property.propertyIdentifier, Is.EqualTo((uint)input.PropertyId)); + Assert.That(property.propertyArrayIndex, Is.EqualTo(input.ArrayIndex)); + Assert.That(requestType, Is.EqualTo(input.RequestType)); + Assert.That(position, Is.EqualTo(input.Position)); + Assert.That(time, Is.EqualTo(input.Time)); + Assert.That(count, Is.EqualTo(input.Count)); + } + [Test] public void should_encode_readrangerequest_ack_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); - var data = ASHRAE.F_3_8(); + var data = ASHRAE.F_3_8_Ack(); // example taken from ANNEX F - Examples of APDU Encoding - F.3.8 var expectedBytes = new byte[] @@ -540,12 +628,36 @@ public void should_encode_readrangerequest_ack_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + /* + * TODO: DecodeReadRangeAcknowledge should return the properties it decodes, and we should compare them to the input + */ + public void should_decode_readrangerequest_ack_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_3_8_Ack(); + + // act + var applicationData = new EncodeBuffer(); + ObjectAccessServices.EncodeLogRecord(applicationData, input.Record1); + ObjectAccessServices.EncodeLogRecord(applicationData, input.Record2); + + ObjectAccessServices.EncodeReadRangeAcknowledge(buffer, input.ObjectId, input.PropertyId, input.Flags, + input.ItemCount, applicationData.ToArray(), input.RequestType, input.FirstSequence); + + ObjectAccessServices.DecodeReadRangeAcknowledge(buffer.buffer, 0, buffer.GetLength(), out var rangeBuffer); + + // assert + Assert.That(rangeBuffer, Is.EquivalentTo(applicationData.ToArray())); + } + [Test] public void should_decode_multiple_logrecords() { // arrange var buffer = new EncodeBuffer(); - var data = ASHRAE.F_3_8(); + var data = ASHRAE.F_3_8_Ack(); // act ObjectAccessServices.EncodeLogRecord(buffer, data.Record1); @@ -577,6 +689,7 @@ public void should_encode_writepropertyrequest_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); + var data = ASHRAE.F_3_9(); // example taken from ANNEX F - Examples of APDU Encoding - F.3.9 var expectedBytes = new byte[] @@ -589,9 +702,8 @@ public void should_encode_writepropertyrequest_according_to_ashrae_example() APDU.EncodeConfirmedServiceRequest(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU1024, 89); - ObjectAccessServices.EncodeWriteProperty(buffer, - new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 1), BacnetPropertyIds.PROP_PRESENT_VALUE, - new List {new BacnetValue(180f)}); + ObjectAccessServices.EncodeWriteProperty(buffer, data.ObjectId, data.PropertyId, data.ValueList, + data.ArrayIndex, data.Priority); var encodedBytes = buffer.ToArray(); @@ -599,6 +711,26 @@ public void should_encode_writepropertyrequest_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_writepropertyrequest_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_3_9(); + + ObjectAccessServices.EncodeWriteProperty(buffer, input.ObjectId, input.PropertyId, input.ValueList, + input.ArrayIndex, input.Priority); + ObjectAccessServices.DecodeWriteProperty(DummyAddress, buffer.buffer, 0, buffer.GetLength(), + out var objectId, out var value); + + // assert + Assert.That(objectId, Is.EqualTo(input.ObjectId)); + Assert.That(value.property.propertyIdentifier, Is.EqualTo((uint)input.PropertyId)); + Assert.That(value.property.propertyArrayIndex, Is.EqualTo(input.ArrayIndex)); + Assert.That(value.value, Is.EquivalentTo(input.ValueList)); + Assert.That(value.priority, Is.EqualTo(input.Priority == ASN1.BACNET_NO_PRIORITY ? 16 : input.Priority)); + } + [Test] public void should_encode_writepropertyrequest_ack_according_to_ashrae_example() { @@ -622,23 +754,7 @@ public void should_encode_writepropertymultiplerequest_according_to_ashrae_examp { // arrange var buffer = new EncodeBuffer(); - var data = new[] - { - new BacnetWriteAccessSpecification( - new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 5), - A(new BacnetWriteAccessSpecification.Property(BacnetPropertyIds.PROP_PRESENT_VALUE, - new BacnetValue(67f)))), - - new BacnetWriteAccessSpecification( - new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 6), - A(new BacnetWriteAccessSpecification.Property(BacnetPropertyIds.PROP_PRESENT_VALUE, - new BacnetValue(67f)))), - - new BacnetWriteAccessSpecification( - new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 7), - A(new BacnetWriteAccessSpecification.Property(BacnetPropertyIds.PROP_PRESENT_VALUE, - new BacnetValue(72f)))), - }; + var data = ASHRAE.F_3_10(); // example taken from ANNEX F - Examples of APDU Encoding - F.3.10 var expectedBytes = new byte[] @@ -661,6 +777,22 @@ public void should_encode_writepropertymultiplerequest_according_to_ashrae_examp Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_writepropertymultiplerequest_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_3_10(); + + // act + ObjectAccessServices.EncodeWritePropertyMultiple(buffer, input); + ObjectAccessServices.DecodeWritePropertyMultiple(DummyAddress, buffer.buffer, 0, buffer.GetLength(), + out var output); + + // assert + Assert.That(output, Is.EquivalentTo(input)); + } + [Test] public void should_encode_writepropertymultiplerequest_ack_according_to_ashrae_example() { diff --git a/Tests/TestData/ASHRAE.cs b/Tests/TestData/ASHRAE.cs index 3de53def..f8b37b70 100644 --- a/Tests/TestData/ASHRAE.cs +++ b/Tests/TestData/ASHRAE.cs @@ -4,6 +4,7 @@ using System.IO.BACnet.Serialize; using System.Linq; using System.Text; +using static System.IO.BACnet.Tests.Helper; namespace System.IO.BACnet.Tests.TestData { @@ -72,7 +73,7 @@ public static BacnetAlarmSummaryData[] public static (BacnetLogRecord Record1, BacnetLogRecord Record2, BacnetObjectId ObjectId, BacnetPropertyIds PropertyId, BacnetBitString Flags, uint ItemCount, BacnetReadRangeRequestTypes RequestType, uint FirstSequence - ) F_3_8() + ) F_3_8_Ack() { var record1 = new BacnetLogRecord(BacnetTrendLogValueType.TL_TYPE_REAL, 18.0, new DateTime(1998, 3, 23, 19, 54, 27), 0); @@ -131,7 +132,9 @@ public static (uint SubscriberProcessIdentifier, BacnetObjectId MonitoredObjectI false, true, 0); } - public static (uint SubscriberProcessIdentifier, BacnetObjectId MonitoredObjectIdentifier, bool CancellationRequest, bool IssueConfirmedNotifications, uint Lifetime, BacnetPropertyReference MonitoredProperty, bool CovIncrementPresent, float CovIncrement) + public static (uint SubscriberProcessIdentifier, BacnetObjectId MonitoredObjectIdentifier, bool + CancellationRequest, bool IssueConfirmedNotifications, uint Lifetime, BacnetPropertyReference + MonitoredProperty, bool CovIncrementPresent, float CovIncrement) F_1_11() { return (18, new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 10), false, true, 60, @@ -148,5 +151,104 @@ public static (bool IsStream, bool EndOfFile, int Position, uint BlockCount, byt return (true, false, 0, 1, data, data.Select(arr => arr.Length).ToArray()); } + + public static (BacnetObjectTypes ObjectType, ICollection ValueList) + F_3_3() + { + var data = new List + { + new BacnetPropertyValue + { + property = new BacnetPropertyReference(BacnetPropertyIds.PROP_OBJECT_NAME), + value = new List {new BacnetValue("Trend 1")} + }, + new BacnetPropertyValue + { + property = new BacnetPropertyReference(BacnetPropertyIds.PROP_FILE_ACCESS_METHOD), + value = new List {new BacnetValue(BacnetFileAccessMethod.RECORD_ACCESS)} + } + }; + + return (BacnetObjectTypes.OBJECT_FILE, data); + } + + public static BacnetObjectId F_3_4() + => new BacnetObjectId(BacnetObjectTypes.OBJECT_GROUP, 6); + + public static (BacnetObjectId ObjectId, BacnetPropertyIds PropertyId, uint ArrayIndex) + F_3_5() + => (new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 5), BacnetPropertyIds.PROP_PRESENT_VALUE, + ASN1.BACNET_ARRAY_ALL); + + public static (BacnetObjectId ObjectId, BacnetPropertyIds PropertyId, IEnumerable ValueList, uint + ArrayIndex) + F_3_5_Ack() + => (new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 5), BacnetPropertyIds.PROP_PRESENT_VALUE, + new List + { + new BacnetValue(72.3f) + }, ASN1.BACNET_ARRAY_ALL); + + public static (BacnetObjectId ObjectId, IList Properties) + F_3_7() + => (new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 16), new List + { + new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), + new BacnetPropertyReference(BacnetPropertyIds.PROP_RELIABILITY) + }); + + public static IList F_3_7_Ack() + => new List + { + new BacnetReadAccessResult(new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 16), + new List + { + new BacnetPropertyValue + { + property = new BacnetPropertyReference(BacnetPropertyIds.PROP_PRESENT_VALUE), + value = new List {new BacnetValue(72.3f)}, + }, + new BacnetPropertyValue + { + property = new BacnetPropertyReference(BacnetPropertyIds.PROP_RELIABILITY), + value = new List + { + new BacnetValue(BacnetReliability.RELIABILITY_NO_FAULT_DETECTED) + }, + } + }) + }; + + public static (BacnetObjectId ObjectId, BacnetPropertyIds PropertyId, BacnetReadRangeRequestTypes RequestType, + uint Position, DateTime Time, int Count, uint ArrayIndex) + F_3_8() + => (new BacnetObjectId(BacnetObjectTypes.OBJECT_TRENDLOG, 1), + BacnetPropertyIds.PROP_LOG_BUFFER, BacnetReadRangeRequestTypes.RR_BY_TIME, 0, + new DateTime(1998, 3, 23, 19, 52, 34), 4, ASN1.BACNET_ARRAY_ALL); + + public static (BacnetObjectId ObjectId, BacnetPropertyIds PropertyId, IEnumerable ValueList, uint + ArrayIndex, uint Priority) + F_3_9() + => (new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 1), BacnetPropertyIds.PROP_PRESENT_VALUE, + new List {new BacnetValue(180f)}, ASN1.BACNET_ARRAY_ALL, 0); + + public static BacnetWriteAccessSpecification[] F_3_10() + => new[] + { + new BacnetWriteAccessSpecification( + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 5), + A(new BacnetWriteAccessSpecification.Property(BacnetPropertyIds.PROP_PRESENT_VALUE, + new BacnetValue(67f)))), + + new BacnetWriteAccessSpecification( + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 6), + A(new BacnetWriteAccessSpecification.Property(BacnetPropertyIds.PROP_PRESENT_VALUE, + new BacnetValue(67f)))), + + new BacnetWriteAccessSpecification( + new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 7), + A(new BacnetWriteAccessSpecification.Property(BacnetPropertyIds.PROP_PRESENT_VALUE, + new BacnetValue(72f)))), + }; } } From 654fcad4dcacd8ed7fb8641dd505755bc73d32bc Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Mon, 5 Mar 2018 15:51:58 +0100 Subject: [PATCH 30/31] covered RemoteDeviceManagementServices --- .../RemoteDeviceManagementServicesTests.cs | 126 ++++++++++++++++-- Tests/TestData/ASHRAE.cs | 20 +++ 2 files changed, 137 insertions(+), 9 deletions(-) diff --git a/Tests/Serialize/Services/RemoteDeviceManagementServicesTests.cs b/Tests/Serialize/Services/RemoteDeviceManagementServicesTests.cs index 618a3e72..e5b1e051 100644 --- a/Tests/Serialize/Services/RemoteDeviceManagementServicesTests.cs +++ b/Tests/Serialize/Services/RemoteDeviceManagementServicesTests.cs @@ -1,4 +1,5 @@ using System.IO.BACnet.Serialize; +using System.IO.BACnet.Tests.TestData; using NUnit.Framework; using static System.IO.BACnet.Tests.Helper; @@ -12,6 +13,7 @@ public void should_encode_devicecommunicationcontrolrequest_according_to_ashrae_ { // arrange var buffer = new EncodeBuffer(); + var data = ASHRAE.F_4_1(); // example taken from ANNEX F - Examples of APDU Encoding - F.4.1 var expectedBytes = new byte[] @@ -25,7 +27,8 @@ public void should_encode_devicecommunicationcontrolrequest_according_to_ashrae_ BacnetConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU1024, 5); - RemoteDeviceManagementServices.EncodeDeviceCommunicationControl(buffer, 5, EnableDisable.DISABLE, "#egbdf!"); + RemoteDeviceManagementServices.EncodeDeviceCommunicationControl(buffer, data.TimeDuration, + data.EnableDisable, data.Password); var encodedBytes = buffer.ToArray(); @@ -33,6 +36,25 @@ public void should_encode_devicecommunicationcontrolrequest_according_to_ashrae_ Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_devicecommunicationcontrolrequest_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_4_1(); + + // act + RemoteDeviceManagementServices.EncodeDeviceCommunicationControl(buffer, input.TimeDuration, + input.EnableDisable, input.Password); + RemoteDeviceManagementServices.DecodeDeviceCommunicationControl(buffer.buffer, 0, buffer.GetLength(), + out var timeDuration, out var enableDisable, out var password); + + // assert + Assert.That(timeDuration, Is.EqualTo(input.TimeDuration)); + Assert.That(enableDisable, Is.EqualTo((uint)input.EnableDisable)); + Assert.That(password, Is.EqualTo(input.Password)); + } + [Test] public void should_encode_devicecommunicationcontrolrequest_ack_according_to_ashrae_example() { @@ -132,6 +154,7 @@ public void should_encode_reinitializedevicerequest_according_to_ashrae_example( { // arrange var buffer = new EncodeBuffer(); + var data = ASHRAE.F_4_4(); // example taken from ANNEX F - Examples of APDU Encoding - F.4.4 var expectedBytes = new byte[] @@ -142,8 +165,7 @@ public void should_encode_reinitializedevicerequest_according_to_ashrae_example( BacnetConfirmedServices.SERVICE_CONFIRMED_REINITIALIZE_DEVICE, BacnetMaxSegments.MAX_SEG0, BacnetMaxAdpu.MAX_APDU128, 2); - RemoteDeviceManagementServices.EncodeReinitializeDevice(buffer, - BacnetReinitializedStates.BACNET_REINIT_WARMSTART, "AbCdEfGh"); + RemoteDeviceManagementServices.EncodeReinitializeDevice(buffer, data.State, data.Password); var encodedBytes = buffer.ToArray(); @@ -151,6 +173,23 @@ public void should_encode_reinitializedevicerequest_according_to_ashrae_example( Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_reinitializedevicerequest_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_4_4(); + + // act + RemoteDeviceManagementServices.EncodeReinitializeDevice(buffer, input.State, input.Password); + RemoteDeviceManagementServices.DecodeReinitializeDevice(buffer.buffer, 0, buffer.GetLength(), out var state, + out var password); + + // assert + Assert.That(state, Is.EqualTo(input.State)); + Assert.That(password, Is.EqualTo(input.Password)); + } + [Test] public void should_encode_reinitializedevicerequest_ack_according_to_ashrae_example() { @@ -181,8 +220,7 @@ public void should_encode_timesynchronizationrequest_according_to_ashrae_example // act APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION); - RemoteDeviceManagementServices.EncodeTimeSync(buffer, - new DateTime(1992, 11, 17, 22, 45, 30).AddMilliseconds(700)); + RemoteDeviceManagementServices.EncodeTimeSync(buffer, ASHRAE.F_4_7()); var encodedBytes = buffer.ToArray(); @@ -190,11 +228,27 @@ public void should_encode_timesynchronizationrequest_according_to_ashrae_example Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_timesynchronizationrequest_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_4_7(); + + // act + RemoteDeviceManagementServices.EncodeTimeSync(buffer, input); + RemoteDeviceManagementServices.DecodeTimeSync(buffer.buffer, 0, buffer.GetLength(), out var dateTime); + + // assert + Assert.That(dateTime, Is.EqualTo(input)); + } + [Test] public void should_encode_whohas_via_objectname_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); + var data = ASHRAE.F_4_8_Name(); // example taken from ANNEX F - Examples of APDU Encoding - F.4.8 var expectedBytes = new byte[] {0x10, 0x07, 0x3D, 0x07, 0x00, 0x4F, 0x41, 0x54, 0x65, 0x6D, 0x70}; @@ -202,7 +256,7 @@ public void should_encode_whohas_via_objectname_according_to_ashrae_example() // act APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_HAS); - RemoteDeviceManagementServices.EncodeWhoHasBroadcast(buffer, -1, -1, default(BacnetObjectId), "OATemp"); + RemoteDeviceManagementServices.EncodeWhoHasBroadcast(buffer, -1, -1, default(BacnetObjectId), data); var encodedBytes = buffer.ToArray(); @@ -210,6 +264,23 @@ public void should_encode_whohas_via_objectname_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_whohas_via_objectname_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_4_8_Name(); + + // act + RemoteDeviceManagementServices.EncodeWhoHasBroadcast(buffer, -1, -1, default(BacnetObjectId), input); + RemoteDeviceManagementServices.DecodeWhoHasBroadcast(buffer.buffer, 0, buffer.GetLength(), out var lowLimit, + out var highLimit, out var objectId, out var objectName); + + + // assert + Assert.That(objectName, Is.EqualTo(input)); + } + [Test] public void should_encode_ihave_following_name_according_to_ashrae_example() { @@ -241,6 +312,7 @@ public void should_encode_whohas_via_id_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); + var data = ASHRAE.F_4_8_Id(); // example taken from ANNEX F - Examples of APDU Encoding - F.4.8 var expectedBytes = new byte[] {0x10, 0x07, 0x2C, 0x00, 0x00, 0x00, 0x03}; @@ -248,8 +320,7 @@ public void should_encode_whohas_via_id_according_to_ashrae_example() // act APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_HAS); - RemoteDeviceManagementServices.EncodeWhoHasBroadcast(buffer, -1, -1, - new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 3), null); + RemoteDeviceManagementServices.EncodeWhoHasBroadcast(buffer, -1, -1, data, null); var encodedBytes = buffer.ToArray(); @@ -257,11 +328,30 @@ public void should_encode_whohas_via_id_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_whohas_via_id_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_4_8_Id(); + + // act + RemoteDeviceManagementServices.EncodeWhoHasBroadcast(buffer, -1, -1, input, null); + RemoteDeviceManagementServices.DecodeWhoHasBroadcast(buffer.buffer, 0, buffer.GetLength(), out var lowLimit, + out var highLimit, out var objectId, out var objectName); + + + // assert + Assert.That(objectId, Is.EqualTo(input)); + } + + [Test] public void should_encode_whois_via_id_according_to_ashrae_example() { // arrange var buffer = new EncodeBuffer(); + var data = ASHRAE.F_4_9(); // example taken from ANNEX F - Examples of APDU Encoding - F.4.9 var expectedBytes = new byte[] { 0x10, 0x08, 0x09, 0x03, 0x19, 0x03 }; @@ -269,7 +359,7 @@ public void should_encode_whois_via_id_according_to_ashrae_example() // act APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS); - RemoteDeviceManagementServices.EncodeWhoIsBroadcast(buffer, 3, 3); + RemoteDeviceManagementServices.EncodeWhoIsBroadcast(buffer, data.LowLimit, data.HighLimit); var encodedBytes = buffer.ToArray(); @@ -277,6 +367,24 @@ public void should_encode_whois_via_id_according_to_ashrae_example() Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); } + [Test] + public void should_decode_whois_via_id_after_encode() + { + // arrange + var buffer = new EncodeBuffer(); + var input = ASHRAE.F_4_9(); + + // act + RemoteDeviceManagementServices.EncodeWhoIsBroadcast(buffer, input.LowLimit, input.HighLimit); + RemoteDeviceManagementServices.DecodeWhoIsBroadcast(buffer.buffer, 0, buffer.GetLength(), out var lowLimit, + out var highLimit); + + + // assert + Assert.That(lowLimit, Is.EqualTo(input.LowLimit)); + Assert.That(highLimit, Is.EqualTo(input.HighLimit)); + } + [Test] public void should_encode_iam_following_id_according_to_ashrae_example() { diff --git a/Tests/TestData/ASHRAE.cs b/Tests/TestData/ASHRAE.cs index f8b37b70..67a3a374 100644 --- a/Tests/TestData/ASHRAE.cs +++ b/Tests/TestData/ASHRAE.cs @@ -250,5 +250,25 @@ public static BacnetWriteAccessSpecification[] F_3_10() A(new BacnetWriteAccessSpecification.Property(BacnetPropertyIds.PROP_PRESENT_VALUE, new BacnetValue(72f)))), }; + + public static (uint TimeDuration, EnableDisable EnableDisable, string Password) + F_4_1() + => (5, EnableDisable.DISABLE, "#egbdf!"); + + public static (BacnetReinitializedStates State, string Password) + F_4_4() + => (BacnetReinitializedStates.BACNET_REINIT_WARMSTART, "AbCdEfGh"); + + public static DateTime F_4_7() + => new DateTime(1992, 11, 17, 22, 45, 30).AddMilliseconds(700); + + public static string F_4_8_Name() + => "OATemp"; + + public static BacnetObjectId F_4_8_Id() + => new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 3); + + public static (int LowLimit, int HighLimit) F_4_9() + => (3, 3); } } From 2073f1093464903611673ef0a77205760c72352a Mon Sep 17 00:00:00 2001 From: Rainer Perl Date: Mon, 5 Mar 2018 16:13:04 +0100 Subject: [PATCH 31/31] added a single test for malformed packets --- Serialize/Services/ObjectAccessServices.cs | 7 ++++- .../Services/ObjectAccessServicesTests.cs | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Serialize/Services/ObjectAccessServices.cs b/Serialize/Services/ObjectAccessServices.cs index e4aa4b59..efb19766 100644 --- a/Serialize/Services/ObjectAccessServices.cs +++ b/Serialize/Services/ObjectAccessServices.cs @@ -315,6 +315,7 @@ public static int DecodeReadPropertyAcknowledge(BacnetAddress address, byte[] bu /* Tag 0: Object ID */ if (!ASN1.decode_is_context_tag(buffer, offset, 0)) return -1; + var len = 1; len += ASN1.decode_object_id(buffer, offset + len, out BacnetObjectTypes type, out var instance); objectId = new BacnetObjectId(type, instance); @@ -322,6 +323,7 @@ public static int DecodeReadPropertyAcknowledge(BacnetAddress address, byte[] bu len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValueType); if (tagNumber != 1) return -1; + len += ASN1.decode_unsigned(buffer, offset + len, lenValueType, out property.propertyIdentifier); /* Tag 2: Optional Array Index */ var tagLen = ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValueType); @@ -342,7 +344,9 @@ public static int DecodeReadPropertyAcknowledge(BacnetAddress address, byte[] bu while (apduLen - len > 1) { tagLen = ASN1.bacapp_decode_application_data(address, buffer, offset + len, apduLen + offset, objectId.Type, (BacnetPropertyIds)property.propertyIdentifier, out var value); - if (tagLen < 0) return -1; + if (tagLen < 0) + return -1; + len += tagLen; valueList.Add(value); } @@ -352,6 +356,7 @@ public static int DecodeReadPropertyAcknowledge(BacnetAddress address, byte[] bu if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 3)) return -1; + len++; return len; diff --git a/Tests/Serialize/Services/ObjectAccessServicesTests.cs b/Tests/Serialize/Services/ObjectAccessServicesTests.cs index e0dcc2e7..09a5c548 100644 --- a/Tests/Serialize/Services/ObjectAccessServicesTests.cs +++ b/Tests/Serialize/Services/ObjectAccessServicesTests.cs @@ -351,6 +351,34 @@ public void should_decode_readpropertyrequest_ack_after_encode() Assert.That(valueList, Is.EquivalentTo(input.ValueList)); } + [TestCase(0)] + [TestCase(5)] + [TestCase(7)] + [TestCase(13)] + public void should_not_decode_malformed_readpropertyrequest_ack(int errorPos) + { + // arrange + var buffer = new EncodeBuffer(); + + var input = ASHRAE.F_3_5_Ack(); + + // act + ObjectAccessServices.EncodeReadPropertyAcknowledge(buffer, input.ObjectId, input.PropertyId, + input.ValueList, input.ArrayIndex); + + var encodedBytes = buffer.ToArray(); + unchecked + { + encodedBytes[errorPos] += 128; + } + + var retVal = ObjectAccessServices.DecodeReadPropertyAcknowledge(DummyAddress, encodedBytes, 0, + encodedBytes.Length, out var objectId, out var propertyReference, out var valueList); + + // assert + Assert.That(retVal, Is.EqualTo(-1)); + } + [Test] public void should_encode_readpropertymultiplerequest_according_to_ashrae_example() {