Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some .NET 9 maintenance and JSON encoder improvements #2922

Merged
merged 17 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyName>Opc.Ua.Security.Certificates</AssemblyName>
<TargetFrameworks>$(LibCoreTargetFrameworks)</TargetFrameworks>
Expand Down Expand Up @@ -42,7 +41,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Formats.Asn1" Version="8.0.1" />
<PackageReference Include="System.Formats.Asn1" Version="9.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
Expand All @@ -53,6 +52,10 @@
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'net9.0' ">
<PackageReference Include="Microsoft.Bcl.Cryptography" Version="9.0.0" />
</ItemGroup>

<Target Name="GetPackagingOutputs" />

</Project>

This file was deleted.

138 changes: 118 additions & 20 deletions Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -704,21 +704,37 @@
}
}

#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
private void WriteSimpleField(string fieldName, string value)
{
if (!string.IsNullOrEmpty(fieldName))
// unlike Span<byte>, Span<char> can not become null, handle the case here
if (value == null)
{
if (value == null)
{
return;
}
WriteSimpleFieldNull(fieldName);
return;
}

WriteSimpleField(fieldName, value.AsSpan(), EscapeOptions.None);
}

private void WriteSimpleField(string fieldName, ReadOnlySpan<char> value, EscapeOptions options = EscapeOptions.None)
{
if (!string.IsNullOrEmpty(fieldName))
{
if (m_commaRequired)
{
m_writer.Write(s_comma);
}

EscapeString(fieldName);
if ((options & EscapeOptions.NoFieldNameEscape) == EscapeOptions.NoFieldNameEscape)
mregen marked this conversation as resolved.
Show resolved Hide resolved
{
m_writer.Write(s_quotation);
m_writer.Write(fieldName);
}
else
{
EscapeString(fieldName);
}
m_writer.Write(s_quotationColon);
}
else
Expand All @@ -729,19 +745,28 @@
}
}

if (value != null)
if ((options & EscapeOptions.Quotes) == EscapeOptions.Quotes)
{
m_writer.Write(value);
if ((options & EscapeOptions.NoValueEscape) == EscapeOptions.NoValueEscape)
{
m_writer.Write(s_quotation);
m_writer.Write(value);
}
else
{
EscapeString(value);
}
m_writer.Write(s_quotation);
}
else
{
m_writer.Write(s_null);
m_writer.Write(value);
}

m_commaRequired = true;
}

private void WriteSimpleField(string fieldName, string value, EscapeOptions options)
#else
private void WriteSimpleField(string fieldName, string value, EscapeOptions options = EscapeOptions.None)
{
if (!string.IsNullOrEmpty(fieldName))
{
Expand Down Expand Up @@ -801,6 +826,7 @@

m_commaRequired = true;
}
#endif

/// <summary>
/// Writes a boolean to the stream.
Expand Down Expand Up @@ -1081,7 +1107,7 @@
/// </summary>
public void WriteByteString(string fieldName, ReadOnlySpan<byte> value)
{
if (value == null)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-macOS-latest-Client.ComplexTypes

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-macOS-latest-Configuration

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-macOS-latest-Configuration

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-ubuntu-latest-Configuration

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-ubuntu-latest-Configuration

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-ubuntu-latest-Security.Certificates

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-ubuntu-latest-Security.Certificates

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-macOS-latest-Gds

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-ubuntu-latest-Client.ComplexTypes

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Configuration

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Configuration

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-ubuntu-latest-Gds

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-ubuntu-latest-PubSub

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-ubuntu-latest-PubSub

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-ubuntu-latest-Server

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Security.Certificates

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Security.Certificates

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Gds

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-ubuntu-latest-Core

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-ubuntu-latest-Core

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-macOS-latest-Security.Certificates

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-macOS-latest-Security.Certificates

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Client.ComplexTypes

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-macOS-latest-PubSub

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-macOS-latest-PubSub

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-PubSub

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-PubSub

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Core

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Core

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-macOS-latest-Server

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Server

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-macOS-latest-Client

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-ubuntu-latest-Client

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Client

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-macOS-latest-Core

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)

Check warning on line 1110 in Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs

View workflow job for this annotation

GitHub Actions / test-macOS-latest-Core

Comparing a span to 'null' might be redundant, the 'null' literal will be implicitly converted to a 'Span<T>.Empty' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2265)
{
WriteSimpleFieldNull(fieldName);
return;
Expand All @@ -1093,7 +1119,33 @@
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}

