Skip to content

Commit c805b8f

Browse files
author
Javad
authored
Fix | SqlLocalDB instance pipename issue with Encryption (#1433)
1 parent 2f50297 commit c805b8f

File tree

6 files changed

+101
-35
lines changed

6 files changed

+101
-35
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,13 @@ private static IntPtr UserInstanceDLLHandle
2222
if (s_userInstanceDLLHandle == IntPtr.Zero)
2323
{
2424
SNINativeMethodWrapper.SNIQueryInfo(SNINativeMethodWrapper.QTypes.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle);
25-
if(s_userInstanceDLLHandle != IntPtr.Zero)
25+
if (s_userInstanceDLLHandle != IntPtr.Zero)
2626
{
2727
SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.UserInstanceDLLHandle | LocalDB - handle obtained");
2828
}
2929
else
3030
{
31-
SNINativeMethodWrapper.SNI_Error sniError;
32-
SNINativeMethodWrapper.SNIGetLastError(out sniError);
31+
SNINativeMethodWrapper.SNIGetLastError(out SNINativeMethodWrapper.SNI_Error sniError);
3332
throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError: (int)sniError.sniError);
3433
}
3534
}

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.cs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,37 @@ namespace Microsoft.Data
1010
{
1111
internal static partial class LocalDBAPI
1212
{
13-
private const string const_localDbPrefix = @"(localdb)\";
13+
private const string LocalDbPrefix = @"(localdb)\";
14+
private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#";
1415

1516

1617
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
1718
private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, uint dwFlags, uint dwLanguageId, StringBuilder buffer, ref uint buflen);
1819

1920
// check if name is in format (localdb)\<InstanceName - not empty> and return instance name if it is
21+
// localDB can also have a format of np:\\.\pipe\LOCALDB#<some number>\tsql\query
2022
internal static string GetLocalDbInstanceNameFromServerName(string serverName)
2123
{
22-
if (serverName == null)
23-
return null;
24-
serverName = serverName.TrimStart(); // it can start with spaces if specified in quotes
25-
if (!serverName.StartsWith(const_localDbPrefix, StringComparison.OrdinalIgnoreCase))
26-
return null;
27-
string instanceName = serverName.Substring(const_localDbPrefix.Length).Trim();
28-
if (instanceName.Length == 0)
29-
return null;
30-
else
31-
return instanceName;
24+
if (serverName is not null)
25+
{
26+
// it can start with spaces if specified in quotes
27+
// Memory allocation is reduced by using ReadOnlySpan
28+
ReadOnlySpan<char> input = serverName.AsSpan().Trim();
29+
if (input.StartsWith(LocalDbPrefix.AsSpan(), StringComparison.OrdinalIgnoreCase))
30+
{
31+
input = input.Slice(LocalDbPrefix.Length);
32+
if (!input.IsEmpty)
33+
{
34+
return input.ToString();
35+
}
36+
}
37+
else if (input.StartsWith(LocalDbPrefix_NP.AsSpan(), StringComparison.OrdinalIgnoreCase))
38+
{
39+
return input.ToString();
40+
}
41+
42+
}
43+
return null;
3244
}
3345
}
3446
}

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,7 @@ internal SNIError GetLastError()
342342
private static string GetLocalDBDataSource(string fullServerName, out bool error)
343343
{
344344
string localDBConnectionString = null;
345-
bool isBadLocalDBDataSource;
346-
string localDBInstance = DataSource.GetLocalDBInstance(fullServerName, out isBadLocalDBDataSource);
345+
string localDBInstance = DataSource.GetLocalDBInstance(fullServerName, out bool isBadLocalDBDataSource);
347346

348347
if (isBadLocalDBDataSource)
349348
{
@@ -381,6 +380,7 @@ internal class DataSource
381380
private const string Slash = @"/";
382381
private const string PipeToken = "pipe";
383382
private const string LocalDbHost = "(localdb)";
383+
private const string LocalDbHost_NP = @"np:\\.\pipe\LOCALDB#";
384384
private const string NamedPipeInstanceNameHeader = "mssql$";
385385
private const string DefaultPipeName = "sql\\query";
386386

@@ -482,11 +482,9 @@ private void PopulateProtocol()
482482
internal static string GetLocalDBInstance(string dataSource, out bool error)
483483
{
484484
string instanceName = null;
485-
486485
// ReadOnlySpan is not supported in netstandard 2.0, but installing System.Memory solves the issue
487486
ReadOnlySpan<char> input = dataSource.AsSpan().TrimStart();
488487
error = false;
489-
490488
// NetStandard 2.0 does not support passing a string to ReadOnlySpan<char>
491489
if (input.StartsWith(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
492490
{
@@ -507,6 +505,11 @@ internal static string GetLocalDBInstance(string dataSource, out bool error)
507505
error = true;
508506
}
509507
}
508+
else if (input.StartsWith(LocalDbHost_NP.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
509+
{
510+
instanceName = input.Trim().ToString();
511+
}
512+
510513
return instanceName;
511514
}
512515

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616

1717
namespace Microsoft.Data
1818
{
19-
2019
internal static class LocalDBAPI
2120
{
22-
const string const_localDbPrefix = @"(localdb)\";
23-
const string const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST";
21+
private const string LocalDbPrefix = @"(localdb)\";
22+
private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#";
23+
const string Const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST";
2424

2525
static PermissionSet _fullTrust = null;
2626
static bool _partialTrustFlagChecked = false;
@@ -30,18 +30,27 @@ internal static class LocalDBAPI
3030
// check if name is in format (localdb)\<InstanceName - not empty> and return instance name if it is
3131
internal static string GetLocalDbInstanceNameFromServerName(string serverName)
3232
{
33-
if (serverName == null)
34-
return null;
35-
serverName = serverName.TrimStart(); // it can start with spaces if specified in quotes
36-
if (!serverName.StartsWith(const_localDbPrefix, StringComparison.OrdinalIgnoreCase))
37-
return null;
38-
string instanceName = serverName.Substring(const_localDbPrefix.Length).Trim();
39-
if (instanceName.Length == 0)
40-
return null;
41-
else
42-
return instanceName;
43-
}
33+
if (serverName is not null)
34+
{
35+
// it can start with spaces if specified in quotes
36+
// Memory allocation is reduced by using ReadOnlySpan
37+
ReadOnlySpan<char> input = serverName.AsSpan().Trim();
38+
if (input.StartsWith(LocalDbPrefix.AsSpan(), StringComparison.OrdinalIgnoreCase))
39+
{
40+
input = input.Slice(LocalDbPrefix.Length);
41+
if (!input.IsEmpty)
42+
{
43+
return input.ToString();
44+
}
45+
}
46+
else if (input.StartsWith(LocalDbPrefix_NP.AsSpan(), StringComparison.OrdinalIgnoreCase))
47+
{
48+
return input.ToString();
49+
}
4450

51+
}
52+
return null;
53+
}
4554

4655
internal static void ReleaseDLLHandles()
4756
{
@@ -261,7 +270,7 @@ internal static void DemandLocalDBPermissions()
261270
{
262271
if (!_partialTrustFlagChecked)
263272
{
264-
object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(const_partialTrustFlagKey);
273+
object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(Const_partialTrustFlagKey);
265274
if (partialTrustFlagValue != null && partialTrustFlagValue is bool)
266275
{
267276
_partialTrustAllowed = (bool)partialTrustFlagValue;

src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
4-
using System.Collections.Generic;
4+
using System;
5+
using System.Diagnostics;
6+
using System.Threading;
57
using Xunit;
68

79
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -13,14 +15,18 @@ public static class LocalDBTest
1315
private static readonly string s_localDbConnectionString = @$"server=(localdb)\{DataTestUtility.LocalDbAppName}";
1416
private static readonly string[] s_sharedLocalDbInstances = new string[] { @$"server=(localdb)\.\{DataTestUtility.LocalDbSharedInstanceName}", @$"server=(localdb)\." };
1517
private static readonly string s_badConnectionString = $@"server=(localdb)\{DataTestUtility.LocalDbAppName};Database=DOES_NOT_EXIST;Pooling=false;";
18+
private static readonly string s_commandPrompt = "cmd.exe";
19+
private static readonly string s_sqlLocalDbInfo = @$"/c SqlLocalDb info {DataTestUtility.LocalDbAppName}";
20+
private static readonly string s_startLocalDbCommand = @$"/c SqlLocalDb start {DataTestUtility.LocalDbAppName}";
21+
private static readonly string s_localDbNamedPipeConnectionString = @$"server={GetLocalDbNamedPipe()}";
1622

17-
static string LocalDbName = DataTestUtility.LocalDbAppName;
1823
#region LocalDbTests
1924
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
2025
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
2126
public static void SqlLocalDbConnectionTest()
2227
{
2328
ConnectionTest(s_localDbConnectionString);
29+
ConnectionTest(s_localDbNamedPipeConnectionString);
2430
}
2531

2632
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
@@ -30,13 +36,15 @@ public static void LocalDBEncryptionNotSupportedTest()
3036
// Encryption is not supported by SQL Local DB.
3137
// But connection should succeed as encryption is disabled by driver.
3238
ConnectionWithEncryptionTest(s_localDbConnectionString);
39+
ConnectionWithEncryptionTest(s_localDbNamedPipeConnectionString);
3340
}
3441

3542
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
3643
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
3744
public static void LocalDBMarsTest()
3845
{
3946
ConnectionWithMarsTest(s_localDbConnectionString);
47+
ConnectionWithMarsTest(s_localDbNamedPipeConnectionString);
4048
}
4149

4250
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
@@ -123,5 +131,39 @@ private static void OpenConnection(string connString)
123131
var result = command.ExecuteScalar();
124132
Assert.NotNull(result);
125133
}
134+
135+
private static string GetLocalDbNamedPipe()
136+
{
137+
string state = ExecuteLocalDBCommandProcess(s_commandPrompt, s_sqlLocalDbInfo, "state");
138+
while (state.Equals("stopped", StringComparison.InvariantCultureIgnoreCase))
139+
{
140+
state = ExecuteLocalDBCommandProcess(s_commandPrompt, s_startLocalDbCommand, "state");
141+
Thread.Sleep(2000);
142+
}
143+
return ExecuteLocalDBCommandProcess(s_commandPrompt, s_sqlLocalDbInfo, "pipeName");
144+
}
145+
146+
private static string ExecuteLocalDBCommandProcess(string filename, string arguments, string infoType)
147+
{
148+
ProcessStartInfo sInfo = new()
149+
{
150+
FileName = filename,
151+
Arguments = arguments,
152+
UseShellExecute = false,
153+
CreateNoWindow = false,
154+
RedirectStandardOutput = true,
155+
RedirectStandardError = true,
156+
};
157+
string[] lines = Process.Start(sInfo).StandardOutput.ReadToEnd().Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
158+
if (infoType.Equals("state"))
159+
{
160+
return lines[5].Split(':')[1].Trim();
161+
}
162+
else if (infoType.Equals("pipeName"))
163+
{
164+
return lines[7].Split(new string[] { "Instance pipe name:" }, StringSplitOptions.None)[1].Trim();
165+
}
166+
return null;
167+
}
126168
}
127169
}

src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"AzureKeyVaultClientSecret": "",
1717
"SupportsIntegratedSecurity": true,
1818
"LocalDbAppName": "",
19+
"LocalDbSharedInstanceName":"",
1920
"SupportsFileStream": false,
2021
"FileStreamDirectory": "",
2122
"UseManagedSNIOnWindows": false,

0 commit comments

Comments
 (0)