Skip to content

Commit

Permalink
Added default value DefaultMaxChunkCount (#1789)
Browse files Browse the repository at this point in the history
* Added default values DefaultRequestMaxChunkCount and DefaultResponseMaxChunkCount
* The negotiated Request and Response MaxChunkCount values are checked when building a chunked message.
  • Loading branch information
mrsuciu committed May 8, 2022
1 parent 51549f5 commit 71af2c1
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 19 deletions.
52 changes: 52 additions & 0 deletions Stack/Opc.Ua.Core/Stack/Tcp/ChannelQuotas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -163,12 +165,62 @@ public int SecurityTokenLifetime
}
}
}

/// <summary>
/// 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.
/// </summary>
public int MaxRequestChunkCount
{
get
{
lock (m_lock)
{
return m_maxRequestChunkCount;
}
}

set
{
lock (m_lock)
{
m_maxRequestChunkCount = value;
}
}
}

/// <summary>
/// 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.
/// </summary>
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;
Expand Down
7 changes: 6 additions & 1 deletion Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,15 @@ public static class TcpMessageLimits
/// </summary>
public const int DefaultMaxBufferSize = 65535;

/// <summary>
/// The default maximum chunk count for Request and Response messages.
/// </summary>
public const int DefaultMaxChunkCount = 16;

/// <summary>
/// The default maximum message size.
/// </summary>
public const int DefaultMaxMessageSize = 16 * 65535;
public const int DefaultMaxMessageSize = DefaultMaxChunkCount * DefaultMaxBufferSize;