WriteSimpleField(fieldName, Convert.ToBase64String(value), EscapeOptions.Quotes | EscapeOptions.NoValueEscape);
if (value.Length > 0)
{
const int maxStackLimit = 1024;
int length = value.Length << 1;
char[] arrayPool = null;
Span<char> chars = length <= maxStackLimit ?
stackalloc char[length] :
(arrayPool = ArrayPool<char>.Shared.Rent(length)).AsSpan(0, length);
try
{
bool success = Convert.TryToBase64Chars(value, chars, out int charsWritten, Base64FormattingOptions.None);
if (success)
{
WriteSimpleField(fieldName, chars.Slice(0, charsWritten), EscapeOptions.Quotes | EscapeOptions.NoValueEscape);
return;
}
}
finally
{
if (arrayPool != null)
{
ArrayPool<char>.Shared.Return(arrayPool);
}
}
}

WriteSimpleField(fieldName, "\"\"");
}
#endif

Expand Down Expand Up @@ -1606,7 +1658,7 @@
{
PushStructure(fieldName);

value?.Encode(this);
value.Encode(this);

PopStructure();
}
Expand Down Expand Up @@ -2771,7 +2823,12 @@
}
else
{
#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
Span<char> valueString = stackalloc char[DateTimeRoundTripKindLength];
WriteSimpleField(fieldName, ConvertUniversalTimeToString(value, valueString), escapeOptions | EscapeOptions.Quotes);
#else
WriteSimpleField(fieldName, ConvertUniversalTimeToString(value), escapeOptions | EscapeOptions.Quotes);
#endif
}
}

Expand Down Expand Up @@ -2991,19 +3048,59 @@
m_nestingLevel++;
}

// The length of the DateTime string encoded by "o"
internal const int DateTimeRoundTripKindLength = 28;
// the index of the last digit which can be omitted if 0
const int DateTimeRoundTripKindLastDigit = DateTimeRoundTripKindLength - 2;
// the index of the first digit which can be omitted (7 digits total)
const int DateTimeRoundTripKindFirstDigit = DateTimeRoundTripKindLastDigit - 7;

/// <summary>
/// Write Utc time in the format "yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK".
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static string ConvertUniversalTimeToString(DateTime value)
#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
internal static ReadOnlySpan<char> ConvertUniversalTimeToString(DateTime value, Span<char> valueString)
{
// The length of the DateTime string encoded by "o"
const int DateTimeRoundTripKindLength = 28;
// the index of the last digit which can be omitted if 0
const int DateTimeRoundTripKindLastDigit = DateTimeRoundTripKindLength - 2;
// the index of the first digit which can be omitted (7 digits total)
const int DateTimeRoundTripKindFirstDigit = DateTimeRoundTripKindLastDigit - 7;
// Note: "o" is a shortcut for "yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK" and implicitly
// uses invariant culture and gregorian calendar, but executes up to 10 times faster.
// But in contrary to the explicit format string, trailing zeroes are not omitted!
if (value.Kind != DateTimeKind.Utc)
{
value.ToUniversalTime().TryFormat(valueString, out int charsWritten, "o", CultureInfo.InvariantCulture);
}
else
{
value.TryFormat(valueString, out int charsWritten, "o", CultureInfo.InvariantCulture);
}

// check if trailing zeroes can be omitted
int i = DateTimeRoundTripKindLastDigit;
while (i > DateTimeRoundTripKindFirstDigit)
{
if (valueString[i] != '0')
{
break;
}
i--;
}

if (i < DateTimeRoundTripKindLastDigit)
{
// check if the decimal point has to be removed too
if (i == DateTimeRoundTripKindFirstDigit)
{
i--;
}
valueString[i + 1] = 'Z';
return valueString.Slice(0, i + 2);
}

return valueString;
}
#else
internal static string ConvertUniversalTimeToString(DateTime value)
{
// Note: "o" is a shortcut for "yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK" and implicitly
// uses invariant culture and gregorian calendar, but executes up to 10 times faster.
// But in contrary to the explicit format string, trailing zeroes are not omitted!
Expand Down Expand Up @@ -3032,6 +3129,7 @@

return valueString;
}
#endif
#endregion
}
}
4 changes: 1 addition & 3 deletions Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -738,9 +738,7 @@ public async Task PublishRequestCount()
};

subscription.FastDataChangeCallback = (_, notification, __) => {
notification.MonitoredItems.ForEach(item => {
Interlocked.Increment(ref numOfNotifications);
});
Interlocked.Add(ref numOfNotifications, notification.MonitoredItems.Count);
};

subscriptionList.Add(subscription);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public IList<ApplicationTestData> ApplicationTestSet(int count)
return testDataSet;
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.", Justification = "Test")]
private ApplicationTestData RandomApplicationTestData()
{
// TODO: set to discoveryserver
Expand Down
Loading
Loading