From 71af2c1d15eff7702e5f8e1cba15a24d6cf061ff Mon Sep 17 00:00:00 2001 From: Suciu Mircea Adrian Date: Sun, 8 May 2022 06:54:09 +0300 Subject: [PATCH] Added default value DefaultMaxChunkCount (#1789) * Added default values DefaultRequestMaxChunkCount and DefaultResponseMaxChunkCount * The negotiated Request and Response MaxChunkCount values are checked when building a chunked message. --- Stack/Opc.Ua.Core/Stack/Tcp/ChannelQuotas.cs | 52 +++++++++++++++++++ Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs | 7 ++- .../Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs | 23 +++++--- .../Stack/Tcp/TcpTransportListener.cs | 8 +++ .../Stack/Tcp/UaSCBinaryChannel.cs | 28 ++++++++-- .../Stack/Tcp/UaSCBinaryClientChannel.cs | 24 ++++++--- .../Stack/Tcp/UaSCBinaryTransportChannel.cs | 7 +++ 7 files changed, 130 insertions(+), 19 deletions(-) diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelQuotas.cs b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelQuotas.cs index 8e72bb147..74d7a1391 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelQuotas.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelQuotas.cs @@ -26,6 +26,8 @@ public ChannelQuotas() m_messageContext = ServiceMessageContext.GlobalContext; m_maxMessageSize = TcpMessageLimits.DefaultMaxMessageSize; m_maxBufferSize = TcpMessageLimits.DefaultMaxMessageSize; + m_maxRequestChunkCount = TcpMessageLimits.DefaultMaxChunkCount; + m_maxResponseChunkCount = TcpMessageLimits.DefaultMaxChunkCount; m_channelLifetime = TcpMessageLimits.DefaultChannelLifetime; m_securityTokenLifetime = TcpMessageLimits.DefaultSecurityTokenLifeTime; } @@ -163,12 +165,62 @@ public int SecurityTokenLifetime } } } + + /// + /// The maximum number of chunks in any request message (used in the Acknowledge Message). + /// The Client shall abort the Message with a Bad_RequestTooLarge StatusCode if a request Message exceeds this value. + /// A value of zero indicates that the Server has no limit. + /// + public int MaxRequestChunkCount + { + get + { + lock (m_lock) + { + return m_maxRequestChunkCount; + } + } + + set + { + lock (m_lock) + { + m_maxRequestChunkCount = value; + } + } + } + + /// + /// The maximum number of chunks in any response message (used in the Hello Message). + /// The Server shall abort the Message with a Bad_ResponseTooLarge Error Message if a response Message exceeds this value. + /// A value of zero indicates that the Client has no limit. + /// + public int MaxResponseChunkCount + { + get + { + lock (m_lock) + { + return m_maxResponseChunkCount; + } + } + + set + { + lock (m_lock) + { + m_maxResponseChunkCount = value; + } + } + } #endregion #region Private Fields private object m_lock = new object(); private int m_maxMessageSize; private int m_maxBufferSize; + private int m_maxRequestChunkCount; + private int m_maxResponseChunkCount; private int m_channelLifetime; private int m_securityTokenLifetime; private IServiceMessageContext m_messageContext; diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs index 6a93146da..9e7496ef0 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs @@ -238,10 +238,15 @@ public static class TcpMessageLimits /// public const int DefaultMaxBufferSize = 65535; + /// + /// The default maximum chunk count for Request and Response messages. + /// + public const int DefaultMaxChunkCount = 16; + /// /// The default maximum message size. /// - public const int DefaultMaxMessageSize = 16 * 65535; + public const int DefaultMaxMessageSize = DefaultMaxChunkCount * DefaultMaxBufferSize; /// /// How long a connection will remain in the server after it goes into a faulted state. diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs index f95289137..652c2004e 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs @@ -553,7 +553,7 @@ private bool ProcessOpenSecureChannelRequest(uint messageType, ArraySegment messageC if (TcpMessageType.IsAbort(messageType)) { Utils.LogWarning(TraceMasks.ServiceDetail, "ChannelId {0}: ProcessRequestMessage RequestId {1} was aborted.", ChannelId, requestId); - chunksToProcess = GetSavedChunks(requestId, messageBody); + chunksToProcess = GetSavedChunks(requestId, messageBody, true); return true; } // check if it is necessary to wait for more chunks. if (!TcpMessageType.IsFinal(messageType)) { - SaveIntermediateChunk(requestId, messageBody); + SaveIntermediateChunk(requestId, messageBody, true); return true; } // Utils.LogTrace("ChannelId {0}: ProcessRequestMessage RequestId {1}", ChannelId, requestId); // get the chunks to process. - chunksToProcess = GetSavedChunks(requestId, messageBody); + chunksToProcess = GetSavedChunks(requestId, messageBody, true); // decode the request. IServiceRequest request = BinaryDecoder.DecodeMessage(new ArraySegmentStream(chunksToProcess), null, Quotas.MessageContext) as IServiceRequest; @@ -953,6 +953,15 @@ private bool ProcessRequestMessage(uint messageType, ArraySegment messageC } } } + + /// + /// Closes the channel in case the message limits have been exceeded + /// + protected override void DoMessageLimitsExceeded() + { + base.DoMessageLimitsExceeded(); + ChannelClosed(); + } #endregion #region Private Fields diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs index 4277a6526..f37cf8c1a 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs @@ -123,6 +123,14 @@ public void Open( m_quotas.MaxMessageSize = configuration.MaxMessageSize; m_quotas.ChannelLifetime = configuration.ChannelLifetime; m_quotas.SecurityTokenLifetime = configuration.SecurityTokenLifetime; + + if (configuration.MaxBufferSize != 0) + { + int maxChunkCount = configuration.MaxMessageSize / configuration.MaxBufferSize; + m_quotas.MaxRequestChunkCount = maxChunkCount; + m_quotas.MaxResponseChunkCount = maxChunkCount; + } + messageContext.MaxArrayLength = configuration.MaxArrayLength; messageContext.MaxByteStringLength = configuration.MaxByteStringLength; messageContext.MaxMessageSize = configuration.MaxMessageSize; diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs index faa15d185..a1b0bbb7c 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs @@ -125,6 +125,9 @@ public UaSCUaBinaryChannel( m_maxRequestMessageSize = quotas.MaxMessageSize; m_maxResponseMessageSize = quotas.MaxMessageSize; + m_maxRequestChunkCount = quotas.MaxRequestChunkCount; + m_maxResponseChunkCount = quotas.MaxResponseChunkCount; + CalculateSymmetricKeySizes(); } #endregion @@ -250,14 +253,16 @@ protected bool VerifySequenceNumber(uint sequenceNumber, string context) /// /// Saves an intermediate chunk for an incoming message. /// - protected void SaveIntermediateChunk(uint requestId, ArraySegment chunk) + protected void SaveIntermediateChunk(uint requestId, ArraySegment chunk, bool isServerContext) { if (m_partialMessageChunks == null) { m_partialMessageChunks = new BufferCollection(); } - if (m_partialRequestId != requestId) + bool chunkOrSizeLimitsExceeded = MessageLimitsExceeded(isServerContext, m_partialMessageChunks.TotalSize, m_partialMessageChunks.Count); + + if ((m_partialRequestId != requestId) || chunkOrSizeLimitsExceeded) { if (m_partialMessageChunks.Count > 0) { @@ -267,6 +272,12 @@ protected void SaveIntermediateChunk(uint requestId, ArraySegment chunk) m_partialMessageChunks.Release(BufferManager, "SaveIntermediateChunk"); } + if (chunkOrSizeLimitsExceeded) + { + DoMessageLimitsExceeded(); + return; + } + if (requestId != 0) { m_partialRequestId = requestId; @@ -277,13 +288,21 @@ protected void SaveIntermediateChunk(uint requestId, ArraySegment chunk) /// /// Returns the chunks saved for message. /// - protected BufferCollection GetSavedChunks(uint requestId, ArraySegment chunk) + protected BufferCollection GetSavedChunks(uint requestId, ArraySegment chunk, bool isServerContext) { - SaveIntermediateChunk(requestId, chunk); + SaveIntermediateChunk(requestId, chunk, isServerContext); BufferCollection savedChunks = m_partialMessageChunks; m_partialMessageChunks = null; return savedChunks; } + + /// + /// Code executed when the + /// + protected virtual void DoMessageLimitsExceeded() + { + Utils.LogError("ChannelId {0}: - Message limits exceeded while building up message. Channel will be closed", ChannelId); + } #endregion #region IMessageSink Members @@ -716,6 +735,7 @@ protected uint ChannelId m_globalChannelId = Utils.Format("{0}-{1}", m_contextId, m_channelId); } } + #endregion #region WriteOperation Class diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs index b080f76bf..ac35df586 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs @@ -590,12 +590,12 @@ private bool ProcessOpenSecureChannelResponse(uint messageType, ArraySegment + /// Closes the channel in case the message limits have been exceeded + /// + protected override void DoMessageLimitsExceeded() + { + base.DoMessageLimitsExceeded(); + Shutdown(new ServiceResult(StatusCodes.BadResponseTooLarge)); + } + #endregion #region Event Handlers @@ -996,7 +1006,7 @@ private void Shutdown(ServiceResult reason) lock (DataLock) { // clear an unprocessed chunks. - SaveIntermediateChunk(0, new ArraySegment()); + SaveIntermediateChunk(0, new ArraySegment(), false); // halt any scheduled tasks. if (m_handshakeTimer != null) @@ -1087,7 +1097,7 @@ private void ForceReconnect(ServiceResult reason) } // clear an unprocessed chunks. - SaveIntermediateChunk(0, new ArraySegment()); + SaveIntermediateChunk(0, new ArraySegment(), false); // halt any scheduled tasks. if (m_handshakeTimer != null) @@ -1385,7 +1395,7 @@ private bool ProcessResponseMessage(uint messageType, ArraySegment message if (TcpMessageType.IsAbort(messageType)) { // get the chunks to process. - chunksToProcess = GetSavedChunks(requestId, messageBody); + chunksToProcess = GetSavedChunks(requestId, messageBody, false); // decoder reason. MemoryStream istrm = new MemoryStream(messageBody.Array, messageBody.Offset, messageBody.Count, false); @@ -1401,12 +1411,12 @@ private bool ProcessResponseMessage(uint messageType, ArraySegment message // check if it is necessary to wait for more chunks. if (!TcpMessageType.IsFinal(messageType)) { - SaveIntermediateChunk(requestId, messageBody); + SaveIntermediateChunk(requestId, messageBody, false); return true; } // get the chunks to process. - chunksToProcess = GetSavedChunks(requestId, messageBody); + chunksToProcess = GetSavedChunks(requestId, messageBody, false); // get response. operation.MessageBody = ParseResponse(chunksToProcess); diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs index 8ae631b4b..08bb85b28 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs @@ -404,6 +404,13 @@ private void SaveSettings(Uri url, TransportChannelSettings settings) m_quotas.ChannelLifetime = m_settings.Configuration.ChannelLifetime; m_quotas.SecurityTokenLifetime = m_settings.Configuration.SecurityTokenLifetime; + if (m_settings.Configuration.MaxBufferSize != 0) + { + int maxChunkCount = m_settings.Configuration.MaxMessageSize / m_settings.Configuration.MaxBufferSize; + m_quotas.MaxRequestChunkCount = maxChunkCount; + m_quotas.MaxResponseChunkCount = maxChunkCount; + } + m_quotas.MessageContext = new ServiceMessageContext() { MaxArrayLength = m_settings.Configuration.MaxArrayLength, MaxByteStringLength = m_settings.Configuration.MaxByteStringLength,