Skip to content

Commit

Permalink
Initial skeleton code for C++ Implementation
Browse files Browse the repository at this point in the history
* Add libcask native dll project to sln
* Add stub implementation and P/Invoke interop to it to tests
* Revert back to CRLF on Windows, there are too many issues with VS putting snippets with CRLF into files and making them have mixed line endings
* Use MSBuild magic so that we
   1. Have a good experience in VS on Windows for C++
   2. Don't regress the C# dev experience x-plat for C#

This currently works only on Windows x64 and only with full VS, but everything is set up there to the point where you can drop a breakpoint in .cpp and hit it from the Test Explorer.

More work will be needed to match this experience for linux, mac, or even VS Code on Windows.
  • Loading branch information
nguerrera committed Dec 12, 2024
1 parent 202dcae commit b0dde11
Show file tree
Hide file tree
Showing 16 changed files with 592 additions and 16 deletions.
87 changes: 80 additions & 7 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@ root = true
charset = utf-8
indent_style = space
indent_size = 2

# .gitattributes is configured to make this work on Windows too, irrespective of git config
end_of_line = lf
insert_final_newline = true

# *WARNING*: If you use the Visual Studio Designer to edit this file, it may
# change this to 'crlf'. Revert it back to 'unset' or x-plat things will
# break.
end_of_line = unset

[*.sln]
end_of_line = crlf
charset = utf-8-bom

[*.lutconfig]
end_of_line = crlf

#### C# Coding Conventions ####
[*.cs]
indent_size = 4
Expand Down Expand Up @@ -171,6 +169,8 @@ dotnet_diagnostic.CA1303.severity = silent

# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = silent
# IDE0072: Add missing cases
dotnet_diagnostic.IDE0072.severity = silent

[*.{cs,vb}]
#### .NET Coding Conventions ####
Expand Down Expand Up @@ -424,3 +424,76 @@ dotnet_naming_style.s_camelcase.capitalization = camel_case

dotnet_style_allow_multiple_blank_lines_experimental = true:silent
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent

### C++ Coding Conventions ###
[*.{c,c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}]
indent_size = 4
cpp_generate_documentation_comments = xml
cpp_indent_braces = false
cpp_indent_multi_line_relative_to = statement_begin
cpp_indent_within_parentheses = indent
cpp_indent_preserve_within_parentheses = true
cpp_indent_case_contents = false
cpp_indent_case_labels = false
cpp_indent_case_contents_when_block = false
cpp_indent_lambda_braces_when_parameter = false
cpp_indent_goto_labels = none
cpp_indent_preprocessor = none
cpp_indent_access_specifiers = false
cpp_indent_namespace_contents = false
cpp_indent_preserve_comments = false
cpp_new_line_before_open_brace_namespace = ignore
cpp_new_line_before_open_brace_type = ignore
cpp_new_line_before_open_brace_function = ignore
cpp_new_line_before_open_brace_block = ignore
cpp_new_line_before_open_brace_lambda = ignore
cpp_new_line_scope_braces_on_separate_lines = false
cpp_new_line_close_brace_same_line_empty_type = true
cpp_new_line_close_brace_same_line_empty_function = true
cpp_new_line_before_catch = false
cpp_new_line_before_else = false
cpp_new_line_before_while_in_do_while = false
cpp_space_before_function_open_parenthesis = ignore
cpp_space_within_parameter_list_parentheses = false
cpp_space_between_empty_parameter_list_parentheses = false
cpp_space_after_keywords_in_control_flow_statements = true
cpp_space_within_control_flow_statement_parentheses = false
cpp_space_before_lambda_open_parenthesis = false
cpp_space_within_cast_parentheses = false
cpp_space_after_cast_close_parenthesis = false
cpp_space_within_expression_parentheses = false
cpp_space_before_block_open_brace = false
cpp_space_between_empty_braces = false
cpp_space_before_initializer_list_open_brace = false
cpp_space_within_initializer_list_braces = false
cpp_space_preserve_in_initializer_list = false
cpp_space_before_open_square_bracket = false
cpp_space_within_square_brackets = false
cpp_space_before_empty_square_brackets = false
cpp_space_between_empty_square_brackets = false
cpp_space_group_square_brackets = false
cpp_space_within_lambda_brackets = false
cpp_space_between_empty_lambda_brackets = false
cpp_space_before_comma = false
cpp_space_after_comma = true
cpp_space_remove_around_member_operators = false
cpp_space_before_inheritance_colon = false
cpp_space_before_constructor_colon = false
cpp_space_remove_before_semicolon = false
cpp_space_after_semicolon = false
cpp_space_remove_around_unary_operator = false
cpp_space_around_binary_operator = ignore
cpp_space_around_assignment_operator = ignore
cpp_space_pointer_reference_alignment = ignore
cpp_space_around_ternary_operator = ignore
cpp_use_unreal_engine_macro_formatting = false
cpp_wrap_preserve_blocks = never
cpp_include_cleanup_add_missing_error_tag_type = suggestion
cpp_include_cleanup_remove_unused_error_tag_type = dimmed
cpp_include_cleanup_optimize_unused_error_tag_type = suggestion
cpp_include_cleanup_sort_after_edits = false
cpp_sort_includes_error_tag_type = none
cpp_sort_includes_priority_case_sensitive = false
cpp_sort_includes_priority_style = quoted
cpp_includes_style = default
cpp_includes_use_forward_slash = false
8 changes: 2 additions & 6 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
# Use unix line endings always, even on Windows
* text=auto eol=lf

