mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 10:58:30 -05:00
Merge branch 'dev'
This commit is contained in:
9
.gitignore
vendored
9
.gitignore
vendored
@@ -13,6 +13,7 @@ project.lock.json
|
|||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
*.exe
|
||||||
|
|
||||||
# Build results
|
# Build results
|
||||||
[Dd]ebug/
|
[Dd]ebug/
|
||||||
@@ -29,7 +30,13 @@ msbuild.log
|
|||||||
msbuild.err
|
msbuild.err
|
||||||
msbuild.wrn
|
msbuild.wrn
|
||||||
|
|
||||||
|
# code coverage artifacts
|
||||||
|
coverage.xml
|
||||||
|
node_modules
|
||||||
|
packages
|
||||||
|
reports
|
||||||
|
opencovertests.xml
|
||||||
|
sqltools.xml
|
||||||
|
|
||||||
# Cross building rootfs
|
# Cross building rootfs
|
||||||
cross/rootfs/
|
cross/rootfs/
|
||||||
|
|||||||
75
BUILD.md
Normal file
75
BUILD.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Usage
|
||||||
|
|
||||||
|
Run `build.(ps1|sh)` with the desired set of arguments (see below for options).
|
||||||
|
The build script itself is `build.cake`, written in C# using the Cake build automation system.
|
||||||
|
All build related activites should be encapsulated in this file for cross-platform access.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
|
||||||
|
## Primary
|
||||||
|
|
||||||
|
`-target=TargetName`: The name of the build task/target to execute (see below for listing and details).
|
||||||
|
Defaults to `Default`.
|
||||||
|
|
||||||
|
`-configuration=(Release|Debug)`: The configuration to build.
|
||||||
|
Defaults to `Release`.
|
||||||
|
|
||||||
|
## Extra
|
||||||
|
|
||||||
|
`-test-configuration=(Release|Debug)`: The configuration to use for the unit tests.
|
||||||
|
Defaults to `Debug`.
|
||||||
|
|
||||||
|
`-install-path=Path`: Path used for the **Install** target.
|
||||||
|
Defaults to `(%USERPROFILE%|$HOME)/.sqltoolsservice/local`
|
||||||
|
|
||||||
|
`-archive`: Enable the generation of publishable archives after a build.
|
||||||
|
|
||||||
|
# Targets
|
||||||
|
|
||||||
|
**Default**: Alias for Local.
|
||||||
|
|
||||||
|
**Local**: Full build including testing for the machine-local runtime.
|
||||||
|
|
||||||
|
**All**: Same as local, but targeting all runtimes selected by `PopulateRuntimes` in `build.cake`.
|
||||||
|
Currently configured to also build for a 32-bit Windows runtime on Windows machines.
|
||||||
|
No additional runtimes are currently selected on non-Windows machines.
|
||||||
|
|
||||||
|
**Quick**: Local build which skips all testing.
|
||||||
|
|
||||||
|
**Install**: Same as quick, but installs the generated binaries into `install-path`.
|
||||||
|
|
||||||
|
**SetPackageVersions**: Updates the dependency versions found within `project.json` files using information from `depversion.json`.
|
||||||
|
Used for maintainence within the project, not needed for end-users. More information below.
|
||||||
|
|
||||||
|
# Configuration files
|
||||||
|
|
||||||
|
## build.json
|
||||||
|
|
||||||
|
A number of build-related options, including folder names for different entities. Interesting options:
|
||||||
|
|
||||||
|
**DotNetInstallScriptURL**: The URL where the .NET SDK install script is located.
|
||||||
|
Can be used to pin to a specific script version, if a breaking change occurs.
|
||||||
|
|
||||||
|
**"DotNetChannel"**: The .NET SDK channel used for retreiving the tools.
|
||||||
|
|
||||||
|
**"DotNetVersion"**: The .NET SDK version used for the build. Can be used to pin to a specific version.
|
||||||
|
Using the string `Latest` will retrieve the latest version.
|
||||||
|
|
||||||
|
## depversion.json
|
||||||
|
|
||||||
|
A listing of all dependencies (and their desired versions) used by `project.json` files throughout the project.
|
||||||
|
Allows for quick and automatic updates to the dependency version numbers using the **SetPackageVersions** target.
|
||||||
|
|
||||||
|
# Artifacts generated
|
||||||
|
|
||||||
|
* Binaries of Microsoft.SqlTools.ServiceLayer and its libraries built for the local machine in `artifacts/publish/Microsoft.SqlTools.ServiceLayer/default/{framework}/`
|
||||||
|
* Scripts to run Microsoft.SqlTools.ServiceLayer at `scripts/SQLTOOLSSERVICE(.Core)(.cmd)`
|
||||||
|
* These scripts are updated for every build and every install.
|
||||||
|
* The scripts point to the installed binary after and install, otherwise just the build folder (reset if a new build occurs without an install).
|
||||||
|
* Binaries of Microsoft.SqlTools.ServiceLayer and its libraries cross-compiled for other runtimes (if selected in **PopulateRuntimes**) `artifacts/publish/Microsoft.SqlTools.ServiceLayer/{runtime}/{framework}/`
|
||||||
|
* Test logs in `artifacts/logs`
|
||||||
|
* Archived binaries in `artifacts/package` (only if `-archive` used on command line)
|
||||||
|
|
||||||
|
# Requirements
|
||||||
|
|
||||||
|
The build system requires Mono to be installed on non-Windows machines as Cake is not built using .NET Core (yet).
|
||||||
507
build.cake
Normal file
507
build.cake
Normal file
@@ -0,0 +1,507 @@
|
|||||||
|
#addin "Newtonsoft.Json"
|
||||||
|
|
||||||
|
#load "scripts/runhelpers.cake"
|
||||||
|
#load "scripts/archiving.cake"
|
||||||
|
#load "scripts/artifacts.cake"
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Net;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
// Basic arguments
|
||||||
|
var target = Argument("target", "Default");
|
||||||
|
var configuration = Argument("configuration", "Release");
|
||||||
|
// Optional arguments
|
||||||
|
var testConfiguration = Argument("test-configuration", "Debug");
|
||||||
|
var installFolder = Argument("install-path", System.IO.Path.Combine(Environment.GetEnvironmentVariable(IsRunningOnWindows() ? "USERPROFILE" : "HOME"),
|
||||||
|
".sqltoolsservice", "local"));
|
||||||
|
var requireArchive = HasArgument("archive");
|
||||||
|
|
||||||
|
// Working directory
|
||||||
|
var workingDirectory = System.IO.Directory.GetCurrentDirectory();
|
||||||
|
|
||||||
|
// System specific shell configuration
|
||||||
|
var shell = IsRunningOnWindows() ? "powershell" : "bash";
|
||||||
|
var shellArgument = IsRunningOnWindows() ? "-NoProfile /Command" : "-C";
|
||||||
|
var shellExtension = IsRunningOnWindows() ? "ps1" : "sh";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class representing build.json
|
||||||
|
/// </summary>
|
||||||
|
public class BuildPlan
|
||||||
|
{
|
||||||
|
public IDictionary<string, string[]> TestProjects { get; set; }
|
||||||
|
public string BuildToolsFolder { get; set; }
|
||||||
|
public string ArtifactsFolder { get; set; }
|
||||||
|
public bool UseSystemDotNetPath { get; set; }
|
||||||
|
public string DotNetFolder { get; set; }
|
||||||
|
public string DotNetInstallScriptURL { get; set; }
|
||||||
|
public string DotNetChannel { get; set; }
|
||||||
|
public string DotNetVersion { get; set; }
|
||||||
|
public string[] Frameworks { get; set; }
|
||||||
|
public string[] Rids { get; set; }
|
||||||
|
public string MainProject { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildPlan = JsonConvert.DeserializeObject<BuildPlan>(
|
||||||
|
System.IO.File.ReadAllText(System.IO.Path.Combine(workingDirectory, "build.json")));
|
||||||
|
|
||||||
|
// Folders and tools
|
||||||
|
var dotnetFolder = System.IO.Path.Combine(workingDirectory, buildPlan.DotNetFolder);
|
||||||
|
var dotnetcli = buildPlan.UseSystemDotNetPath ? "dotnet" : System.IO.Path.Combine(System.IO.Path.GetFullPath(dotnetFolder), "dotnet");
|
||||||
|
var toolsFolder = System.IO.Path.Combine(workingDirectory, buildPlan.BuildToolsFolder);
|
||||||
|
|
||||||
|
var sourceFolder = System.IO.Path.Combine(workingDirectory, "src");
|
||||||
|
var testFolder = System.IO.Path.Combine(workingDirectory, "test");
|
||||||
|
|
||||||
|
var artifactFolder = System.IO.Path.Combine(workingDirectory, buildPlan.ArtifactsFolder);
|
||||||
|
var publishFolder = System.IO.Path.Combine(artifactFolder, "publish");
|
||||||
|
var logFolder = System.IO.Path.Combine(artifactFolder, "logs");
|
||||||
|
var packageFolder = System.IO.Path.Combine(artifactFolder, "package");
|
||||||
|
var scriptFolder = System.IO.Path.Combine(artifactFolder, "scripts");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean artifacts.
|
||||||
|
/// </summary>
|
||||||
|
Task("Cleanup")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
if (System.IO.Directory.Exists(artifactFolder))
|
||||||
|
{
|
||||||
|
System.IO.Directory.Delete(artifactFolder, true);
|
||||||
|
}
|
||||||
|
System.IO.Directory.CreateDirectory(artifactFolder);
|
||||||
|
System.IO.Directory.CreateDirectory(logFolder);
|
||||||
|
System.IO.Directory.CreateDirectory(packageFolder);
|
||||||
|
System.IO.Directory.CreateDirectory(scriptFolder);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pre-build setup tasks.
|
||||||
|
/// </summary>
|
||||||
|
Task("Setup")
|
||||||
|
.IsDependentOn("BuildEnvironment")
|
||||||
|
.IsDependentOn("PopulateRuntimes")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populate the RIDs for the specific environment.
|
||||||
|
/// Use default RID (+ win7-x86 on Windows) for now.
|
||||||
|
/// </summary>
|
||||||
|
Task("PopulateRuntimes")
|
||||||
|
.IsDependentOn("BuildEnvironment")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
buildPlan.Rids = new string[]
|
||||||
|
{
|
||||||
|
"default", // To allow testing the published artifact
|
||||||
|
"win7-x64",
|
||||||
|
"win7-x86",
|
||||||
|
"ubuntu.14.04-x64",
|
||||||
|
"ubuntu.16.04-x64",
|
||||||
|
"centos.7-x64",
|
||||||
|
"rhel.7.2-x64",
|
||||||
|
"debian.8-x64",
|
||||||
|
"fedora.23-x64",
|
||||||
|
"opensuse.13.2-x64",
|
||||||
|
"osx.10.11-x64"
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Install/update build environment.
|
||||||
|
/// </summary>
|
||||||
|
Task("BuildEnvironment")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
var installScript = $"dotnet-install.{shellExtension}";
|
||||||
|
System.IO.Directory.CreateDirectory(dotnetFolder);
|
||||||
|
var scriptPath = System.IO.Path.Combine(dotnetFolder, installScript);
|
||||||
|
using (WebClient client = new WebClient())
|
||||||
|
{
|
||||||
|
client.DownloadFile($"{buildPlan.DotNetInstallScriptURL}/{installScript}", scriptPath);
|
||||||
|
}
|
||||||
|
if (!IsRunningOnWindows())
|
||||||
|
{
|
||||||
|
Run("chmod", $"+x '{scriptPath}'");
|
||||||
|
}
|
||||||
|
var installArgs = $"-Channel {buildPlan.DotNetChannel}";
|
||||||
|
if (!String.IsNullOrEmpty(buildPlan.DotNetVersion))
|
||||||
|
{
|
||||||
|
installArgs = $"{installArgs} -Version {buildPlan.DotNetVersion}";
|
||||||
|
}
|
||||||
|
if (!buildPlan.UseSystemDotNetPath)
|
||||||
|
{
|
||||||
|
installArgs = $"{installArgs} -InstallDir {dotnetFolder}";
|
||||||
|
}
|
||||||
|
Run(shell, $"{shellArgument} {scriptPath} {installArgs}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Run(dotnetcli, "--info");
|
||||||
|
}
|
||||||
|
catch (Win32Exception)
|
||||||
|
{
|
||||||
|
throw new Exception(".NET CLI binary cannot be found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
System.IO.Directory.CreateDirectory(toolsFolder);
|
||||||
|
|
||||||
|
var nugetPath = Environment.GetEnvironmentVariable("NUGET_EXE");
|
||||||
|
var arguments = $"install xunit.runner.console -ExcludeVersion -NoCache -Prerelease -OutputDirectory \"{toolsFolder}\"";
|
||||||
|
if (IsRunningOnWindows())
|
||||||
|
{
|
||||||
|
Run(nugetPath, arguments);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Run("mono", $"\"{nugetPath}\" {arguments}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restore required NuGet packages.
|
||||||
|
/// </summary>
|
||||||
|
Task("Restore")
|
||||||
|
.IsDependentOn("Setup")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
RunRestore(dotnetcli, "restore", sourceFolder)
|
||||||
|
.ExceptionOnError("Failed to restore projects under source code folder.");
|
||||||
|
RunRestore(dotnetcli, "restore --infer-runtimes", testFolder)
|
||||||
|
.ExceptionOnError("Failed to restore projects under test code folder.");
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build Test projects.
|
||||||
|
/// </summary>
|
||||||
|
Task("BuildTest")
|
||||||
|
.IsDependentOn("Setup")
|
||||||
|
.IsDependentOn("Restore")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
foreach (var pair in buildPlan.TestProjects)
|
||||||
|
{
|
||||||
|
foreach (var framework in pair.Value)
|
||||||
|
{
|
||||||
|
var project = pair.Key;
|
||||||
|
var projectFolder = System.IO.Path.Combine(testFolder, project);
|
||||||
|
var runLog = new List<string>();
|
||||||
|
Run(dotnetcli, $"build --framework {framework} --configuration {testConfiguration} \"{projectFolder}\"",
|
||||||
|
new RunOptions
|
||||||
|
{
|
||||||
|
StandardOutputListing = runLog
|
||||||
|
})
|
||||||
|
.ExceptionOnError($"Building test {project} failed for {framework}.");
|
||||||
|
System.IO.File.WriteAllLines(System.IO.Path.Combine(logFolder, $"{project}-{framework}-build.log"), runLog.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run all tests for .NET Desktop and .NET Core
|
||||||
|
/// </summary>
|
||||||
|
Task("TestAll")
|
||||||
|
.IsDependentOn("Test")
|
||||||
|
.IsDependentOn("TestCore")
|
||||||
|
.Does(() =>{});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run all tests for Travis CI .NET Desktop and .NET Core
|
||||||
|
/// </summary>
|
||||||
|
Task("TravisTestAll")
|
||||||
|
.IsDependentOn("Cleanup")
|
||||||
|
.IsDependentOn("TestAll")
|
||||||
|
.Does(() =>{});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run tests for .NET Core (using .NET CLI).
|
||||||
|
/// </summary>
|
||||||
|
Task("TestCore")
|
||||||
|
.IsDependentOn("Setup")
|
||||||
|
.IsDependentOn("Restore")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
var testProjects = buildPlan.TestProjects
|
||||||
|
.Where(pair => pair.Value.Any(framework => framework.Contains("netcoreapp")))
|
||||||
|
.Select(pair => pair.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var testProject in testProjects)
|
||||||
|
{
|
||||||
|
var logFile = System.IO.Path.Combine(logFolder, $"{testProject}-core-result.xml");
|
||||||
|
var testWorkingDir = System.IO.Path.Combine(testFolder, testProject);
|
||||||
|
Run(dotnetcli, $"test -f netcoreapp1.0 -xml \"{logFile}\" -notrait category=failing", testWorkingDir)
|
||||||
|
.ExceptionOnError($"Test {testProject} failed for .NET Core.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run tests for other frameworks (using XUnit2).
|
||||||
|
/// </summary>
|
||||||
|
Task("Test")
|
||||||
|
.IsDependentOn("Setup")
|
||||||
|
.IsDependentOn("BuildTest")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
foreach (var pair in buildPlan.TestProjects)
|
||||||
|
{
|
||||||
|
foreach (var framework in pair.Value)
|
||||||
|
{
|
||||||
|
// Testing against core happens in TestCore
|
||||||
|
if (framework.Contains("netcoreapp"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var project = pair.Key;
|
||||||
|
var frameworkFolder = System.IO.Path.Combine(testFolder, project, "bin", testConfiguration, framework);
|
||||||
|
var runtime = System.IO.Directory.GetDirectories(frameworkFolder).First();
|
||||||
|
var instanceFolder = System.IO.Path.Combine(frameworkFolder, runtime);
|
||||||
|
|
||||||
|
// Copy xunit executable to test folder to solve path errors
|
||||||
|
var xunitToolsFolder = System.IO.Path.Combine(toolsFolder, "xunit.runner.console", "tools");
|
||||||
|
var xunitInstancePath = System.IO.Path.Combine(instanceFolder, "xunit.console.exe");
|
||||||
|
System.IO.File.Copy(System.IO.Path.Combine(xunitToolsFolder, "xunit.console.exe"), xunitInstancePath, true);
|
||||||
|
System.IO.File.Copy(System.IO.Path.Combine(xunitToolsFolder, "xunit.runner.utility.desktop.dll"), System.IO.Path.Combine(instanceFolder, "xunit.runner.utility.desktop.dll"), true);
|
||||||
|
var targetPath = System.IO.Path.Combine(instanceFolder, $"{project}.dll");
|
||||||
|
var logFile = System.IO.Path.Combine(logFolder, $"{project}-{framework}-result.xml");
|
||||||
|
var arguments = $"\"{targetPath}\" -parallel none -xml \"{logFile}\" -notrait category=failing";
|
||||||
|
if (IsRunningOnWindows())
|
||||||
|
{
|
||||||
|
Run(xunitInstancePath, arguments, instanceFolder)
|
||||||
|
.ExceptionOnError($"Test {project} failed for {framework}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Run("mono", $"\"{xunitInstancePath}\" {arguments}", instanceFolder)
|
||||||
|
.ExceptionOnError($"Test {project} failed for {framework}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build, publish and package artifacts.
|
||||||
|
/// Targets all RIDs specified in build.json unless restricted by RestrictToLocalRuntime.
|
||||||
|
/// No dependencies on other tasks to support quick builds.
|
||||||
|
/// </summary>
|
||||||
|
Task("OnlyPublish")
|
||||||
|
.IsDependentOn("Setup")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
var project = buildPlan.MainProject;
|
||||||
|
var projectFolder = System.IO.Path.Combine(sourceFolder, project);
|
||||||
|
foreach (var framework in buildPlan.Frameworks)
|
||||||
|
{
|
||||||
|
foreach (var runtime in buildPlan.Rids)
|
||||||
|
{
|
||||||
|
var outputFolder = System.IO.Path.Combine(publishFolder, project, runtime, framework);
|
||||||
|
var publishArguments = "publish";
|
||||||
|
if (!runtime.Equals("default"))
|
||||||
|
{
|
||||||
|
publishArguments = $"{publishArguments} --runtime {runtime}";
|
||||||
|
}
|
||||||
|
publishArguments = $"{publishArguments} --framework {framework} --configuration {configuration}";
|
||||||
|
publishArguments = $"{publishArguments} --output \"{outputFolder}\" \"{projectFolder}\"";
|
||||||
|
Run(dotnetcli, publishArguments)
|
||||||
|
.ExceptionOnError($"Failed to publish {project} / {framework}");
|
||||||
|
|
||||||
|
if (requireArchive)
|
||||||
|
{
|
||||||
|
Package(runtime, framework, outputFolder, packageFolder, buildPlan.MainProject.ToLower());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CreateRunScript(System.IO.Path.Combine(publishFolder, project, "default"), scriptFolder);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Alias for OnlyPublish.
|
||||||
|
/// Targets all RIDs as specified in build.json.
|
||||||
|
/// </summary>
|
||||||
|
Task("AllPublish")
|
||||||
|
.IsDependentOn("Restore")
|
||||||
|
.IsDependentOn("OnlyPublish")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restrict the RIDs for the local default.
|
||||||
|
/// </summary>
|
||||||
|
Task("RestrictToLocalRuntime")
|
||||||
|
.IsDependentOn("Setup")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
buildPlan.Rids = new string[] {"default"};
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Alias for OnlyPublish.
|
||||||
|
/// Restricts publishing to local RID.
|
||||||
|
/// </summary>
|
||||||
|
Task("LocalPublish")
|
||||||
|
.IsDependentOn("Restore")
|
||||||
|
.IsDependentOn("RestrictToLocalRuntime")
|
||||||
|
.IsDependentOn("OnlyPublish")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test the published binaries if they start up without errors.
|
||||||
|
/// Uses builds corresponding to local RID.
|
||||||
|
/// </summary>
|
||||||
|
Task("TestPublished")
|
||||||
|
.IsDependentOn("Setup")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
var project = buildPlan.MainProject;
|
||||||
|
var projectFolder = System.IO.Path.Combine(sourceFolder, project);
|
||||||
|
var scriptsToTest = new string[] {"SQLTOOLSSERVICE.Core"};//TODO
|
||||||
|
foreach (var script in scriptsToTest)
|
||||||
|
{
|
||||||
|
var scriptPath = System.IO.Path.Combine(scriptFolder, script);
|
||||||
|
var didNotExitWithError = Run($"{shell}", $"{shellArgument} \"{scriptPath}\" -s \"{projectFolder}\" --stdio",
|
||||||
|
new RunOptions
|
||||||
|
{
|
||||||
|
TimeOut = 10000
|
||||||
|
})
|
||||||
|
.DidTimeOut;
|
||||||
|
if (!didNotExitWithError)
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to run {script}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean install path.
|
||||||
|
/// </summary>
|
||||||
|
Task("CleanupInstall")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
if (System.IO.Directory.Exists(installFolder))
|
||||||
|
{
|
||||||
|
System.IO.Directory.Delete(installFolder, true);
|
||||||
|
}
|
||||||
|
System.IO.Directory.CreateDirectory(installFolder);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Quick build.
|
||||||
|
/// </summary>
|
||||||
|
Task("Quick")
|
||||||
|
.IsDependentOn("Cleanup")
|
||||||
|
.IsDependentOn("LocalPublish")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Quick build + install.
|
||||||
|
/// </summary>
|
||||||
|
Task("Install")
|
||||||
|
.IsDependentOn("Cleanup")
|
||||||
|
.IsDependentOn("LocalPublish")
|
||||||
|
.IsDependentOn("CleanupInstall")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
var project = buildPlan.MainProject;
|
||||||
|
foreach (var framework in buildPlan.Frameworks)
|
||||||
|
{
|
||||||
|
var outputFolder = System.IO.Path.GetFullPath(System.IO.Path.Combine(publishFolder, project, "default", framework));
|
||||||
|
var targetFolder = System.IO.Path.GetFullPath(System.IO.Path.Combine(installFolder, framework));
|
||||||
|
// Copy all the folders
|
||||||
|
foreach (var directory in System.IO.Directory.GetDirectories(outputFolder, "*", SearchOption.AllDirectories))
|
||||||
|
System.IO.Directory.CreateDirectory(System.IO.Path.Combine(targetFolder, directory.Substring(outputFolder.Length + 1)));
|
||||||
|
//Copy all the files
|
||||||
|
foreach (string file in System.IO.Directory.GetFiles(outputFolder, "*", SearchOption.AllDirectories))
|
||||||
|
System.IO.File.Copy(file, System.IO.Path.Combine(targetFolder, file.Substring(outputFolder.Length + 1)), true);
|
||||||
|
}
|
||||||
|
CreateRunScript(installFolder, scriptFolder);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Full build targeting all RIDs specified in build.json.
|
||||||
|
/// </summary>
|
||||||
|
Task("All")
|
||||||
|
.IsDependentOn("Cleanup")
|
||||||
|
.IsDependentOn("Restore")
|
||||||
|
.IsDependentOn("TestAll")
|
||||||
|
.IsDependentOn("AllPublish")
|
||||||
|
//.IsDependentOn("TestPublished")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Full build targeting local RID.
|
||||||
|
/// </summary>
|
||||||
|
Task("Local")
|
||||||
|
.IsDependentOn("Cleanup")
|
||||||
|
.IsDependentOn("Restore")
|
||||||
|
.IsDependentOn("TestAll")
|
||||||
|
.IsDependentOn("LocalPublish")
|
||||||
|
// .IsDependentOn("TestPublished")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build centered around producing the final artifacts for Travis
|
||||||
|
///
|
||||||
|
/// The tests are run as a different task "TestAll"
|
||||||
|
/// </summary>
|
||||||
|
Task("Travis")
|
||||||
|
.IsDependentOn("Cleanup")
|
||||||
|
.IsDependentOn("Restore")
|
||||||
|
.IsDependentOn("AllPublish")
|
||||||
|
// .IsDependentOn("TestPublished")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the package versions within project.json files.
|
||||||
|
/// Uses depversion.json file as input.
|
||||||
|
/// </summary>
|
||||||
|
Task("SetPackageVersions")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
var jDepVersion = JObject.Parse(System.IO.File.ReadAllText(System.IO.Path.Combine(workingDirectory, "depversion.json")));
|
||||||
|
var projects = System.IO.Directory.GetFiles(sourceFolder, "project.json", SearchOption.AllDirectories).ToList();
|
||||||
|
projects.AddRange(System.IO.Directory.GetFiles(testFolder, "project.json", SearchOption.AllDirectories));
|
||||||
|
foreach (var project in projects)
|
||||||
|
{
|
||||||
|
var jProject = JObject.Parse(System.IO.File.ReadAllText(project));
|
||||||
|
var dependencies = jProject.SelectTokens("dependencies")
|
||||||
|
.Union(jProject.SelectTokens("frameworks.*.dependencies"))
|
||||||
|
.SelectMany(dependencyToken => dependencyToken.Children<JProperty>());
|
||||||
|
foreach (JProperty dependency in dependencies)
|
||||||
|
{
|
||||||
|
if (jDepVersion[dependency.Name] != null)
|
||||||
|
{
|
||||||
|
dependency.Value = jDepVersion[dependency.Name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.IO.File.WriteAllText(project, JsonConvert.SerializeObject(jProject, Formatting.Indented));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default Task aliases to Local.
|
||||||
|
/// </summary>
|
||||||
|
Task("Default")
|
||||||
|
.IsDependentOn("Local")
|
||||||
|
.Does(() =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default to Local.
|
||||||
|
/// </summary>
|
||||||
|
RunTarget(target);
|
||||||
18
build.json
Normal file
18
build.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"UseSystemDotNetPath": "true",
|
||||||
|
"DotNetFolder": ".dotnet",
|
||||||
|
"DotNetInstallScriptURL": "https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0-preview2/scripts/obtain",
|
||||||
|
"DotNetChannel": "preview",
|
||||||
|
"DotNetVersion": "1.0.0-preview2-003121",
|
||||||
|
"BuildToolsFolder": ".tools",
|
||||||
|
"ArtifactsFolder": "artifacts",
|
||||||
|
"TestProjects": {
|
||||||
|
"Microsoft.SqlTools.ServiceLayer.Test": [
|
||||||
|
"netcoreapp1.0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Frameworks": [
|
||||||
|
"netcoreapp1.0"
|
||||||
|
],
|
||||||
|
"MainProject": "Microsoft.SqlTools.ServiceLayer"
|
||||||
|
}
|
||||||
3
build.ps1
Normal file
3
build.ps1
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
$Env:SQLTOOLSSERVICE_PACKAGE_OSNAME = "win-x64"
|
||||||
|
.\scripts\cake-bootstrap.ps1 -experimental @args
|
||||||
|
exit $LASTEXITCODE
|
||||||
12
build.sh
Normal file
12
build.sh
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Handle to many files on osx
|
||||||
|
if [ "$TRAVIS_OS_NAME" == "osx" ] || [ `uname` == "Darwin" ]; then
|
||||||
|
ulimit -n 4096
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$TRAVIS_OS_NAME" == "osx" ] || [ `uname` == "Darwin" ]; then
|
||||||
|
export SQLTOOLSSERVICE_PACKAGE_OSNAME=osx-x64
|
||||||
|
else
|
||||||
|
export SQLTOOLSSERVICE_PACKAGE_OSNAME=linux-x64
|
||||||
|
fi
|
||||||
|
bash ./scripts/cake-bootstrap.sh "$@"
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"projects": [ "src", "test" ]
|
"projects": [ "src", "test" ],
|
||||||
|
"sdk": {
|
||||||
|
"version": "1.0.0-preview2-003121"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
6
nuget.config
Normal file
6
nuget.config
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration >
|
||||||
|
<packageSources>
|
||||||
|
<add key="DataTools Nuget" value="http://dtnuget/api/v2/" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
||||||
104
scripts/archiving.cake
Normal file
104
scripts/archiving.cake
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#load "runhelpers.cake"
|
||||||
|
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate the build identifier based on the RID and framework identifier.
|
||||||
|
/// Special rules when running on Travis (for publishing purposes).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="runtime">The RID</param>
|
||||||
|
/// <param name="framework">The framework identifier</param>
|
||||||
|
/// <returns>The designated build identifier</returns>
|
||||||
|
string GetBuildIdentifier(string runtime, string framework)
|
||||||
|
{
|
||||||
|
var runtimeShort = "";
|
||||||
|
// Default RID uses package name set in build script
|
||||||
|
if (runtime.Equals("default"))
|
||||||
|
{
|
||||||
|
runtimeShort = Environment.GetEnvironmentVariable("SQLTOOLSSERVICE_PACKAGE_OSNAME");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remove version number. Note: because there are separate versions for Ubuntu 14 and 16,
|
||||||
|
// we treat Ubuntu as a special case.
|
||||||
|
if (runtime.StartsWith("ubuntu.14"))
|
||||||
|
{
|
||||||
|
runtimeShort = "ubuntu14-x64";
|
||||||
|
}
|
||||||
|
else if (runtime.StartsWith("ubuntu.16"))
|
||||||
|
{
|
||||||
|
runtimeShort = "ubuntu16-x64";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
runtimeShort = Regex.Replace(runtime, "(\\d|\\.)*-", "-");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{runtimeShort}-{framework}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate an archive out of the given published folder.
|
||||||
|
/// Use ZIP for Windows runtimes.
|
||||||
|
/// Use TAR.GZ for non-Windows runtimes.
|
||||||
|
/// Use 7z to generate TAR.GZ on Windows if available.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="runtime">The RID</param>
|
||||||
|
/// <param name="contentFolder">The folder containing the files to package</param>
|
||||||
|
/// <param name="archiveName">The target archive name (without extension)</param>
|
||||||
|
void DoArchive(string runtime, string contentFolder, string archiveName)
|
||||||
|
{
|
||||||
|
// On all platforms use ZIP for Windows runtimes
|
||||||
|
if (runtime.Contains("win") || (runtime.Equals("default") && IsRunningOnWindows()))
|
||||||
|
{
|
||||||
|
var zipFile = $"{archiveName}.zip";
|
||||||
|
Zip(contentFolder, zipFile);
|
||||||
|
}
|
||||||
|
// On all platforms use TAR.GZ for Unix runtimes
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var tarFile = $"{archiveName}.tar.gz";
|
||||||
|
// Use 7z to create TAR.GZ on Windows
|
||||||
|
if (IsRunningOnWindows())
|
||||||
|
{
|
||||||
|
var tempFile = $"{archiveName}.tar";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Run("7z", $"a \"{tempFile}\"", contentFolder)
|
||||||
|
.ExceptionOnError($"Tar-ing failed for {contentFolder} {archiveName}");
|
||||||
|
Run("7z", $"a \"{tarFile}\" \"{tempFile}\"", contentFolder)
|
||||||
|
.ExceptionOnError($"Compression failed for {contentFolder} {archiveName}");
|
||||||
|
System.IO.File.Delete(tempFile);
|
||||||
|
}
|
||||||
|
catch(Win32Exception)
|
||||||
|
{
|
||||||
|
Information("Warning: 7z not available on PATH to pack tar.gz results");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Use tar to create TAR.GZ on Unix
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Run("tar", $"czf \"{tarFile}\" .", contentFolder)
|
||||||
|
.ExceptionOnError($"Compression failed for {contentFolder} {archiveName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Package a given output folder using a build identifier generated from the RID and framework identifier.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="runtime">The RID</param>
|
||||||
|
/// <param name="framework">The framework identifier</param>
|
||||||
|
/// <param name="contentFolder">The folder containing the files to package</param>
|
||||||
|
/// <param name="packageFolder">The destination folder for the archive</param>
|
||||||
|
/// <param name="projectName">The project name</param>
|
||||||
|
void Package(string runtime, string framework, string contentFolder, string packageFolder, string projectName)
|
||||||
|
{
|
||||||
|
var buildIdentifier = GetBuildIdentifier(runtime, framework);
|
||||||
|
if (buildIdentifier != null)
|
||||||
|
{
|
||||||
|
DoArchive(runtime, contentFolder, $"{packageFolder}/{projectName}-{buildIdentifier}");
|
||||||
|
}
|
||||||
|
}
|
||||||
43
scripts/artifacts.cake
Normal file
43
scripts/artifacts.cake
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#load "runhelpers.cake"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate the scripts which target the SQLTOOLSSERVICE binaries.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputRoot">The root folder where the publised (or installed) binaries are located</param>
|
||||||
|
void CreateRunScript(string outputRoot, string scriptFolder)
|
||||||
|
{
|
||||||
|
if (IsRunningOnWindows())
|
||||||
|
{
|
||||||
|
var coreScript = System.IO.Path.Combine(scriptFolder, "SQLTOOLSSERVICE.Core.cmd");
|
||||||
|
var sqlToolsServicePath = System.IO.Path.Combine(System.IO.Path.GetFullPath(outputRoot), "{0}", "SQLTOOLSSERVICE");
|
||||||
|
var content = new string[] {
|
||||||
|
"SETLOCAL",
|
||||||
|
"",
|
||||||
|
$"\"{sqlToolsServicePath}\" %*"
|
||||||
|
};
|
||||||
|
if (System.IO.File.Exists(coreScript))
|
||||||
|
{
|
||||||
|
System.IO.File.Delete(coreScript);
|
||||||
|
}
|
||||||
|
content[2] = String.Format(content[2], "netcoreapp1.0");
|
||||||
|
System.IO.File.WriteAllLines(coreScript, content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var coreScript = System.IO.Path.Combine(scriptFolder, "SQLTOOLSSERVICE.Core");
|
||||||
|
var sqlToolsServicePath = System.IO.Path.Combine(System.IO.Path.GetFullPath(outputRoot), "{1}", "SQLTOOLSSERVICE");
|
||||||
|
var content = new string[] {
|
||||||
|
"#!/bin/bash",
|
||||||
|
"",
|
||||||
|
$"{{0}} \"{sqlToolsServicePath}{{2}}\" \"$@\""
|
||||||
|
};
|
||||||
|
|
||||||
|
if (System.IO.File.Exists(coreScript))
|
||||||
|
{
|
||||||
|
System.IO.File.Delete(coreScript);
|
||||||
|
}
|
||||||
|
content[2] = String.Format(content[2], "", "netcoreapp1.0", "");
|
||||||
|
System.IO.File.WriteAllLines(coreScript, content);
|
||||||
|
Run("chmod", $"+x \"{coreScript}\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
110
scripts/cake-bootstrap.ps1
Normal file
110
scripts/cake-bootstrap.ps1
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<#
|
||||||
|
|
||||||
|
.SYNOPSIS
|
||||||
|
This is a Powershell script to bootstrap a Cake build.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This Powershell script will download NuGet if missing, restore NuGet tools (including Cake)
|
||||||
|
and execute your Cake build script with the parameters you provide.
|
||||||
|
|
||||||
|
.PARAMETER Script
|
||||||
|
The build script to execute.
|
||||||
|
.PARAMETER Target
|
||||||
|
The build script target to run.
|
||||||
|
.PARAMETER Configuration
|
||||||
|
The build configuration to use.
|
||||||
|
.PARAMETER Verbosity
|
||||||
|
Specifies the amount of information to be displayed.
|
||||||
|
Tells Cake to use the latest Roslyn release.
|
||||||
|
.PARAMETER WhatIf
|
||||||
|
Performs a dry run of the build script.
|
||||||
|
No tasks will be executed.
|
||||||
|
.PARAMETER Mono
|
||||||
|
Tells Cake to use the Mono scripting engine.
|
||||||
|
|
||||||
|
.LINK
|
||||||
|
http://cakebuild.net
|
||||||
|
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param(
|
||||||
|
[string]$Script = "build.cake",
|
||||||
|
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
|
||||||
|
[string]$Verbosity = "Verbose",
|
||||||
|
[Alias("DryRun","Noop")]
|
||||||
|
[switch]$WhatIf,
|
||||||
|
[switch]$Mono,
|
||||||
|
[switch]$SkipToolPackageRestore,
|
||||||
|
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
|
||||||
|
[string[]]$ScriptArgs
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host "Preparing to run build script..."
|
||||||
|
|
||||||
|
$PS_SCRIPT_ROOT = split-path -parent $MyInvocation.MyCommand.Definition;
|
||||||
|
$TOOLS_DIR = Join-Path $PSScriptRoot "..\.tools"
|
||||||
|
$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe"
|
||||||
|
$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/v3.3.0/nuget.exe"
|
||||||
|
$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe"
|
||||||
|
$PACKAGES_CONFIG = Join-Path $PS_SCRIPT_ROOT "packages.config"
|
||||||
|
|
||||||
|
# Should we use mono?
|
||||||
|
$UseMono = "";
|
||||||
|
if($Mono.IsPresent) {
|
||||||
|
Write-Verbose -Message "Using the Mono based scripting engine."
|
||||||
|
$UseMono = "-mono"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Is this a dry run?
|
||||||
|
$UseDryRun = "";
|
||||||
|
if($WhatIf.IsPresent) {
|
||||||
|
$UseDryRun = "-dryrun"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure tools folder exists
|
||||||
|
if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) {
|
||||||
|
Write-Verbose -Message "Creating tools directory..."
|
||||||
|
New-Item -Path $TOOLS_DIR -Type directory | out-null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Try download NuGet.exe if not exists
|
||||||
|
if (!(Test-Path $NUGET_EXE)) {
|
||||||
|
Write-Verbose -Message "Downloading NuGet.exe..."
|
||||||
|
try {
|
||||||
|
(New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE)
|
||||||
|
} catch {
|
||||||
|
Throw "Could not download NuGet.exe."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save nuget.exe path to environment to be available to child processed
|
||||||
|
$ENV:NUGET_EXE = $NUGET_EXE
|
||||||
|
|
||||||
|
# Restore tools from NuGet?
|
||||||
|
if(-Not $SkipToolPackageRestore.IsPresent)
|
||||||
|
{
|
||||||
|
# Restore packages from NuGet.
|
||||||
|
Push-Location
|
||||||
|
Set-Location $TOOLS_DIR
|
||||||
|
|
||||||
|
Write-Verbose -Message "Restoring tools from NuGet..."
|
||||||
|
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install $PACKAGES_CONFIG -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
|
||||||
|
Write-Verbose -Message ($NuGetOutput | out-string)
|
||||||
|
|
||||||
|
Pop-Location
|
||||||
|
if ($LASTEXITCODE -ne 0)
|
||||||
|
{
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure that Cake has been installed.
|
||||||
|
if (!(Test-Path $CAKE_EXE)) {
|
||||||
|
Throw "Could not find Cake.exe at $CAKE_EXE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start Cake
|
||||||
|
Write-Host "Running build script..."
|
||||||
|
Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $ScriptArgs"
|
||||||
|
exit $LASTEXITCODE
|
||||||
69
scripts/cake-bootstrap.sh
Normal file
69
scripts/cake-bootstrap.sh
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
###############################################################
|
||||||
|
# This is the Cake bootstrapper script that is responsible for
|
||||||
|
# downloading Cake and all specified tools from NuGet.
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
# Define directories.
|
||||||
|
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||||
|
TOOLS_DIR=$SCRIPT_DIR/../.tools
|
||||||
|
export NUGET_EXE=$TOOLS_DIR/nuget.exe
|
||||||
|
CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe
|
||||||
|
PACKAGES_CONFIG=$SCRIPT_DIR/packages.config
|
||||||
|
|
||||||
|
# Define default arguments.
|
||||||
|
SCRIPT="build.cake"
|
||||||
|
VERBOSITY="verbose"
|
||||||
|
DRYRUN=
|
||||||
|
SHOW_VERSION=false
|
||||||
|
SCRIPT_ARGUMENTS=()
|
||||||
|
|
||||||
|
# Parse arguments.
|
||||||
|
for i in "$@"; do
|
||||||
|
case $1 in
|
||||||
|
-s|--script) SCRIPT="$2"; shift ;;
|
||||||
|
-v|--verbosity) VERBOSITY="$2"; shift ;;
|
||||||
|
-d|--dryrun) DRYRUN="-dryrun" ;;
|
||||||
|
--version) SHOW_VERSION=true ;;
|
||||||
|
--) shift; SCRIPT_ARGUMENTS+=("$@"); break ;;
|
||||||
|
*) SCRIPT_ARGUMENTS+=("$1") ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# Make sure the tools folder exist.
|
||||||
|
if [ ! -d "$TOOLS_DIR" ]; then
|
||||||
|
mkdir "$TOOLS_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Download NuGet if it does not exist.
|
||||||
|
if [ ! -f "$NUGET_EXE" ]; then
|
||||||
|
echo "Downloading NuGet..."
|
||||||
|
curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/v3.3.0/nuget.exe
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "An error occured while downloading nuget.exe."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Restore tools from NuGet.
|
||||||
|
pushd "$TOOLS_DIR" >/dev/null
|
||||||
|
mono "$NUGET_EXE" install "$PACKAGES_CONFIG" -ExcludeVersion -OutputDirectory "$TOOLS_DIR"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Could not restore NuGet packages."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
# Make sure that Cake has been installed.
|
||||||
|
if [ ! -f "$CAKE_EXE" ]; then
|
||||||
|
echo "Could not find Cake.exe at '$CAKE_EXE'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start Cake
|
||||||
|
if $SHOW_VERSION; then
|
||||||
|
exec mono "$CAKE_EXE" -version
|
||||||
|
else
|
||||||
|
exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY $DRYRUN "${SCRIPT_ARGUMENTS[@]}"
|
||||||
|
fi
|
||||||
5
scripts/packages.config
Normal file
5
scripts/packages.config
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="Cake" version="0.10.1" />
|
||||||
|
<package id="Newtonsoft.Json" version="8.0.3" />
|
||||||
|
</packages>
|
||||||
204
scripts/runhelpers.cake
Normal file
204
scripts/runhelpers.cake
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class encompassing the optional settings for running processes.
|
||||||
|
/// </summary>
|
||||||
|
public class RunOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The working directory of the process.
|
||||||
|
/// </summary>
|
||||||
|
public string WorkingDirectory { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Container logging the StandardOutput content.
|
||||||
|
/// </summary>
|
||||||
|
public IList<string> StandardOutputListing { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Desired maximum time-out for the process
|
||||||
|
/// </summary>
|
||||||
|
public int TimeOut { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper for the exit code and state.
|
||||||
|
/// Used to query the result of an execution with method calls.
|
||||||
|
/// </summary>
|
||||||
|
public struct ExitStatus
|
||||||
|
{
|
||||||
|
private int _code;
|
||||||
|
private bool _timeOut;
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor when the execution finished.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="code">The exit code</param>
|
||||||
|
public ExitStatus(int code)
|
||||||
|
{
|
||||||
|
this._code = code;
|
||||||
|
this._timeOut = false;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor when the execution potentially timed out.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="code">The exit code</param>
|
||||||
|
/// <param name="timeOut">True if the execution timed out</param>
|
||||||
|
public ExitStatus(int code, bool timeOut)
|
||||||
|
{
|
||||||
|
this._code = code;
|
||||||
|
this._timeOut = timeOut;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Flag signalling that the execution timed out.
|
||||||
|
/// </summary>
|
||||||
|
public bool DidTimeOut { get { return _timeOut; } }
|
||||||
|
/// <summary>
|
||||||
|
/// Implicit conversion from ExitStatus to the exit code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exitStatus">The exit status</param>
|
||||||
|
/// <returns>The exit code</returns>
|
||||||
|
public static implicit operator int(ExitStatus exitStatus)
|
||||||
|
{
|
||||||
|
return exitStatus._code;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Trigger Exception for non-zero exit code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="errorMessage">The message to use in the Exception</param>
|
||||||
|
/// <returns>The exit status for further queries</returns>
|
||||||
|
public ExitStatus ExceptionOnError(string errorMessage)
|
||||||
|
{
|
||||||
|
if (this._code != 0)
|
||||||
|
{
|
||||||
|
throw new Exception(errorMessage);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run the given executable with the given arguments.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exec">Executable to run</param>
|
||||||
|
/// <param name="args">Arguments</param>
|
||||||
|
/// <returns>The exit status for further queries</returns>
|
||||||
|
ExitStatus Run(string exec, string args)
|
||||||
|
{
|
||||||
|
return Run(exec, args, new RunOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run the given executable with the given arguments.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exec">Executable to run</param>
|
||||||
|
/// <param name="args">Arguments</param>
|
||||||
|
/// <param name="workingDirectory">Working directory</param>
|
||||||
|
/// <returns>The exit status for further queries</returns>
|
||||||
|
ExitStatus Run(string exec, string args, string workingDirectory)
|
||||||
|
{
|
||||||
|
return Run(exec, args,
|
||||||
|
new RunOptions()
|
||||||
|
{
|
||||||
|
WorkingDirectory = workingDirectory
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run the given executable with the given arguments.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exec">Executable to run</param>
|
||||||
|
/// <param name="args">Arguments</param>
|
||||||
|
/// <param name="runOptions">Optional settings</param>
|
||||||
|
/// <returns>The exit status for further queries</returns>
|
||||||
|
ExitStatus Run(string exec, string args, RunOptions runOptions)
|
||||||
|
{
|
||||||
|
var workingDirectory = runOptions.WorkingDirectory ?? System.IO.Directory.GetCurrentDirectory();
|
||||||
|
var process = System.Diagnostics.Process.Start(
|
||||||
|
new ProcessStartInfo(exec, args)
|
||||||
|
{
|
||||||
|
WorkingDirectory = workingDirectory,
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = runOptions.StandardOutputListing != null
|
||||||
|
});
|
||||||
|
if (runOptions.StandardOutputListing != null)
|
||||||
|
{
|
||||||
|
process.OutputDataReceived += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.Data != null)
|
||||||
|
{
|
||||||
|
runOptions.StandardOutputListing.Add(e.Data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
process.BeginOutputReadLine();
|
||||||
|
}
|
||||||
|
if (runOptions.TimeOut == 0)
|
||||||
|
{
|
||||||
|
process.WaitForExit();
|
||||||
|
return new ExitStatus(process.ExitCode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool finished = process.WaitForExit(runOptions.TimeOut);
|
||||||
|
if (finished)
|
||||||
|
{
|
||||||
|
return new ExitStatus(process.ExitCode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
KillProcessTree(process);
|
||||||
|
return new ExitStatus(0, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run restore with the given arguments
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exec">Executable to run</param>
|
||||||
|
/// <param name="args">Arguments</param>
|
||||||
|
/// <param name="runOptions">Optional settings</param>
|
||||||
|
/// <returns>The exit status for further queries</returns>
|
||||||
|
ExitStatus RunRestore(string exec, string args, string workingDirectory)
|
||||||
|
{
|
||||||
|
Information("Restoring packages....");
|
||||||
|
var p = StartAndReturnProcess(exec,
|
||||||
|
new ProcessSettings
|
||||||
|
{
|
||||||
|
Arguments = args,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
WorkingDirectory = workingDirectory
|
||||||
|
});
|
||||||
|
p.WaitForExit();
|
||||||
|
var exitCode = p.GetExitCode();
|
||||||
|
|
||||||
|
if (exitCode == 0)
|
||||||
|
{
|
||||||
|
Information("Package restore successful!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(string.Join("\n", p.GetStandardOutput()));
|
||||||
|
}
|
||||||
|
return new ExitStatus(exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kill the given process and all its child processes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="process">Root process</param>
|
||||||
|
public void KillProcessTree(Process process)
|
||||||
|
{
|
||||||
|
// Child processes are not killed on Windows by default
|
||||||
|
// Use TASKKILL to kill the process hierarchy rooted in the process
|
||||||
|
if (IsRunningOnWindows())
|
||||||
|
{
|
||||||
|
StartProcess($"TASKKILL",
|
||||||
|
new ProcessSettings
|
||||||
|
{
|
||||||
|
Arguments = $"/PID {process.Id} /T /F",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
process.Kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
sqltoolsservice.sln
Normal file
43
sqltoolsservice.sln
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 14
|
||||||
|
VisualStudioVersion = 14.0.25420.1
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2BBD7364-054F-4693-97CD-1C395E3E84A9}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{32DC973E-9EEA-4694-B1C2-B031167AB945}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.gitignore = .gitignore
|
||||||
|
global.json = global.json
|
||||||
|
nuget.config = nuget.config
|
||||||
|
README.md = README.md
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.SqlTools.ServiceLayer", "src\Microsoft.SqlTools.ServiceLayer\Microsoft.SqlTools.ServiceLayer.xproj", "{0D61DC2B-DA66-441D-B9D0-F76C98F780F9}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.SqlTools.ServiceLayer.Test", "test\Microsoft.SqlTools.ServiceLayer.Test\Microsoft.SqlTools.ServiceLayer.Test.xproj", "{2D771D16-9D85-4053-9F79-E2034737DEEF}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{0D61DC2B-DA66-441D-B9D0-F76C98F780F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0D61DC2B-DA66-441D-B9D0-F76C98F780F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0D61DC2B-DA66-441D-B9D0-F76C98F780F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0D61DC2B-DA66-441D-B9D0-F76C98F780F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2D771D16-9D85-4053-9F79-E2034737DEEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2D771D16-9D85-4053-9F79-E2034737DEEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2D771D16-9D85-4053-9F79-E2034737DEEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2D771D16-9D85-4053-9F79-E2034737DEEF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{0D61DC2B-DA66-441D-B9D0-F76C98F780F9} = {2BBD7364-054F-4693-97CD-1C395E3E84A9}
|
||||||
|
{2D771D16-9D85-4053-9F79-E2034737DEEF} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Data.Common;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information pertaining to a unique connection instance.
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public ConnectionInfo(ISqlConnectionFactory factory, string ownerUri, ConnectionDetails details)
|
||||||
|
{
|
||||||
|
Factory = factory;
|
||||||
|
OwnerUri = ownerUri;
|
||||||
|
ConnectionDetails = details;
|
||||||
|
ConnectionId = Guid.NewGuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unique Id, helpful to identify a connection info object
|
||||||
|
/// </summary>
|
||||||
|
public Guid ConnectionId { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URI identifying the owner/user of the connection. Could be a file, service, resource, etc.
|
||||||
|
/// </summary>
|
||||||
|
public string OwnerUri { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Factory used for creating the SQL connection associated with the connection info.
|
||||||
|
/// </summary>
|
||||||
|
public ISqlConnectionFactory Factory {get; private set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Properties used for creating/opening the SQL connection.
|
||||||
|
/// </summary>
|
||||||
|
public ConnectionDetails ConnectionDetails { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The connection to the SQL database that commands will be run against.
|
||||||
|
/// </summary>
|
||||||
|
public DbConnection SqlConnection { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,533 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main class for the Connection Management services
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Singleton service instance
|
||||||
|
/// </summary>
|
||||||
|
private static Lazy<ConnectionService> instance
|
||||||
|
= new Lazy<ConnectionService>(() => new ConnectionService());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the singleton service instance
|
||||||
|
/// </summary>
|
||||||
|
public static ConnectionService Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return instance.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The SQL connection factory object
|
||||||
|
/// </summary>
|
||||||
|
private ISqlConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
private Dictionary<string, ConnectionInfo> ownerToConnectionMap = new Dictionary<string, ConnectionInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service host object for sending/receiving requests/events.
|
||||||
|
/// Internal for testing purposes.
|
||||||
|
/// </summary>
|
||||||
|
internal IProtocolEndpoint ServiceHost
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor is private since it's a singleton class
|
||||||
|
/// </summary>
|
||||||
|
private ConnectionService()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback for onconnection handler
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sqlConnection"></param>
|
||||||
|
public delegate Task OnConnectionHandler(ConnectionInfo info);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
// Callback for ondisconnect handler
|
||||||
|
/// </summary>
|
||||||
|
public delegate Task OnDisconnectHandler(ConnectionSummary summary);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of onconnection handlers
|
||||||
|
/// </summary>
|
||||||
|
private readonly List<OnConnectionHandler> onConnectionActivities = new List<OnConnectionHandler>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of ondisconnect handlers
|
||||||
|
/// </summary>
|
||||||
|
private readonly List<OnDisconnectHandler> onDisconnectActivities = new List<OnDisconnectHandler>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the SQL connection factory instance
|
||||||
|
/// </summary>
|
||||||
|
public ISqlConnectionFactory ConnectionFactory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (this.connectionFactory == null)
|
||||||
|
{
|
||||||
|
this.connectionFactory = new SqlConnectionFactory();
|
||||||
|
}
|
||||||
|
return this.connectionFactory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test constructor that injects dependency interfaces
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testFactory"></param>
|
||||||
|
public ConnectionService(ISqlConnectionFactory testFactory)
|
||||||
|
{
|
||||||
|
this.connectionFactory = testFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to link a URI to an actively used connection for this URI
|
||||||
|
public bool TryFindConnection(string ownerUri, out ConnectionInfo connectionInfo)
|
||||||
|
{
|
||||||
|
return this.ownerToConnectionMap.TryGetValue(ownerUri, out connectionInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Open a connection with the specified connection details
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionParams"></param>
|
||||||
|
public ConnectResponse Connect(ConnectParams connectionParams)
|
||||||
|
{
|
||||||
|
// Validate parameters
|
||||||
|
string paramValidationErrorMessage;
|
||||||
|
if (connectionParams == null)
|
||||||
|
{
|
||||||
|
return new ConnectResponse()
|
||||||
|
{
|
||||||
|
Messages = "Error: Connection parameters cannot be null."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (!connectionParams.IsValid(out paramValidationErrorMessage))
|
||||||
|
{
|
||||||
|
return new ConnectResponse()
|
||||||
|
{
|
||||||
|
Messages = paramValidationErrorMessage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve if it is an existing connection
|
||||||
|
// Disconnect active connection if the URI is already connected
|
||||||
|
ConnectionInfo connectionInfo;
|
||||||
|
if (ownerToConnectionMap.TryGetValue(connectionParams.OwnerUri, out connectionInfo) )
|
||||||
|
{
|
||||||
|
var disconnectParams = new DisconnectParams()
|
||||||
|
{
|
||||||
|
OwnerUri = connectionParams.OwnerUri
|
||||||
|
};
|
||||||
|
Disconnect(disconnectParams);
|
||||||
|
}
|
||||||
|
connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection);
|
||||||
|
|
||||||
|
// try to connect
|
||||||
|
var response = new ConnectResponse();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// build the connection string from the input parameters
|
||||||
|
string connectionString = ConnectionService.BuildConnectionString(connectionInfo.ConnectionDetails);
|
||||||
|
|
||||||
|
// create a sql connection instance
|
||||||
|
connectionInfo.SqlConnection = connectionInfo.Factory.CreateSqlConnection(connectionString);
|
||||||
|
connectionInfo.SqlConnection.Open();
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
response.Messages = ex.ToString();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerToConnectionMap[connectionParams.OwnerUri] = connectionInfo;
|
||||||
|
|
||||||
|
// invoke callback notifications
|
||||||
|
foreach (var activity in this.onConnectionActivities)
|
||||||
|
{
|
||||||
|
activity(connectionInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the connection result
|
||||||
|
response.ConnectionId = connectionInfo.ConnectionId.ToString();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Close a connection with the specified connection details.
|
||||||
|
/// </summary>
|
||||||
|
public bool Disconnect(DisconnectParams disconnectParams)
|
||||||
|
{
|
||||||
|
// Validate parameters
|
||||||
|
if (disconnectParams == null || string.IsNullOrEmpty(disconnectParams.OwnerUri))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the connection owned by the URI
|
||||||
|
ConnectionInfo info;
|
||||||
|
if (!ownerToConnectionMap.TryGetValue(disconnectParams.OwnerUri, out info))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the connection
|
||||||
|
info.SqlConnection.Close();
|
||||||
|
|
||||||
|
// Remove URI mapping
|
||||||
|
ownerToConnectionMap.Remove(disconnectParams.OwnerUri);
|
||||||
|
|
||||||
|
// Invoke callback notifications
|
||||||
|
foreach (var activity in this.onDisconnectActivities)
|
||||||
|
{
|
||||||
|
activity(info.ConnectionDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List all databases on the server specified
|
||||||
|
/// </summary>
|
||||||
|
public ListDatabasesResponse ListDatabases(ListDatabasesParams listDatabasesParams)
|
||||||
|
{
|
||||||
|
// Verify parameters
|
||||||
|
var owner = listDatabasesParams.OwnerUri;
|
||||||
|
if (string.IsNullOrEmpty(owner))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("OwnerUri cannot be null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the existing connection as a base for the search
|
||||||
|
ConnectionInfo info;
|
||||||
|
if (!TryFindConnection(owner, out info))
|
||||||
|
{
|
||||||
|
throw new Exception("Specified OwnerUri \"" + owner + "\" does not have an existing connection");
|
||||||
|
}
|
||||||
|
ConnectionDetails connectionDetails = info.ConnectionDetails.Clone();
|
||||||
|
|
||||||
|
// Connect to master and query sys.databases
|
||||||
|
connectionDetails.DatabaseName = "master";
|
||||||
|
var connection = this.ConnectionFactory.CreateSqlConnection(BuildConnectionString(connectionDetails));
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
DbCommand command = connection.CreateCommand();
|
||||||
|
command.CommandText = "SELECT name FROM sys.databases";
|
||||||
|
command.CommandTimeout = 15;
|
||||||
|
command.CommandType = CommandType.Text;
|
||||||
|
var reader = command.ExecuteReader();
|
||||||
|
|
||||||
|
List<string> results = new List<string>();
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
results.Add(reader[0].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.Close();
|
||||||
|
|
||||||
|
ListDatabasesResponse response = new ListDatabasesResponse();
|
||||||
|
response.DatabaseNames = results.ToArray();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializeService(IProtocolEndpoint serviceHost)
|
||||||
|
{
|
||||||
|
this.ServiceHost = serviceHost;
|
||||||
|
|
||||||
|
// Register request and event handlers with the Service Host
|
||||||
|
serviceHost.SetRequestHandler(ConnectionRequest.Type, HandleConnectRequest);
|
||||||
|
serviceHost.SetRequestHandler(DisconnectRequest.Type, HandleDisconnectRequest);
|
||||||
|
serviceHost.SetRequestHandler(ListDatabasesRequest.Type, HandleListDatabasesRequest);
|
||||||
|
|
||||||
|
// Register the configuration update handler
|
||||||
|
WorkspaceService<SqlToolsSettings>.Instance.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a new method to be called when the onconnection request is submitted
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="activity"></param>
|
||||||
|
public void RegisterOnConnectionTask(OnConnectionHandler activity)
|
||||||
|
{
|
||||||
|
onConnectionActivities.Add(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a new method to be called when the ondisconnect request is submitted
|
||||||
|
/// </summary>
|
||||||
|
public void RegisterOnDisconnectTask(OnDisconnectHandler activity)
|
||||||
|
{
|
||||||
|
onDisconnectActivities.Add(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle new connection requests
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionDetails"></param>
|
||||||
|
/// <param name="requestContext"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected async Task HandleConnectRequest(
|
||||||
|
ConnectParams connectParams,
|
||||||
|
RequestContext<ConnectResponse> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleConnectRequest");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// open connection base on request details
|
||||||
|
ConnectResponse result = ConnectionService.Instance.Connect(connectParams);
|
||||||
|
await requestContext.SendResult(result);
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
await requestContext.SendError(ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle disconnect requests
|
||||||
|
/// </summary>
|
||||||
|
protected async Task HandleDisconnectRequest(
|
||||||
|
DisconnectParams disconnectParams,
|
||||||
|
RequestContext<bool> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleDisconnectRequest");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool result = ConnectionService.Instance.Disconnect(disconnectParams);
|
||||||
|
await requestContext.SendResult(result);
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
await requestContext.SendError(ex.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle requests to list databases on the current server
|
||||||
|
/// </summary>
|
||||||
|
protected async Task HandleListDatabasesRequest(
|
||||||
|
ListDatabasesParams listDatabasesParams,
|
||||||
|
RequestContext<ListDatabasesResponse> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "ListDatabasesRequest");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ListDatabasesResponse result = ConnectionService.Instance.ListDatabases(listDatabasesParams);
|
||||||
|
await requestContext.SendResult(result);
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
await requestContext.SendError(ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task HandleDidChangeConfigurationNotification(
|
||||||
|
SqlToolsSettings newSettings,
|
||||||
|
SqlToolsSettings oldSettings,
|
||||||
|
EventContext eventContext)
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build a connection string from a connection details instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionDetails"></param>
|
||||||
|
public static string BuildConnectionString(ConnectionDetails connectionDetails)
|
||||||
|
{
|
||||||
|
SqlConnectionStringBuilder connectionBuilder = new SqlConnectionStringBuilder();
|
||||||
|
connectionBuilder["Data Source"] = connectionDetails.ServerName;
|
||||||
|
connectionBuilder["User Id"] = connectionDetails.UserName;
|
||||||
|
connectionBuilder["Password"] = connectionDetails.Password;
|
||||||
|
|
||||||
|
// Check for any optional parameters
|
||||||
|
if (!string.IsNullOrEmpty(connectionDetails.DatabaseName))
|
||||||
|
{
|
||||||
|
connectionBuilder["Initial Catalog"] = connectionDetails.DatabaseName;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(connectionDetails.AuthenticationType))
|
||||||
|
{
|
||||||
|
switch(connectionDetails.AuthenticationType)
|
||||||
|
{
|
||||||
|
case "Integrated":
|
||||||
|
connectionBuilder.IntegratedSecurity = true;
|
||||||
|
break;
|
||||||
|
case "SqlLogin":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException(string.Format("Invalid value \"{0}\" for AuthenticationType. Valid values are \"Integrated\" and \"SqlLogin\".", connectionDetails.AuthenticationType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (connectionDetails.Encrypt.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.Encrypt = connectionDetails.Encrypt.Value;
|
||||||
|
}
|
||||||
|
if (connectionDetails.TrustServerCertificate.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.TrustServerCertificate = connectionDetails.TrustServerCertificate.Value;
|
||||||
|
}
|
||||||
|
if (connectionDetails.PersistSecurityInfo.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.PersistSecurityInfo = connectionDetails.PersistSecurityInfo.Value;
|
||||||
|
}
|
||||||
|
if (connectionDetails.ConnectTimeout.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.ConnectTimeout = connectionDetails.ConnectTimeout.Value;
|
||||||
|
}
|
||||||
|
if (connectionDetails.ConnectRetryCount.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.ConnectRetryCount = connectionDetails.ConnectRetryCount.Value;
|
||||||
|
}
|
||||||
|
if (connectionDetails.ConnectRetryInterval.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.ConnectRetryInterval = connectionDetails.ConnectRetryInterval.Value;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(connectionDetails.ApplicationName))
|
||||||
|
{
|
||||||
|
connectionBuilder.ApplicationName = connectionDetails.ApplicationName;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(connectionDetails.WorkstationId))
|
||||||
|
{
|
||||||
|
connectionBuilder.WorkstationID = connectionDetails.WorkstationId;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(connectionDetails.ApplicationIntent))
|
||||||
|
{
|
||||||
|
ApplicationIntent intent;
|
||||||
|
switch (connectionDetails.ApplicationIntent)
|
||||||
|
{
|
||||||
|
case "ReadOnly":
|
||||||
|
intent = ApplicationIntent.ReadOnly;
|
||||||
|
break;
|
||||||
|
case "ReadWrite":
|
||||||
|
intent = ApplicationIntent.ReadWrite;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException(string.Format("Invalid value \"{0}\" for ApplicationIntent. Valid values are \"ReadWrite\" and \"ReadOnly\".", connectionDetails.ApplicationIntent));
|
||||||
|
}
|
||||||
|
connectionBuilder.ApplicationIntent = intent;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(connectionDetails.CurrentLanguage))
|
||||||
|
{
|
||||||
|
connectionBuilder.CurrentLanguage = connectionDetails.CurrentLanguage;
|
||||||
|
}
|
||||||
|
if (connectionDetails.Pooling.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.Pooling = connectionDetails.Pooling.Value;
|
||||||
|
}
|
||||||
|
if (connectionDetails.MaxPoolSize.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.MaxPoolSize = connectionDetails.MaxPoolSize.Value;
|
||||||
|
}
|
||||||
|
if (connectionDetails.MinPoolSize.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.MinPoolSize = connectionDetails.MinPoolSize.Value;
|
||||||
|
}
|
||||||
|
if (connectionDetails.LoadBalanceTimeout.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.LoadBalanceTimeout = connectionDetails.LoadBalanceTimeout.Value;
|
||||||
|
}
|
||||||
|
if (connectionDetails.Replication.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.Replication = connectionDetails.Replication.Value;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(connectionDetails.AttachDbFilename))
|
||||||
|
{
|
||||||
|
connectionBuilder.AttachDBFilename = connectionDetails.AttachDbFilename;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(connectionDetails.FailoverPartner))
|
||||||
|
{
|
||||||
|
connectionBuilder.FailoverPartner = connectionDetails.FailoverPartner;
|
||||||
|
}
|
||||||
|
if (connectionDetails.MultiSubnetFailover.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.MultiSubnetFailover = connectionDetails.MultiSubnetFailover.Value;
|
||||||
|
}
|
||||||
|
if (connectionDetails.MultipleActiveResultSets.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.MultipleActiveResultSets = connectionDetails.MultipleActiveResultSets.Value;
|
||||||
|
}
|
||||||
|
if (connectionDetails.PacketSize.HasValue)
|
||||||
|
{
|
||||||
|
connectionBuilder.PacketSize = connectionDetails.PacketSize.Value;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(connectionDetails.TypeSystemVersion))
|
||||||
|
{
|
||||||
|
connectionBuilder.TypeSystemVersion = connectionDetails.TypeSystemVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectionBuilder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Change the database context of a connection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ownerUri">URI of the owner of the connection</param>
|
||||||
|
/// <param name="newDatabaseName">Name of the database to change the connection to</param>
|
||||||
|
public void ChangeConnectionDatabaseContext(string ownerUri, string newDatabaseName)
|
||||||
|
{
|
||||||
|
ConnectionInfo info;
|
||||||
|
if (TryFindConnection(ownerUri, out info))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (info.SqlConnection.State == ConnectionState.Open)
|
||||||
|
{
|
||||||
|
info.SqlConnection.ChangeDatabase(newDatabaseName);
|
||||||
|
}
|
||||||
|
info.ConnectionDetails.DatabaseName = newDatabaseName;
|
||||||
|
|
||||||
|
// Fire a connection changed event
|
||||||
|
ConnectionChangedParams parameters = new ConnectionChangedParams();
|
||||||
|
ConnectionSummary summary = (ConnectionSummary)(info.ConnectionDetails);
|
||||||
|
parameters.Connection = summary.Clone();
|
||||||
|
parameters.OwnerUri = ownerUri;
|
||||||
|
ServiceHost.SendEvent(ConnectionChangedNotification.Type, parameters);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Write(
|
||||||
|
LogLevel.Error,
|
||||||
|
string.Format(
|
||||||
|
"Exception caught while trying to change database context to [{0}] for OwnerUri [{1}]. Exception:{2}",
|
||||||
|
newDatabaseName,
|
||||||
|
ownerUri,
|
||||||
|
e.ToString())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters for the Connect Request.
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A URI identifying the owner of the connection. This will most commonly be a file in the workspace
|
||||||
|
/// or a virtual file representing an object in a database.
|
||||||
|
/// </summary>
|
||||||
|
public string OwnerUri { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the required parameters to initialize a connection to a database.
|
||||||
|
/// A connection will identified by its server name, database name and user name.
|
||||||
|
/// This may be changed in the future to support multiple connections with different
|
||||||
|
/// connection properties to the same database.
|
||||||
|
/// </summary>
|
||||||
|
public ConnectionDetails Connection { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods to ConnectParams
|
||||||
|
/// </summary>
|
||||||
|
public static class ConnectParamsExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Check that the fields in ConnectParams are all valid
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsValid(this ConnectParams parameters, out string errorMessage)
|
||||||
|
{
|
||||||
|
errorMessage = string.Empty;
|
||||||
|
if (string.IsNullOrEmpty(parameters.OwnerUri))
|
||||||
|
{
|
||||||
|
errorMessage = "Error: OwnerUri cannot be null or empty.";
|
||||||
|
}
|
||||||
|
else if (parameters.Connection == null)
|
||||||
|
{
|
||||||
|
errorMessage = "Error: Connection details object cannot be null.";
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrEmpty(parameters.Connection.ServerName))
|
||||||
|
{
|
||||||
|
errorMessage = "Error: ServerName cannot be null or empty.";
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrEmpty(parameters.Connection.AuthenticationType) || parameters.Connection.AuthenticationType == "SqlLogin")
|
||||||
|
{
|
||||||
|
// For SqlLogin, username/password cannot be empty
|
||||||
|
if (string.IsNullOrEmpty(parameters.Connection.UserName))
|
||||||
|
{
|
||||||
|
errorMessage = "Error: UserName cannot be null or empty when using SqlLogin authentication.";
|
||||||
|
}
|
||||||
|
else if( string.IsNullOrEmpty(parameters.Connection.Password))
|
||||||
|
{
|
||||||
|
errorMessage = "Error: Password cannot be null or empty when using SqlLogin authentication.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(errorMessage))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Message format for the connection result response
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectResponse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A GUID representing a unique connection ID
|
||||||
|
/// </summary>
|
||||||
|
public string ConnectionId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets any connection error messages
|
||||||
|
/// </summary>
|
||||||
|
public string Messages { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ConnectionChanged notification mapping entry
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionChangedNotification
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<ConnectionChangedParams> Type =
|
||||||
|
EventType<ConnectionChangedParams>.Create("connection/connectionchanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters for the ConnectionChanged Notification.
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionChangedParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A URI identifying the owner of the connection. This will most commonly be a file in the workspace
|
||||||
|
/// or a virtual file representing an object in a database.
|
||||||
|
/// </summary>
|
||||||
|
public string OwnerUri { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the high-level properties about the connection, for display to the user.
|
||||||
|
/// </summary>
|
||||||
|
public ConnectionSummary Connection { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Message format for the initial connection request
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If this contract is ever changed, be sure to update ConnectionDetailsExtensions methods.
|
||||||
|
/// </remarks>
|
||||||
|
public class ConnectionDetails : ConnectionSummary
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the connection password
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the authentication to use.
|
||||||
|
/// </summary>
|
||||||
|
public string AuthenticationType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a Boolean value that indicates whether SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed.
|
||||||
|
/// </summary>
|
||||||
|
public bool? Encrypt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value that indicates whether the channel will be encrypted while bypassing walking the certificate chain to validate trust.
|
||||||
|
/// </summary>
|
||||||
|
public bool? TrustServerCertificate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a Boolean value that indicates if security-sensitive information, such as the password, is not returned as part of the connection if the connection is open or has ever been in an open state.
|
||||||
|
/// </summary>
|
||||||
|
public bool? PersistSecurityInfo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error.
|
||||||
|
/// </summary>
|
||||||
|
public int? ConnectTimeout { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of reconnections attempted after identifying that there was an idle connection failure.
|
||||||
|
/// </summary>
|
||||||
|
public int? ConnectRetryCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Amount of time (in seconds) between each reconnection attempt after identifying that there was an idle connection failure.
|
||||||
|
/// </summary>
|
||||||
|
public int? ConnectRetryInterval { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the application associated with the connection string.
|
||||||
|
/// </summary>
|
||||||
|
public string ApplicationName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the workstation connecting to SQL Server.
|
||||||
|
/// </summary>
|
||||||
|
public string WorkstationId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Declares the application workload type when connecting to a database in an SQL Server Availability Group.
|
||||||
|
/// </summary>
|
||||||
|
public string ApplicationIntent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the SQL Server Language record name.
|
||||||
|
/// </summary>
|
||||||
|
public string CurrentLanguage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a Boolean value that indicates whether the connection will be pooled or explicitly opened every time that the connection is requested.
|
||||||
|
/// </summary>
|
||||||
|
public bool? Pooling { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum number of connections allowed in the connection pool for this specific connection string.
|
||||||
|
/// </summary>
|
||||||
|
public int? MaxPoolSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the minimum number of connections allowed in the connection pool for this specific connection string.
|
||||||
|
/// </summary>
|
||||||
|
public int? MinPoolSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the minimum time, in seconds, for the connection to live in the connection pool before being destroyed.
|
||||||
|
/// </summary>
|
||||||
|
public int? LoadBalanceTimeout { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a Boolean value that indicates whether replication is supported using the connection.
|
||||||
|
/// </summary>
|
||||||
|
public bool? Replication { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a string that contains the name of the primary data file. This includes the full path name of an attachable database.
|
||||||
|
/// </summary>
|
||||||
|
public string AttachDbFilename { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name or address of the partner server to connect to if the primary server is down.
|
||||||
|
/// </summary>
|
||||||
|
public string FailoverPartner { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If your application is connecting to an AlwaysOn availability group (AG) on different subnets, setting MultiSubnetFailover=true provides faster detection of and connection to the (currently) active server.
|
||||||
|
/// </summary>
|
||||||
|
public bool? MultiSubnetFailover { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When true, an application can maintain multiple active result sets (MARS).
|
||||||
|
/// </summary>
|
||||||
|
public bool? MultipleActiveResultSets { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the size in bytes of the network packets used to communicate with an instance of SQL Server.
|
||||||
|
/// </summary>
|
||||||
|
public int? PacketSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a string value that indicates the type system the application expects.
|
||||||
|
/// </summary>
|
||||||
|
public string TypeSystemVersion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the ConnectionDetails contract class
|
||||||
|
/// </summary>
|
||||||
|
public static class ConnectionDetailsExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a copy of a connection details object.
|
||||||
|
/// </summary>
|
||||||
|
public static ConnectionDetails Clone(this ConnectionDetails details)
|
||||||
|
{
|
||||||
|
return new ConnectionDetails()
|
||||||
|
{
|
||||||
|
ServerName = details.ServerName,
|
||||||
|
DatabaseName = details.DatabaseName,
|
||||||
|
UserName = details.UserName,
|
||||||
|
Password = details.Password,
|
||||||
|
AuthenticationType = details.AuthenticationType,
|
||||||
|
Encrypt = details.Encrypt,
|
||||||
|
TrustServerCertificate = details.TrustServerCertificate,
|
||||||
|
PersistSecurityInfo = details.PersistSecurityInfo,
|
||||||
|
ConnectTimeout = details.ConnectTimeout,
|
||||||
|
ConnectRetryCount = details.ConnectRetryCount,
|
||||||
|
ConnectRetryInterval = details.ConnectRetryInterval,
|
||||||
|
ApplicationName = details.ApplicationName,
|
||||||
|
WorkstationId = details.WorkstationId,
|
||||||
|
ApplicationIntent = details.ApplicationIntent,
|
||||||
|
CurrentLanguage = details.CurrentLanguage,
|
||||||
|
Pooling = details.Pooling,
|
||||||
|
MaxPoolSize = details.MaxPoolSize,
|
||||||
|
MinPoolSize = details.MinPoolSize,
|
||||||
|
LoadBalanceTimeout = details.LoadBalanceTimeout,
|
||||||
|
Replication = details.Replication,
|
||||||
|
AttachDbFilename = details.AttachDbFilename,
|
||||||
|
FailoverPartner = details.FailoverPartner,
|
||||||
|
MultiSubnetFailover = details.MultiSubnetFailover,
|
||||||
|
MultipleActiveResultSets = details.MultipleActiveResultSets,
|
||||||
|
PacketSize = details.PacketSize,
|
||||||
|
TypeSystemVersion = details.TypeSystemVersion
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Connect request mapping entry
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<ConnectParams, ConnectResponse> Type =
|
||||||
|
RequestType<ConnectParams, ConnectResponse>.Create("connection/connect");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides high level information about a connection.
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionSummary
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the connection server name
|
||||||
|
/// </summary>
|
||||||
|
public string ServerName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the connection database name
|
||||||
|
/// </summary>
|
||||||
|
public string DatabaseName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the connection user name
|
||||||
|
/// </summary>
|
||||||
|
public string UserName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Treats connections as the same if their server, db and usernames all match
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionSummaryComparer : IEqualityComparer<ConnectionSummary>
|
||||||
|
{
|
||||||
|
public bool Equals(ConnectionSummary x, ConnectionSummary y)
|
||||||
|
{
|
||||||
|
if(x == y) { return true; }
|
||||||
|
else if(x != null)
|
||||||
|
{
|
||||||
|
if(y == null) { return false; }
|
||||||
|
|
||||||
|
// Compare server, db, username. Note: server is case-insensitive in the driver
|
||||||
|
return string.Compare(x.ServerName, y.ServerName, StringComparison.OrdinalIgnoreCase) == 0
|
||||||
|
&& string.Compare(x.DatabaseName, y.DatabaseName, StringComparison.Ordinal) == 0
|
||||||
|
&& string.Compare(x.UserName, y.UserName, StringComparison.Ordinal) == 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(ConnectionSummary obj)
|
||||||
|
{
|
||||||
|
int hashcode = 31;
|
||||||
|
if(obj != null)
|
||||||
|
{
|
||||||
|
if(obj.ServerName != null)
|
||||||
|
{
|
||||||
|
hashcode ^= obj.ServerName.GetHashCode();
|
||||||
|
}
|
||||||
|
if (obj.DatabaseName != null)
|
||||||
|
{
|
||||||
|
hashcode ^= obj.DatabaseName.GetHashCode();
|
||||||
|
}
|
||||||
|
if (obj.UserName != null)
|
||||||
|
{
|
||||||
|
hashcode ^= obj.UserName.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hashcode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods to ConnectionSummary
|
||||||
|
/// </summary>
|
||||||
|
public static class ConnectionSummaryExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a copy of a ConnectionSummary object
|
||||||
|
/// </summary>
|
||||||
|
public static ConnectionSummary Clone(this ConnectionSummary summary)
|
||||||
|
{
|
||||||
|
return new ConnectionSummary()
|
||||||
|
{
|
||||||
|
ServerName = summary.ServerName,
|
||||||
|
DatabaseName = summary.DatabaseName,
|
||||||
|
UserName = summary.UserName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters for the Disconnect Request.
|
||||||
|
/// </summary>
|
||||||
|
public class DisconnectParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A URI identifying the owner of the connection. This will most commonly be a file in the workspace
|
||||||
|
/// or a virtual file representing an object in a database.
|
||||||
|
/// </summary>
|
||||||
|
public string OwnerUri { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnect request mapping entry
|
||||||
|
/// </summary>
|
||||||
|
public class DisconnectRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<DisconnectParams, bool> Type =
|
||||||
|
RequestType<DisconnectParams, bool>.Create("connection/disconnect");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters for the List Databases Request.
|
||||||
|
/// </summary>
|
||||||
|
public class ListDatabasesParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// URI of the owner of the connection requesting the list of databases.
|
||||||
|
/// </summary>
|
||||||
|
public string OwnerUri { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// List databases request mapping entry
|
||||||
|
/// </summary>
|
||||||
|
public class ListDatabasesRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<ListDatabasesParams, ListDatabasesResponse> Type =
|
||||||
|
RequestType<ListDatabasesParams, ListDatabasesResponse>.Create("connection/listdatabases");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Message format for the list databases response
|
||||||
|
/// </summary>
|
||||||
|
public class ListDatabasesResponse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of database names.
|
||||||
|
/// </summary>
|
||||||
|
public string[] DatabaseNames { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Data.Common;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for the SQL Connection factory
|
||||||
|
/// </summary>
|
||||||
|
public interface ISqlConnectionFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new SQL Connection object
|
||||||
|
/// </summary>
|
||||||
|
DbConnection CreateSqlConnection(string connectionString);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Connection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Factory class to create SqlClientConnections
|
||||||
|
/// The purpose of the factory is to make it easier to mock out the database
|
||||||
|
/// in 'offline' unit test scenarios.
|
||||||
|
/// </summary>
|
||||||
|
public class SqlConnectionFactory : ISqlConnectionFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new SqlConnection object
|
||||||
|
/// </summary>
|
||||||
|
public DbConnection CreateSqlConnection(string connectionString)
|
||||||
|
{
|
||||||
|
return new SqlConnection(connectionString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A Credential containing information needed to log into a resource. This is primarily
|
||||||
|
/// defined as a unique <see cref="CredentialId"/> with an associated <see cref="Password"/>
|
||||||
|
/// that's linked to it.
|
||||||
|
/// </summary>
|
||||||
|
public class Credential
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A unique ID to identify the credential being saved.
|
||||||
|
/// </summary>
|
||||||
|
public string CredentialId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Password stored for this credential.
|
||||||
|
/// </summary>
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default Constructor
|
||||||
|
/// </summary>
|
||||||
|
public Credential()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor used when only <paramref name="credentialId"/> is known
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="credentialId"><see cref="CredentialId"/></param>
|
||||||
|
public Credential(string credentialId)
|
||||||
|
: this(credentialId, null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="credentialId"><see cref="CredentialId"/></param>
|
||||||
|
/// <param name="password"><see cref="Password"/></param>
|
||||||
|
public Credential(string credentialId, string password)
|
||||||
|
{
|
||||||
|
CredentialId = credentialId;
|
||||||
|
Password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Credential Copy(Credential credential)
|
||||||
|
{
|
||||||
|
return new Credential
|
||||||
|
{
|
||||||
|
CredentialId = credential.CredentialId,
|
||||||
|
Password = credential.Password
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates the credential has all the properties needed to look up the password
|
||||||
|
/// </summary>
|
||||||
|
public static void ValidateForLookup(Credential credential)
|
||||||
|
{
|
||||||
|
Validate.IsNotNull("credential", credential);
|
||||||
|
Validate.IsNotNullOrEmptyString("credential.CredentialId", credential.CredentialId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates the credential has all the properties needed to save a password
|
||||||
|
/// </summary>
|
||||||
|
public static void ValidateForSave(Credential credential)
|
||||||
|
{
|
||||||
|
ValidateForLookup(credential);
|
||||||
|
Validate.IsNotNullOrEmptyString("credential.Password", credential.Password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read Credential request mapping entry. Expects a Credential with CredentialId,
|
||||||
|
/// and responds with the <see cref="Credential.Password"/> filled in if found
|
||||||
|
/// </summary>
|
||||||
|
public class ReadCredentialRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Request definition
|
||||||
|
/// </summary>
|
||||||
|
public static readonly
|
||||||
|
RequestType<Credential, Credential> Type =
|
||||||
|
RequestType<Credential, Credential>.Create("credential/read");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save Credential request mapping entry
|
||||||
|
/// </summary>
|
||||||
|
public class SaveCredentialRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Request definition
|
||||||
|
/// </summary>
|
||||||
|
public static readonly
|
||||||
|
RequestType<Credential, bool> Type =
|
||||||
|
RequestType<Credential, bool>.Create("credential/save");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete Credential request mapping entry
|
||||||
|
/// </summary>
|
||||||
|
public class DeleteCredentialRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Request definition
|
||||||
|
/// </summary>
|
||||||
|
public static readonly
|
||||||
|
RequestType<Credential, bool> Type =
|
||||||
|
RequestType<Credential, bool>.Create("credential/delete");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Credentials.Linux;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Credentials.OSX;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Credentials.Win32;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Service responsible for securing credentials in a platform-neutral manner. This provides
|
||||||
|
/// a generic API for read, save and delete credentials
|
||||||
|
/// </summary>
|
||||||
|
public class CredentialService
|
||||||
|
{
|
||||||
|
internal static string DefaultSecretsFolder = ".sqlsecrets";
|
||||||
|
internal const string DefaultSecretsFile = "sqlsecrets.json";
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Singleton service instance
|
||||||
|
/// </summary>
|
||||||
|
private static Lazy<CredentialService> instance
|
||||||
|
= new Lazy<CredentialService>(() => new CredentialService());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the singleton service instance
|
||||||
|
/// </summary>
|
||||||
|
public static CredentialService Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return instance.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ICredentialStore credStore;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default constructor is private since it's a singleton class
|
||||||
|
/// </summary>
|
||||||
|
private CredentialService()
|
||||||
|
: this(null, new LinuxCredentialStore.StoreConfig()
|
||||||
|
{ CredentialFolder = DefaultSecretsFolder, CredentialFile = DefaultSecretsFile, IsRelativeToUserHomeDir = true})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal for testing purposes only
|
||||||
|
/// </summary>
|
||||||
|
internal CredentialService(ICredentialStore store, LinuxCredentialStore.StoreConfig config)
|
||||||
|
{
|
||||||
|
this.credStore = store != null ? store : GetStoreForOS(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal for testing purposes only
|
||||||
|
/// </summary>
|
||||||
|
internal static ICredentialStore GetStoreForOS(LinuxCredentialStore.StoreConfig config)
|
||||||
|
{
|
||||||
|
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
return new Win32CredentialStore();
|
||||||
|
}
|
||||||
|
else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||||
|
{
|
||||||
|
return new OSXCredentialStore();
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
return new LinuxCredentialStore(config);
|
||||||
|
}
|
||||||
|
throw new InvalidOperationException("Platform not currently supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializeService(IProtocolEndpoint serviceHost)
|
||||||
|
{
|
||||||
|
// Register request and event handlers with the Service Host
|
||||||
|
serviceHost.SetRequestHandler(ReadCredentialRequest.Type, HandleReadCredentialRequest);
|
||||||
|
serviceHost.SetRequestHandler(SaveCredentialRequest.Type, HandleSaveCredentialRequest);
|
||||||
|
serviceHost.SetRequestHandler(DeleteCredentialRequest.Type, HandleDeleteCredentialRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task HandleReadCredentialRequest(Credential credential, RequestContext<Credential> requestContext)
|
||||||
|
{
|
||||||
|
Func<Credential> doRead = () =>
|
||||||
|
{
|
||||||
|
return ReadCredential(credential);
|
||||||
|
};
|
||||||
|
await HandleRequest(doRead, requestContext, "HandleReadCredentialRequest");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Credential ReadCredential(Credential credential)
|
||||||
|
{
|
||||||
|
Credential.ValidateForLookup(credential);
|
||||||
|
|
||||||
|
Credential result = Credential.Copy(credential);
|
||||||
|
string password;
|
||||||
|
if (credStore.TryGetPassword(credential.CredentialId, out password))
|
||||||
|
{
|
||||||
|
result.Password = password;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task HandleSaveCredentialRequest(Credential credential, RequestContext<bool> requestContext)
|
||||||
|
{
|
||||||
|
Func<bool> doSave = () =>
|
||||||
|
{
|
||||||
|
Credential.ValidateForSave(credential);
|
||||||
|
return credStore.Save(credential);
|
||||||
|
};
|
||||||
|
await HandleRequest(doSave, requestContext, "HandleSaveCredentialRequest");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task HandleDeleteCredentialRequest(Credential credential, RequestContext<bool> requestContext)
|
||||||
|
{
|
||||||
|
Func<bool> doDelete = () =>
|
||||||
|
{
|
||||||
|
Credential.ValidateForLookup(credential);
|
||||||
|
return credStore.DeletePassword(credential.CredentialId);
|
||||||
|
};
|
||||||
|
await HandleRequest(doDelete, requestContext, "HandleDeleteCredentialRequest");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleRequest<T>(Func<T> handler, RequestContext<T> requestContext, string requestType)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, requestType);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
T result = handler();
|
||||||
|
await requestContext.SendResult(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await requestContext.SendError(ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="ICredentialStore"/> support securely saving and retrieving passwords
|
||||||
|
/// </summary>
|
||||||
|
public interface ICredentialStore
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Saves a Password linked to a given Credential
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="credential">
|
||||||
|
/// A <see cref="Credential"/> to be saved.
|
||||||
|
/// <see cref="Credential.CredentialId"/> and <see cref="Credential.Password"/> are required
|
||||||
|
/// </param>
|
||||||
|
/// <returns>True if successful, false otherwise</returns>
|
||||||
|
bool Save(Credential credential);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a Password and sets it into a <see cref="Credential"/> object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="credentialId">The name of the credential to find the password for. This is required</param>
|
||||||
|
/// <param name="password">Out value</param>
|
||||||
|
/// <returns>true if password was found, false otherwise</returns>
|
||||||
|
bool TryGetPassword(string credentialId, out string password);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a password linked to a given credential
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="credentialId">The name of the credential to find the password for. This is required</param>
|
||||||
|
/// <returns>True if password existed and was deleted, false otherwise</returns>
|
||||||
|
bool DeletePassword(string credentialId);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||||
|
{
|
||||||
|
internal static class InteropUtils
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the length in bytes for a Unicode string, for use in interop where length must be defined
|
||||||
|
/// </summary>
|
||||||
|
public static UInt32 GetLengthInBytes(string value)
|
||||||
|
{
|
||||||
|
|
||||||
|
return Convert.ToUInt32( (value != null ? Encoding.Unicode.GetByteCount(value) : 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CopyToString(IntPtr ptr, int length)
|
||||||
|
{
|
||||||
|
if (ptr == IntPtr.Zero || length == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] pwdBytes = new byte[length];
|
||||||
|
Marshal.Copy(ptr, pwdBytes, 0, (int)length);
|
||||||
|
return Encoding.Unicode.GetString(pwdBytes, 0, (int)length);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Simplified class to enable writing a set of credentials to/from disk
|
||||||
|
/// </summary>
|
||||||
|
public class CredentialsWrapper
|
||||||
|
{
|
||||||
|
public List<Credential> Credentials { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||||
|
{
|
||||||
|
public class FileTokenStorage
|
||||||
|
{
|
||||||
|
private const int OwnerAccessMode = 384; // Permission 0600 - owner read/write, nobody else has access
|
||||||
|
|
||||||
|
private object lockObject = new object();
|
||||||
|
|
||||||
|
private string fileName;
|
||||||
|
|
||||||
|
public FileTokenStorage(string fileName)
|
||||||
|
{
|
||||||
|
Validate.IsNotNullOrEmptyString("fileName", fileName);
|
||||||
|
this.fileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddEntries(IEnumerable<Credential> newEntries, IEnumerable<Credential> existingEntries)
|
||||||
|
{
|
||||||
|
var allEntries = existingEntries.Concat(newEntries);
|
||||||
|
this.SaveEntries(allEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
this.SaveEntries(new List<Credential>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Credential> LoadEntries()
|
||||||
|
{
|
||||||
|
if(!File.Exists(this.fileName))
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<Credential>();
|
||||||
|
}
|
||||||
|
|
||||||
|
string serializedCreds;
|
||||||
|
lock (lockObject)
|
||||||
|
{
|
||||||
|
serializedCreds = File.ReadAllText(this.fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
CredentialsWrapper creds = JsonConvert.DeserializeObject<CredentialsWrapper>(serializedCreds, Constants.JsonSerializerSettings);
|
||||||
|
if(creds != null)
|
||||||
|
{
|
||||||
|
return creds.Credentials;
|
||||||
|
}
|
||||||
|
return Enumerable.Empty<Credential>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveEntries(IEnumerable<Credential> entries)
|
||||||
|
{
|
||||||
|
CredentialsWrapper credentials = new CredentialsWrapper() { Credentials = entries.ToList() };
|
||||||
|
string serializedCreds = JsonConvert.SerializeObject(credentials, Constants.JsonSerializerSettings);
|
||||||
|
|
||||||
|
lock(lockObject)
|
||||||
|
{
|
||||||
|
WriteToFile(this.fileName, serializedCreds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteToFile(string filePath, string fileContents)
|
||||||
|
{
|
||||||
|
string dir = Path.GetDirectoryName(filePath);
|
||||||
|
if(!Directory.Exists(dir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite file, then use ChMod to ensure we have
|
||||||
|
File.WriteAllText(filePath, fileContents);
|
||||||
|
// set appropriate permissions so only current user can read/write
|
||||||
|
Interop.Sys.ChMod(filePath, OwnerAccessMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||||
|
{
|
||||||
|
internal static partial class Interop
|
||||||
|
{
|
||||||
|
/// <summary>Common Unix errno error codes.</summary>
|
||||||
|
internal enum Error
|
||||||
|
{
|
||||||
|
// These values were defined in src/Native/System.Native/fxerrno.h
|
||||||
|
//
|
||||||
|
// They compare against values obtained via Interop.Sys.GetLastError() not Marshal.GetLastWin32Error()
|
||||||
|
// which obtains the raw errno that varies between unixes. The strong typing as an enum is meant to
|
||||||
|
// prevent confusing the two. Casting to or from int is suspect. Use GetLastErrorInfo() if you need to
|
||||||
|
// correlate these to the underlying platform values or obtain the corresponding error message.
|
||||||
|
//
|
||||||
|
|
||||||
|
SUCCESS = 0,
|
||||||
|
|
||||||
|
E2BIG = 0x10001, // Argument list too long.
|
||||||
|
EACCES = 0x10002, // Permission denied.
|
||||||
|
EADDRINUSE = 0x10003, // Address in use.
|
||||||
|
EADDRNOTAVAIL = 0x10004, // Address not available.
|
||||||
|
EAFNOSUPPORT = 0x10005, // Address family not supported.
|
||||||
|
EAGAIN = 0x10006, // Resource unavailable, try again (same value as EWOULDBLOCK),
|
||||||
|
EALREADY = 0x10007, // Connection already in progress.
|
||||||
|
EBADF = 0x10008, // Bad file descriptor.
|
||||||
|
EBADMSG = 0x10009, // Bad message.
|
||||||
|
EBUSY = 0x1000A, // Device or resource busy.
|
||||||
|
ECANCELED = 0x1000B, // Operation canceled.
|
||||||
|
ECHILD = 0x1000C, // No child processes.
|
||||||
|
ECONNABORTED = 0x1000D, // Connection aborted.
|
||||||
|
ECONNREFUSED = 0x1000E, // Connection refused.
|
||||||
|
ECONNRESET = 0x1000F, // Connection reset.
|
||||||
|
EDEADLK = 0x10010, // Resource deadlock would occur.
|
||||||
|
EDESTADDRREQ = 0x10011, // Destination address required.
|
||||||
|
EDOM = 0x10012, // Mathematics argument out of domain of function.
|
||||||
|
EDQUOT = 0x10013, // Reserved.
|
||||||
|
EEXIST = 0x10014, // File exists.
|
||||||
|
EFAULT = 0x10015, // Bad address.
|
||||||
|
EFBIG = 0x10016, // File too large.
|
||||||
|
EHOSTUNREACH = 0x10017, // Host is unreachable.
|
||||||
|
EIDRM = 0x10018, // Identifier removed.
|
||||||
|
EILSEQ = 0x10019, // Illegal byte sequence.
|
||||||
|
EINPROGRESS = 0x1001A, // Operation in progress.
|
||||||
|
EINTR = 0x1001B, // Interrupted function.
|
||||||
|
EINVAL = 0x1001C, // Invalid argument.
|
||||||
|
EIO = 0x1001D, // I/O error.
|
||||||
|
EISCONN = 0x1001E, // Socket is connected.
|
||||||
|
EISDIR = 0x1001F, // Is a directory.
|
||||||
|
ELOOP = 0x10020, // Too many levels of symbolic links.
|
||||||
|
EMFILE = 0x10021, // File descriptor value too large.
|
||||||
|
EMLINK = 0x10022, // Too many links.
|
||||||
|
EMSGSIZE = 0x10023, // Message too large.
|
||||||
|
EMULTIHOP = 0x10024, // Reserved.
|
||||||
|
ENAMETOOLONG = 0x10025, // Filename too long.
|
||||||
|
ENETDOWN = 0x10026, // Network is down.
|
||||||
|
ENETRESET = 0x10027, // Connection aborted by network.
|
||||||
|
ENETUNREACH = 0x10028, // Network unreachable.
|
||||||
|
ENFILE = 0x10029, // Too many files open in system.
|
||||||
|
ENOBUFS = 0x1002A, // No buffer space available.
|
||||||
|
ENODEV = 0x1002C, // No such device.
|
||||||
|
ENOENT = 0x1002D, // No such file or directory.
|
||||||
|
ENOEXEC = 0x1002E, // Executable file format error.
|
||||||
|
ENOLCK = 0x1002F, // No locks available.
|
||||||
|
ENOLINK = 0x10030, // Reserved.
|
||||||
|
ENOMEM = 0x10031, // Not enough space.
|
||||||
|
ENOMSG = 0x10032, // No message of the desired type.
|
||||||
|
ENOPROTOOPT = 0x10033, // Protocol not available.
|
||||||
|
ENOSPC = 0x10034, // No space left on device.
|
||||||
|
ENOSYS = 0x10037, // Function not supported.
|
||||||
|
ENOTCONN = 0x10038, // The socket is not connected.
|
||||||
|
ENOTDIR = 0x10039, // Not a directory or a symbolic link to a directory.
|
||||||
|
ENOTEMPTY = 0x1003A, // Directory not empty.
|
||||||
|
ENOTSOCK = 0x1003C, // Not a socket.
|
||||||
|
ENOTSUP = 0x1003D, // Not supported (same value as EOPNOTSUP).
|
||||||
|
ENOTTY = 0x1003E, // Inappropriate I/O control operation.
|
||||||
|
ENXIO = 0x1003F, // No such device or address.
|
||||||
|
EOVERFLOW = 0x10040, // Value too large to be stored in data type.
|
||||||
|
EPERM = 0x10042, // Operation not permitted.
|
||||||
|
EPIPE = 0x10043, // Broken pipe.
|
||||||
|
EPROTO = 0x10044, // Protocol error.
|
||||||
|
EPROTONOSUPPORT = 0x10045, // Protocol not supported.
|
||||||
|
EPROTOTYPE = 0x10046, // Protocol wrong type for socket.
|
||||||
|
ERANGE = 0x10047, // Result too large.
|
||||||
|
EROFS = 0x10048, // Read-only file system.
|
||||||
|
ESPIPE = 0x10049, // Invalid seek.
|
||||||
|
ESRCH = 0x1004A, // No such process.
|
||||||
|
ESTALE = 0x1004B, // Reserved.
|
||||||
|
ETIMEDOUT = 0x1004D, // Connection timed out.
|
||||||
|
ETXTBSY = 0x1004E, // Text file busy.
|
||||||
|
EXDEV = 0x1004F, // Cross-device link.
|
||||||
|
ESOCKTNOSUPPORT = 0x1005E, // Socket type not supported.
|
||||||
|
EPFNOSUPPORT = 0x10060, // Protocol family not supported.
|
||||||
|
ESHUTDOWN = 0x1006C, // Socket shutdown.
|
||||||
|
EHOSTDOWN = 0x10070, // Host is down.
|
||||||
|
ENODATA = 0x10071, // No data available.
|
||||||
|
|
||||||
|
// POSIX permits these to have the same value and we make them always equal so
|
||||||
|
// that CoreFX cannot introduce a dependency on distinguishing between them that
|
||||||
|
// would not work on all platforms.
|
||||||
|
EOPNOTSUPP = ENOTSUP, // Operation not supported on socket.
|
||||||
|
EWOULDBLOCK = EAGAIN, // Operation would block.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Represents a platform-agnostic Error and underlying platform-specific errno
|
||||||
|
internal struct ErrorInfo
|
||||||
|
{
|
||||||
|
private Error _error;
|
||||||
|
private int _rawErrno;
|
||||||
|
|
||||||
|
internal ErrorInfo(int errno)
|
||||||
|
{
|
||||||
|
_error = Interop.Sys.ConvertErrorPlatformToPal(errno);
|
||||||
|
_rawErrno = errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ErrorInfo(Error error)
|
||||||
|
{
|
||||||
|
_error = error;
|
||||||
|
_rawErrno = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Error Error
|
||||||
|
{
|
||||||
|
get { return _error; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int RawErrno
|
||||||
|
{
|
||||||
|
get { return _rawErrno == -1 ? (_rawErrno = Interop.Sys.ConvertErrorPalToPlatform(_error)) : _rawErrno; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string GetErrorMessage()
|
||||||
|
{
|
||||||
|
return Interop.Sys.StrError(RawErrno);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
"RawErrno: {0} Error: {1} GetErrorMessage: {2}", // No localization required; text is member names used for debugging purposes
|
||||||
|
RawErrno, Error, GetErrorMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal partial class Sys
|
||||||
|
{
|
||||||
|
internal static Error GetLastError()
|
||||||
|
{
|
||||||
|
return ConvertErrorPlatformToPal(Marshal.GetLastWin32Error());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static ErrorInfo GetLastErrorInfo()
|
||||||
|
{
|
||||||
|
return new ErrorInfo(Marshal.GetLastWin32Error());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string StrError(int platformErrno)
|
||||||
|
{
|
||||||
|
int maxBufferLength = 1024; // should be long enough for most any UNIX error
|
||||||
|
IntPtr buffer = Marshal.AllocHGlobal(maxBufferLength);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IntPtr message = StrErrorR(platformErrno, buffer, maxBufferLength);
|
||||||
|
|
||||||
|
if (message == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// This means the buffer was not large enough, but still contains
|
||||||
|
// as much of the error message as possible and is guaranteed to
|
||||||
|
// be null-terminated. We're not currently resizing/retrying because
|
||||||
|
// maxBufferLength is large enough in practice, but we could do
|
||||||
|
// so here in the future if necessary.
|
||||||
|
message = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
string returnMsg = Marshal.PtrToStringAnsi(message);
|
||||||
|
return returnMsg;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Deallocate the buffer we created
|
||||||
|
Marshal.FreeHGlobal(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPlatformToPal")]
|
||||||
|
internal static extern Error ConvertErrorPlatformToPal(int platformErrno);
|
||||||
|
|
||||||
|
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPalToPlatform")]
|
||||||
|
internal static extern int ConvertErrorPalToPlatform(Error error);
|
||||||
|
|
||||||
|
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_StrErrorR")]
|
||||||
|
private static extern IntPtr StrErrorR(int platformErrno, IntPtr buffer, int bufferSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: extension method can't be nested inside Interop class.
|
||||||
|
internal static class InteropErrorExtensions
|
||||||
|
{
|
||||||
|
// Intended usage is e.g. Interop.Error.EFAIL.Info() for brevity
|
||||||
|
// vs. new Interop.ErrorInfo(Interop.Error.EFAIL) for synthesizing
|
||||||
|
// errors. Errors originated from the system should be obtained
|
||||||
|
// via GetLastErrorInfo(), not GetLastError().Info() as that will
|
||||||
|
// convert twice, which is not only inefficient but also lossy if
|
||||||
|
// we ever encounter a raw errno that no equivalent in the Error
|
||||||
|
// enum.
|
||||||
|
public static Interop.ErrorInfo Info(this Interop.Error error)
|
||||||
|
{
|
||||||
|
return new Interop.ErrorInfo(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||||
|
{
|
||||||
|
internal static partial class Interop
|
||||||
|
{
|
||||||
|
internal static partial class Sys
|
||||||
|
{
|
||||||
|
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ChMod", SetLastError = true)]
|
||||||
|
internal static extern int ChMod(string path, int mode);
|
||||||
|
|
||||||
|
internal struct Passwd
|
||||||
|
{
|
||||||
|
internal IntPtr Name; // char*
|
||||||
|
internal IntPtr Password; // char*
|
||||||
|
internal uint UserId;
|
||||||
|
internal uint GroupId;
|
||||||
|
internal IntPtr UserInfo; // char*
|
||||||
|
internal IntPtr HomeDirectory; // char*
|
||||||
|
internal IntPtr Shell; // char*
|
||||||
|
};
|
||||||
|
|
||||||
|
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetPwUidR", SetLastError = false)]
|
||||||
|
internal static extern int GetPwUidR(uint uid, out Passwd pwd, IntPtr buf, int bufLen);
|
||||||
|
|
||||||
|
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetEUid")]
|
||||||
|
internal static extern uint GetEUid();
|
||||||
|
|
||||||
|
private static partial class Libraries
|
||||||
|
{
|
||||||
|
internal const string SystemNative = "System.Native";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Linux implementation of the credential store.
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// This entire implementation may need to be revised to support encryption of
|
||||||
|
/// passwords and protection of them when loaded into memory.
|
||||||
|
/// </remarks>
|
||||||
|
/// </summary>
|
||||||
|
internal class LinuxCredentialStore : ICredentialStore
|
||||||
|
{
|
||||||
|
internal struct StoreConfig
|
||||||
|
{
|
||||||
|
public string CredentialFolder { get; set; }
|
||||||
|
public string CredentialFile { get; set; }
|
||||||
|
public bool IsRelativeToUserHomeDir { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private string credentialFolderPath;
|
||||||
|
private string credentialFileName;
|
||||||
|
private FileTokenStorage storage;
|
||||||
|
|
||||||
|
public LinuxCredentialStore(StoreConfig config)
|
||||||
|
{
|
||||||
|
Validate.IsNotNull("config", config);
|
||||||
|
Validate.IsNotNullOrEmptyString("credentialFolder", config.CredentialFolder);
|
||||||
|
Validate.IsNotNullOrEmptyString("credentialFileName", config.CredentialFile);
|
||||||
|
|
||||||
|
this.credentialFolderPath = config.IsRelativeToUserHomeDir ? GetUserScopedDirectory(config.CredentialFolder) : config.CredentialFolder;
|
||||||
|
this.credentialFileName = config.CredentialFile;
|
||||||
|
|
||||||
|
|
||||||
|
string combinedPath = Path.Combine(this.credentialFolderPath, this.credentialFileName);
|
||||||
|
storage = new FileTokenStorage(combinedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DeletePassword(string credentialId)
|
||||||
|
{
|
||||||
|
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||||
|
IEnumerable<Credential> creds;
|
||||||
|
if (LoadCredentialsAndFilterById(credentialId, out creds))
|
||||||
|
{
|
||||||
|
storage.SaveEntries(creds);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets filtered credentials with a specific ID filtered out
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the credential to filter was removed, false if it was not found</returns>
|
||||||
|
private bool LoadCredentialsAndFilterById(string idToFilter, out IEnumerable<Credential> creds)
|
||||||
|
{
|
||||||
|
bool didRemove = false;
|
||||||
|
creds = storage.LoadEntries().Where(cred =>
|
||||||
|
{
|
||||||
|
if (IsCredentialMatch(idToFilter, cred))
|
||||||
|
{
|
||||||
|
didRemove = true;
|
||||||
|
return false; // filter this out
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).ToList(); // Call ToList ensures Where clause is executed so didRemove can be evaluated
|
||||||
|
|
||||||
|
return didRemove;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsCredentialMatch(string credentialId, Credential cred)
|
||||||
|
{
|
||||||
|
return string.Equals(credentialId, cred.CredentialId, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetPassword(string credentialId, out string password)
|
||||||
|
{
|
||||||
|
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||||
|
Credential cred = storage.LoadEntries().FirstOrDefault(c => IsCredentialMatch(credentialId, c));
|
||||||
|
if (cred != null)
|
||||||
|
{
|
||||||
|
password = cred.Password;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else this was not found in the list
|
||||||
|
password = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Save(Credential credential)
|
||||||
|
{
|
||||||
|
Credential.ValidateForSave(credential);
|
||||||
|
|
||||||
|
// Load the credentials, removing the existing Cred for this
|
||||||
|
IEnumerable<Credential> creds;
|
||||||
|
LoadCredentialsAndFilterById(credential.CredentialId, out creds);
|
||||||
|
storage.SaveEntries(creds.Append(credential));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal for testing purposes only
|
||||||
|
/// </summary>
|
||||||
|
internal string CredentialFolderPath
|
||||||
|
{
|
||||||
|
get { return this.credentialFolderPath; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Concatenates a directory to the user home directory's path
|
||||||
|
/// </summary>
|
||||||
|
internal static string GetUserScopedDirectory(string userPath)
|
||||||
|
{
|
||||||
|
string homeDir = GetHomeDirectory() ?? string.Empty;
|
||||||
|
return Path.Combine(homeDir, userPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Gets the current user's home directory.</summary>
|
||||||
|
/// <returns>The path to the home directory, or null if it could not be determined.</returns>
|
||||||
|
internal static string GetHomeDirectory()
|
||||||
|
{
|
||||||
|
// First try to get the user's home directory from the HOME environment variable.
|
||||||
|
// This should work in most cases.
|
||||||
|
string userHomeDirectory = Environment.GetEnvironmentVariable("HOME");
|
||||||
|
if (!string.IsNullOrEmpty(userHomeDirectory))
|
||||||
|
{
|
||||||
|
return userHomeDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In initialization conditions, however, the "HOME" environment variable may
|
||||||
|
// not yet be set. For such cases, consult with the password entry.
|
||||||
|
|
||||||
|
// First try with a buffer that should suffice for 99% of cases.
|
||||||
|
// Note that, theoretically, userHomeDirectory may be null in the success case
|
||||||
|
// if we simply couldn't find a home directory for the current user.
|
||||||
|
// In that case, we pass back the null value and let the caller decide
|
||||||
|
// what to do.
|
||||||
|
return GetHomeDirectoryFromPw();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetHomeDirectoryFromPw()
|
||||||
|
{
|
||||||
|
string userHomeDirectory = null;
|
||||||
|
const int BufLen = 1024;
|
||||||
|
if (TryGetHomeDirectoryFromPasswd(BufLen, out userHomeDirectory))
|
||||||
|
{
|
||||||
|
return userHomeDirectory;
|
||||||
|
}
|
||||||
|
// Fallback to heap allocations if necessary, growing the buffer until
|
||||||
|
// we succeed. TryGetHomeDirectory will throw if there's an unexpected error.
|
||||||
|
int lastBufLen = BufLen;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
lastBufLen *= 2;
|
||||||
|
if (TryGetHomeDirectoryFromPasswd(lastBufLen, out userHomeDirectory))
|
||||||
|
{
|
||||||
|
return userHomeDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Wrapper for getpwuid_r.</summary>
|
||||||
|
/// <param name="bufLen">The length of the buffer to use when storing the password result.</param>
|
||||||
|
/// <param name="path">The resulting path; null if the user didn't have an entry.</param>
|
||||||
|
/// <returns>true if the call was successful (path may still be null); false is a larger buffer is needed.</returns>
|
||||||
|
private static bool TryGetHomeDirectoryFromPasswd(int bufLen, out string path)
|
||||||
|
{
|
||||||
|
// Call getpwuid_r to get the passwd struct
|
||||||
|
Interop.Sys.Passwd passwd;
|
||||||
|
IntPtr buffer = Marshal.AllocHGlobal(bufLen);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int error = Interop.Sys.GetPwUidR(Interop.Sys.GetEUid(), out passwd, buffer, bufLen);
|
||||||
|
|
||||||
|
// If the call succeeds, give back the home directory path retrieved
|
||||||
|
if (error == 0)
|
||||||
|
{
|
||||||
|
Debug.Assert(passwd.HomeDirectory != IntPtr.Zero);
|
||||||
|
path = Marshal.PtrToStringAnsi(passwd.HomeDirectory);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the current user's entry could not be found, give back null
|
||||||
|
// path, but still return true as false indicates the buffer was
|
||||||
|
// too small.
|
||||||
|
if (error == -1)
|
||||||
|
{
|
||||||
|
path = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorInfo = new Interop.ErrorInfo(error);
|
||||||
|
|
||||||
|
// If the call failed because the buffer was too small, return false to
|
||||||
|
// indicate the caller should try again with a larger buffer.
|
||||||
|
if (errorInfo.Error == Interop.Error.ERANGE)
|
||||||
|
{
|
||||||
|
path = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, fail.
|
||||||
|
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Deallocate the buffer we created
|
||||||
|
Marshal.FreeHGlobal(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// 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;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||||
|
{
|
||||||
|
internal static partial class Interop
|
||||||
|
{
|
||||||
|
internal static partial class CoreFoundation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tells the OS what encoding the passed in String is in. These come from the CFString.h header file in the CoreFoundation framework.
|
||||||
|
/// </summary>
|
||||||
|
private enum CFStringBuiltInEncodings : uint
|
||||||
|
{
|
||||||
|
kCFStringEncodingMacRoman = 0,
|
||||||
|
kCFStringEncodingWindowsLatin1 = 0x0500,
|
||||||
|
kCFStringEncodingISOLatin1 = 0x0201,
|
||||||
|
kCFStringEncodingNextStepLatin = 0x0B01,
|
||||||
|
kCFStringEncodingASCII = 0x0600,
|
||||||
|
kCFStringEncodingUnicode = 0x0100,
|
||||||
|
kCFStringEncodingUTF8 = 0x08000100,
|
||||||
|
kCFStringEncodingNonLossyASCII = 0x0BFF,
|
||||||
|
|
||||||
|
kCFStringEncodingUTF16 = 0x0100,
|
||||||
|
kCFStringEncodingUTF16BE = 0x10000100,
|
||||||
|
kCFStringEncodingUTF16LE = 0x14000100,
|
||||||
|
kCFStringEncodingUTF32 = 0x0c000100,
|
||||||
|
kCFStringEncodingUTF32BE = 0x18000100,
|
||||||
|
kCFStringEncodingUTF32LE = 0x1c000100
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="allocator">Should be IntPtr.Zero</param>
|
||||||
|
/// <param name="str">The string to get a CFStringRef for</param>
|
||||||
|
/// <param name="encoding">The encoding of the str variable. This should be UTF 8 for OS X</param>
|
||||||
|
/// <returns>Returns a pointer to a CFString on success; otherwise, returns IntPtr.Zero</returns>
|
||||||
|
/// <remarks>For *nix systems, the CLR maps ANSI to UTF-8, so be explicit about that</remarks>
|
||||||
|
[DllImport(Interop.Libraries.CoreFoundationLibrary, CharSet = CharSet.Ansi)]
|
||||||
|
private static extern SafeCreateHandle CFStringCreateWithCString(
|
||||||
|
IntPtr allocator,
|
||||||
|
string str,
|
||||||
|
CFStringBuiltInEncodings encoding);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str">The string to get a CFStringRef for</param>
|
||||||
|
/// <returns>Returns a valid SafeCreateHandle to a CFString on success; otherwise, returns an invalid SafeCreateHandle</returns>
|
||||||
|
internal static SafeCreateHandle CFStringCreateWithCString(string str)
|
||||||
|
{
|
||||||
|
return CFStringCreateWithCString(IntPtr.Zero, str, CFStringBuiltInEncodings.kCFStringEncodingUTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="allocator">Should be IntPtr.Zero</param>
|
||||||
|
/// <param name="values">The values to put in the array</param>
|
||||||
|
/// <param name="numValues">The number of values in the array</param>
|
||||||
|
/// <param name="callbacks">Should be IntPtr.Zero</param>
|
||||||
|
/// <returns>Returns a pointer to a CFArray on success; otherwise, returns IntPtr.Zero</returns>
|
||||||
|
[DllImport(Interop.Libraries.CoreFoundationLibrary)]
|
||||||
|
private static extern SafeCreateHandle CFArrayCreate(
|
||||||
|
IntPtr allocator,
|
||||||
|
[MarshalAs(UnmanagedType.LPArray)]
|
||||||
|
IntPtr[] values,
|
||||||
|
ulong numValues,
|
||||||
|
IntPtr callbacks);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">The values to put in the array</param>
|
||||||
|
/// <param name="numValues">The number of values in the array</param>
|
||||||
|
/// <returns>Returns a valid SafeCreateHandle to a CFArray on success; otherwise, returns an invalid SafeCreateHandle</returns>
|
||||||
|
internal static SafeCreateHandle CFArrayCreate(IntPtr[] values, ulong numValues)
|
||||||
|
{
|
||||||
|
return CFArrayCreate(IntPtr.Zero, values, numValues, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// You should retain a Core Foundation object when you receive it from elsewhere
|
||||||
|
/// (that is, you did not create or copy it) and you want it to persist. If you
|
||||||
|
/// retain a Core Foundation object you are responsible for releasing it
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">The CFType object to retain. This value must not be NULL</param>
|
||||||
|
/// <returns>The input value</param>
|
||||||
|
[DllImport(Interop.Libraries.CoreFoundationLibrary)]
|
||||||
|
internal extern static IntPtr CFRetain(IntPtr ptr);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decrements the reference count on the specified object and, if the ref count hits 0, cleans up the object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">The pointer on which to decrement the reference count.</param>
|
||||||
|
[DllImport(Interop.Libraries.CoreFoundationLibrary)]
|
||||||
|
internal extern static void CFRelease(IntPtr ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||||
|
{
|
||||||
|
internal static partial class Interop
|
||||||
|
{
|
||||||
|
private static partial class Libraries
|
||||||
|
{
|
||||||
|
internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
|
||||||
|
internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices";
|
||||||
|
internal const string SecurityLibrary = "/System/Library/Frameworks/Security.framework/Security";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,459 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||||
|
{
|
||||||
|
internal partial class Interop
|
||||||
|
{
|
||||||
|
internal partial class Security
|
||||||
|
{
|
||||||
|
|
||||||
|
[DllImport(Libraries.SecurityLibrary, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||||
|
internal static extern OSStatus SecKeychainAddGenericPassword(IntPtr keyChainRef, UInt32 serviceNameLength, string serviceName,
|
||||||
|
UInt32 accountNameLength, string accountName, UInt32 passwordLength, IntPtr password, [Out] IntPtr itemRef);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find a generic password based on the attributes passed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="keyChainRef">
|
||||||
|
/// A reference to an array of keychains to search, a single keychain, or NULL to search the user's default keychain search list.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="serviceNameLength">The length of the buffer pointed to by serviceName.</param>
|
||||||
|
/// <param name="serviceName">A pointer to a string containing the service name.</param>
|
||||||
|
/// <param name="accountNameLength">The length of the buffer pointed to by accountName.</param>
|
||||||
|
/// <param name="accountName">A pointer to a string containing the account name.</param>
|
||||||
|
/// <param name="passwordLength">On return, the length of the buffer pointed to by passwordData.</param>
|
||||||
|
/// <param name="password">
|
||||||
|
/// On return, a pointer to a data buffer containing the password.
|
||||||
|
/// Your application must call SecKeychainItemFreeContent(NULL, passwordData)
|
||||||
|
/// to release this data buffer when it is no longer needed.Pass NULL if you are not interested in retrieving the password data at
|
||||||
|
/// this time, but simply want to find the item reference.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="itemRef">On return, a reference to the keychain item which was found.</param>
|
||||||
|
/// <returns>A result code that should be in <see cref="OSStatus"/></returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// The SecKeychainFindGenericPassword function finds the first generic password item which matches the attributes you provide.
|
||||||
|
/// Most attributes are optional; you should pass only as many as you need to narrow the search sufficiently for your application's intended use.
|
||||||
|
/// SecKeychainFindGenericPassword optionally returns a reference to the found item.
|
||||||
|
/// </remarks>
|
||||||
|
[DllImport(Libraries.SecurityLibrary, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||||
|
internal static extern OSStatus SecKeychainFindGenericPassword(IntPtr keyChainRef, UInt32 serviceNameLength, string serviceName,
|
||||||
|
UInt32 accountNameLength, string accountName, out UInt32 passwordLength, out IntPtr password, out IntPtr itemRef);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases the memory used by the keychain attribute list and the keychain data retrieved in a previous call to SecKeychainItemCopyContent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="attrList">A pointer to the attribute list to release. Pass NULL to ignore this parameter.</param>
|
||||||
|
/// <param name="data">A pointer to the data buffer to release. Pass NULL to ignore this parameter.</param>
|
||||||
|
/// <returns>A result code that should be in <see cref="OSStatus"/></returns>
|
||||||
|
[DllImport(Libraries.SecurityLibrary, SetLastError = true)]
|
||||||
|
internal static extern OSStatus SecKeychainItemFreeContent([In] IntPtr attrList, [In] IntPtr data);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a keychain item from the default keychain's permanent data store.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="itemRef">A keychain item reference of the item to delete.</param>
|
||||||
|
/// <returns>A result code that should be in <see cref="OSStatus"/></returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// If itemRef has not previously been added to the keychain, SecKeychainItemDelete does nothing and returns ErrSecSuccess.
|
||||||
|
/// IMPORTANT: SecKeychainItemDelete does not dispose the memory occupied by the item reference itself;
|
||||||
|
/// use the CFRelease function when you are completely * * finished with an item.
|
||||||
|
/// </remarks>
|
||||||
|
[DllImport(Libraries.SecurityLibrary, SetLastError = true)]
|
||||||
|
internal static extern OSStatus SecKeychainItemDelete(SafeHandle itemRef);
|
||||||
|
|
||||||
|
#region OSStatus Codes
|
||||||
|
/// <summary>Common Unix errno error codes.</summary>
|
||||||
|
internal enum OSStatus
|
||||||
|
{
|
||||||
|
ErrSecSuccess = 0, /* No error. */
|
||||||
|
ErrSecUnimplemented = -4, /* Function or operation not implemented. */
|
||||||
|
ErrSecDskFull = -34,
|
||||||
|
ErrSecIO = -36, /*I/O error (bummers)*/
|
||||||
|
|
||||||
|
ErrSecParam = -50, /* One or more parameters passed to a function were not valid. */
|
||||||
|
ErrSecWrPerm = -61, /* write permissions error*/
|
||||||
|
ErrSecAllocate = -108, /* Failed to allocate memory. */
|
||||||
|
ErrSecUserCanceled = -128, /* User canceled the operation. */
|
||||||
|
ErrSecBadReq = -909, /* Bad parameter or invalid state for operation. */
|
||||||
|
|
||||||
|
ErrSecInternalComponent = -2070,
|
||||||
|
ErrSecCoreFoundationUnknown = -4960,
|
||||||
|
|
||||||
|
ErrSecNotAvailable = -25291, /* No keychain is available. You may need to restart your computer. */
|
||||||
|
ErrSecReadOnly = -25292, /* This keychain cannot be modified. */
|
||||||
|
ErrSecAuthFailed = -25293, /* The user name or passphrase you entered is not correct. */
|
||||||
|
ErrSecNoSuchKeychain = -25294, /* The specified keychain could not be found. */
|
||||||
|
ErrSecInvalidKeychain = -25295, /* The specified keychain is not a valid keychain file. */
|
||||||
|
ErrSecDuplicateKeychain = -25296, /* A keychain with the same name already exists. */
|
||||||
|
ErrSecDuplicateCallback = -25297, /* The specified callback function is already installed. */
|
||||||
|
ErrSecInvalidCallback = -25298, /* The specified callback function is not valid. */
|
||||||
|
ErrSecDuplicateItem = -25299, /* The specified item already exists in the keychain. */
|
||||||
|
ErrSecItemNotFound = -25300, /* The specified item could not be found in the keychain. */
|
||||||
|
ErrSecBufferTooSmall = -25301, /* There is not enough memory available to use the specified item. */
|
||||||
|
ErrSecDataTooLarge = -25302, /* This item contains information which is too large or in a format that cannot be displayed. */
|
||||||
|
ErrSecNoSuchAttr = -25303, /* The specified attribute does not exist. */
|
||||||
|
ErrSecInvalidItemRef = -25304, /* The specified item is no longer valid. It may have been deleted from the keychain. */
|
||||||
|
ErrSecInvalidSearchRef = -25305, /* Unable to search the current keychain. */
|
||||||
|
ErrSecNoSuchClass = -25306, /* The specified item does not appear to be a valid keychain item. */
|
||||||
|
ErrSecNoDefaultKeychain = -25307, /* A default keychain could not be found. */
|
||||||
|
ErrSecInteractionNotAllowed = -25308, /* User interaction is not allowed. */
|
||||||
|
ErrSecReadOnlyAttr = -25309, /* The specified attribute could not be modified. */
|
||||||
|
ErrSecWrongSecVersion = -25310, /* This keychain was created by a different version of the system software and cannot be opened. */
|
||||||
|
ErrSecKeySizeNotAllowed = -25311, /* This item specifies a key size which is too large. */
|
||||||
|
ErrSecNoStorageModule = -25312, /* A required component (data storage module) could not be loaded. You may need to restart your computer. */
|
||||||
|
ErrSecNoCertificateModule = -25313, /* A required component (certificate module) could not be loaded. You may need to restart your computer. */
|
||||||
|
ErrSecNoPolicyModule = -25314, /* A required component (policy module) could not be loaded. You may need to restart your computer. */
|
||||||
|
ErrSecInteractionRequired = -25315, /* User interaction is required, but is currently not allowed. */
|
||||||
|
ErrSecDataNotAvailable = -25316, /* The contents of this item cannot be retrieved. */
|
||||||
|
ErrSecDataNotModifiable = -25317, /* The contents of this item cannot be modified. */
|
||||||
|
ErrSecCreateChainFailed = -25318, /* One or more certificates required to validate this certificate cannot be found. */
|
||||||
|
ErrSecInvalidPrefsDomain = -25319, /* The specified preferences domain is not valid. */
|
||||||
|
ErrSecInDarkWake = -25320, /* In dark wake, no UI possible */
|
||||||
|
|
||||||
|
ErrSecACLNotSimple = -25240, /* The specified access control list is not in standard (simple) form. */
|
||||||
|
ErrSecPolicyNotFound = -25241, /* The specified policy cannot be found. */
|
||||||
|
ErrSecInvalidTrustSetting = -25242, /* The specified trust setting is invalid. */
|
||||||
|
ErrSecNoAccessForItem = -25243, /* The specified item has no access control. */
|
||||||
|
ErrSecInvalidOwnerEdit = -25244, /* Invalid attempt to change the owner of this item. */
|
||||||
|
ErrSecTrustNotAvailable = -25245, /* No trust results are available. */
|
||||||
|
ErrSecUnsupportedFormat = -25256, /* Import/Export format unsupported. */
|
||||||
|
ErrSecUnknownFormat = -25257, /* Unknown format in import. */
|
||||||
|
ErrSecKeyIsSensitive = -25258, /* Key material must be wrapped for export. */
|
||||||
|
ErrSecMultiplePrivKeys = -25259, /* An attempt was made to import multiple private keys. */
|
||||||
|
ErrSecPassphraseRequired = -25260, /* Passphrase is required for import/export. */
|
||||||
|
ErrSecInvalidPasswordRef = -25261, /* The password reference was invalid. */
|
||||||
|
ErrSecInvalidTrustSettings = -25262, /* The Trust Settings Record was corrupted. */
|
||||||
|
ErrSecNoTrustSettings = -25263, /* No Trust Settings were found. */
|
||||||
|
ErrSecPkcs12VerifyFailure = -25264, /* MAC verification failed during PKCS12 import (wrong password?) */
|
||||||
|
ErrSecNotSigner = -26267, /* A certificate was not signed by its proposed parent. */
|
||||||
|
|
||||||
|
ErrSecDecode = -26275, /* Unable to decode the provided data. */
|
||||||
|
|
||||||
|
ErrSecServiceNotAvailable = -67585, /* The required service is not available. */
|
||||||
|
ErrSecInsufficientClientID = -67586, /* The client ID is not correct. */
|
||||||
|
ErrSecDeviceReset = -67587, /* A device reset has occurred. */
|
||||||
|
ErrSecDeviceFailed = -67588, /* A device failure has occurred. */
|
||||||
|
ErrSecAppleAddAppACLSubject = -67589, /* Adding an application ACL subject failed. */
|
||||||
|
ErrSecApplePublicKeyIncomplete = -67590, /* The public key is incomplete. */
|
||||||
|
ErrSecAppleSignatureMismatch = -67591, /* A signature mismatch has occurred. */
|
||||||
|
ErrSecAppleInvalidKeyStartDate = -67592, /* The specified key has an invalid start date. */
|
||||||
|
ErrSecAppleInvalidKeyEndDate = -67593, /* The specified key has an invalid end date. */
|
||||||
|
ErrSecConversionError = -67594, /* A conversion error has occurred. */
|
||||||
|
ErrSecAppleSSLv2Rollback = -67595, /* A SSLv2 rollback error has occurred. */
|
||||||
|
ErrSecDiskFull = -34, /* The disk is full. */
|
||||||
|
ErrSecQuotaExceeded = -67596, /* The quota was exceeded. */
|
||||||
|
ErrSecFileTooBig = -67597, /* The file is too big. */
|
||||||
|
ErrSecInvalidDatabaseBlob = -67598, /* The specified database has an invalid blob. */
|
||||||
|
ErrSecInvalidKeyBlob = -67599, /* The specified database has an invalid key blob. */
|
||||||
|
ErrSecIncompatibleDatabaseBlob = -67600, /* The specified database has an incompatible blob. */
|
||||||
|
ErrSecIncompatibleKeyBlob = -67601, /* The specified database has an incompatible key blob. */
|
||||||
|
ErrSecHostNameMismatch = -67602, /* A host name mismatch has occurred. */
|
||||||
|
ErrSecUnknownCriticalExtensionFlag = -67603, /* There is an unknown critical extension flag. */
|
||||||
|
ErrSecNoBasicConstraints = -67604, /* No basic constraints were found. */
|
||||||
|
ErrSecNoBasicConstraintsCA = -67605, /* No basic CA constraints were found. */
|
||||||
|
ErrSecInvalidAuthorityKeyID = -67606, /* The authority key ID is not valid. */
|
||||||
|
ErrSecInvalidSubjectKeyID = -67607, /* The subject key ID is not valid. */
|
||||||
|
ErrSecInvalidKeyUsageForPolicy = -67608, /* The key usage is not valid for the specified policy. */
|
||||||
|
ErrSecInvalidExtendedKeyUsage = -67609, /* The extended key usage is not valid. */
|
||||||
|
ErrSecInvalidIDLinkage = -67610, /* The ID linkage is not valid. */
|
||||||
|
ErrSecPathLengthConstraintExceeded = -67611, /* The path length constraint was exceeded. */
|
||||||
|
ErrSecInvalidRoot = -67612, /* The root or anchor certificate is not valid. */
|
||||||
|
ErrSecCRLExpired = -67613, /* The CRL has expired. */
|
||||||
|
ErrSecCRLNotValidYet = -67614, /* The CRL is not yet valid. */
|
||||||
|
ErrSecCRLNotFound = -67615, /* The CRL was not found. */
|
||||||
|
ErrSecCRLServerDown = -67616, /* The CRL server is down. */
|
||||||
|
ErrSecCRLBadURI = -67617, /* The CRL has a bad Uniform Resource Identifier. */
|
||||||
|
ErrSecUnknownCertExtension = -67618, /* An unknown certificate extension was encountered. */
|
||||||
|
ErrSecUnknownCRLExtension = -67619, /* An unknown CRL extension was encountered. */
|
||||||
|
ErrSecCRLNotTrusted = -67620, /* The CRL is not trusted. */
|
||||||
|
ErrSecCRLPolicyFailed = -67621, /* The CRL policy failed. */
|
||||||
|
ErrSecIDPFailure = -67622, /* The issuing distribution point was not valid. */
|
||||||
|
ErrSecSMIMEEmailAddressesNotFound = -67623, /* An email address mismatch was encountered. */
|
||||||
|
ErrSecSMIMEBadExtendedKeyUsage = -67624, /* The appropriate extended key usage for SMIME was not found. */
|
||||||
|
ErrSecSMIMEBadKeyUsage = -67625, /* The key usage is not compatible with SMIME. */
|
||||||
|
ErrSecSMIMEKeyUsageNotCritical = -67626, /* The key usage extension is not marked as critical. */
|
||||||
|
ErrSecSMIMENoEmailAddress = -67627, /* No email address was found in the certificate. */
|
||||||
|
ErrSecSMIMESubjAltNameNotCritical = -67628, /* The subject alternative name extension is not marked as critical. */
|
||||||
|
ErrSecSSLBadExtendedKeyUsage = -67629, /* The appropriate extended key usage for SSL was not found. */
|
||||||
|
ErrSecOCSPBadResponse = -67630, /* The OCSP response was incorrect or could not be parsed. */
|
||||||
|
ErrSecOCSPBadRequest = -67631, /* The OCSP request was incorrect or could not be parsed. */
|
||||||
|
ErrSecOCSPUnavailable = -67632, /* OCSP service is unavailable. */
|
||||||
|
ErrSecOCSPStatusUnrecognized = -67633, /* The OCSP server did not recognize this certificate. */
|
||||||
|
ErrSecEndOfData = -67634, /* An end-of-data was detected. */
|
||||||
|
ErrSecIncompleteCertRevocationCheck = -67635, /* An incomplete certificate revocation check occurred. */
|
||||||
|
ErrSecNetworkFailure = -67636, /* A network failure occurred. */
|
||||||
|
ErrSecOCSPNotTrustedToAnchor = -67637, /* The OCSP response was not trusted to a root or anchor certificate. */
|
||||||
|
ErrSecRecordModified = -67638, /* The record was modified. */
|
||||||
|
ErrSecOCSPSignatureError = -67639, /* The OCSP response had an invalid signature. */
|
||||||
|
ErrSecOCSPNoSigner = -67640, /* The OCSP response had no signer. */
|
||||||
|
ErrSecOCSPResponderMalformedReq = -67641, /* The OCSP responder was given a malformed request. */
|
||||||
|
ErrSecOCSPResponderInternalError = -67642, /* The OCSP responder encountered an internal error. */
|
||||||
|
ErrSecOCSPResponderTryLater = -67643, /* The OCSP responder is busy, try again later. */
|
||||||
|
ErrSecOCSPResponderSignatureRequired = -67644, /* The OCSP responder requires a signature. */
|
||||||
|
ErrSecOCSPResponderUnauthorized = -67645, /* The OCSP responder rejected this request as unauthorized. */
|
||||||
|
ErrSecOCSPResponseNonceMismatch = -67646, /* The OCSP response nonce did not match the request. */
|
||||||
|
ErrSecCodeSigningBadCertChainLength = -67647, /* Code signing encountered an incorrect certificate chain length. */
|
||||||
|
ErrSecCodeSigningNoBasicConstraints = -67648, /* Code signing found no basic constraints. */
|
||||||
|
ErrSecCodeSigningBadPathLengthConstraint= -67649, /* Code signing encountered an incorrect path length constraint. */
|
||||||
|
ErrSecCodeSigningNoExtendedKeyUsage = -67650, /* Code signing found no extended key usage. */
|
||||||
|
ErrSecCodeSigningDevelopment = -67651, /* Code signing indicated use of a development-only certificate. */
|
||||||
|
ErrSecResourceSignBadCertChainLength = -67652, /* Resource signing has encountered an incorrect certificate chain length. */
|
||||||
|
ErrSecResourceSignBadExtKeyUsage = -67653, /* Resource signing has encountered an error in the extended key usage. */
|
||||||
|
ErrSecTrustSettingDeny = -67654, /* The trust setting for this policy was set to Deny. */
|
||||||
|
ErrSecInvalidSubjectName = -67655, /* An invalid certificate subject name was encountered. */
|
||||||
|
ErrSecUnknownQualifiedCertStatement = -67656, /* An unknown qualified certificate statement was encountered. */
|
||||||
|
ErrSecMobileMeRequestQueued = -67657, /* The MobileMe request will be sent during the next connection. */
|
||||||
|
ErrSecMobileMeRequestRedirected = -67658, /* The MobileMe request was redirected. */
|
||||||
|
ErrSecMobileMeServerError = -67659, /* A MobileMe server error occurred. */
|
||||||
|
ErrSecMobileMeServerNotAvailable = -67660, /* The MobileMe server is not available. */
|
||||||
|
ErrSecMobileMeServerAlreadyExists = -67661, /* The MobileMe server reported that the item already exists. */
|
||||||
|
ErrSecMobileMeServerServiceErr = -67662, /* A MobileMe service error has occurred. */
|
||||||
|
ErrSecMobileMeRequestAlreadyPending = -67663, /* A MobileMe request is already pending. */
|
||||||
|
ErrSecMobileMeNoRequestPending = -67664, /* MobileMe has no request pending. */
|
||||||
|
ErrSecMobileMeCSRVerifyFailure = -67665, /* A MobileMe CSR verification failure has occurred. */
|
||||||
|
ErrSecMobileMeFailedConsistencyCheck = -67666, /* MobileMe has found a failed consistency check. */
|
||||||
|
ErrSecNotInitialized = -67667, /* A function was called without initializing CSSM. */
|
||||||
|
ErrSecInvalidHandleUsage = -67668, /* The CSSM handle does not match with the service type. */
|
||||||
|
ErrSecPVCReferentNotFound = -67669, /* A reference to the calling module was not found in the list of authorized callers. */
|
||||||
|
ErrSecFunctionIntegrityFail = -67670, /* A function address was not within the verified module. */
|
||||||
|
ErrSecInternalError = -67671, /* An internal error has occurred. */
|
||||||
|
ErrSecMemoryError = -67672, /* A memory error has occurred. */
|
||||||
|
ErrSecInvalidData = -67673, /* Invalid data was encountered. */
|
||||||
|
ErrSecMDSError = -67674, /* A Module Directory Service error has occurred. */
|
||||||
|
ErrSecInvalidPointer = -67675, /* An invalid pointer was encountered. */
|
||||||
|
ErrSecSelfCheckFailed = -67676, /* Self-check has failed. */
|
||||||
|
ErrSecFunctionFailed = -67677, /* A function has failed. */
|
||||||
|
ErrSecModuleManifestVerifyFailed = -67678, /* A module manifest verification failure has occurred. */
|
||||||
|
ErrSecInvalidGUID = -67679, /* An invalid GUID was encountered. */
|
||||||
|
ErrSecInvalidHandle = -67680, /* An invalid handle was encountered. */
|
||||||
|
ErrSecInvalidDBList = -67681, /* An invalid DB list was encountered. */
|
||||||
|
ErrSecInvalidPassthroughID = -67682, /* An invalid passthrough ID was encountered. */
|
||||||
|
ErrSecInvalidNetworkAddress = -67683, /* An invalid network address was encountered. */
|
||||||
|
ErrSecCRLAlreadySigned = -67684, /* The certificate revocation list is already signed. */
|
||||||
|
ErrSecInvalidNumberOfFields = -67685, /* An invalid number of fields were encountered. */
|
||||||
|
ErrSecVerificationFailure = -67686, /* A verification failure occurred. */
|
||||||
|
ErrSecUnknownTag = -67687, /* An unknown tag was encountered. */
|
||||||
|
ErrSecInvalidSignature = -67688, /* An invalid signature was encountered. */
|
||||||
|
ErrSecInvalidName = -67689, /* An invalid name was encountered. */
|
||||||
|
ErrSecInvalidCertificateRef = -67690, /* An invalid certificate reference was encountered. */
|
||||||
|
ErrSecInvalidCertificateGroup = -67691, /* An invalid certificate group was encountered. */
|
||||||
|
ErrSecTagNotFound = -67692, /* The specified tag was not found. */
|
||||||
|
ErrSecInvalidQuery = -67693, /* The specified query was not valid. */
|
||||||
|
ErrSecInvalidValue = -67694, /* An invalid value was detected. */
|
||||||
|
ErrSecCallbackFailed = -67695, /* A callback has failed. */
|
||||||
|
ErrSecACLDeleteFailed = -67696, /* An ACL delete operation has failed. */
|
||||||
|
ErrSecACLReplaceFailed = -67697, /* An ACL replace operation has failed. */
|
||||||
|
ErrSecACLAddFailed = -67698, /* An ACL add operation has failed. */
|
||||||
|
ErrSecACLChangeFailed = -67699, /* An ACL change operation has failed. */
|
||||||
|
ErrSecInvalidAccessCredentials = -67700, /* Invalid access credentials were encountered. */
|
||||||
|
ErrSecInvalidRecord = -67701, /* An invalid record was encountered. */
|
||||||
|
ErrSecInvalidACL = -67702, /* An invalid ACL was encountered. */
|
||||||
|
ErrSecInvalidSampleValue = -67703, /* An invalid sample value was encountered. */
|
||||||
|
ErrSecIncompatibleVersion = -67704, /* An incompatible version was encountered. */
|
||||||
|
ErrSecPrivilegeNotGranted = -67705, /* The privilege was not granted. */
|
||||||
|
ErrSecInvalidScope = -67706, /* An invalid scope was encountered. */
|
||||||
|
ErrSecPVCAlreadyConfigured = -67707, /* The PVC is already configured. */
|
||||||
|
ErrSecInvalidPVC = -67708, /* An invalid PVC was encountered. */
|
||||||
|
ErrSecEMMLoadFailed = -67709, /* The EMM load has failed. */
|
||||||
|
ErrSecEMMUnloadFailed = -67710, /* The EMM unload has failed. */
|
||||||
|
ErrSecAddinLoadFailed = -67711, /* The add-in load operation has failed. */
|
||||||
|
ErrSecInvalidKeyRef = -67712, /* An invalid key was encountered. */
|
||||||
|
ErrSecInvalidKeyHierarchy = -67713, /* An invalid key hierarchy was encountered. */
|
||||||
|
ErrSecAddinUnloadFailed = -67714, /* The add-in unload operation has failed. */
|
||||||
|
ErrSecLibraryReferenceNotFound = -67715, /* A library reference was not found. */
|
||||||
|
ErrSecInvalidAddinFunctionTable = -67716, /* An invalid add-in function table was encountered. */
|
||||||
|
ErrSecInvalidServiceMask = -67717, /* An invalid service mask was encountered. */
|
||||||
|
ErrSecModuleNotLoaded = -67718, /* A module was not loaded. */
|
||||||
|
ErrSecInvalidSubServiceID = -67719, /* An invalid subservice ID was encountered. */
|
||||||
|
ErrSecAttributeNotInContext = -67720, /* An attribute was not in the context. */
|
||||||
|
ErrSecModuleManagerInitializeFailed = -67721, /* A module failed to initialize. */
|
||||||
|
ErrSecModuleManagerNotFound = -67722, /* A module was not found. */
|
||||||
|
ErrSecEventNotificationCallbackNotFound = -67723, /* An event notification callback was not found. */
|
||||||
|
ErrSecInputLengthError = -67724, /* An input length error was encountered. */
|
||||||
|
ErrSecOutputLengthError = -67725, /* An output length error was encountered. */
|
||||||
|
ErrSecPrivilegeNotSupported = -67726, /* The privilege is not supported. */
|
||||||
|
ErrSecDeviceError = -67727, /* A device error was encountered. */
|
||||||
|
ErrSecAttachHandleBusy = -67728, /* The CSP handle was busy. */
|
||||||
|
ErrSecNotLoggedIn = -67729, /* You are not logged in. */
|
||||||
|
ErrSecAlgorithmMismatch = -67730, /* An algorithm mismatch was encountered. */
|
||||||
|
ErrSecKeyUsageIncorrect = -67731, /* The key usage is incorrect. */
|
||||||
|
ErrSecKeyBlobTypeIncorrect = -67732, /* The key blob type is incorrect. */
|
||||||
|
ErrSecKeyHeaderInconsistent = -67733, /* The key header is inconsistent. */
|
||||||
|
ErrSecUnsupportedKeyFormat = -67734, /* The key header format is not supported. */
|
||||||
|
ErrSecUnsupportedKeySize = -67735, /* The key size is not supported. */
|
||||||
|
ErrSecInvalidKeyUsageMask = -67736, /* The key usage mask is not valid. */
|
||||||
|
ErrSecUnsupportedKeyUsageMask = -67737, /* The key usage mask is not supported. */
|
||||||
|
ErrSecInvalidKeyAttributeMask = -67738, /* The key attribute mask is not valid. */
|
||||||
|
ErrSecUnsupportedKeyAttributeMask = -67739, /* The key attribute mask is not supported. */
|
||||||
|
ErrSecInvalidKeyLabel = -67740, /* The key label is not valid. */
|
||||||
|
ErrSecUnsupportedKeyLabel = -67741, /* The key label is not supported. */
|
||||||
|
ErrSecInvalidKeyFormat = -67742, /* The key format is not valid. */
|
||||||
|
ErrSecUnsupportedVectorOfBuffers = -67743, /* The vector of buffers is not supported. */
|
||||||
|
ErrSecInvalidInputVector = -67744, /* The input vector is not valid. */
|
||||||
|
ErrSecInvalidOutputVector = -67745, /* The output vector is not valid. */
|
||||||
|
ErrSecInvalidContext = -67746, /* An invalid context was encountered. */
|
||||||
|
ErrSecInvalidAlgorithm = -67747, /* An invalid algorithm was encountered. */
|
||||||
|
ErrSecInvalidAttributeKey = -67748, /* A key attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeKey = -67749, /* A key attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeInitVector = -67750, /* An init vector attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeInitVector = -67751, /* An init vector attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeSalt = -67752, /* A salt attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeSalt = -67753, /* A salt attribute was missing. */
|
||||||
|
ErrSecInvalidAttributePadding = -67754, /* A padding attribute was not valid. */
|
||||||
|
ErrSecMissingAttributePadding = -67755, /* A padding attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeRandom = -67756, /* A random number attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeRandom = -67757, /* A random number attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeSeed = -67758, /* A seed attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeSeed = -67759, /* A seed attribute was missing. */
|
||||||
|
ErrSecInvalidAttributePassphrase = -67760, /* A passphrase attribute was not valid. */
|
||||||
|
ErrSecMissingAttributePassphrase = -67761, /* A passphrase attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeKeyLength = -67762, /* A key length attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeKeyLength = -67763, /* A key length attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeBlockSize = -67764, /* A block size attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeBlockSize = -67765, /* A block size attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeOutputSize = -67766, /* An output size attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeOutputSize = -67767, /* An output size attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeRounds = -67768, /* The number of rounds attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeRounds = -67769, /* The number of rounds attribute was missing. */
|
||||||
|
ErrSecInvalidAlgorithmParms = -67770, /* An algorithm parameters attribute was not valid. */
|
||||||
|
ErrSecMissingAlgorithmParms = -67771, /* An algorithm parameters attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeLabel = -67772, /* A label attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeLabel = -67773, /* A label attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeKeyType = -67774, /* A key type attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeKeyType = -67775, /* A key type attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeMode = -67776, /* A mode attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeMode = -67777, /* A mode attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeEffectiveBits = -67778, /* An effective bits attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeEffectiveBits = -67779, /* An effective bits attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeStartDate = -67780, /* A start date attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeStartDate = -67781, /* A start date attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeEndDate = -67782, /* An end date attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeEndDate = -67783, /* An end date attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeVersion = -67784, /* A version attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeVersion = -67785, /* A version attribute was missing. */
|
||||||
|
ErrSecInvalidAttributePrime = -67786, /* A prime attribute was not valid. */
|
||||||
|
ErrSecMissingAttributePrime = -67787, /* A prime attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeBase = -67788, /* A base attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeBase = -67789, /* A base attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeSubprime = -67790, /* A subprime attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeSubprime = -67791, /* A subprime attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeIterationCount = -67792, /* An iteration count attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeIterationCount = -67793, /* An iteration count attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeDLDBHandle = -67794, /* A database handle attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeDLDBHandle = -67795, /* A database handle attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeAccessCredentials = -67796, /* An access credentials attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeAccessCredentials = -67797, /* An access credentials attribute was missing. */
|
||||||
|
ErrSecInvalidAttributePublicKeyFormat = -67798, /* A public key format attribute was not valid. */
|
||||||
|
ErrSecMissingAttributePublicKeyFormat = -67799, /* A public key format attribute was missing. */
|
||||||
|
ErrSecInvalidAttributePrivateKeyFormat = -67800, /* A private key format attribute was not valid. */
|
||||||
|
ErrSecMissingAttributePrivateKeyFormat = -67801, /* A private key format attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeSymmetricKeyFormat = -67802, /* A symmetric key format attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeSymmetricKeyFormat = -67803, /* A symmetric key format attribute was missing. */
|
||||||
|
ErrSecInvalidAttributeWrappedKeyFormat = -67804, /* A wrapped key format attribute was not valid. */
|
||||||
|
ErrSecMissingAttributeWrappedKeyFormat = -67805, /* A wrapped key format attribute was missing. */
|
||||||
|
ErrSecStagedOperationInProgress = -67806, /* A staged operation is in progress. */
|
||||||
|
ErrSecStagedOperationNotStarted = -67807, /* A staged operation was not started. */
|
||||||
|
ErrSecVerifyFailed = -67808, /* A cryptographic verification failure has occurred. */
|
||||||
|
ErrSecQuerySizeUnknown = -67809, /* The query size is unknown. */
|
||||||
|
ErrSecBlockSizeMismatch = -67810, /* A block size mismatch occurred. */
|
||||||
|
ErrSecPublicKeyInconsistent = -67811, /* The public key was inconsistent. */
|
||||||
|
ErrSecDeviceVerifyFailed = -67812, /* A device verification failure has occurred. */
|
||||||
|
ErrSecInvalidLoginName = -67813, /* An invalid login name was detected. */
|
||||||
|
ErrSecAlreadyLoggedIn = -67814, /* The user is already logged in. */
|
||||||
|
ErrSecInvalidDigestAlgorithm = -67815, /* An invalid digest algorithm was detected. */
|
||||||
|
ErrSecInvalidCRLGroup = -67816, /* An invalid CRL group was detected. */
|
||||||
|
ErrSecCertificateCannotOperate = -67817, /* The certificate cannot operate. */
|
||||||
|
ErrSecCertificateExpired = -67818, /* An expired certificate was detected. */
|
||||||
|
ErrSecCertificateNotValidYet = -67819, /* The certificate is not yet valid. */
|
||||||
|
ErrSecCertificateRevoked = -67820, /* The certificate was revoked. */
|
||||||
|
ErrSecCertificateSuspended = -67821, /* The certificate was suspended. */
|
||||||
|
ErrSecInsufficientCredentials = -67822, /* Insufficient credentials were detected. */
|
||||||
|
ErrSecInvalidAction = -67823, /* The action was not valid. */
|
||||||
|
ErrSecInvalidAuthority = -67824, /* The authority was not valid. */
|
||||||
|
ErrSecVerifyActionFailed = -67825, /* A verify action has failed. */
|
||||||
|
ErrSecInvalidCertAuthority = -67826, /* The certificate authority was not valid. */
|
||||||
|
ErrSecInvaldCRLAuthority = -67827, /* The CRL authority was not valid. */
|
||||||
|
ErrSecInvalidCRLEncoding = -67828, /* The CRL encoding was not valid. */
|
||||||
|
ErrSecInvalidCRLType = -67829, /* The CRL type was not valid. */
|
||||||
|
ErrSecInvalidCRL = -67830, /* The CRL was not valid. */
|
||||||
|
ErrSecInvalidFormType = -67831, /* The form type was not valid. */
|
||||||
|
ErrSecInvalidID = -67832, /* The ID was not valid. */
|
||||||
|
ErrSecInvalidIdentifier = -67833, /* The identifier was not valid. */
|
||||||
|
ErrSecInvalidIndex = -67834, /* The index was not valid. */
|
||||||
|
ErrSecInvalidPolicyIdentifiers = -67835, /* The policy identifiers are not valid. */
|
||||||
|
ErrSecInvalidTimeString = -67836, /* The time specified was not valid. */
|
||||||
|
ErrSecInvalidReason = -67837, /* The trust policy reason was not valid. */
|
||||||
|
ErrSecInvalidRequestInputs = -67838, /* The request inputs are not valid. */
|
||||||
|
ErrSecInvalidResponseVector = -67839, /* The response vector was not valid. */
|
||||||
|
ErrSecInvalidStopOnPolicy = -67840, /* The stop-on policy was not valid. */
|
||||||
|
ErrSecInvalidTuple = -67841, /* The tuple was not valid. */
|
||||||
|
ErrSecMultipleValuesUnsupported = -67842, /* Multiple values are not supported. */
|
||||||
|
ErrSecNotTrusted = -67843, /* The trust policy was not trusted. */
|
||||||
|
ErrSecNoDefaultAuthority = -67844, /* No default authority was detected. */
|
||||||
|
ErrSecRejectedForm = -67845, /* The trust policy had a rejected form. */
|
||||||
|
ErrSecRequestLost = -67846, /* The request was lost. */
|
||||||
|
ErrSecRequestRejected = -67847, /* The request was rejected. */
|
||||||
|
ErrSecUnsupportedAddressType = -67848, /* The address type is not supported. */
|
||||||
|
ErrSecUnsupportedService = -67849, /* The service is not supported. */
|
||||||
|
ErrSecInvalidTupleGroup = -67850, /* The tuple group was not valid. */
|
||||||
|
ErrSecInvalidBaseACLs = -67851, /* The base ACLs are not valid. */
|
||||||
|
ErrSecInvalidTupleCredendtials = -67852, /* The tuple credentials are not valid. */
|
||||||
|
ErrSecInvalidEncoding = -67853, /* The encoding was not valid. */
|
||||||
|
ErrSecInvalidValidityPeriod = -67854, /* The validity period was not valid. */
|
||||||
|
ErrSecInvalidRequestor = -67855, /* The requestor was not valid. */
|
||||||
|
ErrSecRequestDescriptor = -67856, /* The request descriptor was not valid. */
|
||||||
|
ErrSecInvalidBundleInfo = -67857, /* The bundle information was not valid. */
|
||||||
|
ErrSecInvalidCRLIndex = -67858, /* The CRL index was not valid. */
|
||||||
|
ErrSecNoFieldValues = -67859, /* No field values were detected. */
|
||||||
|
ErrSecUnsupportedFieldFormat = -67860, /* The field format is not supported. */
|
||||||
|
ErrSecUnsupportedIndexInfo = -67861, /* The index information is not supported. */
|
||||||
|
ErrSecUnsupportedLocality = -67862, /* The locality is not supported. */
|
||||||
|
ErrSecUnsupportedNumAttributes = -67863, /* The number of attributes is not supported. */
|
||||||
|
ErrSecUnsupportedNumIndexes = -67864, /* The number of indexes is not supported. */
|
||||||
|
ErrSecUnsupportedNumRecordTypes = -67865, /* The number of record types is not supported. */
|
||||||
|
ErrSecFieldSpecifiedMultiple = -67866, /* Too many fields were specified. */
|
||||||
|
ErrSecIncompatibleFieldFormat = -67867, /* The field format was incompatible. */
|
||||||
|
ErrSecInvalidParsingModule = -67868, /* The parsing module was not valid. */
|
||||||
|
ErrSecDatabaseLocked = -67869, /* The database is locked. */
|
||||||
|
ErrSecDatastoreIsOpen = -67870, /* The data store is open. */
|
||||||
|
ErrSecMissingValue = -67871, /* A missing value was detected. */
|
||||||
|
ErrSecUnsupportedQueryLimits = -67872, /* The query limits are not supported. */
|
||||||
|
ErrSecUnsupportedNumSelectionPreds = -67873, /* The number of selection predicates is not supported. */
|
||||||
|
ErrSecUnsupportedOperator = -67874, /* The operator is not supported. */
|
||||||
|
ErrSecInvalidDBLocation = -67875, /* The database location is not valid. */
|
||||||
|
ErrSecInvalidAccessRequest = -67876, /* The access request is not valid. */
|
||||||
|
ErrSecInvalidIndexInfo = -67877, /* The index information is not valid. */
|
||||||
|
ErrSecInvalidNewOwner = -67878, /* The new owner is not valid. */
|
||||||
|
ErrSecInvalidModifyMode = -67879, /* The modify mode is not valid. */
|
||||||
|
ErrSecMissingRequiredExtension = -67880, /* A required certificate extension is missing. */
|
||||||
|
ErrSecExtendedKeyUsageNotCritical = -67881, /* The extended key usage extension was not marked critical. */
|
||||||
|
ErrSecTimestampMissing = -67882, /* A timestamp was expected but was not found. */
|
||||||
|
ErrSecTimestampInvalid = -67883, /* The timestamp was not valid. */
|
||||||
|
ErrSecTimestampNotTrusted = -67884, /* The timestamp was not trusted. */
|
||||||
|
ErrSecTimestampServiceNotAvailable = -67885, /* The timestamp service is not available. */
|
||||||
|
ErrSecTimestampBadAlg = -67886, /* An unrecognized or unsupported Algorithm Identifier in timestamp. */
|
||||||
|
ErrSecTimestampBadRequest = -67887, /* The timestamp transaction is not permitted or supported. */
|
||||||
|
ErrSecTimestampBadDataFormat = -67888, /* The timestamp data submitted has the wrong format. */
|
||||||
|
ErrSecTimestampTimeNotAvailable = -67889, /* The time source for the Timestamp Authority is not available. */
|
||||||
|
ErrSecTimestampUnacceptedPolicy = -67890, /* The requested policy is not supported by the Timestamp Authority. */
|
||||||
|
ErrSecTimestampUnacceptedExtension = -67891, /* The requested extension is not supported by the Timestamp Authority. */
|
||||||
|
ErrSecTimestampAddInfoNotAvailable = -67892, /* The additional information requested is not available. */
|
||||||
|
ErrSecTimestampSystemFailure = -67893, /* The timestamp request cannot be handled due to system failure. */
|
||||||
|
ErrSecSigningTimeMissing = -67894, /* A signing time was expected but was not found. */
|
||||||
|
ErrSecTimestampRejection = -67895, /* A timestamp transaction was rejected. */
|
||||||
|
ErrSecTimestampWaiting = -67896, /* A timestamp transaction is waiting. */
|
||||||
|
ErrSecTimestampRevocationWarning = -67897, /* A timestamp authority revocation warning was issued. */
|
||||||
|
ErrSecTimestampRevocationNotification = -67898, /* A timestamp authority revocation notification was issued. */
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.OSX
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// OSX implementation of the credential store
|
||||||
|
/// </summary>
|
||||||
|
internal class OSXCredentialStore : ICredentialStore
|
||||||
|
{
|
||||||
|
public bool DeletePassword(string credentialId)
|
||||||
|
{
|
||||||
|
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||||
|
return DeletePasswordImpl(credentialId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetPassword(string credentialId, out string password)
|
||||||
|
{
|
||||||
|
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||||
|
return FindPassword(credentialId, out password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Save(Credential credential)
|
||||||
|
{
|
||||||
|
Credential.ValidateForSave(credential);
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
// Note: OSX blocks AddPassword if the credential
|
||||||
|
// already exists, so for now we delete the password if already present since we're updating
|
||||||
|
// the value. In the future, we could consider updating but it's low value to solve this
|
||||||
|
DeletePasswordImpl(credential.CredentialId);
|
||||||
|
|
||||||
|
// Now add the password
|
||||||
|
result = AddGenericPassword(credential);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AddGenericPassword(Credential credential)
|
||||||
|
{
|
||||||
|
IntPtr passwordPtr = Marshal.StringToCoTaskMemUni(credential.Password);
|
||||||
|
Interop.Security.OSStatus status = Interop.Security.SecKeychainAddGenericPassword(
|
||||||
|
IntPtr.Zero,
|
||||||
|
InteropUtils.GetLengthInBytes(credential.CredentialId),
|
||||||
|
credential.CredentialId,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
InteropUtils.GetLengthInBytes(credential.Password),
|
||||||
|
passwordPtr,
|
||||||
|
IntPtr.Zero);
|
||||||
|
|
||||||
|
return status == Interop.Security.OSStatus.ErrSecSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the first password matching this credential
|
||||||
|
/// </summary>
|
||||||
|
private bool FindPassword(string credentialId, out string password)
|
||||||
|
{
|
||||||
|
password = null;
|
||||||
|
using (KeyChainItemHandle handle = LookupKeyChainItem(credentialId))
|
||||||
|
{
|
||||||
|
if( handle == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
password = handle.Password;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyChainItemHandle LookupKeyChainItem(string credentialId)
|
||||||
|
{
|
||||||
|
UInt32 passwordLength;
|
||||||
|
IntPtr passwordPtr;
|
||||||
|
IntPtr item;
|
||||||
|
Interop.Security.OSStatus status = Interop.Security.SecKeychainFindGenericPassword(
|
||||||
|
IntPtr.Zero,
|
||||||
|
InteropUtils.GetLengthInBytes(credentialId),
|
||||||
|
credentialId,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
out passwordLength,
|
||||||
|
out passwordPtr,
|
||||||
|
out item);
|
||||||
|
|
||||||
|
if(status == Interop.Security.OSStatus.ErrSecSuccess)
|
||||||
|
{
|
||||||
|
return new KeyChainItemHandle(item, passwordPtr, passwordLength);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DeletePasswordImpl(string credentialId)
|
||||||
|
{
|
||||||
|
// Find password, then Delete, then cleanup
|
||||||
|
using (KeyChainItemHandle handle = LookupKeyChainItem(credentialId))
|
||||||
|
{
|
||||||
|
if (handle == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Interop.Security.OSStatus status = Interop.Security.SecKeychainItemDelete(handle);
|
||||||
|
return status == Interop.Security.OSStatus.ErrSecSuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class KeyChainItemHandle : SafeCreateHandle
|
||||||
|
{
|
||||||
|
private IntPtr passwordPtr;
|
||||||
|
private int passwordLength;
|
||||||
|
|
||||||
|
public KeyChainItemHandle() : base()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyChainItemHandle(IntPtr itemPtr) : this(itemPtr, IntPtr.Zero, 0)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyChainItemHandle(IntPtr itemPtr, IntPtr passwordPtr, UInt32 passwordLength)
|
||||||
|
: base(itemPtr)
|
||||||
|
{
|
||||||
|
this.passwordPtr = passwordPtr;
|
||||||
|
this.passwordLength = (int) passwordLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Password
|
||||||
|
{
|
||||||
|
get {
|
||||||
|
if (IsInvalid)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return InteropUtils.CopyToString(passwordPtr, passwordLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected override bool ReleaseHandle()
|
||||||
|
{
|
||||||
|
if (passwordPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Interop.Security.SecKeychainItemFreeContent(IntPtr.Zero, passwordPtr);
|
||||||
|
}
|
||||||
|
base.ReleaseHandle();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// 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;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials
|
||||||
|
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is a wrapper around the Create pattern in OS X where
|
||||||
|
/// if a Create* function is called, the caller must also CFRelease
|
||||||
|
/// on the same pointer in order to correctly free the memory.
|
||||||
|
/// </summary>
|
||||||
|
[System.Security.SecurityCritical]
|
||||||
|
internal partial class SafeCreateHandle : SafeHandle
|
||||||
|
{
|
||||||
|
internal SafeCreateHandle() : base(IntPtr.Zero, true) { }
|
||||||
|
|
||||||
|
internal SafeCreateHandle(IntPtr ptr) : base(IntPtr.Zero, true)
|
||||||
|
{
|
||||||
|
this.SetHandle(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Security.SecurityCritical]
|
||||||
|
protected override bool ReleaseHandle()
|
||||||
|
{
|
||||||
|
Interop.CoreFoundation.CFRelease(handle);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsInvalid
|
||||||
|
{
|
||||||
|
[System.Security.SecurityCritical]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return handle == IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||||
|
// Licensed under the Apache License 2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||||
|
{
|
||||||
|
internal static class SecureStringHelper
|
||||||
|
{
|
||||||
|
// Methods
|
||||||
|
internal static SecureString CreateSecureString(string plainString)
|
||||||
|
{
|
||||||
|
SecureString str = new SecureString();
|
||||||
|
if (!string.IsNullOrEmpty(plainString))
|
||||||
|
{
|
||||||
|
foreach (char c in plainString)
|
||||||
|
{
|
||||||
|
str.AppendChar(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
str.MakeReadOnly();
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string CreateString(SecureString value)
|
||||||
|
{
|
||||||
|
IntPtr ptr = SecureStringMarshal.SecureStringToGlobalAllocUnicode(value);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Marshal.PtrToStringUni(ptr);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.ZeroFreeGlobalAllocUnicode(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||||
|
{
|
||||||
|
// TODO Replace this strings class with a resx file
|
||||||
|
internal class CredentialResources
|
||||||
|
{
|
||||||
|
public const string PasswordLengthExceeded = "The password has exceeded 512 bytes.";
|
||||||
|
public const string TargetRequiredForDelete = "Target must be specified to delete a credential.";
|
||||||
|
public const string TargetRequiredForLookup = "Target must be specified to check existance of a credential.";
|
||||||
|
public const string CredentialDisposed = "Win32Credential object is already disposed.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
//
|
||||||
|
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||||
|
// Licensed under the Apache License 2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||||
|
{
|
||||||
|
public class CredentialSet: List<Win32Credential>, IDisposable
|
||||||
|
{
|
||||||
|
bool _disposed;
|
||||||
|
|
||||||
|
public CredentialSet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public CredentialSet(string target)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(target))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("target");
|
||||||
|
}
|
||||||
|
Target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Target { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
|
||||||
|
// Prevent GC Collection since we have already disposed of this object
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~CredentialSet()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (Count > 0)
|
||||||
|
{
|
||||||
|
ForEach(cred => cred.Dispose());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CredentialSet Load()
|
||||||
|
{
|
||||||
|
LoadInternal();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadInternal()
|
||||||
|
{
|
||||||
|
uint count;
|
||||||
|
|
||||||
|
IntPtr pCredentials = IntPtr.Zero;
|
||||||
|
bool result = NativeMethods.CredEnumerateW(Target, 0, out count, out pCredentials);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Error, string.Format("Win32Exception: {0}", new Win32Exception(Marshal.GetLastWin32Error()).ToString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in all of the pointers first
|
||||||
|
IntPtr[] ptrCredList = new IntPtr[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
ptrCredList[i] = Marshal.ReadIntPtr(pCredentials, IntPtr.Size*i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now let's go through all of the pointers in the list
|
||||||
|
// and create our Credential object(s)
|
||||||
|
List<NativeMethods.CriticalCredentialHandle> credentialHandles =
|
||||||
|
ptrCredList.Select(ptrCred => new NativeMethods.CriticalCredentialHandle(ptrCred)).ToList();
|
||||||
|
|
||||||
|
IEnumerable<Win32Credential> existingCredentials = credentialHandles
|
||||||
|
.Select(handle => handle.GetCredential())
|
||||||
|
.Select(nativeCredential =>
|
||||||
|
{
|
||||||
|
Win32Credential credential = new Win32Credential();
|
||||||
|
credential.LoadInternal(nativeCredential);
|
||||||
|
return credential;
|
||||||
|
});
|
||||||
|
AddRange(existingCredentials);
|
||||||
|
|
||||||
|
// The individual credentials should not be free'd
|
||||||
|
credentialHandles.ForEach(handle => handle.SetHandleAsInvalid());
|
||||||
|
|
||||||
|
// Clean up memory to the Enumeration pointer
|
||||||
|
NativeMethods.CredFree(pCredentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||||
|
// Licensed under the Apache License 2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||||
|
{
|
||||||
|
public enum CredentialType: uint
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Generic = 1,
|
||||||
|
DomainPassword = 2,
|
||||||
|
DomainCertificate = 3,
|
||||||
|
DomainVisiblePassword = 4
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// This file is used by Code Analysis to maintain SuppressMessage
|
||||||
|
// attributes that are applied to this project.
|
||||||
|
// Project-level suppressions either have no target or are given
|
||||||
|
// a specific target and scoped to a namespace, type, member, etc.
|
||||||
|
//
|
||||||
|
// To add a suppression to this file, right-click the message in the
|
||||||
|
// Code Analysis results, point to "Suppress Message", and click
|
||||||
|
// "In Suppression File".
|
||||||
|
// You do not need to add suppressions to this file manually.
|
||||||
|
|
||||||
|
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "CredentialManagement.CredentialSet.#LoadInternal()")]
|
||||||
|
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable", Scope = "type", Target = "CredentialManagement.NativeMethods+CREDENTIAL")]
|
||||||
|
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "7", Scope = "member", Target = "CredentialManagement.NativeMethods.#CredUnPackAuthenticationBuffer(System.Int32,System.IntPtr,System.UInt32,System.Text.StringBuilder,System.Int32&,System.Text.StringBuilder,System.Int32&,System.Text.StringBuilder,System.Int32&)")]
|
||||||
|
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "5", Scope = "member", Target = "CredentialManagement.NativeMethods.#CredUnPackAuthenticationBuffer(System.Int32,System.IntPtr,System.UInt32,System.Text.StringBuilder,System.Int32&,System.Text.StringBuilder,System.Int32&,System.Text.StringBuilder,System.Int32&)")]
|
||||||
|
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "3", Scope = "member", Target = "CredentialManagement.NativeMethods.#CredUnPackAuthenticationBuffer(System.Int32,System.IntPtr,System.UInt32,System.Text.StringBuilder,System.Int32&,System.Text.StringBuilder,System.Int32&,System.Text.StringBuilder,System.Int32&)")]
|
||||||
|
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "CredentialManagement.SecureStringHelper.#CreateString(System.Security.SecureString)")]
|
||||||
|
<EFBFBD>
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
//
|
||||||
|
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||||
|
// Licensed under the Apache License 2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||||
|
{
|
||||||
|
internal class NativeMethods
|
||||||
|
{
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal struct CREDENTIAL
|
||||||
|
{
|
||||||
|
public int Flags;
|
||||||
|
public int Type;
|
||||||
|
[MarshalAs(UnmanagedType.LPWStr)]
|
||||||
|
public string TargetName;
|
||||||
|
[MarshalAs(UnmanagedType.LPWStr)]
|
||||||
|
public string Comment;
|
||||||
|
public long LastWritten;
|
||||||
|
public int CredentialBlobSize;
|
||||||
|
public IntPtr CredentialBlob;
|
||||||
|
public int Persist;
|
||||||
|
public int AttributeCount;
|
||||||
|
public IntPtr Attributes;
|
||||||
|
[MarshalAs(UnmanagedType.LPWStr)]
|
||||||
|
public string TargetAlias;
|
||||||
|
[MarshalAs(UnmanagedType.LPWStr)]
|
||||||
|
public string UserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||||
|
internal static extern bool CredRead(string target, CredentialType type, int reservedFlag, out IntPtr CredentialPtr);
|
||||||
|
|
||||||
|
[DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||||
|
internal static extern bool CredWrite([In] ref CREDENTIAL userCredential, [In] UInt32 flags);
|
||||||
|
|
||||||
|
[DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)]
|
||||||
|
internal static extern bool CredFree([In] IntPtr cred);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode)]
|
||||||
|
internal static extern bool CredDelete(StringBuilder target, CredentialType type, int flags);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
internal static extern bool CredEnumerateW(string filter, int flag, out uint count, out IntPtr pCredentials);
|
||||||
|
|
||||||
|
[DllImport("ole32.dll")]
|
||||||
|
internal static extern void CoTaskMemFree(IntPtr ptr);
|
||||||
|
|
||||||
|
|
||||||
|
internal abstract class CriticalHandleZeroOrMinusOneIsInvalid : CriticalHandle
|
||||||
|
{
|
||||||
|
protected CriticalHandleZeroOrMinusOneIsInvalid() : base(IntPtr.Zero)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsInvalid
|
||||||
|
{
|
||||||
|
get { return handle == new IntPtr(0) || handle == new IntPtr(-1); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid
|
||||||
|
{
|
||||||
|
// Set the handle.
|
||||||
|
internal CriticalCredentialHandle(IntPtr preexistingHandle)
|
||||||
|
{
|
||||||
|
SetHandle(preexistingHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal CREDENTIAL GetCredential()
|
||||||
|
{
|
||||||
|
if (!IsInvalid)
|
||||||
|
{
|
||||||
|
// Get the Credential from the mem location
|
||||||
|
return (CREDENTIAL)Marshal.PtrToStructure<CREDENTIAL>(handle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Invalid CriticalHandle!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform any specific actions to release the handle in the ReleaseHandle method.
|
||||||
|
// Often, you need to use Pinvoke to make a call into the Win32 API to release the
|
||||||
|
// handle. In this case, however, we can use the Marshal class to release the unmanaged memory.
|
||||||
|
|
||||||
|
override protected bool ReleaseHandle()
|
||||||
|
{
|
||||||
|
// If the handle was set, free it. Return success.
|
||||||
|
if (!IsInvalid)
|
||||||
|
{
|
||||||
|
// NOTE: We should also ZERO out the memory allocated to the handle, before free'ing it
|
||||||
|
// so there are no traces of the sensitive data left in memory.
|
||||||
|
CredFree(handle);
|
||||||
|
// Mark the handle as invalid for future users.
|
||||||
|
SetHandleAsInvalid();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Return false.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||||
|
// Licensed under the Apache License 2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||||
|
{
|
||||||
|
public enum PersistanceType : uint
|
||||||
|
{
|
||||||
|
Session = 1,
|
||||||
|
LocalComputer = 2,
|
||||||
|
Enterprise = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
//
|
||||||
|
// Code originally from http://credentialmanagement.codeplex.com/,
|
||||||
|
// Licensed under the Apache License 2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||||
|
{
|
||||||
|
public class Win32Credential: IDisposable
|
||||||
|
{
|
||||||
|
bool disposed;
|
||||||
|
|
||||||
|
CredentialType type;
|
||||||
|
string target;
|
||||||
|
SecureString password;
|
||||||
|
string username;
|
||||||
|
string description;
|
||||||
|
DateTime lastWriteTime;
|
||||||
|
PersistanceType persistanceType;
|
||||||
|
|
||||||
|
public Win32Credential()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Win32Credential(string username)
|
||||||
|
: this(username, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Win32Credential(string username, string password)
|
||||||
|
: this(username, password, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Win32Credential(string username, string password, string target)
|
||||||
|
: this(username, password, target, CredentialType.Generic)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Win32Credential(string username, string password, string target, CredentialType type)
|
||||||
|
{
|
||||||
|
Username = username;
|
||||||
|
Password = password;
|
||||||
|
Target = target;
|
||||||
|
Type = type;
|
||||||
|
PersistanceType = PersistanceType.Session;
|
||||||
|
lastWriteTime = DateTime.MinValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
|
||||||
|
// Prevent GC Collection since we have already disposed of this object
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
SecurePassword.Clear();
|
||||||
|
SecurePassword.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckNotDisposed()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(CredentialResources.CredentialDisposed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string Username {
|
||||||
|
get
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
username = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string Password
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return SecureStringHelper.CreateString(SecurePassword);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
SecurePassword = SecureStringHelper.CreateSecureString(string.IsNullOrEmpty(value) ? string.Empty : value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public SecureString SecurePassword
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
return null == password ? new SecureString() : password.Copy();
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
if (null != password)
|
||||||
|
{
|
||||||
|
password.Clear();
|
||||||
|
password.Dispose();
|
||||||
|
}
|
||||||
|
password = null == value ? new SecureString() : value.Copy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string Target
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
target = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
description = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime LastWriteTime
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return LastWriteTimeUtc.ToLocalTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public DateTime LastWriteTimeUtc
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
return lastWriteTime;
|
||||||
|
}
|
||||||
|
private set { lastWriteTime = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public CredentialType Type
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
type = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PersistanceType PersistanceType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
return persistanceType;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
persistanceType = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Save()
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
|
||||||
|
byte[] passwordBytes = Encoding.Unicode.GetBytes(Password);
|
||||||
|
if (Password.Length > (512))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(CredentialResources.PasswordLengthExceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeMethods.CREDENTIAL credential = new NativeMethods.CREDENTIAL();
|
||||||
|
credential.TargetName = Target;
|
||||||
|
credential.UserName = Username;
|
||||||
|
credential.CredentialBlob = Marshal.StringToCoTaskMemUni(Password);
|
||||||
|
credential.CredentialBlobSize = passwordBytes.Length;
|
||||||
|
credential.Comment = Description;
|
||||||
|
credential.Type = (int)Type;
|
||||||
|
credential.Persist = (int) PersistanceType;
|
||||||
|
|
||||||
|
bool result = NativeMethods.CredWrite(ref credential, 0);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LastWriteTimeUtc = DateTime.UtcNow;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Delete()
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Target))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(CredentialResources.TargetRequiredForDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder target = string.IsNullOrEmpty(Target) ? new StringBuilder() : new StringBuilder(Target);
|
||||||
|
bool result = NativeMethods.CredDelete(target, Type, 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Load()
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
|
||||||
|
IntPtr credPointer;
|
||||||
|
|
||||||
|
bool result = NativeMethods.CredRead(Target, Type, 0, out credPointer);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
using (NativeMethods.CriticalCredentialHandle credentialHandle = new NativeMethods.CriticalCredentialHandle(credPointer))
|
||||||
|
{
|
||||||
|
LoadInternal(credentialHandle.GetCredential());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Exists()
|
||||||
|
{
|
||||||
|
CheckNotDisposed();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Target))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(CredentialResources.TargetRequiredForLookup);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (Win32Credential existing = new Win32Credential { Target = Target, Type = Type })
|
||||||
|
{
|
||||||
|
return existing.Load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void LoadInternal(NativeMethods.CREDENTIAL credential)
|
||||||
|
{
|
||||||
|
Username = credential.UserName;
|
||||||
|
if (credential.CredentialBlobSize > 0)
|
||||||
|
{
|
||||||
|
Password = Marshal.PtrToStringUni(credential.CredentialBlob, credential.CredentialBlobSize / 2);
|
||||||
|
}
|
||||||
|
Target = credential.TargetName;
|
||||||
|
Type = (CredentialType)credential.Type;
|
||||||
|
PersistanceType = (PersistanceType)credential.Persist;
|
||||||
|
Description = credential.Comment;
|
||||||
|
LastWriteTimeUtc = DateTime.FromFileTimeUtc(credential.LastWritten);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Win32 implementation of the credential store
|
||||||
|
/// </summary>
|
||||||
|
internal class Win32CredentialStore : ICredentialStore
|
||||||
|
{
|
||||||
|
private const string AnyUsername = "*";
|
||||||
|
|
||||||
|
public bool DeletePassword(string credentialId)
|
||||||
|
{
|
||||||
|
using (Win32Credential cred = new Win32Credential() { Target = credentialId, Username = AnyUsername })
|
||||||
|
{
|
||||||
|
return cred.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetPassword(string credentialId, out string password)
|
||||||
|
{
|
||||||
|
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
|
||||||
|
password = null;
|
||||||
|
|
||||||
|
using (CredentialSet set = new CredentialSet(credentialId).Load())
|
||||||
|
{
|
||||||
|
// Note: Credentials are disposed on disposal of the set
|
||||||
|
Win32Credential foundCred = null;
|
||||||
|
if (set.Count > 0)
|
||||||
|
{
|
||||||
|
foundCred = set[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundCred != null)
|
||||||
|
{
|
||||||
|
password = foundCred.Password;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Save(Credential credential)
|
||||||
|
{
|
||||||
|
Credential.ValidateForSave(credential);
|
||||||
|
|
||||||
|
using (Win32Credential cred =
|
||||||
|
new Win32Credential(AnyUsername, credential.Password, credential.CredentialId, CredentialType.Generic)
|
||||||
|
{ PersistanceType = PersistanceType.LocalComputer })
|
||||||
|
{
|
||||||
|
return cred.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a class that describes the capabilities of a language
|
/// Defines a class that describes the capabilities of a language
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters to be used for reporting hosting-level errors, such as protocol violations
|
||||||
|
/// </summary>
|
||||||
|
public class HostingErrorParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The message of the error
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HostingErrorEvent
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
EventType<HostingErrorParams> Type =
|
||||||
|
EventType<HostingErrorParams>.Create("hosting/error");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts
|
||||||
{
|
{
|
||||||
public class InitializeRequest
|
public class InitializeRequest
|
||||||
{
|
{
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts
|
||||||
{
|
{
|
||||||
public class ServerCapabilities
|
public class ServerCapabilities
|
||||||
{
|
{
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a message that is sent from the client to request
|
/// Defines a message that is sent from the client to request
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a message that is sent from the client to request
|
||||||
|
/// the version of the server.
|
||||||
|
/// </summary>
|
||||||
|
public class VersionRequest
|
||||||
|
{
|
||||||
|
public static readonly
|
||||||
|
RequestType<object, string> Type =
|
||||||
|
RequestType<object, string>.Create("version");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Serializers;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a base implementation for servers and their clients over a
|
/// Defines a base implementation for servers and their clients over a
|
||||||
@@ -7,8 +7,9 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides a client implementation for the standard I/O channel.
|
/// Provides a client implementation for the standard I/O channel.
|
||||||
@@ -6,8 +6,9 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides a server implementation for the standard I/O channel.
|
/// Provides a server implementation for the standard I/O channel.
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||||
{
|
{
|
||||||
public static class Constants
|
public static class Constants
|
||||||
{
|
{
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines an event type with a particular method name.
|
/// Defines an event type with a particular method name.
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines all possible message types.
|
/// Defines all possible message types.
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts
|
||||||
{
|
{
|
||||||
[DebuggerDisplay("RequestType MethodName = {MethodName}")]
|
[DebuggerDisplay("RequestType MethodName = {MethodName}")]
|
||||||
public class RequestType<TParams, TResult>
|
public class RequestType<TParams, TResult>
|
||||||
@@ -4,8 +4,9 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides context for a received event so that handlers
|
/// Provides context for a received event so that handlers
|
||||||
@@ -4,10 +4,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||||
{
|
{
|
||||||
internal interface IMessageSender
|
public interface IMessageSender
|
||||||
{
|
{
|
||||||
Task SendEvent<TParams>(
|
Task SendEvent<TParams>(
|
||||||
EventType<TParams> eventType,
|
EventType<TParams> eventType,
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A ProtocolEndpoint is used for inter-process communication. Services can register to
|
||||||
|
/// respond to requests and events, send their own requests, and listen for notifications
|
||||||
|
/// sent by the other side of the endpoint
|
||||||
|
/// </summary>
|
||||||
|
public interface IProtocolEndpoint : IMessageSender
|
||||||
|
{
|
||||||
|
void SetRequestHandler<TParams, TResult>(
|
||||||
|
RequestType<TParams, TResult> requestType,
|
||||||
|
Func<TParams, RequestContext<TResult>, Task> requestHandler);
|
||||||
|
|
||||||
|
void SetEventHandler<TParams>(
|
||||||
|
EventType<TParams> eventType,
|
||||||
|
Func<TParams, EventContext, Task> eventHandler);
|
||||||
|
|
||||||
|
void SetEventHandler<TParams>(
|
||||||
|
EventType<TParams> eventType,
|
||||||
|
Func<TParams, EventContext, Task> eventHandler,
|
||||||
|
bool overrideExisting);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,15 +3,17 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||||
{
|
{
|
||||||
public class MessageDispatcher
|
public class MessageDispatcher
|
||||||
{
|
{
|
||||||
@@ -197,10 +199,9 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|||||||
this.SynchronizationContext = SynchronizationContext.Current;
|
this.SynchronizationContext = SynchronizationContext.Current;
|
||||||
|
|
||||||
// Run the message loop
|
// Run the message loop
|
||||||
bool isRunning = true;
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
while (isRunning && !cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
Message newMessage = null;
|
Message newMessage;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -209,12 +210,12 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|||||||
}
|
}
|
||||||
catch (MessageParseException e)
|
catch (MessageParseException e)
|
||||||
{
|
{
|
||||||
// TODO: Write an error response
|
string message = string.Format("Exception occurred while parsing message: {0}", e.Message);
|
||||||
|
Logger.Write(LogLevel.Error, message);
|
||||||
Logger.Write(
|
await MessageWriter.WriteEvent(HostingErrorEvent.Type, new HostingErrorParams
|
||||||
LogLevel.Error,
|
{
|
||||||
"Could not parse a message that was received:\r\n\r\n" +
|
Message = message
|
||||||
e.ToString());
|
});
|
||||||
|
|
||||||
// Continue the loop
|
// Continue the loop
|
||||||
continue;
|
continue;
|
||||||
@@ -226,18 +227,29 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
var b = e.Message;
|
// Log the error and send an error event to the client
|
||||||
newMessage = null;
|
string message = string.Format("Exception occurred while receiving message: {0}", e.Message);
|
||||||
|
Logger.Write(LogLevel.Error, message);
|
||||||
|
await MessageWriter.WriteEvent(HostingErrorEvent.Type, new HostingErrorParams
|
||||||
|
{
|
||||||
|
Message = message
|
||||||
|
});
|
||||||
|
|
||||||
|
// Continue the loop
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The message could be null if there was an error parsing the
|
// The message could be null if there was an error parsing the
|
||||||
// previous message. In this case, do not try to dispatch it.
|
// previous message. In this case, do not try to dispatch it.
|
||||||
if (newMessage != null)
|
if (newMessage != null)
|
||||||
{
|
{
|
||||||
|
// Verbose logging
|
||||||
|
string logMessage = string.Format("Received message of type[{0}] and method[{1}]",
|
||||||
|
newMessage.MessageType, newMessage.Method);
|
||||||
|
Logger.Write(LogLevel.Verbose, logMessage);
|
||||||
|
|
||||||
// Process the message
|
// Process the message
|
||||||
await this.DispatchMessage(
|
await this.DispatchMessage(newMessage, this.MessageWriter);
|
||||||
newMessage,
|
|
||||||
this.MessageWriter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||||
{
|
{
|
||||||
public class MessageParseException : Exception
|
public class MessageParseException : Exception
|
||||||
{
|
{
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the possible message protocol types.
|
/// Defines the possible message protocol types.
|
||||||
@@ -3,16 +3,17 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||||
{
|
{
|
||||||
public class MessageReader
|
public class MessageReader
|
||||||
{
|
{
|
||||||
@@ -23,22 +24,22 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|||||||
|
|
||||||
private const int CR = 0x0D;
|
private const int CR = 0x0D;
|
||||||
private const int LF = 0x0A;
|
private const int LF = 0x0A;
|
||||||
private static string[] NewLineDelimiters = new string[] { Environment.NewLine };
|
private static readonly string[] NewLineDelimiters = { Environment.NewLine };
|
||||||
|
|
||||||
private Stream inputStream;
|
private readonly Stream inputStream;
|
||||||
private IMessageSerializer messageSerializer;
|
private readonly IMessageSerializer messageSerializer;
|
||||||
private Encoding messageEncoding;
|
private readonly Encoding messageEncoding;
|
||||||
|
|
||||||
private ReadState readState;
|
private ReadState readState;
|
||||||
private bool needsMoreData = true;
|
private bool needsMoreData = true;
|
||||||
private int readOffset;
|
private int readOffset;
|
||||||
private int bufferEndOffset;
|
private int bufferEndOffset;
|
||||||
private byte[] messageBuffer = new byte[DefaultBufferSize];
|
private byte[] messageBuffer;
|
||||||
|
|
||||||
private int expectedContentLength;
|
private int expectedContentLength;
|
||||||
private Dictionary<string, string> messageHeaders;
|
private Dictionary<string, string> messageHeaders;
|
||||||
|
|
||||||
enum ReadState
|
private enum ReadState
|
||||||
{
|
{
|
||||||
Headers,
|
Headers,
|
||||||
Content
|
Content
|
||||||
@@ -83,7 +84,7 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|||||||
this.needsMoreData = false;
|
this.needsMoreData = false;
|
||||||
|
|
||||||
// Do we need to look for message headers?
|
// Do we need to look for message headers?
|
||||||
if (this.readState == ReadState.Headers &&
|
if (this.readState == ReadState.Headers &&
|
||||||
!this.TryReadMessageHeaders())
|
!this.TryReadMessageHeaders())
|
||||||
{
|
{
|
||||||
// If we don't have enough data to read headers yet, keep reading
|
// If we don't have enough data to read headers yet, keep reading
|
||||||
@@ -92,7 +93,7 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do we need to look for message content?
|
// Do we need to look for message content?
|
||||||
if (this.readState == ReadState.Content &&
|
if (this.readState == ReadState.Content &&
|
||||||
!this.TryReadMessageContent(out messageContent))
|
!this.TryReadMessageContent(out messageContent))
|
||||||
{
|
{
|
||||||
// If we don't have enough data yet to construct the content, keep reading
|
// If we don't have enough data yet to construct the content, keep reading
|
||||||
@@ -104,16 +105,12 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now that we have a message, reset the buffer's state
|
||||||
|
ShiftBufferBytesAndShrink(readOffset);
|
||||||
|
|
||||||
// Get the JObject for the JSON content
|
// Get the JObject for the JSON content
|
||||||
JObject messageObject = JObject.Parse(messageContent);
|
JObject messageObject = JObject.Parse(messageContent);
|
||||||
|
|
||||||
// Load the message
|
|
||||||
Logger.Write(
|
|
||||||
LogLevel.Verbose,
|
|
||||||
string.Format(
|
|
||||||
"READ MESSAGE:\r\n\r\n{0}",
|
|
||||||
messageObject.ToString(Formatting.Indented)));
|
|
||||||
|
|
||||||
// Return the parsed message
|
// Return the parsed message
|
||||||
return this.messageSerializer.DeserializeMessage(messageObject);
|
return this.messageSerializer.DeserializeMessage(messageObject);
|
||||||
}
|
}
|
||||||
@@ -160,8 +157,7 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|||||||
{
|
{
|
||||||
int scanOffset = this.readOffset;
|
int scanOffset = this.readOffset;
|
||||||
|
|
||||||
// Scan for the final double-newline that marks the
|
// Scan for the final double-newline that marks the end of the header lines
|
||||||
// end of the header lines
|
|
||||||
while (scanOffset + 3 < this.bufferEndOffset &&
|
while (scanOffset + 3 < this.bufferEndOffset &&
|
||||||
(this.messageBuffer[scanOffset] != CR ||
|
(this.messageBuffer[scanOffset] != CR ||
|
||||||
this.messageBuffer[scanOffset + 1] != LF ||
|
this.messageBuffer[scanOffset + 1] != LF ||
|
||||||
@@ -171,45 +167,51 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|||||||
scanOffset++;
|
scanOffset++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No header or body separator found (e.g CRLFCRLF)
|
// Make sure we haven't reached the end of the buffer without finding a separator (e.g CRLFCRLF)
|
||||||
if (scanOffset + 3 >= this.bufferEndOffset)
|
if (scanOffset + 3 >= this.bufferEndOffset)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messageHeaders = new Dictionary<string, string>();
|
// Convert the header block into a array of lines
|
||||||
|
var headers = Encoding.ASCII.GetString(this.messageBuffer, this.readOffset, scanOffset)
|
||||||
|
.Split(NewLineDelimiters, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
var headers =
|
try
|
||||||
Encoding.ASCII
|
|
||||||
.GetString(this.messageBuffer, this.readOffset, scanOffset)
|
|
||||||
.Split(NewLineDelimiters, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
// Read each header and store it in the dictionary
|
|
||||||
foreach (var header in headers)
|
|
||||||
{
|
{
|
||||||
int currentLength = header.IndexOf(':');
|
// Read each header and store it in the dictionary
|
||||||
if (currentLength == -1)
|
this.messageHeaders = new Dictionary<string, string>();
|
||||||
|
foreach (var header in headers)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Message header must separate key and value using :");
|
int currentLength = header.IndexOf(':');
|
||||||
|
if (currentLength == -1)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Message header must separate key and value using :");
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = header.Substring(0, currentLength);
|
||||||
|
var value = header.Substring(currentLength + 1).Trim();
|
||||||
|
this.messageHeaders[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = header.Substring(0, currentLength);
|
// Parse out the content length as an int
|
||||||
var value = header.Substring(currentLength + 1).Trim();
|
string contentLengthString;
|
||||||
this.messageHeaders[key] = value;
|
if (!this.messageHeaders.TryGetValue("Content-Length", out contentLengthString))
|
||||||
}
|
{
|
||||||
|
throw new MessageParseException("", "Fatal error: Content-Length header must be provided.");
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure a Content-Length header was present, otherwise it
|
// Parse the content length to an integer
|
||||||
// is a fatal error
|
if (!int.TryParse(contentLengthString, out this.expectedContentLength))
|
||||||
string contentLengthString = null;
|
{
|
||||||
if (!this.messageHeaders.TryGetValue("Content-Length", out contentLengthString))
|
throw new MessageParseException("", "Fatal error: Content-Length value is not an integer.");
|
||||||
{
|
}
|
||||||
throw new MessageParseException("", "Fatal error: Content-Length header must be provided.");
|
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
// Parse the content length to an integer
|
|
||||||
if (!int.TryParse(contentLengthString, out this.expectedContentLength))
|
|
||||||
{
|
{
|
||||||
throw new MessageParseException("", "Fatal error: Content-Length value is not an integer.");
|
// The content length was invalid or missing. Trash the buffer we've read
|
||||||
|
ShiftBufferBytesAndShrink(scanOffset + 4);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip past the headers plus the newline characters
|
// Skip past the headers plus the newline characters
|
||||||
@@ -232,31 +234,40 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert the message contents to a string using the specified encoding
|
// Convert the message contents to a string using the specified encoding
|
||||||
messageContent =
|
messageContent = this.messageEncoding.GetString(
|
||||||
this.messageEncoding.GetString(
|
this.messageBuffer,
|
||||||
this.messageBuffer,
|
this.readOffset,
|
||||||
this.readOffset,
|
this.expectedContentLength);
|
||||||
this.expectedContentLength);
|
|
||||||
|
|
||||||
// Move the remaining bytes to the front of the buffer for the next message
|
readOffset += expectedContentLength;
|
||||||
var remainingByteCount = this.bufferEndOffset - (this.expectedContentLength + this.readOffset);
|
|
||||||
Buffer.BlockCopy(
|
|
||||||
this.messageBuffer,
|
|
||||||
this.expectedContentLength + this.readOffset,
|
|
||||||
this.messageBuffer,
|
|
||||||
0,
|
|
||||||
remainingByteCount);
|
|
||||||
|
|
||||||
// Reset the offsets for the next read
|
// Done reading content, now look for headers for the next message
|
||||||
this.readOffset = 0;
|
|
||||||
this.bufferEndOffset = remainingByteCount;
|
|
||||||
|
|
||||||
// Done reading content, now look for headers
|
|
||||||
this.readState = ReadState.Headers;
|
this.readState = ReadState.Headers;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ShiftBufferBytesAndShrink(int bytesToRemove)
|
||||||
|
{
|
||||||
|
// Create a new buffer that is shrunken by the number of bytes to remove
|
||||||
|
// Note: by using Max, we can guarantee a buffer of at least default buffer size
|
||||||
|
byte[] newBuffer = new byte[Math.Max(messageBuffer.Length - bytesToRemove, DefaultBufferSize)];
|
||||||
|
|
||||||
|
// If we need to do shifting, do the shifting
|
||||||
|
if (bytesToRemove <= messageBuffer.Length)
|
||||||
|
{
|
||||||
|
// Copy the existing buffer starting at the offset to remove
|
||||||
|
Buffer.BlockCopy(messageBuffer, bytesToRemove, newBuffer, 0, bufferEndOffset - bytesToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the new buffer the message buffer
|
||||||
|
messageBuffer = newBuffer;
|
||||||
|
|
||||||
|
// Reset the read offset and the end offset
|
||||||
|
readOffset = 0;
|
||||||
|
bufferEndOffset -= bytesToRemove;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,14 +3,16 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||||
{
|
{
|
||||||
public class MessageWriter
|
public class MessageWriter
|
||||||
{
|
{
|
||||||
@@ -3,19 +3,20 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides behavior for a client or server endpoint that
|
/// Provides behavior for a client or server endpoint that
|
||||||
/// communicates using the specified protocol.
|
/// communicates using the specified protocol.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProtocolEndpoint : IMessageSender
|
public class ProtocolEndpoint : IMessageSender, IProtocolEndpoint
|
||||||
{
|
{
|
||||||
private bool isStarted;
|
private bool isStarted;
|
||||||
private int currentMessageId;
|
private int currentMessageId;
|
||||||
@@ -3,10 +3,11 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
|
||||||
{
|
{
|
||||||
public class RequestContext<TResult>
|
public class RequestContext<TResult>
|
||||||
{
|
{
|
||||||
@@ -19,7 +20,9 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|||||||
this.messageWriter = messageWriter;
|
this.messageWriter = messageWriter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendResult(TResult resultDetails)
|
public RequestContext() { }
|
||||||
|
|
||||||
|
public virtual async Task SendResult(TResult resultDetails)
|
||||||
{
|
{
|
||||||
await this.messageWriter.WriteResponse<TResult>(
|
await this.messageWriter.WriteResponse<TResult>(
|
||||||
resultDetails,
|
resultDetails,
|
||||||
@@ -27,14 +30,14 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|||||||
requestMessage.Id);
|
requestMessage.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendEvent<TParams>(EventType<TParams> eventType, TParams eventParams)
|
public virtual async Task SendEvent<TParams>(EventType<TParams> eventType, TParams eventParams)
|
||||||
{
|
{
|
||||||
await this.messageWriter.WriteEvent(
|
await this.messageWriter.WriteEvent(
|
||||||
eventType,
|
eventType,
|
||||||
eventParams);
|
eventParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendError(object errorDetails)
|
public virtual async Task SendError(object errorDetails)
|
||||||
{
|
{
|
||||||
await this.messageWriter.WriteMessage(
|
await this.messageWriter.WriteMessage(
|
||||||
Message.ResponseError(
|
Message.ResponseError(
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a common interface for message serializers.
|
/// Defines a common interface for message serializers.
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Serializers
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serializes messages in the JSON RPC format. Used primarily
|
/// Serializes messages in the JSON RPC format. Used primarily
|
||||||
@@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Serializers
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serializes messages in the V8 format. Used primarily for debug adapters.
|
/// Serializes messages in the V8 format. Used primarily for debug adapters.
|
||||||
168
src/Microsoft.SqlTools.ServiceLayer/Hosting/ServiceHost.cs
Normal file
168
src/Microsoft.SqlTools.ServiceLayer/Hosting/ServiceHost.cs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SQL Tools VS Code Language Server request handler. Provides the entire JSON RPC
|
||||||
|
/// implementation for sending/receiving JSON requests and dispatching the requests to
|
||||||
|
/// handlers that are registered prior to startup.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ServiceHost : ServiceHostBase
|
||||||
|
{
|
||||||
|
#region Singleton Instance Code
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Singleton instance of the service host for internal storage
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Lazy<ServiceHost> instance = new Lazy<ServiceHost>(() => new ServiceHost());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current instance of the ServiceHost
|
||||||
|
/// </summary>
|
||||||
|
public static ServiceHost Instance
|
||||||
|
{
|
||||||
|
get { return instance.Value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs new instance of ServiceHost using the host and profile details provided.
|
||||||
|
/// Access is private to ensure only one instance exists at a time.
|
||||||
|
/// </summary>
|
||||||
|
private ServiceHost() : base(new StdioServerChannel())
|
||||||
|
{
|
||||||
|
// Initialize the shutdown activities
|
||||||
|
shutdownCallbacks = new List<ShutdownCallback>();
|
||||||
|
initializeCallbacks = new List<InitializeCallback>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provide initialization that must occur after the service host is started
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
// Register the requests that this service host will handle
|
||||||
|
this.SetRequestHandler(InitializeRequest.Type, this.HandleInitializeRequest);
|
||||||
|
this.SetRequestHandler(ShutdownRequest.Type, this.HandleShutdownRequest);
|
||||||
|
this.SetRequestHandler(VersionRequest.Type, HandleVersionRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Member Variables
|
||||||
|
|
||||||
|
public delegate Task ShutdownCallback(object shutdownParams, RequestContext<object> shutdownRequestContext);
|
||||||
|
|
||||||
|
public delegate Task InitializeCallback(InitializeRequest startupParams, RequestContext<InitializeResult> requestContext);
|
||||||
|
|
||||||
|
private readonly List<ShutdownCallback> shutdownCallbacks;
|
||||||
|
|
||||||
|
private readonly List<InitializeCallback> initializeCallbacks;
|
||||||
|
|
||||||
|
private static readonly Version serviceVersion = Assembly.GetEntryAssembly().GetName().Version;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new callback to be called when the shutdown request is submitted
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">Callback to perform when a shutdown request is submitted</param>
|
||||||
|
public void RegisterShutdownTask(ShutdownCallback callback)
|
||||||
|
{
|
||||||
|
shutdownCallbacks.Add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a new method to be called when the initialize request is submitted
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">Callback to perform when an initialize request is submitted</param>
|
||||||
|
public void RegisterInitializeTask(InitializeCallback callback)
|
||||||
|
{
|
||||||
|
initializeCallbacks.Add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Request Handlers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the shutdown event for the Language Server
|
||||||
|
/// </summary>
|
||||||
|
private async Task HandleShutdownRequest(object shutdownParams, RequestContext<object> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Normal, "Service host is shutting down...");
|
||||||
|
|
||||||
|
// Call all the shutdown methods provided by the service components
|
||||||
|
Task[] shutdownTasks = shutdownCallbacks.Select(t => t(shutdownParams, requestContext)).ToArray();
|
||||||
|
await Task.WhenAll(shutdownTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the initialization request
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="initializeParams"></param>
|
||||||
|
/// <param name="requestContext"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task HandleInitializeRequest(InitializeRequest initializeParams, RequestContext<InitializeResult> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleInitializationRequest");
|
||||||
|
|
||||||
|
// Call all tasks that registered on the initialize request
|
||||||
|
var initializeTasks = initializeCallbacks.Select(t => t(initializeParams, requestContext));
|
||||||
|
await Task.WhenAll(initializeTasks);
|
||||||
|
|
||||||
|
// TODO: Figure out where this needs to go to be agnostic of the language
|
||||||
|
|
||||||
|
// Send back what this server can do
|
||||||
|
await requestContext.SendResult(
|
||||||
|
new InitializeResult
|
||||||
|
{
|
||||||
|
Capabilities = new ServerCapabilities
|
||||||
|
{
|
||||||
|
TextDocumentSync = TextDocumentSyncKind.Incremental,
|
||||||
|
DefinitionProvider = true,
|
||||||
|
ReferencesProvider = true,
|
||||||
|
DocumentHighlightProvider = true,
|
||||||
|
DocumentSymbolProvider = true,
|
||||||
|
WorkspaceSymbolProvider = true,
|
||||||
|
CompletionProvider = new CompletionOptions
|
||||||
|
{
|
||||||
|
ResolveProvider = true,
|
||||||
|
TriggerCharacters = new string[] { ".", "-", ":", "\\" }
|
||||||
|
},
|
||||||
|
SignatureHelpProvider = new SignatureHelpOptions
|
||||||
|
{
|
||||||
|
TriggerCharacters = new string[] { " " } // TODO: Other characters here?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the version request. Sends back the server version as result.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task HandleVersionRequest(
|
||||||
|
object versionRequestParams,
|
||||||
|
RequestContext<string> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleVersionRequest");
|
||||||
|
await requestContext.SendResult(serviceVersion.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
||||||
|
{
|
||||||
|
public abstract class ServiceHostBase : ProtocolEndpoint
|
||||||
|
{
|
||||||
|
private bool isStarted;
|
||||||
|
private TaskCompletionSource<bool> serverExitedTask;
|
||||||
|
|
||||||
|
protected ServiceHostBase(ChannelBase serverChannel) :
|
||||||
|
base(serverChannel, MessageProtocolType.LanguageServer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task OnStart()
|
||||||
|
{
|
||||||
|
// Register handlers for server lifetime messages
|
||||||
|
|
||||||
|
this.SetEventHandler(ExitNotification.Type, this.HandleExitNotification);
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleExitNotification(
|
||||||
|
object exitParams,
|
||||||
|
EventContext eventContext)
|
||||||
|
{
|
||||||
|
// Stop the server channel
|
||||||
|
await this.Stop();
|
||||||
|
|
||||||
|
// Notify any waiter that the server has exited
|
||||||
|
if (this.serverExitedTask != null)
|
||||||
|
{
|
||||||
|
this.serverExitedTask.SetResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main class for Autocomplete functionality
|
||||||
|
/// </summary>
|
||||||
|
public class AutoCompleteService
|
||||||
|
{
|
||||||
|
#region Singleton Instance Implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Singleton service instance
|
||||||
|
/// </summary>
|
||||||
|
private static Lazy<AutoCompleteService> instance
|
||||||
|
= new Lazy<AutoCompleteService>(() => new AutoCompleteService());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the singleton service instance
|
||||||
|
/// </summary>
|
||||||
|
public static AutoCompleteService Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return instance.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default, parameterless constructor.
|
||||||
|
/// Internal constructor for use in test cases only
|
||||||
|
/// </summary>
|
||||||
|
internal AutoCompleteService()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private ConnectionService connectionService = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal for testing purposes only
|
||||||
|
/// </summary>
|
||||||
|
internal ConnectionService ConnectionServiceInstance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if(connectionService == null)
|
||||||
|
{
|
||||||
|
connectionService = ConnectionService.Instance;
|
||||||
|
}
|
||||||
|
return connectionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
connectionService = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializeService(ServiceHost serviceHost)
|
||||||
|
{
|
||||||
|
// Register auto-complete request handler
|
||||||
|
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
|
||||||
|
|
||||||
|
// Register a callback for when a connection is created
|
||||||
|
ConnectionServiceInstance.RegisterOnConnectionTask(UpdateAutoCompleteCache);
|
||||||
|
|
||||||
|
// Register a callback for when a connection is closed
|
||||||
|
ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Auto-complete completion provider request callback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="textDocumentPosition"></param>
|
||||||
|
/// <param name="requestContext"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static async Task HandleCompletionRequest(
|
||||||
|
TextDocumentPosition textDocumentPosition,
|
||||||
|
RequestContext<CompletionItem[]> requestContext)
|
||||||
|
{
|
||||||
|
// get the current list of completion items and return to client
|
||||||
|
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(
|
||||||
|
textDocumentPosition.TextDocument.Uri);
|
||||||
|
|
||||||
|
ConnectionInfo connInfo;
|
||||||
|
ConnectionService.Instance.TryFindConnection(
|
||||||
|
scriptFile.ClientFilePath,
|
||||||
|
out connInfo);
|
||||||
|
|
||||||
|
var completionItems = Instance.GetCompletionItems(
|
||||||
|
textDocumentPosition, scriptFile, connInfo);
|
||||||
|
|
||||||
|
await requestContext.SendResult(completionItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a reference to an autocomplete cache from a URI. If
|
||||||
|
/// it is the last URI connected to a particular connection,
|
||||||
|
/// then remove the cache.
|
||||||
|
/// </summary>
|
||||||
|
public async Task RemoveAutoCompleteCacheUriReference(ConnectionSummary summary)
|
||||||
|
{
|
||||||
|
// currently this method is disabled, but we need to reimplement now that the
|
||||||
|
// implementation of the 'cache' has changed.
|
||||||
|
await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the cached autocomplete candidate list when the user connects to a database
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info"></param>
|
||||||
|
public async Task UpdateAutoCompleteCache(ConnectionInfo info)
|
||||||
|
{
|
||||||
|
await Task.Run( () =>
|
||||||
|
{
|
||||||
|
if (!LanguageService.Instance.ScriptParseInfoMap.ContainsKey(info.OwnerUri))
|
||||||
|
{
|
||||||
|
var sqlConn = info.SqlConnection as SqlConnection;
|
||||||
|
if (sqlConn != null)
|
||||||
|
{
|
||||||
|
var srvConn = new ServerConnection(sqlConn);
|
||||||
|
var displayInfoProvider = new MetadataDisplayInfoProvider();
|
||||||
|
var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(srvConn);
|
||||||
|
var binder = BinderProvider.CreateBinder(metadataProvider);
|
||||||
|
|
||||||
|
LanguageService.Instance.ScriptParseInfoMap.Add(info.OwnerUri,
|
||||||
|
new ScriptParseInfo()
|
||||||
|
{
|
||||||
|
Binder = binder,
|
||||||
|
MetadataProvider = metadataProvider,
|
||||||
|
MetadataDisplayInfoProvider = displayInfoProvider
|
||||||
|
});
|
||||||
|
|
||||||
|
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(info.OwnerUri);
|
||||||
|
|
||||||
|
LanguageService.Instance.ParseAndBind(scriptFile, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the position of the previous delimeter for autocomplete token replacement.
|
||||||
|
/// SQL Parser may have similar functionality in which case we'll delete this method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sql"></param>
|
||||||
|
/// <param name="startRow"></param>
|
||||||
|
/// <param name="startColumn"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private int PositionOfPrevDelimeter(string sql, int startRow, int startColumn)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(sql))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int prevLineColumns = 0;
|
||||||
|
for (int i = 0; i < startRow; ++i)
|
||||||
|
{
|
||||||
|
while (sql[prevLineColumns] != '\n' && prevLineColumns < sql.Length)
|
||||||
|
{
|
||||||
|
++prevLineColumns;
|
||||||
|
}
|
||||||
|
++prevLineColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
startColumn += prevLineColumns;
|
||||||
|
|
||||||
|
if (startColumn - 1 < sql.Length)
|
||||||
|
{
|
||||||
|
while (--startColumn >= prevLineColumns)
|
||||||
|
{
|
||||||
|
if (sql[startColumn] == ' '
|
||||||
|
|| sql[startColumn] == '\t'
|
||||||
|
|| sql[startColumn] == '\n'
|
||||||
|
|| sql[startColumn] == '.'
|
||||||
|
|| sql[startColumn] == '+'
|
||||||
|
|| sql[startColumn] == '-'
|
||||||
|
|| sql[startColumn] == '*'
|
||||||
|
|| sql[startColumn] == '>'
|
||||||
|
|| sql[startColumn] == '<'
|
||||||
|
|| sql[startColumn] == '='
|
||||||
|
|| sql[startColumn] == '/'
|
||||||
|
|| sql[startColumn] == '%')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return startColumn + 1 - prevLineColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether a reparse and bind is required to provide autocomplete
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info"></param>
|
||||||
|
/// <returns>TEMP: Currently hard-coded to false for perf</returns>
|
||||||
|
private bool RequiresReparse(ScriptParseInfo info)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a list of Declaration objects to CompletionItem objects
|
||||||
|
/// since VS Code expects CompletionItems but SQL Parser works with Declarations
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="suggestions"></param>
|
||||||
|
/// <param name="cursorRow"></param>
|
||||||
|
/// <param name="cursorColumn"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private CompletionItem[] ConvertDeclarationsToCompletionItems(
|
||||||
|
IEnumerable<Declaration> suggestions,
|
||||||
|
int row,
|
||||||
|
int startColumn,
|
||||||
|
int endColumn)
|
||||||
|
{
|
||||||
|
List<CompletionItem> completions = new List<CompletionItem>();
|
||||||
|
foreach (var autoCompleteItem in suggestions)
|
||||||
|
{
|
||||||
|
// convert the completion item candidates into CompletionItems
|
||||||
|
completions.Add(new CompletionItem()
|
||||||
|
{
|
||||||
|
Label = autoCompleteItem.Title,
|
||||||
|
Kind = CompletionItemKind.Keyword,
|
||||||
|
Detail = autoCompleteItem.Title,
|
||||||
|
Documentation = autoCompleteItem.Description,
|
||||||
|
TextEdit = new TextEdit
|
||||||
|
{
|
||||||
|
NewText = autoCompleteItem.Title,
|
||||||
|
Range = new Range
|
||||||
|
{
|
||||||
|
Start = new Position
|
||||||
|
{
|
||||||
|
Line = row,
|
||||||
|
Character = startColumn
|
||||||
|
},
|
||||||
|
End = new Position
|
||||||
|
{
|
||||||
|
Line = row,
|
||||||
|
Character = endColumn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return completions.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the completion item list for the current text position.
|
||||||
|
/// This method does not await cache builds since it expects to return quickly
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="textDocumentPosition"></param>
|
||||||
|
public CompletionItem[] GetCompletionItems(
|
||||||
|
TextDocumentPosition textDocumentPosition,
|
||||||
|
ScriptFile scriptFile,
|
||||||
|
ConnectionInfo connInfo)
|
||||||
|
{
|
||||||
|
string filePath = textDocumentPosition.TextDocument.Uri;
|
||||||
|
|
||||||
|
// Take a reference to the list at a point in time in case we update and replace the list
|
||||||
|
if (connInfo == null
|
||||||
|
|| !LanguageService.Instance.ScriptParseInfoMap.ContainsKey(textDocumentPosition.TextDocument.Uri))
|
||||||
|
{
|
||||||
|
return new CompletionItem[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// reparse and bind the SQL statement if needed
|
||||||
|
var scriptParseInfo = LanguageService.Instance.ScriptParseInfoMap[textDocumentPosition.TextDocument.Uri];
|
||||||
|
if (RequiresReparse(scriptParseInfo))
|
||||||
|
{
|
||||||
|
LanguageService.Instance.ParseAndBind(scriptFile, connInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scriptParseInfo.ParseResult == null)
|
||||||
|
{
|
||||||
|
return new CompletionItem[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the completion list from SQL Parser
|
||||||
|
var suggestions = Resolver.FindCompletions(
|
||||||
|
scriptParseInfo.ParseResult,
|
||||||
|
textDocumentPosition.Position.Line + 1,
|
||||||
|
textDocumentPosition.Position.Character + 1,
|
||||||
|
scriptParseInfo.MetadataDisplayInfoProvider);
|
||||||
|
|
||||||
|
// convert the suggestion list to the VS Code format
|
||||||
|
return ConvertDeclarationsToCompletionItems(
|
||||||
|
suggestions,
|
||||||
|
textDocumentPosition.Position.Line,
|
||||||
|
PositionOfPrevDelimeter(
|
||||||
|
scriptFile.Contents,
|
||||||
|
textDocumentPosition.Position.Line,
|
||||||
|
textDocumentPosition.Position.Character),
|
||||||
|
textDocumentPosition.Position.Character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,10 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||||
{
|
{
|
||||||
public class CompletionRequest
|
public class CompletionRequest
|
||||||
{
|
{
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||||
{
|
{
|
||||||
public class DefinitionRequest
|
public class DefinitionRequest
|
||||||
{
|
{
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||||
{
|
{
|
||||||
public class PublishDiagnosticsNotification
|
public class PublishDiagnosticsNotification
|
||||||
{
|
{
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||||
{
|
{
|
||||||
public enum DocumentHighlightKind
|
public enum DocumentHighlightKind
|
||||||
{
|
{
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||||
{
|
{
|
||||||
public class ExpandAliasRequest
|
public class ExpandAliasRequest
|
||||||
{
|
{
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||||
{
|
{
|
||||||
public class FindModuleRequest
|
public class FindModuleRequest
|
||||||
{
|
{
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||||
{
|
{
|
||||||
public class MarkedString
|
public class MarkedString
|
||||||
{
|
{
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||||
{
|
{
|
||||||
class InstallModuleRequest
|
class InstallModuleRequest
|
||||||
{
|
{
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||||
{
|
{
|
||||||
public class ReferencesRequest
|
public class ReferencesRequest
|
||||||
{
|
{
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||||
{
|
{
|
||||||
public class ShowOnlineHelpRequest
|
public class ShowOnlineHelpRequest
|
||||||
{
|
{
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
|
||||||
{
|
{
|
||||||
public class SignatureHelpRequest
|
public class SignatureHelpRequest
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,538 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
|
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main class for Language Service functionality including anything that reqires knowledge of
|
||||||
|
/// the language to perfom, such as definitions, intellisense, etc.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class LanguageService
|
||||||
|
{
|
||||||
|
|
||||||
|
#region Singleton Instance Implementation
|
||||||
|
|
||||||
|
private static readonly Lazy<LanguageService> instance = new Lazy<LanguageService>(() => new LanguageService());
|
||||||
|
|
||||||
|
private Lazy<Dictionary<string, ScriptParseInfo>> scriptParseInfoMap
|
||||||
|
= new Lazy<Dictionary<string, ScriptParseInfo>>(() => new Dictionary<string, ScriptParseInfo>());
|
||||||
|
|
||||||
|
internal Dictionary<string, ScriptParseInfo> ScriptParseInfoMap
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.scriptParseInfoMap.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LanguageService Instance
|
||||||
|
{
|
||||||
|
get { return instance.Value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default, parameterless constructor.
|
||||||
|
/// </summary>
|
||||||
|
internal LanguageService()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
private static CancellationTokenSource ExistingRequestCancellation { get; set; }
|
||||||
|
|
||||||
|
private SqlToolsSettings CurrentSettings
|
||||||
|
{
|
||||||
|
get { return WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private Workspace.Workspace CurrentWorkspace
|
||||||
|
{
|
||||||
|
get { return WorkspaceService<SqlToolsSettings>.Instance.Workspace; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current SQL Tools context
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private SqlToolsContext Context { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the Language Service instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceHost"></param>
|
||||||
|
/// <param name="context"></param>
|
||||||
|
public void InitializeService(ServiceHost serviceHost, SqlToolsContext context)
|
||||||
|
{
|
||||||
|
// Register the requests that this service will handle
|
||||||
|
serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest);
|
||||||
|
serviceHost.SetRequestHandler(ReferencesRequest.Type, HandleReferencesRequest);
|
||||||
|
serviceHost.SetRequestHandler(CompletionResolveRequest.Type, HandleCompletionResolveRequest);
|
||||||
|
serviceHost.SetRequestHandler(SignatureHelpRequest.Type, HandleSignatureHelpRequest);
|
||||||
|
serviceHost.SetRequestHandler(DocumentHighlightRequest.Type, HandleDocumentHighlightRequest);
|
||||||
|
serviceHost.SetRequestHandler(HoverRequest.Type, HandleHoverRequest);
|
||||||
|
serviceHost.SetRequestHandler(DocumentSymbolRequest.Type, HandleDocumentSymbolRequest);
|
||||||
|
serviceHost.SetRequestHandler(WorkspaceSymbolRequest.Type, HandleWorkspaceSymbolRequest);
|
||||||
|
|
||||||
|
// Register a no-op shutdown task for validation of the shutdown logic
|
||||||
|
serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) =>
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "Shutting down language service");
|
||||||
|
await Task.FromResult(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register the configuration update handler
|
||||||
|
WorkspaceService<SqlToolsSettings>.Instance.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification);
|
||||||
|
|
||||||
|
// Register the file change update handler
|
||||||
|
WorkspaceService<SqlToolsSettings>.Instance.RegisterTextDocChangeCallback(HandleDidChangeTextDocumentNotification);
|
||||||
|
|
||||||
|
// Register the file open update handler
|
||||||
|
WorkspaceService<SqlToolsSettings>.Instance.RegisterTextDocOpenCallback(HandleDidOpenTextDocumentNotification);
|
||||||
|
|
||||||
|
// Store the SqlToolsContext for future use
|
||||||
|
Context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the SQL text and binds it to the SMO metadata provider if connected
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <param name="sqlText"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
|
||||||
|
{
|
||||||
|
ScriptParseInfo parseInfo = null;
|
||||||
|
if (this.ScriptParseInfoMap.ContainsKey(scriptFile.ClientFilePath))
|
||||||
|
{
|
||||||
|
parseInfo = this.ScriptParseInfoMap[scriptFile.ClientFilePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse current SQL file contents to retrieve a list of errors
|
||||||
|
ParseOptions parseOptions = new ParseOptions();
|
||||||
|
ParseResult parseResult = Parser.IncrementalParse(
|
||||||
|
scriptFile.Contents,
|
||||||
|
parseInfo != null ? parseInfo.ParseResult : null,
|
||||||
|
parseOptions);
|
||||||
|
|
||||||
|
// save previous result for next incremental parse
|
||||||
|
if (parseInfo != null)
|
||||||
|
{
|
||||||
|
parseInfo.ParseResult = parseResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connInfo != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<ParseResult> parseResults = new List<ParseResult>();
|
||||||
|
parseResults.Add(parseResult);
|
||||||
|
parseInfo.Binder.Bind(
|
||||||
|
parseResults,
|
||||||
|
connInfo.ConnectionDetails.DatabaseName,
|
||||||
|
BindMode.Batch);
|
||||||
|
}
|
||||||
|
catch (ConnectionException)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||||
|
}
|
||||||
|
catch (SqlParserInternalBinderError)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of semantic diagnostic marks for the provided script file
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scriptFile"></param>
|
||||||
|
public ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
|
||||||
|
{
|
||||||
|
ConnectionInfo connInfo;
|
||||||
|
ConnectionService.Instance.TryFindConnection(
|
||||||
|
scriptFile.ClientFilePath,
|
||||||
|
out connInfo);
|
||||||
|
|
||||||
|
var parseResult = ParseAndBind(scriptFile, connInfo);
|
||||||
|
|
||||||
|
// build a list of SQL script file markers from the errors
|
||||||
|
List<ScriptFileMarker> markers = new List<ScriptFileMarker>();
|
||||||
|
foreach (var error in parseResult.Errors)
|
||||||
|
{
|
||||||
|
markers.Add(new ScriptFileMarker()
|
||||||
|
{
|
||||||
|
Message = error.Message,
|
||||||
|
Level = ScriptFileMarkerLevel.Error,
|
||||||
|
ScriptRegion = new ScriptRegion()
|
||||||
|
{
|
||||||
|
File = scriptFile.FilePath,
|
||||||
|
StartLineNumber = error.Start.LineNumber,
|
||||||
|
StartColumnNumber = error.Start.ColumnNumber,
|
||||||
|
StartOffset = 0,
|
||||||
|
EndLineNumber = error.End.LineNumber,
|
||||||
|
EndColumnNumber = error.End.ColumnNumber,
|
||||||
|
EndOffset = 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return markers.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Request Handlers
|
||||||
|
|
||||||
|
private static async Task HandleDefinitionRequest(
|
||||||
|
TextDocumentPosition textDocumentPosition,
|
||||||
|
RequestContext<Location[]> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleDefinitionRequest");
|
||||||
|
await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleReferencesRequest(
|
||||||
|
ReferencesParams referencesParams,
|
||||||
|
RequestContext<Location[]> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleReferencesRequest");
|
||||||
|
await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleCompletionResolveRequest(
|
||||||
|
CompletionItem completionItem,
|
||||||
|
RequestContext<CompletionItem> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleCompletionResolveRequest");
|
||||||
|
await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleSignatureHelpRequest(
|
||||||
|
TextDocumentPosition textDocumentPosition,
|
||||||
|
RequestContext<SignatureHelp> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleSignatureHelpRequest");
|
||||||
|
await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleDocumentHighlightRequest(
|
||||||
|
TextDocumentPosition textDocumentPosition,
|
||||||
|
RequestContext<DocumentHighlight[]> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleDocumentHighlightRequest");
|
||||||
|
await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleHoverRequest(
|
||||||
|
TextDocumentPosition textDocumentPosition,
|
||||||
|
RequestContext<Hover> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleHoverRequest");
|
||||||
|
await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleDocumentSymbolRequest(
|
||||||
|
DocumentSymbolParams documentSymbolParams,
|
||||||
|
RequestContext<SymbolInformation[]> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleDocumentSymbolRequest");
|
||||||
|
await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleWorkspaceSymbolRequest(
|
||||||
|
WorkspaceSymbolParams workspaceSymbolParams,
|
||||||
|
RequestContext<SymbolInformation[]> requestContext)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "HandleWorkspaceSymbolRequest");
|
||||||
|
await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Handlers for Events from Other Services
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle the file open notification
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scriptFile"></param>
|
||||||
|
/// <param name="eventContext"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task HandleDidOpenTextDocumentNotification(
|
||||||
|
ScriptFile scriptFile,
|
||||||
|
EventContext eventContext)
|
||||||
|
{
|
||||||
|
await this.RunScriptDiagnostics(
|
||||||
|
new ScriptFile[] { scriptFile },
|
||||||
|
eventContext);
|
||||||
|
|
||||||
|
await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles text document change events
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="textChangeParams"></param>
|
||||||
|
/// <param name="eventContext"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task HandleDidChangeTextDocumentNotification(ScriptFile[] changedFiles, EventContext eventContext)
|
||||||
|
{
|
||||||
|
await this.RunScriptDiagnostics(
|
||||||
|
changedFiles.ToArray(),
|
||||||
|
eventContext);
|
||||||
|
|
||||||
|
await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle the file configuration change notification
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newSettings"></param>
|
||||||
|
/// <param name="oldSettings"></param>
|
||||||
|
/// <param name="eventContext"></param>
|
||||||
|
public async Task HandleDidChangeConfigurationNotification(
|
||||||
|
SqlToolsSettings newSettings,
|
||||||
|
SqlToolsSettings oldSettings,
|
||||||
|
EventContext eventContext)
|
||||||
|
{
|
||||||
|
// If script analysis settings have changed we need to clear & possibly update the current diagnostic records.
|
||||||
|
bool oldScriptAnalysisEnabled = oldSettings.ScriptAnalysis.Enable.HasValue;
|
||||||
|
if ((oldScriptAnalysisEnabled != newSettings.ScriptAnalysis.Enable))
|
||||||
|
{
|
||||||
|
// If the user just turned off script analysis or changed the settings path, send a diagnostics
|
||||||
|
// event to clear the analysis markers that they already have.
|
||||||
|
if (!newSettings.ScriptAnalysis.Enable.Value)
|
||||||
|
{
|
||||||
|
ScriptFileMarker[] emptyAnalysisDiagnostics = new ScriptFileMarker[0];
|
||||||
|
|
||||||
|
foreach (var scriptFile in WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetOpenedFiles())
|
||||||
|
{
|
||||||
|
await PublishScriptDiagnostics(scriptFile, emptyAnalysisDiagnostics, eventContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await this.RunScriptDiagnostics(CurrentWorkspace.GetOpenedFiles(), eventContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the settings in the current
|
||||||
|
CurrentSettings.EnableProfileLoading = newSettings.EnableProfileLoading;
|
||||||
|
CurrentSettings.ScriptAnalysis.Update(newSettings.ScriptAnalysis, CurrentWorkspace.WorkspacePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Helpers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs script diagnostics on changed files
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filesToAnalyze"></param>
|
||||||
|
/// <param name="eventContext"></param>
|
||||||
|
private Task RunScriptDiagnostics(ScriptFile[] filesToAnalyze, EventContext eventContext)
|
||||||
|
{
|
||||||
|
if (!CurrentSettings.ScriptAnalysis.Enable.Value)
|
||||||
|
{
|
||||||
|
// If the user has disabled script analysis, skip it entirely
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's an existing task, attempt to cancel it
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ExistingRequestCancellation != null)
|
||||||
|
{
|
||||||
|
// Try to cancel the request
|
||||||
|
ExistingRequestCancellation.Cancel();
|
||||||
|
|
||||||
|
// If cancellation didn't throw an exception,
|
||||||
|
// clean up the existing token
|
||||||
|
ExistingRequestCancellation.Dispose();
|
||||||
|
ExistingRequestCancellation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Write(
|
||||||
|
LogLevel.Error,
|
||||||
|
string.Format(
|
||||||
|
"Exception while cancelling analysis task:\n\n{0}",
|
||||||
|
e.ToString()));
|
||||||
|
|
||||||
|
TaskCompletionSource<bool> cancelTask = new TaskCompletionSource<bool>();
|
||||||
|
cancelTask.SetCanceled();
|
||||||
|
return cancelTask.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a fresh cancellation token and then start the task.
|
||||||
|
// We create this on a different TaskScheduler so that we
|
||||||
|
// don't block the main message loop thread.
|
||||||
|
ExistingRequestCancellation = new CancellationTokenSource();
|
||||||
|
Task.Factory.StartNew(
|
||||||
|
() =>
|
||||||
|
DelayThenInvokeDiagnostics(
|
||||||
|
750,
|
||||||
|
filesToAnalyze,
|
||||||
|
eventContext,
|
||||||
|
ExistingRequestCancellation.Token),
|
||||||
|
CancellationToken.None,
|
||||||
|
TaskCreationOptions.None,
|
||||||
|
TaskScheduler.Default);
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actually run the script diagnostics after waiting for some small delay
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delayMilliseconds"></param>
|
||||||
|
/// <param name="filesToAnalyze"></param>
|
||||||
|
/// <param name="eventContext"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
private async Task DelayThenInvokeDiagnostics(
|
||||||
|
int delayMilliseconds,
|
||||||
|
ScriptFile[] filesToAnalyze,
|
||||||
|
EventContext eventContext,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// First of all, wait for the desired delay period before
|
||||||
|
// analyzing the provided list of files
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(delayMilliseconds, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// If the task is cancelled, exit directly
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've made it past the delay period then we don't care
|
||||||
|
// about the cancellation token anymore. This could happen
|
||||||
|
// when the user stops typing for long enough that the delay
|
||||||
|
// period ends but then starts typing while analysis is going
|
||||||
|
// on. It makes sense to send back the results from the first
|
||||||
|
// delay period while the second one is ticking away.
|
||||||
|
|
||||||
|
// Get the requested files
|
||||||
|
foreach (ScriptFile scriptFile in filesToAnalyze)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "Analyzing script file: " + scriptFile.FilePath);
|
||||||
|
ScriptFileMarker[] semanticMarkers = GetSemanticMarkers(scriptFile);
|
||||||
|
Logger.Write(LogLevel.Verbose, "Analysis complete.");
|
||||||
|
|
||||||
|
await PublishScriptDiagnostics(scriptFile, semanticMarkers, eventContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send the diagnostic results back to the host application
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scriptFile"></param>
|
||||||
|
/// <param name="semanticMarkers"></param>
|
||||||
|
/// <param name="eventContext"></param>
|
||||||
|
private static async Task PublishScriptDiagnostics(
|
||||||
|
ScriptFile scriptFile,
|
||||||
|
ScriptFileMarker[] semanticMarkers,
|
||||||
|
EventContext eventContext)
|
||||||
|
{
|
||||||
|
var allMarkers = scriptFile.SyntaxMarkers != null
|
||||||
|
? scriptFile.SyntaxMarkers.Concat(semanticMarkers)
|
||||||
|
: semanticMarkers;
|
||||||
|
|
||||||
|
// Always send syntax and semantic errors. We want to
|
||||||
|
// make sure no out-of-date markers are being displayed.
|
||||||
|
await eventContext.SendEvent(
|
||||||
|
PublishDiagnosticsNotification.Type,
|
||||||
|
new PublishDiagnosticsNotification
|
||||||
|
{
|
||||||
|
Uri = scriptFile.ClientFilePath,
|
||||||
|
Diagnostics =
|
||||||
|
allMarkers
|
||||||
|
.Select(GetDiagnosticFromMarker)
|
||||||
|
.ToArray()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a ScriptFileMarker to a Diagnostic that is Language Service compatible
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scriptFileMarker"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static Diagnostic GetDiagnosticFromMarker(ScriptFileMarker scriptFileMarker)
|
||||||
|
{
|
||||||
|
return new Diagnostic
|
||||||
|
{
|
||||||
|
Severity = MapDiagnosticSeverity(scriptFileMarker.Level),
|
||||||
|
Message = scriptFileMarker.Message,
|
||||||
|
Range = new Range
|
||||||
|
{
|
||||||
|
Start = new Position
|
||||||
|
{
|
||||||
|
Line = scriptFileMarker.ScriptRegion.StartLineNumber - 1,
|
||||||
|
Character = scriptFileMarker.ScriptRegion.StartColumnNumber - 1
|
||||||
|
},
|
||||||
|
End = new Position
|
||||||
|
{
|
||||||
|
Line = scriptFileMarker.ScriptRegion.EndLineNumber - 1,
|
||||||
|
Character = scriptFileMarker.ScriptRegion.EndColumnNumber - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Map ScriptFileMarker severity to Diagnostic severity
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="markerLevel"></param>
|
||||||
|
private static DiagnosticSeverity MapDiagnosticSeverity(ScriptFileMarkerLevel markerLevel)
|
||||||
|
{
|
||||||
|
switch (markerLevel)
|
||||||
|
{
|
||||||
|
case ScriptFileMarkerLevel.Error:
|
||||||
|
return DiagnosticSeverity.Error;
|
||||||
|
|
||||||
|
case ScriptFileMarkerLevel.Warning:
|
||||||
|
return DiagnosticSeverity.Warning;
|
||||||
|
|
||||||
|
case ScriptFileMarkerLevel.Information:
|
||||||
|
return DiagnosticSeverity.Information;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return DiagnosticSeverity.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user