diff --git a/BACnet.csproj b/BACnet.csproj index 1d16323..02c5f53 100644 --- a/BACnet.csproj +++ b/BACnet.csproj @@ -62,6 +62,7 @@ + @@ -78,9 +79,12 @@ + + + @@ -108,7 +112,6 @@ - @@ -139,10 +142,15 @@ + + + + + @@ -167,7 +175,6 @@ - diff --git a/BACnet.csproj.DotSettings b/BACnet.csproj.DotSettings index 39a2495..f608791 100644 --- a/BACnet.csproj.DotSettings +++ b/BACnet.csproj.DotSettings @@ -1,6 +1,8 @@  True True + True + True True NEVER True diff --git a/BACnet.sln.DotSettings b/BACnet.sln.DotSettings index 2d443df..a1ca10f 100644 --- a/BACnet.sln.DotSettings +++ b/BACnet.sln.DotSettings @@ -1,5 +1,6 @@  APDU + ASHRAE ASN BBMD BVLC diff --git a/BACnetClient.cs b/BACnetClient.cs index 24761d4..711c702 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; @@ -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,8 +249,8 @@ 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) - 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); @@ -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.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 { @@ -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.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"); } 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}"); @@ -635,9 +635,9 @@ 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, type, service, invokeId); + APDU.EncodeComplexAck(encodedBuffer, service, invokeId, type); segments.Add(Tuple.Create(sequenceNumber, copy)); // doesn't include BVLC or NPDU } @@ -844,8 +844,8 @@ 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); - Services.EncodeWhoIsBroadcast(b, lowLimit, highLimit); + APDU.EncodeUnconfirmedServiceRequest(b, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS); + RemoteDeviceManagementServices.EncodeWhoIsBroadcast(b, lowLimit, highLimit); var sent = false; @@ -888,8 +888,8 @@ 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); + 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,8 +908,8 @@ 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); + 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,8 +922,8 @@ 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); - Services.EncodeIhaveBroadcast(b, deviceId, objId, objName); + 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,8 +935,8 @@ 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); + 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,10 +957,10 @@ 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); - Services.EncodeTimeSync(buffer, dateTime); + 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); } @@ -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,8 +1070,8 @@ 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, - 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) @@ -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, 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, propertyId, valueList, _writepriority), 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.EncodeWritePropertyMultiple(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); @@ -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); @@ -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); @@ -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); @@ -1407,17 +1407,17 @@ 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, - 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.EncodeCOVNotify(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values), waitForTransmit); } @@ -1544,8 +1544,8 @@ 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); - Services.EncodeCOVNotifyUnconfirmed(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values); + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION); + 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); @@ -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); @@ -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 => { - Services.EncodeReadPropertyAcknowledge(b, objectId, property.propertyIdentifier, property.propertyArrayIndex, value); + ObjectAccessServices.EncodeReadPropertyAcknowledge(b, objectId, (BacnetPropertyIds) property.propertyIdentifier, value, property.propertyArrayIndex); }); }); } @@ -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, (BacnetPropertyIds) property.propertyIdentifier, BacnetBitString.ConvertFromInt((uint)status), itemCount, applicationData, requestType, firstSequenceNo, property.propertyArrayIndex); }); }); } @@ -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); } @@ -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); } @@ -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/BacnetAlarmSummaryData.cs b/Base/BacnetAlarmSummaryData.cs index 87611bd..3f68f71 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/Base/BacnetBitString.cs b/Base/BacnetBitString.cs index accac10..3d81123 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/BacnetGenericTime.cs b/Base/BacnetGenericTime.cs index 322a6da..0e2b3c6 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/BacnetObjectPropertyReference.cs b/Base/BacnetObjectPropertyReference.cs new file mode 100644 index 0000000..d2ecf0e --- /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 fb9db11..b5f7a53 100644 --- a/Base/BacnetPropertyReference.cs +++ b/Base/BacnetPropertyReference.cs @@ -1,11 +1,19 @@ -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 { public uint propertyIdentifier; public uint propertyArrayIndex; /* optional */ - 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/Base/BacnetPropertyState.cs b/Base/BacnetPropertyState.cs deleted file mode 100644 index e7a7da3..0000000 --- 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/BacnetPropertyValue.cs b/Base/BacnetPropertyValue.cs index 1455e17..06f7ab4 100644 --- a/Base/BacnetPropertyValue.cs +++ b/Base/BacnetPropertyValue.cs @@ -1,8 +1,10 @@ using System.Collections.Generic; +using System.Linq; namespace System.IO.BACnet { - public struct BacnetPropertyValue + // TODO you have been flagged for refactoring due to un-C#-iness + public struct BacnetPropertyValue : IEquatable { public BacnetPropertyReference property; public IList value; @@ -12,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 11d8dec..b25e59b 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; @@ -88,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 new file mode 100644 index 0000000..84eb712 --- /dev/null +++ b/Base/BacnetWriteAccessSpecification.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace System.IO.BACnet +{ + 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 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; } + 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; + } + + 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/Base/Enums/EnableDisable.cs b/Base/Enums/EnableDisable.cs new file mode 100644 index 0000000..d91be2b --- /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/Base/Enums/EnumUtils.cs b/Base/Enums/EnumUtils.cs index 472a6bf..1139e64 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/BufferReady.cs b/Base/EventNotification/EventValues/BufferReady.cs index 15c7aa5..751c299 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 e8263ad..1138d96 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 3e513e1..4442949 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 c594630..09b7b36 100644 --- a/Base/EventNotification/EventValues/ChangeOfState.cs +++ b/Base/EventNotification/EventValues/ChangeOfState.cs @@ -1,8 +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 BacnetPropertyState NewState { get; set; } + public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_CHANGE_OF_STATE; + 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 9668eaf..9e2c189 100644 --- a/Base/EventNotification/EventValues/ChangeOfValue.cs +++ b/Base/EventNotification/EventValues/ChangeOfValue.cs @@ -1,10 +1,37 @@ +using System.Diagnostics.CodeAnalysis; + namespace System.IO.BACnet.EventNotification.EventValues { - public class ChangeOfValue : EventValuesBase + public abstract class ChangeOfValue : EventValuesBase, IHasStatusFlags { - public BacnetBitString ChangedBits { get; set; } - public float ChangeValue { get; set; } - public BacnetCOVTypes Tag { get; set; } + public override BacnetEventTypes EventType => BacnetEventTypes.EVENT_CHANGE_OF_VALUE; + 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 Create(float value) + => new ChangeOfValue(value, BacnetCOVTypes.CHANGE_OF_VALUE_REAL); + + public static ChangeOfValue Create(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"); + } + } } \ No newline at end of file diff --git a/Base/EventNotification/EventValues/EventValuesBase.cs b/Base/EventNotification/EventValues/EventValuesBase.cs index 394bd53..51711dc 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 98f5448..e5ec45e 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 ef02296..a77107e 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 b488eac..2395ab7 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 1eafa0e..df0dad9 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/Base/IHasStatusFlags.cs b/Base/IHasStatusFlags.cs new file mode 100644 index 0000000..3436fe4 --- /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 0000000..1a41c4e --- /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/APDU.cs b/Serialize/APDU.cs index 48c3a22..056c32f 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, @@ -67,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; @@ -83,7 +87,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 +106,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 +119,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 d67e94e..584c0de 100644 --- a/Serialize/ASN1.cs +++ b/Serialize/ASN1.cs @@ -1,5 +1,9 @@ using System.Collections.Generic; +using System.IO.BACnet.Base; +using System.IO.BACnet.EventNotification; +using System.IO.BACnet.EventNotification.EventValues; using System.Text; +using Microsoft.Win32; namespace System.IO.BACnet.Serialize { @@ -21,6 +25,20 @@ public class ASN1 /// public static Func CustomTagResolver; + 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); @@ -185,6 +203,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 */ @@ -362,7 +381,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 @@ -456,6 +475,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? @@ -516,6 +539,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); @@ -538,70 +600,85 @@ 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, StateTransition value, out ChangeOfState changeOfState) { - switch (value.tag) + encode_opening_tag(buffer, tagNumber); + 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: - /* FIXME: assert(0); - return a negative len? */ - break; + throw new ArgumentOutOfRangeException($"type {value.GetType()} is not supported"); } + encode_closing_tag(buffer, tagNumber); } public static void encode_context_bitstring(EncodeBuffer buffer, byte tagNumber, BacnetBitString bitString) @@ -656,6 +733,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); @@ -688,10 +768,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) @@ -704,10 +791,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; } @@ -781,6 +865,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_bacnet_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) @@ -1063,7 +1179,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; @@ -1105,6 +1235,12 @@ public static int decode_device_obj_property_ref(byte[] buffer, int offset, int 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) @@ -1127,8 +1263,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; @@ -1649,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; @@ -1703,8 +1841,9 @@ 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); - value.Value = enumeratedValue; + len = decode_unsigned(buffer, offset, lenValueType, out uint enumeratedValue); + // ReSharper disable once AssignNullToNotNullAttribute - this is checked at the top + value.Value = Enum.ToObject(enumType, enumeratedValue); break; case BacnetApplicationTags.BACNET_APPLICATION_TAG_DATE: @@ -1916,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! */ @@ -1925,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; } @@ -1939,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; @@ -1981,7 +2135,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; @@ -2009,7 +2163,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; } @@ -2118,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); @@ -2155,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); @@ -2279,7 +2451,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) @@ -2350,67 +2522,91 @@ 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)) - { - 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; 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(BacnetPropertyState); - - 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) - return -1; + throw new InvalidOperationException("expected tag not found"); var sectionLength = 0; - switch (value.tag) + switch (tag) { - case BacnetPropertyState.BacnetPropertyStateTypes.BOOLEAN_VALUE: - value.state.boolean_value = lenValueType == 1; + case 0: // bool + sectionLength = 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: - return -1; + throw new ArgumentOutOfRangeException($"tag {tag} is not supported"); } return len + sectionLength; @@ -2439,17 +2635,54 @@ 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)) { - 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); 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; + } + + 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/EncodeBuffer.cs b/Serialize/EncodeBuffer.cs index 04abb01..5397d72 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/Serialize/Services.cs b/Serialize/Services.cs deleted file mode 100644 index 7b54c2e..0000000 --- a/Serialize/Services.cs +++ /dev/null @@ -1,2659 +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 var 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 var 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; - } - - 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 = 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 var 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 var 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 var 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 var eventTypeValue); - decodedStateTransition.Add(e => e.EventType = 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 var 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 var 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 var 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() - : 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; - } - - 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 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 StateTransition changeOfValue: - ASN1.encode_opening_tag(buffer, 2); - ASN1.encode_opening_tag(buffer, 0); - - switch (changeOfValue.EventValues.Tag) - { - case BacnetCOVTypes.CHANGE_OF_VALUE_REAL: - ASN1.encode_context_real(buffer, 1, changeOfValue.EventValues.ChangeValue); - break; - 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}'"); - } - - ASN1.encode_closing_tag(buffer, 0); - ASN1.encode_context_bitstring(buffer, 1, changeOfValue.EventValues.StatusFlags); - 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 _); - - /* 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 _); - 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); - - 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 var 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 var 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 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; - 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 0000000..50bf3d9 --- /dev/null +++ b/Serialize/Services/AlarmAndEventServices.cs @@ -0,0 +1,974 @@ +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 EncodeCOVNotify(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 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; + + 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 += 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; + + /* 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) + { + 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); + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out tagNumber, out lenValue); + 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 var acknowledgedTransitions); + + var value = new BacnetAlarmSummaryData(new BacnetObjectId(type, instance), alarmState, 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.bacapp_decode_timestamp(buffer, offset + len, out var timeStamp); + value.eventTimeStamps[i] = timeStamp; + } + + 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[offset+len++] == 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 0000000..72edd22 --- /dev/null +++ b/Serialize/Services/FileAccessServices.cs @@ -0,0 +1,323 @@ +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; + 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; + } + + // 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); + 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 0000000..efb1976 --- /dev/null +++ b/Serialize/Services/ObjectAccessServices.cs @@ -0,0 +1,1057 @@ +using System.Collections.Generic; +using System.Linq; + +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); + } + + 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) + { + /* 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); + + EncodeCreateObjectInternal(buffer, valueList); + } + + private static void EncodeCreateObjectInternal(EncodeBuffer buffer, ICollection valueList) + { + if (valueList == null || valueList.Count == 0) + 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 object identifier, out ICollection valuesRefs) + { + var len = 0; + + 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; + + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var identifierTag, out lenValue); + + switch (identifierTag) + { + 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; + + 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 linkedPropertyValues = 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, objectType.Value, + (BacnetPropertyIds) propertyId, out var value); + + if (l <= 0) return -1; + len += l; + values.Add(value); + } + len++; + newEntry.value = values; + } + else + return -1; + + linkedPropertyValues.AddLast(newEntry); + } + + /* Closing tag 1 - List of Properties */ + if (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) + return -1; + len++; + + valuesRefs = linkedPropertyValues; + + return len; + } + + public static void EncodeCreateObjectAcknowledge(EncodeBuffer buffer, BacnetObjectId objectId) + { + 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; + + 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, BacnetPropertyIds 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 <= BacnetPropertyIds.MAX_BACNET_PROPERTY_ID) + { + /* check bounds so that we could create malformed + messages for testing */ + ASN1.encode_context_enumerated(buffer, 1, (uint)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, + 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_unsigned(buffer, 1, (uint)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, 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_unsigned(buffer, 1, (uint)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, + 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_unsigned(buffer, 1, (uint)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); + } + } + + /* + * 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; + 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[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); + + 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, + 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_unsigned(buffer, 1, (uint)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; + } + + // 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) + { + foreach (var objectWithProps in writeAccessSpec) + { + + ASN1.encode_context_object_id(buffer, 0, objectWithProps.ObjectId); + /* Tag 1: sequence of WriteAccessSpecification */ + ASN1.encode_opening_tag(buffer, 1); + + foreach (var prop in objectWithProps.Properties) + { + /* 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, 1); + } + } + + public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buffer, int offset, int apduLen, out IEnumerable writeAccessSpec) + { + var len = 0; + + var retVal = new List(); + + do + { + /* Context tag 0 - Object ID */ + len += ASN1.decode_tag_number_and_value(buffer, offset + len, out var tagNumber, out var lenValue); + + if (tagNumber != 0) + throw new InvalidOperationException($"expected object id"); + + 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"); + + var linkedList = new LinkedList(); + + while (!ASN1.decode_is_closing_tag_number(buffer, offset + len, 1)) + { + 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"); + + 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) + 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); + } + + 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++; + + } while (len < apduLen); + + + writeAccessSpec = retVal; + + return len; + } + + public static void EncodeWritePropertyMultiple(EncodeBuffer buffer, ICollection valueList) + { + foreach (var value in valueList) + EncodeWritePropertyMultiple(buffer, value.objectIdentifier, value.values); + } + } +} diff --git a/Serialize/Services/RemoteDeviceManagementServices.cs b/Serialize/Services/RemoteDeviceManagementServices.cs new file mode 100644 index 0000000..e77b590 --- /dev/null +++ b/Serialize/Services/RemoteDeviceManagementServices.cs @@ -0,0 +1,337 @@ +namespace System.IO.BACnet.Serialize +{ + public static class RemoteDeviceManagementServices + { + 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_unsigned(buffer, 1, (uint)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); + + 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); + } + + 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 c194ae9..e7232fb 100644 --- a/Tests/BACnet.Tests.csproj +++ b/Tests/BACnet.Tests.csproj @@ -44,12 +44,17 @@ + - + + + + + @@ -60,5 +65,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 0000000..a198f2d --- /dev/null +++ b/Tests/BACnet.Tests.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Tests/Base/BacnetObjectPropertyReferenceTests.cs b/Tests/Base/BacnetObjectPropertyReferenceTests.cs new file mode 100644 index 0000000..40b93de --- /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/Base/EventNotification/EventNotificationTests.cs b/Tests/Base/EventNotification/EventNotificationTests.cs index c993a6a..5549919 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,6 +9,11 @@ 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(NotificationData))] public void should_override_tostring(Type type) @@ -20,6 +27,22 @@ 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.Create(123.456f)); + + 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())); + } + [Test] public void should_raise_oneventnotify_when_sending_changeoflifesafety_data() { @@ -38,7 +61,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 +79,344 @@ 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 = 17.01f, + 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>(ChangeOfStateFactory.Create(value) + .SetStatusFlags(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(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); + + // 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>( + ChangeOfValueFactory + .Create(123.456f) + .SetStatusFlags(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>( + ChangeOfValueFactory + .Create(BacnetBitString.Parse("101")) + .SetStatusFlags(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 = 12.34f, + ReferenceValue = 56.78f, + SetPointValue = 91.011f, + 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 566e28f..f948390 100644 --- a/Tests/Helper.cs +++ b/Tests/Helper.cs @@ -1,13 +1,19 @@ using System.Collections; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; using NUnit.Framework; 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() + public static (BacnetClient Client1, BacnetClient Client2) CreateConnectedClients() { var transport1 = new InMemoryTransport(); var client1 = new BacnetClient(transport1); @@ -25,6 +31,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()) @@ -49,5 +63,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/Services/AlarmAndEventServicesTests.cs b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs new file mode 100644 index 0000000..55da106 --- /dev/null +++ b/Tests/Serialize/Services/AlarmAndEventServicesTests.cs @@ -0,0 +1,644 @@ +using System.Collections.Generic; +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 +{ + [TestFixture] + public class AlarmAndEventServicesTests + { + [Test] + public void should_encode_confirmendcovnotificationrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var data = ASHRAE.F_1_2(); + + // 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, + BacnetConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 15); + + 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)); + 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]); + } + + [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, 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 = ASHRAE.F_1_3(); + + // 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, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION); + + 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_encode_confirmendeventnotificationrequest_according_to_ashrae_example() + { + // arrange + var buffer = new EncodeBuffer(); + var data = ASHRAE.F_1_4(); + + // 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, + BacnetConfirmedServices.SERVICE_CONFIRMED_EVENT_NOTIFICATION, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 16); + + AlarmAndEventServices.EncodeEventNotifyData(buffer, data); + + var encodedBytes = buffer.ToArray(); + + // assert + 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() + { + // 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, 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, + BacnetConfirmedServices.SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 7); + + 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), + 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, BacnetConfirmedServices.SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, 7); + + var encodedBytes = buffer.ToArray(); + + // assert + 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 = 80.1f, + StatusFlags = BacnetBitString.Parse("1000"), + Deadband = 1.0f, + ExceededLimit = 80.0f + }) + { + 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, + BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION); + + AlarmAndEventServices.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, + 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(); + var data = ASHRAE.F_1_6(); + + // 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, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_ALARM_SUMMARY, 1); + + AlarmAndEventServices.EncodeAlarmSummary(buffer, data[0].ObjectIdentifier, data[0].AlarmState, + data[0].AcknowledgedTransitions); + + AlarmAndEventServices.EncodeAlarmSummary(buffer, data[1].ObjectIdentifier, data[1].AlarmState, + data[1].AcknowledgedTransitions); + + var encodedBytes = buffer.ToArray(); + + // assert + 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() + { + // 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, + 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 = ASHRAE.F_1_8(); + + // 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, BacnetConfirmedServices.SERVICE_CONFIRMED_GET_EVENT_INFORMATION, 1); + + AlarmAndEventServices.EncodeGetEventInformationAcknowledge(buffer, data.Data, data.MoreEvents); + + var encodedBytes = buffer.ToArray(); + + // assert + 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() + { + // 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, + BacnetConfirmedServices.SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 15); + + AlarmAndEventServices.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, 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(); + var data = ASHRAE.F_1_10(); + + // 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, + BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 15); + + AlarmAndEventServices.EncodeSubscribeCOV(buffer, data.SubscriberProcessIdentifier, + data.MonitoredObjectIdentifier, data.CancellationRequest, data.IssueConfirmedNotifications, + data.Lifetime); + + var encodedBytes = buffer.ToArray(); + + // assert + 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() + { + // 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, 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(); + var data = ASHRAE.F_1_11(); + + // 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, + BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 15); + + AlarmAndEventServices.EncodeSubscribeProperty(buffer, data.SubscriberProcessIdentifier, + data.MonitoredObjectIdentifier, data.CancellationRequest, data.IssueConfirmedNotifications, + data.Lifetime, data.MonitoredProperty, data.CovIncrementPresent, data.CovIncrement); + + var encodedBytes = buffer.ToArray(); + + // assert + 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() + { + // 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, BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, 15); + + var encodedBytes = buffer.ToArray(); + + // assert + Assert.That(encodedBytes, Is.EquivalentTo(expectedBytes)); + } + } +} \ 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 0000000..4e2cb81 --- /dev/null +++ b/Tests/Serialize/Services/FileAccessServicesTests.cs @@ -0,0 +1,311 @@ +using System.IO.BACnet.Serialize; +using System.IO.BACnet.Tests.TestData; +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, + 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 = ASHRAE.F_2_1(); + + // 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, BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, 0); + + FileAccessServices.EncodeAtomicReadFileAcknowledge(buffer, data.IsStream, data.EndOfFile, data.Position, + data.BlockCount, data.Blocks, data.Counts); + + var encodedBytes = buffer.ToArray(); + + // assert + 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() + { + // 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, + 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, + 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)); + } + + [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() + { + // 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, + 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, + 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, + 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, + BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, 85); + + FileAccessServices.EncodeAtomicWriteFileAcknowledge(buffer, false, 14); + + var encodedBytes = buffer.ToArray(); + + // 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/Serialize/Services/ObjectAccessServicesTests.cs b/Tests/Serialize/Services/ObjectAccessServicesTests.cs new file mode 100644 index 0000000..09a5c54 --- /dev/null +++ b/Tests/Serialize/Services/ObjectAccessServicesTests.cs @@ -0,0 +1,842 @@ +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; + +namespace System.IO.BACnet.Tests.Serialize +{ + [TestFixture] + class ObjectAccessServicesTests + { + [Test] + 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, + 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, + 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 input = ASHRAE.F_3_3(); + + // 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, + BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU1024, 86); + + ObjectAccessServices.EncodeCreateObject(buffer, input.ObjectType, input.ValueList); + + var encodedBytes = buffer.ToArray(); + + // assert + 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] + 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, + BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU1024, 87); + + ObjectAccessServices.EncodeDeleteObject(buffer, ASHRAE.F_3_4()); + + var encodedBytes = buffer.ToArray(); + + // assert + 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() + { + // 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(); + 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}; + + // act + APDU.EncodeConfirmedServiceRequest(buffer, + BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU50, 1); + + ObjectAccessServices.EncodeReadProperty(buffer, data.ObjectId, data.PropertyId, data.ArrayIndex); + + var encodedBytes = buffer.ToArray(); + + // assert + 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 = ASHRAE.F_3_5_Ack(); + + // 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, data.ObjectId, data.PropertyId, data.ValueList, + data.ArrayIndex); + + var encodedBytes = buffer.ToArray(); + + // assert + 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)); + } + + [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() + { + // arrange + var buffer = new EncodeBuffer(); + + var data = ASHRAE.F_3_7(); + + // 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, + BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU1024, 241); + + ObjectAccessServices.EncodeReadPropertyMultiple(buffer, data.ObjectId, data.Properties); + + var encodedBytes = buffer.ToArray(); + + // assert + 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 = ASHRAE.F_3_7_Ack(); + + // 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_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() + { + // 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, + 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(); + var data = ASHRAE.F_3_8(); + + // 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, + BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, BacnetMaxSegments.MAX_SEG0, + BacnetMaxAdpu.MAX_APDU206, 1, + type: BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST | BacnetPduTypes.SEGMENTED_RESPONSE_ACCEPTED); + + ObjectAccessServices.EncodeReadRange(buffer, data.ObjectId, data.PropertyId, data.RequestType, + data.Position, data.Time, data.Count, data.ArrayIndex); + + var encodedBytes = buffer.ToArray(); + + // assert + 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_Ack(); + + // example taken from ANNEX F - Examples of APDU Encoding - F.3.8 + var expectedBytes = new byte[] + { + 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 + APDU.EncodeComplexAck(buffer, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, 1); + + var applicationData = new EncodeBuffer(); + ObjectAccessServices.EncodeLogRecord(applicationData, data.Record1); + ObjectAccessServices.EncodeLogRecord(applicationData, data.Record2); + + ObjectAccessServices.EncodeReadRangeAcknowledge(buffer, data.ObjectId, data.PropertyId, data.Flags, + data.ItemCount, applicationData.ToArray(), data.RequestType, data.FirstSequence); + + var encodedBytes = buffer.ToArray(); + + // assert + 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_Ack(); + + // act + 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 + * + + 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 + 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])); + 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(); + var data = ASHRAE.F_3_9(); + + // 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, data.ObjectId, data.PropertyId, data.ValueList, + data.ArrayIndex, data.Priority); + + var encodedBytes = buffer.ToArray(); + + // assert + 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() + { + // 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 = ASHRAE.F_3_10(); + + // 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_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() + { + // 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)); + } + } +} diff --git a/Tests/Serialize/Services/RemoteDeviceManagementServicesTests.cs b/Tests/Serialize/Services/RemoteDeviceManagementServicesTests.cs new file mode 100644 index 0000000..e5b1e05 --- /dev/null +++ b/Tests/Serialize/Services/RemoteDeviceManagementServicesTests.cs @@ -0,0 +1,473 @@ +using System.IO.BACnet.Serialize; +using System.IO.BACnet.Tests.TestData; +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(); + var data = ASHRAE.F_4_1(); + + // 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, data.TimeDuration, + data.EnableDisable, data.Password); + + var encodedBytes = buffer.ToArray(); + + // assert + 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() + { + // 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(); + var data = ASHRAE.F_4_4(); + + // 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, data.State, data.Password); + + var encodedBytes = buffer.ToArray(); + + // assert + 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() + { + // 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, ASHRAE.F_4_7()); + + var encodedBytes = buffer.ToArray(); + + // assert + 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}; + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_HAS); + + RemoteDeviceManagementServices.EncodeWhoHasBroadcast(buffer, -1, -1, default(BacnetObjectId), data); + + var encodedBytes = buffer.ToArray(); + + // assert + 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() + { + // 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(); + 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}; + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_HAS); + + RemoteDeviceManagementServices.EncodeWhoHasBroadcast(buffer, -1, -1, data, null); + + var encodedBytes = buffer.ToArray(); + + // assert + 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 }; + + // act + APDU.EncodeUnconfirmedServiceRequest(buffer, BacnetUnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS); + + RemoteDeviceManagementServices.EncodeWhoIsBroadcast(buffer, data.LowLimit, data.HighLimit); + + var encodedBytes = buffer.ToArray(); + + // assert + 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() + { + // 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 +")); + } + } +} diff --git a/Tests/Serialize/ServicesTests.cs b/Tests/Serialize/ServicesTests.cs deleted file mode 100644 index c3ad570..0000000 --- a/Tests/Serialize/ServicesTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -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/TestData/ASHRAE.cs b/Tests/TestData/ASHRAE.cs new file mode 100644 index 0000000..67a3a37 --- /dev/null +++ b/Tests/TestData/ASHRAE.cs @@ -0,0 +1,274 @@ +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.Text; +using static System.IO.BACnet.Tests.Helper; + +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 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 + ) F_3_8_Ack() + { + 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); + } + + 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); + } + + 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()); + } + + 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)))), + }; + + 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); + } +}