Skip to content

Conversation

edwardneal
Copy link
Contributor

Description

This should cover all necessary functional changes to TdsParser. With these changes merged, the class as a whole should be ready to merge after TdsParserStateObject is dealt with.

Not all of these changes are functional - some are simply necessary to see the real ones clearly. Possibly impactful commits here are:

  • 878fcd4: when TdsParser was writing ints and there was space in the packet buffer, WriteInt would write the bytes to a stackalloc'd Span<byte> and then copy into the buffer. I've eliminated the unnecessary intermediary span, and we now write directly into the buffer.
  • a8be2f1: netfx had more detailed error messages when the parsing of a TDS stream failed. This ports those messages over to netcore.
  • 01b02db: netfx will currently enforce the validation of a server's SSL certificate if Trust Server Certificate is false and the connection's AccessToken or AccessTokenCallback is set, but netcore doesn't currently check the AccessTokenCallback property. This commit changes the behaviour of netcore to match netfx.

There's also one other commit (6d98357) which looks more impactful than it is. s_xmlMetadataSubstituteSequence and s_jsonMetadataSubstituteSequence are the same length, so there's no runtime impact here.

Issues

Relates to #1261.

Testing

Unit tests passed locally.

netcore: removed a simple WriteInt which did nothing besides call BinaryPrimitives.
netcore: when there's space in the packet buffer, WriteInt will now write to it directly (rather than write to a stack-allocated span and copy.)
This removes the need for TdsParser.netcore.cs.
Messages of the netfx exceptions are more detailed
Added mask to netcore logic.
Also removed mask on _encryptionOption from netfx - this will never be outside the range of EncryptionOptions.OPTIONS_MASK.
… is set and certificate is not automatically trusted
This passed state around, but never passed enough state to remove the state machine. Sync with netcore.
@edwardneal edwardneal requested a review from a team as a code owner August 10, 2025 20:33
@@ -10342,27 +10377,23 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet
// This is in its own method to avoid always allocating the lambda in TDSExecuteRPCParameter
private void TDSExecuteRPCParameterSetupWriteCompletion(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout, bool inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, bool isCommandProc, bool sync, TaskCompletionSource<object> completion, int startRpc, int startParam, Task writeParamTask)
{
AsyncHelper.ContinueTaskWithState(
AsyncHelper.ContinueTask(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we want the state parameter anymore?

Copy link
Contributor Author

@edwardneal edwardneal Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd switched away from using a state parameter because it wasn't doing anything and it wasn't present in netcore. We pass this as the state, cast it back to a TdsParser, then don't use it anywhere in the lambda expression (which closes over another 11 variables); it's used once in the failure path (which closes over another variable, so can't currently be made static either.)

The halfway house doesn't really help anyone, but I'm happy enough to reintroduce the state parameter and use a tuple in both places if you'd prefer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, if it's not used downstream that's fine. The netore version is typically better than the netfx.

@@ -11160,7 +11191,7 @@ internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int coun
stateObj.WriteByte(md.scale);
break;
case SqlDbTypeExtensions.Json:
stateObj.WriteByteArray(s_jsonMetadataSubstituteSequence, s_xmlMetadataSubstituteSequence.Length, 0);
stateObj.WriteByteArray(s_jsonMetadataSubstituteSequence, s_jsonMetadataSubstituteSequence.Length, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any test coverage for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect it to be implicitly covered by the SqlBulkCopy tests which handle JSON data. The variables in question are on lines 137-141 (link), and we can verify that they're the same length directly.

Comment on lines 7344 to 7345
WriteGuid((Guid)value, stateObj);
Debug.Assert(length == 16, "Invalid length for guid type in com+ object");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably invert these lines. Generally asserts come before the work they are preconditions for.

@paulmedynski paulmedynski self-assigned this Aug 27, 2025
@paulmedynski
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 2 pipeline(s).

Copy link
Contributor

@paulmedynski paulmedynski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, with a few questions/suggestions.

Remove unnecessary conditional compilation.
Add XML documentation to clarify SerializeShort, WriteShort et al.
@paulmedynski
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@mdaigle mdaigle self-assigned this Aug 28, 2025
paulmedynski
paulmedynski previously approved these changes Aug 28, 2025
//
// Writes a guid, either to a specific buffer or to the wire.
//
private static void SerializeGuid(in Guid v, Span<byte> buffer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would like to see some unit tests for these to ensure they behave the same between netfx and netcore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The methods which write/serialize the numeric types are covered by the existing tests, but I've found and widened ParametersTest.Test_WithGuidValue_ShouldReturnGuid to handle SqlGuids (with and without values) to ensure these are covered.

@@ -13841,8 +13841,7 @@ internal string TraceString()
_statistics == null ? bool.TrueString : bool.FalseString,
_statisticsIsInTransaction ? bool.TrueString : bool.FalseString,
_fPreserveTransaction ? bool.TrueString : bool.FalseString,
_connHandler == null ? "(null)" : _connHandler.ConnectionOptions.MultiSubnetFailover.ToString((IFormatProvider)null),
_connHandler == null ? "(null)" : _connHandler.ConnectionOptions.TransparentNetworkIPResolution.ToString((IFormatProvider)null));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this get reintroduced under an #if NETFRAMEWORK somewhere? This option is still valid for netfx

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The option's still valid, but the format string doesn't actually include an identifier referencing this argument. We could either remove the unused argument (current option) or change the format string so that it uses the argument - I've got no strong preference either way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add it to the format string. The trace message is misleading as-is because the parallel connectivity behavior can still be enabled (via TNIR) even if multisubnetfailover is disabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I've changed the format string to clear up whether the setting is for MSF or TNIR, although netcore will just print a hardcoded false for the latter.

@@ -11160,7 +11191,7 @@ internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int coun
stateObj.WriteByte(md.scale);
break;
case SqlDbTypeExtensions.Json:
stateObj.WriteByteArray(s_jsonMetadataSubstituteSequence, s_xmlMetadataSubstituteSequence.Length, 0);
stateObj.WriteByteArray(s_jsonMetadataSubstituteSequence, s_jsonMetadataSubstituteSequence.Length, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any test coverage for this?

Both platforms will now show the TransparentNetworkIPResolution status. This is always disabled on netcore.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Inconsistent server certificate validation between .NET Core and .NET Framework
4 participants