Skip to content

Commit

Permalink
Fix | SqlLocalDB instance pipename issue with Encryption (#1475)
Browse files Browse the repository at this point in the history
  • Loading branch information
DavoudEshtehari authored Jan 14, 2022
1 parent c636ebc commit 807226e
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@ private static IntPtr UserInstanceDLLHandle
if (s_userInstanceDLLHandle == IntPtr.Zero)
{
SNINativeMethodWrapper.SNIQueryInfo(SNINativeMethodWrapper.QTypes.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle);
if(s_userInstanceDLLHandle != IntPtr.Zero)
if (s_userInstanceDLLHandle != IntPtr.Zero)
{
SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.UserInstanceDLLHandle | LocalDB - handle obtained");
}
else
{
SNINativeMethodWrapper.SNI_Error sniError;
SNINativeMethodWrapper.SNIGetLastError(out sniError);
SNINativeMethodWrapper.SNIGetLastError(out SNINativeMethodWrapper.SNI_Error sniError);
throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError: (int)sniError.sniError);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,37 @@ namespace Microsoft.Data
{
internal static partial class LocalDBAPI
{
private const string const_localDbPrefix = @"(localdb)\";
private const string LocalDbPrefix = @"(localdb)\";
private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#";


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

// check if name is in format (localdb)\<InstanceName - not empty> and return instance name if it is
// localDB can also have a format of np:\\.\pipe\LOCALDB#<some number>\tsql\query
internal static string GetLocalDbInstanceNameFromServerName(string serverName)
{
if (serverName == null)
return null;
serverName = serverName.TrimStart(); // it can start with spaces if specified in quotes
if (!serverName.StartsWith(const_localDbPrefix, StringComparison.OrdinalIgnoreCase))
return null;
string instanceName = serverName.Substring(const_localDbPrefix.Length).Trim();
if (instanceName.Length == 0)
return null;
else
return instanceName;
if (serverName is not null)
{
// it can start with spaces if specified in quotes
// Memory allocation is reduced by using ReadOnlySpan
ReadOnlySpan<char> input = serverName.AsSpan().Trim();
if (input.StartsWith(LocalDbPrefix.AsSpan(), StringComparison.OrdinalIgnoreCase))
{
input = input.Slice(LocalDbPrefix.Length);
if (!input.IsEmpty)
{
return input.ToString();
}
}
else if (input.StartsWith(LocalDbPrefix_NP.AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return input.ToString();
}

}
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,7 @@ internal SNIError GetLastError()
private static string GetLocalDBDataSource(string fullServerName, out bool error)
{
string localDBConnectionString = null;
bool isBadLocalDBDataSource;
string localDBInstance = DataSource.GetLocalDBInstance(fullServerName, out isBadLocalDBDataSource);
string localDBInstance = DataSource.GetLocalDBInstance(fullServerName, out bool isBadLocalDBDataSource);

if (isBadLocalDBDataSource)
{
Expand Down Expand Up @@ -381,6 +380,7 @@ internal class DataSource
private const string Slash = @"/";
private const string PipeToken = "pipe";
private const string LocalDbHost = "(localdb)";
private const string LocalDbHost_NP = @"np:\\.\pipe\LOCALDB#";
private const string NamedPipeInstanceNameHeader = "mssql$";
private const string DefaultPipeName = "sql\\query";

Expand Down Expand Up @@ -482,11 +482,9 @@ private void PopulateProtocol()
internal static string GetLocalDBInstance(string dataSource, out bool error)
{
string instanceName = null;

// ReadOnlySpan is not supported in netstandard 2.0, but installing System.Memory solves the issue
ReadOnlySpan<char> input = dataSource.AsSpan().TrimStart();
error = false;

// NetStandard 2.0 does not support passing a string to ReadOnlySpan<char>
if (input.StartsWith(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
{
Expand All @@ -507,6 +505,11 @@ internal static string GetLocalDBInstance(string dataSource, out bool error)
error = true;
}
}
else if (input.StartsWith(LocalDbHost_NP.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
{
instanceName = input.Trim().ToString();
}

return instanceName;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@

namespace Microsoft.Data
{

internal static class LocalDBAPI
{
const string const_localDbPrefix = @"(localdb)\";
const string const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST";
private const string LocalDbPrefix = @"(localdb)\";
private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#";
const string Const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST";

static PermissionSet _fullTrust = null;
static bool _partialTrustFlagChecked = false;
Expand All @@ -30,18 +30,27 @@ internal static class LocalDBAPI
// check if name is in format (localdb)\<InstanceName - not empty> and return instance name if it is
internal static string GetLocalDbInstanceNameFromServerName(string serverName)
{
if (serverName == null)
return null;
serverName = serverName.TrimStart(); // it can start with spaces if specified in quotes
if (!serverName.StartsWith(const_localDbPrefix, StringComparison.OrdinalIgnoreCase))
return null;
string instanceName = serverName.Substring(const_localDbPrefix.Length).Trim();
if (instanceName.Length == 0)
return null;
else
return instanceName;
}
if (serverName is not null)
{
// it can start with spaces if specified in quotes
// Memory allocation is reduced by using ReadOnlySpan
ReadOnlySpan<char> input = serverName.AsSpan().Trim();
if (input.StartsWith(LocalDbPrefix.AsSpan(), StringComparison.OrdinalIgnoreCase))
{
input = input.Slice(LocalDbPrefix.Length);
if (!input.IsEmpty)
{
return input.ToString();
}
}
else if (input.StartsWith(LocalDbPrefix_NP.AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return input.ToString();
}

}
return null;
}

internal static void ReleaseDLLHandles()
{
Expand Down Expand Up @@ -261,7 +270,7 @@ internal static void DemandLocalDBPermissions()
{
if (!_partialTrustFlagChecked)
{
object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(const_partialTrustFlagKey);
object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(Const_partialTrustFlagKey);
if (partialTrustFlagValue != null && partialTrustFlagValue is bool)
{
_partialTrustAllowed = (bool)partialTrustFlagValue;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System;
using System.Diagnostics;
using System.Threading;
using Xunit;

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

static string LocalDbName = DataTestUtility.LocalDbAppName;
#region LocalDbTests
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
public static void SqlLocalDbConnectionTest()
{
ConnectionTest(s_localDbConnectionString);
ConnectionTest(s_localDbNamedPipeConnectionString);
}

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

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
public static void LocalDBMarsTest()
{
ConnectionWithMarsTest(s_localDbConnectionString);
ConnectionWithMarsTest(s_localDbNamedPipeConnectionString);
}

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
Expand Down Expand Up @@ -123,5 +131,39 @@ private static void OpenConnection(string connString)
var result = command.ExecuteScalar();
Assert.NotNull(result);
}

private static string GetLocalDbNamedPipe()
{
string state = ExecuteLocalDBCommandProcess(s_commandPrompt, s_sqlLocalDbInfo, "state");
while (state.Equals("stopped", StringComparison.InvariantCultureIgnoreCase))
{
state = ExecuteLocalDBCommandProcess(s_commandPrompt, s_startLocalDbCommand, "state");
Thread.Sleep(2000);
}
return ExecuteLocalDBCommandProcess(s_commandPrompt, s_sqlLocalDbInfo, "pipeName");
}

private static string ExecuteLocalDBCommandProcess(string filename, string arguments, string infoType)
{
ProcessStartInfo sInfo = new()
{
FileName = filename,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
};
string[] lines = Process.Start(sInfo).StandardOutput.ReadToEnd().Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
if (infoType.Equals("state"))
{
return lines[5].Split(':')[1].Trim();
}
else if (infoType.Equals("pipeName"))
{
return lines[7].Split(new string[] { "Instance pipe name:" }, StringSplitOptions.None)[1].Trim();
}
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"AzureKeyVaultClientSecret": "",
"SupportsIntegratedSecurity": true,
"LocalDbAppName": "",
"LocalDbSharedInstanceName":"",
"SupportsFileStream": false,
"FileStreamDirectory": "",
"UseManagedSNIOnWindows": false,
Expand Down

0 comments on commit 807226e

Please sign in to comment.