# Exceptions to above for files that VS saves with CRLF always
*.sln eol=crlf
*.lutconfig eol=crlf
# Normalize line endings to LF in repo, checkout CRLF on Windows
* text=auto

# Allow comments in JSON in GitHub rendering
*.json linguist-language=JSON-with-Comments
7 changes: 5 additions & 2 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ jobs:
run: dotnet restore src

- name: Check Formatting
run: dotnet format --verify-no-changes src

run: dotnet format --verify-no-changes src --verbosity diagnostic
- name: Build
run: dotnet build src -c ${{matrix.configuration}} --no-restore

- name: Build With C++ support
if: matrix.os == 'windows-latest'
run: msbuild src /p:Configuration=${{matrix.configuration}} /p:Platform=x64

- name: Test
run: dotnet test src -c ${{matrix.configuration}} --no-build
1 change: 0 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"editor.insertSpaces": true,
"editor.tabSize": 2,
"files.encoding": "utf8",
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.exclude": {
"bld/**": true,
Expand Down
6 changes: 6 additions & 0 deletions src/Cask.sln
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{0C3A2105-9369-461A-92AB-3D39CA120B83}"
ProjectSection(SolutionItems) = preProject
..\.editorconfig = ..\.editorconfig
..\.gitattributes = ..\.gitattributes
..\.gitignore = ..\.gitignore
Cask.lutconfig = Cask.lutconfig
Directory.Build.props = Directory.Build.props
Directory.Build.rsp = Directory.Build.rsp
Expand Down Expand Up @@ -51,6 +53,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflows", "Workflows", "{
..\.github\workflows\validate.yml = ..\.github\workflows\validate.yml
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcask", "libcask\libcask.vcxproj", "{14013CD3-B963-4851-AA9A-7C7A2F110A52}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -73,6 +77,8 @@ Global
{FB74046B-2FF6-4316-85B1-39A28D945A18}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB74046B-2FF6-4316-85B1-39A28D945A18}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB74046B-2FF6-4316-85B1-39A28D945A18}.Release|Any CPU.Build.0 = Release|Any CPU
{14013CD3-B963-4851-AA9A-7C7A2F110A52}.Debug|Any CPU.ActiveCfg = Debug|x64
{14013CD3-B963-4851-AA9A-7C7A2F110A52}.Release|Any CPU.ActiveCfg = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
21 changes: 21 additions & 0 deletions src/Tests/Cask.Tests/Cask.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net472</TargetFrameworks>
<OSIsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</OSIsWindows>
<OSIsX64 Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'X64'">true</OSIsX64>
<MSBuildIsNETFramework Condition="'$(MSBuildRuntimeType)' == 'full'">true</MSBuildIsNETFramework>
<BuildCpp Condition="'$(OSIsWindows)' == 'true' and '$(MSBuildIsNETFramework)' == 'true' and '$(OSIsX64)' == 'true'">true</BuildCpp>
<!-- TODO: Our custom C++ build logic is breaking fast-up-to-date for this project. -->
<DisableFastUpToDateCheck>True</DisableFastUpToDateCheck>
<EnableUnmanagedDebugging>true</EnableUnmanagedDebugging>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Cask\Cask.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(BuildCpp)' == 'true'">
<CppProjectReference Include="..\..\libcask\libcask.vcxproj" Properties="Platform=x64" />
<Content Include="$(ArtifactsPath)\bin\libcask\$(Configuration.ToLowerInvariant())_x64\libcask.dll" CopyToOutputDirectory="PreserveNewest" Visible="false" />
</ItemGroup>

<Target Name="BuildCppProjectReference" BeforeTargets="DispatchToInnerBuilds">
<MSBuild Projects="@(CppProjectReference)" />
</Target>

<Target Name="CleanCppProjectReference" BeforeTargets="Clean" Condition="'$(TargetFramework)' == ''">
<MSBuild Projects="@(CppProjectReference)" Targets="Clean" />
</Target>