/// <summary>
/// How long a connection will remain in the server after it goes into a faulted state.
Expand Down
23 changes: 16 additions & 7 deletions Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ private bool ProcessOpenSecureChannelRequest(uint messageType, ArraySegment<byte
// check if it is necessary to wait for more chunks.
if (!TcpMessageType.IsFinal(messageType))
{
SaveIntermediateChunk(requestId, messageBody);
SaveIntermediateChunk(requestId, messageBody, true);
return false;
}

Expand All @@ -564,7 +564,7 @@ private bool ProcessOpenSecureChannelRequest(uint messageType, ArraySegment<byte
token.ServerNonce = CreateNonce();

// get the chunks to process.
chunksToProcess = GetSavedChunks(requestId, messageBody);
chunksToProcess = GetSavedChunks(requestId, messageBody, true);

OpenSecureChannelRequest request = (OpenSecureChannelRequest)BinaryDecoder.DecodeMessage(
new ArraySegmentStream(chunksToProcess),
Expand Down Expand Up @@ -806,12 +806,12 @@ private bool ProcessCloseSecureChannelRequest(uint messageType, ArraySegment<byt
// check if it is necessary to wait for more chunks.
if (!TcpMessageType.IsFinal(messageType))
{
SaveIntermediateChunk(requestId, messageBody);
SaveIntermediateChunk(requestId, messageBody, true);
return false;
}

// get the chunks to process.
chunksToProcess = GetSavedChunks(requestId, messageBody);
chunksToProcess = GetSavedChunks(requestId, messageBody, true);

CloseSecureChannelRequest request = BinaryDecoder.DecodeMessage(
new ArraySegmentStream(chunksToProcess),
Expand Down Expand Up @@ -899,21 +899,21 @@ private bool ProcessRequestMessage(uint messageType, ArraySegment<byte> 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;
Expand Down Expand Up @@ -953,6 +953,15 @@ private bool ProcessRequestMessage(uint messageType, ArraySegment<byte> messageC
}
}
}

/// <summary>
/// Closes the channel in case the message limits have been exceeded
/// </summary>
protected override void DoMessageLimitsExceeded()
{
base.DoMessageLimitsExceeded();
ChannelClosed();
}
#endregion

#region Private Fields
Expand Down
8 changes: 8 additions & 0 deletions Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 24 additions & 4 deletions Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ public UaSCUaBinaryChannel(
m_maxRequestMessageSize = quotas.MaxMessageSize;
m_maxResponseMessageSize = quotas.MaxMessageSize;

m_maxRequestChunkCount = quotas.MaxRequestChunkCount;
m_maxResponseChunkCount = quotas.MaxResponseChunkCount;

CalculateSymmetricKeySizes();
}
#endregion
Expand Down Expand Up @@ -250,14 +253,16 @@ protected bool VerifySequenceNumber(uint sequenceNumber, string context)
/// <summary>
/// Saves an intermediate chunk for an incoming message.
/// </summary>
protected void SaveIntermediateChunk(uint requestId, ArraySegment<byte> chunk)
protected void SaveIntermediateChunk(uint requestId, ArraySegment<byte> 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)
{
Expand All @@ -267,6 +272,12 @@ protected void SaveIntermediateChunk(uint requestId, ArraySegment<byte> chunk)
m_partialMessageChunks.Release(BufferManager, "SaveIntermediateChunk");
}

if (chunkOrSizeLimitsExceeded)
{
DoMessageLimitsExceeded();
return;
}

if (requestId != 0)
{
m_partialRequestId = requestId;
Expand All @@ -277,13 +288,21 @@ protected void SaveIntermediateChunk(uint requestId, ArraySegment<byte> chunk)
/// <summary>
/// Returns the chunks saved for message.
/// </summary>
protected BufferCollection GetSavedChunks(uint requestId, ArraySegment<byte> chunk)
protected BufferCollection GetSavedChunks(uint requestId, ArraySegment<byte> chunk, bool isServerContext)
{
SaveIntermediateChunk(requestId, chunk);
SaveIntermediateChunk(requestId, chunk, isServerContext);
BufferCollection savedChunks = m_partialMessageChunks;
m_partialMessageChunks = null;
return savedChunks;
}

/// <summary>
/// Code executed when the
/// </summary>
protected virtual void DoMessageLimitsExceeded()
{
Utils.LogError("ChannelId {0}: - Message limits exceeded while building up message. Channel will be closed", ChannelId);
}
#endregion

#region IMessageSink Members
Expand Down Expand Up @@ -716,6 +735,7 @@ protected uint ChannelId
m_globalChannelId = Utils.Format("{0}-{1}", m_contextId, m_channelId);
}
}

#endregion

#region WriteOperation Class
Expand Down
24 changes: 17 additions & 7 deletions Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -590,12 +590,12 @@ private bool ProcessOpenSecureChannelResponse(uint messageType, ArraySegment<byt
// check if it is necessary to wait for more chunks.
if (!TcpMessageType.IsFinal(messageType))
{
SaveIntermediateChunk(requestId, messageBody);
SaveIntermediateChunk(requestId, messageBody, false);
return false;
}

// get the chunks to process.
chunksToProcess = GetSavedChunks(requestId, messageBody);
chunksToProcess = GetSavedChunks(requestId, messageBody, false);

// read message body.
OpenSecureChannelResponse response = ParseResponse(chunksToProcess) as OpenSecureChannelResponse;
Expand Down Expand Up @@ -666,6 +666,16 @@ private bool ProcessOpenSecureChannelResponse(uint messageType, ArraySegment<byt

return false;
}

/// <summary>
/// Closes the channel in case the message limits have been exceeded
/// </summary>
protected override void DoMessageLimitsExceeded()
{
base.DoMessageLimitsExceeded();
Shutdown(new ServiceResult(StatusCodes.BadResponseTooLarge));
}

#endregion

#region Event Handlers
Expand Down Expand Up @@ -996,7 +1006,7 @@ private void Shutdown(ServiceResult reason)
lock (DataLock)
{
// clear an unprocessed chunks.
SaveIntermediateChunk(0, new ArraySegment<byte>());
SaveIntermediateChunk(0, new ArraySegment<byte>(), false);

// halt any scheduled tasks.
if (m_handshakeTimer != null)
Expand Down Expand Up @@ -1087,7 +1097,7 @@ private void ForceReconnect(ServiceResult reason)
}

// clear an unprocessed chunks.
SaveIntermediateChunk(0, new ArraySegment<byte>());
SaveIntermediateChunk(0, new ArraySegment<byte>(), false);

// halt any scheduled tasks.
if (m_handshakeTimer != null)
Expand Down Expand Up @@ -1385,7 +1395,7 @@ private bool ProcessResponseMessage(uint messageType, ArraySegment<byte> 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);
Expand All @@ -1401,12 +1411,12 @@ private bool ProcessResponseMessage(uint messageType, ArraySegment<byte> 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);
Expand Down
7 changes: 7 additions & 0 deletions Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 71af2c1

Please sign in to comment.