Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: DVC client example for devolutions-agent DVC server #129

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ obj/
.vscode/
.nupkg
dependencies/
package/
package/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "dotnet/now-proto"]
path = dotnet/now-proto
url = https://github.com/Devolutions/now-proto.git
1 change: 1 addition & 0 deletions dotnet/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
bin/
obj/
.vs/
*.csproj.user
Directory.Build.props
launchSettings.json
1 change: 1 addition & 0 deletions dotnet/AxInterop.MSTSCLib/AxInterop.MSTSCLib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<Nullable>disable</Nullable>
<GenerateAssemblyInfo>True</GenerateAssemblyInfo>
<OutputPath>$(CMakeOutputPath)</OutputPath>
<TargetFrameworks>net8.0-windows</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions dotnet/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,11 @@ include_external_msproject(Devolutions.MsRdpEx
include_external_msproject(MsRdpEx_App
"${CMAKE_CURRENT_SOURCE_DIR}/MsRdpEx_App/MsRdpEx_App.csproj")

include_external_msproject(NowProto
"${CMAKE_CURRENT_SOURCE_DIR}/now-proto/nugets/Devolutions.NowProto/Devolutions.NowProto.csproj")

include_external_msproject(NowClient
"${CMAKE_CURRENT_SOURCE_DIR}/now-proto/nugets/Devolutions.NowClient/Devolutions.NowClient.csproj")

configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Directory.Build.props.in"
"${CMAKE_CURRENT_SOURCE_DIR}/Directory.Build.props" @ONLY)
7 changes: 3 additions & 4 deletions dotnet/Devolutions.MsRdpEx/Devolutions.MsRdpEx.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>True</GenerateAssemblyInfo>
<SuppressDependenciesWhenPacking>True</SuppressDependenciesWhenPacking>
<TargetFrameworks>net8.0-windows</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
Expand All @@ -30,10 +31,8 @@
</ItemGroup>

<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(CMakeOutputPath)/MsRdpEx.dll" DestinationFolder="$(OutputPath)"
OverwriteReadOnlyFiles="true" Condition="Exists('$(CMakeOutputPath)/MsRdpEx.dll')" />
<Copy SourceFiles="$(CMakeOutputPath)/MsRdpEx.pdb" DestinationFolder="$(OutputPath)"
OverwriteReadOnlyFiles="true" Condition="Exists('$(CMakeOutputPath)/MsRdpEx.pdb')" />
<Copy SourceFiles="$(CMakeOutputPath)/MsRdpEx.dll" DestinationFolder="$(OutputPath)" OverwriteReadOnlyFiles="true" Condition="Exists('$(CMakeOutputPath)/MsRdpEx.dll')" />
<Copy SourceFiles="$(CMakeOutputPath)/MsRdpEx.pdb" DestinationFolder="$(OutputPath)" OverwriteReadOnlyFiles="true" Condition="Exists('$(CMakeOutputPath)/MsRdpEx.pdb')" />
</Target>

<ItemGroup>
Expand Down
192 changes: 192 additions & 0 deletions dotnet/MsRdpEx_App/DvcClientLib.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/// <summary>
/// Bindings for the native `dvc_client.dll` library (written in rust).
/// </summary>

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

namespace MsRdpEx_App
{
internal class DvcClientFfiException : Exception
{
public DvcClientFfiException(string message) : base(message) {}
}

internal class DvcClientError : Exception
{
public DvcClientError(string message) : base(message) { }
}

internal enum DvcClientResponseKind
{
UpdateUi,
SendMessage,
Error,
None,
}

internal class DvcClientResponse
{
public DvcClientResponse(IntPtr result)
{
self = result;

var kind_ptr = DvcClientLib.dvcc_response_get_kind(self);
if (kind_ptr != IntPtr.Zero)
{
var kind_str = Marshal.PtrToStringAnsi(kind_ptr);
switch (kind_str)
{
case "update_ui":
kind = DvcClientResponseKind.UpdateUi;
break;
case "send_message":
kind = DvcClientResponseKind.SendMessage;
break;
case "error":
kind = DvcClientResponseKind.Error;

var data = GetData();
var message = Encoding.UTF8.GetString(data);
throw new DvcClientError(message);
default:
throw new DvcClientFfiException("Unknown response kind");
}
}
else
{
kind = DvcClientResponseKind.None;
}
}

public DvcClientResponseKind Kind
{
get
{
return kind;
}
}

public string AsUiUpdate()
{
if (kind != DvcClientResponseKind.UpdateUi)
{
throw new DvcClientFfiException("Response is not an update_ui");
}

var data = GetData();
return Encoding.UTF8.GetString(data);
}

public IntPtr GetDataPtr()
{
if (kind == DvcClientResponseKind.None)
{
throw new DvcClientFfiException("Response has no data");
}

var data_ptr = DvcClientLib.dvcc_response_get_data(self);

if (data_ptr == IntPtr.Zero)
{
throw new DvcClientFfiException("Response data is NULL");
}

return data_ptr;
}

public uint GetDataLen()
{
var data_len = DvcClientLib.dvcc_response_get_data_len(self);

return (uint)data_len;
}

private byte[] GetData()
{
if (kind == DvcClientResponseKind.None)
{
throw new DvcClientFfiException("Response has no data");
}

var data_ptr = DvcClientLib.dvcc_response_get_data(self);
var data_len = DvcClientLib.dvcc_response_get_data_len(self);

if (data_ptr == IntPtr.Zero)
{
throw new DvcClientFfiException("Response data is NULL");
}

var data = new byte[data_len];
Marshal.Copy(data_ptr, data, 0, (int)data_len);
return data;
}

~DvcClientResponse()
{
DvcClientLib.dvcc_response_destroy(self);
}

private IntPtr self;
private DvcClientResponseKind kind;
}

internal class DvcClientCtx
{
public DvcClientCtx()
{
self = DvcClientLib.dvcc_init();
}

public DvcClientResponse HandleUi(string request)
{
// String to byte[]
var req = Encoding.UTF8.GetBytes(request);

var result = DvcClientLib.dvcc_handle_ui(self, req, req.Length);
return new DvcClientResponse(result);
}

public DvcClientResponse HandleData(byte[] data)
{
var result = DvcClientLib.dvcc_handle_data(self, data, data.Length);
return new DvcClientResponse(result);
}

~DvcClientCtx()
{
// TODO: IDisposable?
DvcClientLib.dvcc_destroy(self);
}

IntPtr self;
}

internal class DvcClientLib
{
[DllImport("dvc_client.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr dvcc_init();

[DllImport("dvc_client.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void dvcc_destroy(IntPtr ctx);

[DllImport("dvc_client.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr dvcc_handle_ui(IntPtr ctx, byte[] req, nint req_len);

[DllImport("dvc_client.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr dvcc_handle_data(IntPtr ctx, byte[] data, nint data_len);

[DllImport("dvc_client.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void dvcc_response_destroy(IntPtr result);

[DllImport("dvc_client.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr dvcc_response_get_kind(IntPtr result);

[DllImport("dvc_client.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr dvcc_response_get_data(IntPtr result);

[DllImport("dvc_client.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern nint dvcc_response_get_data_len(IntPtr result);
}
}
Loading