</Project>
139 changes: 139 additions & 0 deletions src/Tests/Cask.Tests/CppCaskTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Runtime.InteropServices;
using System.Text;

using Xunit;


using static System.Runtime.InteropServices.UnmanagedType;

[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]

namespace CommonAnnotatedSecurityKeys.Tests;

// WIP: These tests are disabled because the C++ implementation is stubbed out.
// To enable them, flip the return value of IsSupportedTestClass in
// TestFilter.cs.


// CA2101: Specify marshaling for P/Invoke string arguments
// Supppressed due to false positives: https://github.com/dotnet/roslyn-analyzers/issues/7502
#pragma warning disable CA2101

// SYSLIB1054: Use 'LibraryImportAttribute' instead of 'DllImportAttribute' /o
// generate P/Invoke marshalling code at compile time This is very cool but
// would require additional work to switch between LibraryImport and DllImport
// based on target framework.
#pragma warning disable SYSLIB1054

public class CppCaskTests : CaskTestsBase
{
public CppCaskTests() : base(new Implementation())
{
}

private sealed class Implementation : ICask
{
public bool CompareHash(string candidateHash,
byte[] derivationInput,
string secret,
int secretEntropyInBytes = 32)
{
return NativeMethods.Cask_CompareHash(candidateHash, derivationInput, derivationInput.Length, secret, secretEntropyInBytes);
}

public string GenerateHash(byte[] derivationInput,
string secret,
int secretEntropyInBytes = 32)
{
int size = NativeMethods.Cask_GenerateHash(derivationInput, derivationInput.Length, secret, secretEntropyInBytes, null, 0);
byte[] bytes = new byte[size];
size = NativeMethods.Cask_GenerateHash(derivationInput, derivationInput.Length, secret, secretEntropyInBytes, bytes, size);
Assert.True(size == bytes.Length, "Cask_GenerateKey did not use as many bytes as it said it would.");
return Encoding.UTF8.GetString(bytes, 0, size - 1); // - 1 to remove null terminator
}

public string GenerateKey(string providerSignature,
string allocatorCode,
string? reserved = null,
int secretEntropyInBytes = 32)
{
int size = NativeMethods.Cask_GenerateKey(providerSignature, allocatorCode, reserved, secretEntropyInBytes, null, 0);
byte[] bytes = new byte[size];
size = NativeMethods.Cask_GenerateKey(providerSignature, allocatorCode, reserved, secretEntropyInBytes, bytes, size);
Assert.True(size == bytes.Length, "Cask_GenerateKey did not use as many bytes as it said it would.");
return Encoding.UTF8.GetString(bytes, 0, size - 1); // -1 to remove null terminator
}

public bool IsCask(string keyOrHash)
{
return NativeMethods.Cask_IsCask(keyOrHash);
}

public bool IsCaskBytes(byte[] bytes)
{
return NativeMethods.Cask_IsCaskBytes(bytes, bytes.Length);
}

Mock ICask.MockFillRandom(FillRandomAction fillRandom)
{
throw new NotImplementedException();
}

Mock ICask.MockUtcNow(UtcNowFunc getUtcNow)
{
throw new NotImplementedException();
}

private static class NativeMethods
{
[DllImport("libcask")]
[return: MarshalAs(I1)]
public static extern bool Cask_IsCask(
[MarshalAs(LPUTF8Str)] string keyOrHash);

[DllImport("libcask")]
[return: MarshalAs(I1)]
public static extern bool Cask_IsCaskBytes(
byte[] keyOrHash,
int length);

[DllImport("libcask")]
[return: MarshalAs(I1)]
public static extern bool Cask_CompareHash(
[MarshalAs(LPUTF8Str)]
string candidateHash,
byte[] derivationInput,
int derivationInputLength,
[MarshalAs(LPUTF8Str)] string secret,
int secretEntropyInBytes);

[DllImport("libcask")]
public static extern int Cask_GenerateKey(
[MarshalAs(LPUTF8Str)]
string providerSignature,
[MarshalAs(LPUTF8Str)]
string allocatorCode,
[MarshalAs(LPUTF8Str)]
string? providerData,
int secretEntropyInBytes,
byte[]? output,
int outputCapacity);

[DllImport("libcask")]
public static extern int Cask_GenerateHash(
byte[] derivationInput,
int derivationInputLength,
[MarshalAs(LPUTF8Str)]
string secret,
int secretEntropyInBytes,
byte[]? output,
int outputCapacity);
}
}
}

#pragma warning restore CA2101
#pragma warning restore SYSLIB1054
8 changes: 8 additions & 0 deletions src/Tests/Cask.Tests/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
"Cask.Tests": {
"commandName": "Project",
"nativeDebugging": true
}
}
}
Loading

0 comments on commit b0dde11

Please sign in to comment.