From 16ff2837ef760173d230f7ac98cceeb41bc23daf Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 21 Mar 2024 22:24:28 -0400 Subject: [PATCH 01/44] WIP: halfway through connectors --- Cargo.lock | 59 +++++++++ Cargo.toml | 1 + ffi/Cargo.toml | 18 +++ ffi/dotnet/ClientConnector.cs | 106 ++++++++++++++++ ffi/dotnet/ClientConnectorState.cs | 63 ++++++++++ ffi/dotnet/Config.cs | 63 ++++++++++ ffi/dotnet/DiplomatRuntime.cs | 172 ++++++++++++++++++++++++++ ffi/dotnet/RawClientConnector.cs | 30 +++++ ffi/dotnet/RawClientConnectorState.cs | 21 ++++ ffi/dotnet/RawConfig.cs | 21 ++++ ffi/dotnet/RawSocketAddr.cs | 21 ++++ ffi/dotnet/RawStaticChannelSet.cs | 21 ++++ ffi/dotnet/SocketAddr.cs | 63 ++++++++++ ffi/dotnet/StaticChannelSet.cs | 63 ++++++++++ ffi/src/connector.rs | 153 +++++++++++++++++++++++ ffi/src/dvc.rs | 8 ++ ffi/src/error.rs | 76 ++++++++++++ ffi/src/lib.rs | 5 + ffi/src/svc.rs | 7 ++ ffi/src/utils/mod.rs | 34 +++++ 20 files changed, 1005 insertions(+) create mode 100644 ffi/Cargo.toml create mode 100644 ffi/dotnet/ClientConnector.cs create mode 100644 ffi/dotnet/ClientConnectorState.cs create mode 100644 ffi/dotnet/Config.cs create mode 100644 ffi/dotnet/DiplomatRuntime.cs create mode 100644 ffi/dotnet/RawClientConnector.cs create mode 100644 ffi/dotnet/RawClientConnectorState.cs create mode 100644 ffi/dotnet/RawConfig.cs create mode 100644 ffi/dotnet/RawSocketAddr.cs create mode 100644 ffi/dotnet/RawStaticChannelSet.cs create mode 100644 ffi/dotnet/SocketAddr.cs create mode 100644 ffi/dotnet/StaticChannelSet.cs create mode 100644 ffi/src/connector.rs create mode 100644 ffi/src/dvc.rs create mode 100644 ffi/src/error.rs create mode 100644 ffi/src/lib.rs create mode 100644 ffi/src/svc.rs create mode 100644 ffi/src/utils/mod.rs diff --git a/Cargo.lock b/Cargo.lock index f01939893..adbd3a54d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -858,6 +858,39 @@ dependencies = [ "subtle", ] +[[package]] +name = "diplomat" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31672b3ebc3c7866c3c98726f7a9a5ac8f13962e77d3c8225f6be49a7b8c5f2" +dependencies = [ + "diplomat_core", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "diplomat-runtime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b0f23d549a46540e26e5490cd44c64ced0d762959f1ffdec6ab0399634cf3c" + +[[package]] +name = "diplomat_core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfaa5e13e8b8735d2338f2836c06cd8643902ab87dda1dd07dbb351998ddc127" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "serde", + "smallvec", + "strck_ident", + "syn 2.0.48", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -1073,6 +1106,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ffi" +version = "0.1.0" +dependencies = [ + "diplomat", + "diplomat-runtime", + "ironrdp", + "tracing", +] + [[package]] name = "fiat-crypto" version = "0.2.5" @@ -3758,6 +3801,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "strck" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be91090ded9d8f979d9fe921777342d37e769e0b6b7296843a7a38247240e917" + +[[package]] +name = "strck_ident" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c3802b169b3858a44667f221c9a0b3136e6019936ea926fc97fbad8af77202" +dependencies = [ + "strck", + "unicode-ident", +] + [[package]] name = "strict-num" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index e8938c6c4..27956328b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "crates/*", "xtask", + "ffi", ] resolver = "2" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml new file mode 100644 index 000000000..8e1cd28a6 --- /dev/null +++ b/ffi/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ffi" +version = "0.1.0" +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +authors.workspace = true +keywords.workspace = true +categories.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +diplomat = "0.7.0" +diplomat-runtime = "0.7.0" +ironrdp = {workspace = true, features = ["connector","dvc","svc"]} +tracing.workspace = true diff --git a/ffi/dotnet/ClientConnector.cs b/ffi/dotnet/ClientConnector.cs new file mode 100644 index 000000000..f63ea931c --- /dev/null +++ b/ffi/dotnet/ClientConnector.cs @@ -0,0 +1,106 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Interop.Diplomat; +#pragma warning restore 0105 + +namespace Interop; + +#nullable enable + +public partial class ClientConnector: IDisposable +{ + private unsafe Raw.ClientConnector* _inner; + + /// + /// Creates a managed ClientConnector from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ClientConnector(Raw.ClientConnector* handle) + { + _inner = handle; + } + + /// + /// A ClientConnector allocated on Rust side. + /// + public static ClientConnector New(Config config) + { + unsafe + { + Raw.Config* configRaw; + configRaw = config.AsFFI(); + if (configRaw == null) + { + throw new ObjectDisposedException("Config"); + } + Raw.ClientConnector* retVal = Raw.ClientConnector.New(configRaw); + return new ClientConnector(retVal); + } + } + + /// + /// Must use + /// + /// + /// A ClientConnector allocated on Rust side. + /// + public ClientConnector WithServerAddr(SocketAddr serverAddr) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.SocketAddr* serverAddrRaw; + serverAddrRaw = serverAddr.AsFFI(); + if (serverAddrRaw == null) + { + throw new ObjectDisposedException("SocketAddr"); + } + Raw.ClientConnector* retVal = Raw.ClientConnector.WithServerAddr(_inner, serverAddrRaw); + return new ClientConnector(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ClientConnector* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ClientConnector.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ClientConnector() + { + Dispose(); + } +} diff --git a/ffi/dotnet/ClientConnectorState.cs b/ffi/dotnet/ClientConnectorState.cs new file mode 100644 index 000000000..e683b8a56 --- /dev/null +++ b/ffi/dotnet/ClientConnectorState.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Interop.Diplomat; +#pragma warning restore 0105 + +namespace Interop; + +#nullable enable + +public partial class ClientConnectorState: IDisposable +{ + private unsafe Raw.ClientConnectorState* _inner; + + /// + /// Creates a managed ClientConnectorState from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ClientConnectorState(Raw.ClientConnectorState* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ClientConnectorState* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ClientConnectorState.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ClientConnectorState() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Config.cs b/ffi/dotnet/Config.cs new file mode 100644 index 000000000..7bfd13850 --- /dev/null +++ b/ffi/dotnet/Config.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Interop.Diplomat; +#pragma warning restore 0105 + +namespace Interop; + +#nullable enable + +public partial class Config: IDisposable +{ + private unsafe Raw.Config* _inner; + + /// + /// Creates a managed Config from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe Config(Raw.Config* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.Config* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.Config.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~Config() + { + Dispose(); + } +} diff --git a/ffi/dotnet/DiplomatRuntime.cs b/ffi/dotnet/DiplomatRuntime.cs new file mode 100644 index 000000000..059a9c571 --- /dev/null +++ b/ffi/dotnet/DiplomatRuntime.cs @@ -0,0 +1,172 @@ +// Automatically generated by Diplomat + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Interop.Diplomat; + +#nullable enable + +[UnmanagedFunctionPointer(CallingConvention.Cdecl)] +delegate void WriteableFlush(IntPtr self); + +[UnmanagedFunctionPointer(CallingConvention.Cdecl)] +[return: MarshalAs(UnmanagedType.U1)] +delegate bool WriteableGrow(IntPtr self, nuint capacity); + +[Serializable] +[StructLayout(LayoutKind.Sequential)] +public struct DiplomatWriteable : IDisposable +{ + // About the current approach: + // Ideally DiplomatWriteable should wrap the native string type and grows/writes directly into the internal buffer. + // However, there is no native string type backed by UTF-8 in dotnet frameworks. + // Alternative could be to provide Diplomat's own `Utf8String` type, but this is not trivial and mostly useless + // on its own because all other dotnet/C# APIs are expecting the standard UTF-16 encoded string/String type. + // API is expected to become overall less ergonomic by forcing users to explicitly convert between UTF-16 and UTF-8 anyway in such case. + // It could be useful for copy-free interactions between Diplomat-generated API though. + // Also, there is no way to re-use the unmanaged buffer as-is to get a managed byte[] + // (hence `ToUtf8Bytes` is copying the internal buffer from unmanaged to managed memory). + // It's encouraged to enable memoization when applicable (#129). + + IntPtr context; + IntPtr buf; + nuint len; + nuint cap; + readonly IntPtr flush; + readonly IntPtr grow; + + public DiplomatWriteable() + { + WriteableFlush flushFunc = Flush; + WriteableGrow growFunc = Grow; + + IntPtr flushFuncPtr = Marshal.GetFunctionPointerForDelegate(flushFunc); + IntPtr growFuncPtr = Marshal.GetFunctionPointerForDelegate(growFunc); + + // flushFunc and growFunc are managed objects and might be disposed of by the garbage collector. + // To prevent this, we make the context hold the references and protect the context itself + // for automatic disposal by moving it behind a GCHandle. + DiplomatWriteableContext ctx = new DiplomatWriteableContext(); + ctx.flushFunc = flushFunc; + ctx.growFunc = growFunc; + GCHandle ctxHandle = GCHandle.Alloc(ctx); + + context = GCHandle.ToIntPtr(ctxHandle); + buf = Marshal.AllocHGlobal(64); + len = 0; + cap = 64; + flush = flushFuncPtr; + grow = growFuncPtr; + } + + public byte[] ToUtf8Bytes() + { + if (len > int.MaxValue) + { + throw new IndexOutOfRangeException("DiplomatWriteable buffer is too big"); + } + byte[] managedArray = new byte[(int)len]; + Marshal.Copy(buf, managedArray, 0, (int)len); + return managedArray; + } + + public string ToUnicode() + { +#if NET6_0_OR_GREATER + if (len > int.MaxValue) + { + throw new IndexOutOfRangeException("DiplomatWriteable buffer is too big"); + } + return Marshal.PtrToStringUTF8(buf, (int) len); +#else + byte[] utf8 = ToUtf8Bytes(); + return DiplomatUtils.Utf8ToString(utf8); +#endif + } + + public void Dispose() + { + if (buf != IntPtr.Zero) + { + Marshal.FreeHGlobal(buf); + buf = IntPtr.Zero; + } + + if (context != IntPtr.Zero) + { + GCHandle.FromIntPtr(context).Free(); + context = IntPtr.Zero; + } + } + + private static void Flush(IntPtr self) + { + // Nothing to do + } + + [return: MarshalAs(UnmanagedType.U1)] + private unsafe static bool Grow(IntPtr writeable, nuint capacity) + { + if (writeable == IntPtr.Zero) + { + return false; + } + DiplomatWriteable* self = (DiplomatWriteable*)writeable; + + nuint newCap = capacity; + if (newCap > int.MaxValue) + { + return false; + } + + IntPtr newBuf; + try + { + newBuf = Marshal.AllocHGlobal((int)newCap); + } + catch (OutOfMemoryException) + { + return false; + } + + Buffer.MemoryCopy((void*)self->buf, (void*)newBuf, newCap, self->cap); + Marshal.FreeHGlobal(self->buf); + self->buf = newBuf; + self->cap = newCap; + + return true; + } +} + +internal struct DiplomatWriteableContext +{ + internal WriteableFlush flushFunc; + internal WriteableGrow growFunc; +} + +internal static class DiplomatUtils +{ + internal static byte[] StringToUtf8(string s) + { + int size = Encoding.UTF8.GetByteCount(s); + byte[] buf = new byte[size]; + Encoding.UTF8.GetBytes(s, 0, s.Length, buf, 0); + return buf; + } + + internal static string Utf8ToString(byte[] utf8) + { + char[] chars = new char[utf8.Length]; + Encoding.UTF8.GetChars(utf8, 0, utf8.Length, chars, 0); + return new string(chars); + } +} + +public class DiplomatOpaqueException : Exception +{ + public DiplomatOpaqueException() : base("The FFI function failed with an opaque error") { } +} diff --git a/ffi/dotnet/RawClientConnector.cs b/ffi/dotnet/RawClientConnector.cs new file mode 100644 index 000000000..6cb269a5a --- /dev/null +++ b/ffi/dotnet/RawClientConnector.cs @@ -0,0 +1,30 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Interop.Diplomat; +#pragma warning restore 0105 + +namespace Interop.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ClientConnector +{ + private const string NativeLib = "rust"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_new", ExactSpelling = true)] + public static unsafe extern ClientConnector* New(Config* config); + + /// + /// Must use + /// + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_with_server_addr", ExactSpelling = true)] + public static unsafe extern ClientConnector* WithServerAddr(ClientConnector* self, SocketAddr* serverAddr); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ClientConnector* self); +} diff --git a/ffi/dotnet/RawClientConnectorState.cs b/ffi/dotnet/RawClientConnectorState.cs new file mode 100644 index 000000000..ef7bb2702 --- /dev/null +++ b/ffi/dotnet/RawClientConnectorState.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Interop.Diplomat; +#pragma warning restore 0105 + +namespace Interop.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ClientConnectorState +{ + private const string NativeLib = "rust"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnectorState_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ClientConnectorState* self); +} diff --git a/ffi/dotnet/RawConfig.cs b/ffi/dotnet/RawConfig.cs new file mode 100644 index 000000000..3e9d934c9 --- /dev/null +++ b/ffi/dotnet/RawConfig.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Interop.Diplomat; +#pragma warning restore 0105 + +namespace Interop.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct Config +{ + private const string NativeLib = "rust"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Config_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(Config* self); +} diff --git a/ffi/dotnet/RawSocketAddr.cs b/ffi/dotnet/RawSocketAddr.cs new file mode 100644 index 000000000..3ddfcda06 --- /dev/null +++ b/ffi/dotnet/RawSocketAddr.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Interop.Diplomat; +#pragma warning restore 0105 + +namespace Interop.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct SocketAddr +{ + private const string NativeLib = "rust"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SocketAddr_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(SocketAddr* self); +} diff --git a/ffi/dotnet/RawStaticChannelSet.cs b/ffi/dotnet/RawStaticChannelSet.cs new file mode 100644 index 000000000..49f276e07 --- /dev/null +++ b/ffi/dotnet/RawStaticChannelSet.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Interop.Diplomat; +#pragma warning restore 0105 + +namespace Interop.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct StaticChannelSet +{ + private const string NativeLib = "rust"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "StaticChannelSet_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(StaticChannelSet* self); +} diff --git a/ffi/dotnet/SocketAddr.cs b/ffi/dotnet/SocketAddr.cs new file mode 100644 index 000000000..b765416d9 --- /dev/null +++ b/ffi/dotnet/SocketAddr.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Interop.Diplomat; +#pragma warning restore 0105 + +namespace Interop; + +#nullable enable + +public partial class SocketAddr: IDisposable +{ + private unsafe Raw.SocketAddr* _inner; + + /// + /// Creates a managed SocketAddr from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe SocketAddr(Raw.SocketAddr* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.SocketAddr* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.SocketAddr.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~SocketAddr() + { + Dispose(); + } +} diff --git a/ffi/dotnet/StaticChannelSet.cs b/ffi/dotnet/StaticChannelSet.cs new file mode 100644 index 000000000..0041ba7d2 --- /dev/null +++ b/ffi/dotnet/StaticChannelSet.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Interop.Diplomat; +#pragma warning restore 0105 + +namespace Interop; + +#nullable enable + +public partial class StaticChannelSet: IDisposable +{ + private unsafe Raw.StaticChannelSet* _inner; + + /// + /// Creates a managed StaticChannelSet from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe StaticChannelSet(Raw.StaticChannelSet* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.StaticChannelSet* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.StaticChannelSet.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~StaticChannelSet() + { + Dispose(); + } +} diff --git a/ffi/src/connector.rs b/ffi/src/connector.rs new file mode 100644 index 000000000..c58034dad --- /dev/null +++ b/ffi/src/connector.rs @@ -0,0 +1,153 @@ +macro_rules! take_if_not_none { + ($expr:expr) => {{ + if let Some(value) = $expr.take() { + value + } else { + panic!("Inner value is None") + } + }}; +} + +#[diplomat::bridge] +pub mod ffi { + use diplomat_runtime::DiplomatWriteable; + use ironrdp::connector::Sequence as _; + use std::fmt::Write; + + use crate::{ + error::ffi::IronRdpError, + utils::ffi::{SocketAddr, VecU8}, + }; + + #[diplomat::opaque] // We must use Option here, as ClientConnector is not Clone and have functions that consume it + pub struct ClientConnector(pub Option); + + #[diplomat::opaque] + pub struct Config(pub ironrdp::connector::Config); + + #[diplomat::opaque] + pub struct ClientConnectorState(pub ironrdp::connector::ClientConnectorState); + + // Basic Impl for ClientConnector + impl ClientConnector { + pub fn new(config: &Config) -> Box { + Box::new(ClientConnector(Some(ironrdp::connector::ClientConnector::new( + config.0.clone(), + )))) + } + + /// Must use + pub fn with_server_addr(&mut self, server_addr: &SocketAddr) { + let connector = take_if_not_none!(self.0); + let server_addr = server_addr.0.clone(); + self.0 = Some(connector.with_server_addr(server_addr)); + } + + // FIXME: We need to create opaque for ironrdp::svc::StaticChannelSet + /// Must use + pub fn with_static_channel_rdp_snd(&mut self) { + let connector = take_if_not_none!(self.0); + self.0 = Some(connector.with_static_channel(ironrdp::rdpsnd::Rdpsnd::new())); + } + + // FIXME: We need to create opaque for ironrdp::rdpdr::Rdpdr + /// Must use + pub fn with_static_channel_rdpdr(&mut self, computer_name: &str, smart_card_device_id: u32) { + let connector = take_if_not_none!(self.0); + self.0 = Some( + connector.with_static_channel( + ironrdp::rdpdr::Rdpdr::new( + Box::new(ironrdp::rdpdr::NoopRdpdrBackend {}), + computer_name.to_string(), + ) + .with_smartcard(smart_card_device_id), + ), + ); + } + + pub fn should_perform_security_upgrade(&self) -> bool { + let Some(connector) = self.0.as_ref() else { + panic!("Inner value is None") + }; + connector.should_perform_security_upgrade() + } + + pub fn mark_security_upgrade_as_done(&mut self) { + let Some(connector) = self.0.as_mut() else { + panic!("Inner value is None") + }; + connector.mark_security_upgrade_as_done(); + } + + pub fn should_perform_credssp(&self) -> bool { + let Some(connector) = self.0.as_ref() else { + panic!("Inner value is None") + }; + connector.should_perform_credssp() + } + + pub fn mark_credssp_as_done(&mut self) { + let Some(connector) = self.0.as_mut() else { + panic!("Inner value is None") + }; + connector.mark_credssp_as_done(); + } + } + + #[diplomat::opaque] + pub struct PduHintResult<'a>(pub Option<&'a dyn ironrdp::pdu::PduHint>); + + impl<'a> PduHintResult<'a> { + pub fn is_some(&'a self) -> bool { + self.0.is_some() + } + + pub fn find_size(&'a self, buffer: &VecU8) -> Result, Box> { + let Some(pdu_hint) = self.0 else { + return Ok(None); + }; + + let size = pdu_hint.find_size(buffer.0.as_slice())?; + + Ok(size) + } + } + + #[diplomat::opaque] + pub struct State<'a>(pub &'a dyn ironrdp::connector::State); + + impl<'a> State<'a> { + pub fn get_name(&'a self,writeable:&'a mut DiplomatWriteable) -> Result<(), Box>{ + let name = self.0.name(); + write!(writeable, "{}", name)?; + Ok(()) + } + + pub fn is_terminal(&'a self) -> bool { + self.0.is_terminal() + } + + pub fn as_any(&'a self) -> Box { + Box::new(crate::utils::ffi::Any(self.0.as_any())) + } + + } + + + impl ClientConnector { + pub fn next_pdu_hint(&self) -> Box { + let Some(connector) = self.0.as_ref() else { + panic!("Inner value is None") + }; + Box::new(PduHintResult(connector.next_pdu_hint())) + } + + pub fn state(&self) -> Box { + let Some(connector) = self.0.as_ref() else { + panic!("Inner value is None") + }; + Box::new(State(connector.state())) + } + } + +} diff --git a/ffi/src/dvc.rs b/ffi/src/dvc.rs new file mode 100644 index 000000000..58e01dc50 --- /dev/null +++ b/ffi/src/dvc.rs @@ -0,0 +1,8 @@ + + +#[diplomat::bridge] +pub mod ffi { + + #[diplomat::opaque] + pub struct DrdynvcChannel(pub ironrdp::dvc::DrdynvcClient); +} \ No newline at end of file diff --git a/ffi/src/error.rs b/ffi/src/error.rs new file mode 100644 index 000000000..801f50423 --- /dev/null +++ b/ffi/src/error.rs @@ -0,0 +1,76 @@ +use std::fmt; + +impl Into for ironrdp::pdu::PduError { + fn into(self) -> ffi::IronRdpErrorKind { + match self { + _ => ffi::IronRdpErrorKind::PduError, + } + } +} + +impl Into for std::io::Error { + fn into(self) -> ffi::IronRdpErrorKind { + match self.kind() { + _ => ffi::IronRdpErrorKind::IO, + } + } +} + +impl Into for std::fmt::Error { + fn into(self) -> ffi::IronRdpErrorKind { + ffi::IronRdpErrorKind::Generic + } +} + +impl From for Box +where + T: Into + ToString, +{ + fn from(value: T) -> Self { + let repr = value.to_string(); + let kind = value.into(); + Box::new(ffi::IronRdpError(IronRdpErrorInner { repr, kind })) + } +} + +struct IronRdpErrorInner { + pub repr: String, + pub kind: ffi::IronRdpErrorKind, +} + +#[diplomat::bridge] +pub mod ffi { + use diplomat_runtime::DiplomatWriteable; + use std::fmt::Write as _; + + /// Kind associated to a Picky Error + #[derive(Clone, Copy)] + pub enum IronRdpErrorKind { + /// Generic Picky error + Generic, + PduError, + IO, + } + + /// Stringified Picky error along with an error kind. + #[diplomat::opaque] + pub struct IronRdpError(pub(super) super::IronRdpErrorInner); + + impl IronRdpError { + /// Returns the error as a string. + pub fn to_display(&self, writeable: &mut DiplomatWriteable) { + let _ = write!(writeable, "{}", self.0.repr); + writeable.flush(); + } + + /// Prints the error string. + pub fn print(&self) { + println!("{}", self.0.repr); + } + + /// Returns the error kind. + pub fn get_kind(&self) -> IronRdpErrorKind { + self.0.kind + } + } +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs new file mode 100644 index 000000000..619c58bbd --- /dev/null +++ b/ffi/src/lib.rs @@ -0,0 +1,5 @@ +pub mod utils; +pub mod connector; +pub mod svc; +pub mod dvc; +pub mod error; \ No newline at end of file diff --git a/ffi/src/svc.rs b/ffi/src/svc.rs new file mode 100644 index 000000000..b100ea17a --- /dev/null +++ b/ffi/src/svc.rs @@ -0,0 +1,7 @@ + +#[diplomat::bridge] +pub mod ffi { + + #[diplomat::opaque] + pub struct StaticChannelSet(pub ironrdp::svc::StaticChannelSet); +} \ No newline at end of file diff --git a/ffi/src/utils/mod.rs b/ffi/src/utils/mod.rs new file mode 100644 index 000000000..98fdabc20 --- /dev/null +++ b/ffi/src/utils/mod.rs @@ -0,0 +1,34 @@ + +#[diplomat::bridge] +pub mod ffi { + + #[diplomat::opaque] + pub struct SocketAddr(pub std::net::SocketAddr); + + #[diplomat::opaque] + pub struct VecU8(pub Vec); + + impl VecU8 { + pub fn from_byte(bytes:&[u8]) -> Box { + Box::new(VecU8(bytes.to_vec())) + } + + + pub fn get_size(&self) -> usize { + self.0.len() + } + + + pub fn fill(&self, buffer: &mut [u8]) { + if buffer.len() < self.0.len() { + //TODO: FIX: Should not panic, for prototype only + panic!("Buffer is too small") + } + buffer.copy_from_slice(&self.0) + } + } + + #[diplomat::opaque] + pub struct Any<'a>(pub &'a dyn std::any::Any); + +} \ No newline at end of file From 55ebdf421af564d5fdee86406b3435b8261f87d4 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 25 Mar 2024 13:44:54 -0400 Subject: [PATCH 02/44] WIP: connector finished, untested --- Cargo.lock | 19 +- ffi/Cargo.toml | 10 +- ffi/dotnet-interop-conf.toml | 10 + ffi/dotnet/ClientConnector.cs | 106 -------- ffi/dotnet/Devolutions.IronRdp/.gitignore | 2 + ffi/dotnet/Devolutions.IronRdp/Class1.cs | 6 + .../Devolutions.IronRdp.csproj | 9 + .../Devolutions.IronRdp/Generated/Any.cs | 63 +++++ .../Generated/BlockingTcpFrame.cs | 109 ++++++++ .../Generated/BlockingUpgradedFrame.cs | 87 ++++++ .../Generated/ClientConnector.cs | 256 ++++++++++++++++++ .../Generated}/ClientConnectorState.cs | 4 +- .../Generated}/Config.cs | 4 +- .../Generated/ConnectionResult.cs | 223 +++++++++++++++ .../Generated/DesktopSize.cs | 105 +++++++ .../Generated}/DiplomatRuntime.cs | 2 +- .../Generated/DrdynvcChannel.cs | 63 +++++ .../Generated/GraphicsConfig.cs | 168 ++++++++++++ .../Generated/IronRdpBlocking.cs | 220 +++++++++++++++ .../Generated/IronRdpError.cs | 142 ++++++++++ .../Generated/IronRdpErrorKind.cs | 21 ++ .../Generated/IronRdpException.cs | 30 ++ .../Generated/KerberosConfig.cs | 63 +++++ .../Generated/PduHintResult.cs | 105 +++++++ .../Devolutions.IronRdp/Generated/RawAny.cs | 21 ++ .../Generated/RawBlockingTcpFrame.cs | 27 ++ .../Generated/RawBlockingUpgradedFrame.cs | 24 ++ .../Generated/RawClientConnector.cs | 60 ++++ .../Generated}/RawClientConnectorState.cs | 6 +- .../Generated}/RawConfig.cs | 6 +- .../Generated/RawConnectionResult.cs | 44 +++ ...awConnectorFfiResultBoolBoxIronRdpError.cs | 46 ++++ ...ctorFfiResultOptBoxUsizeBoxIronRdpError.cs | 46 ++++ ...awConnectorFfiResultVoidBoxIronRdpError.cs | 36 +++ .../Generated/RawDesktopSize.cs | 27 ++ .../Generated/RawDrdynvcChannel.cs | 21 ++ .../Generated/RawGraphicsConfig.cs | 40 +++ .../Generated/RawIronRdpBlocking.cs | 36 +++ .../Generated/RawIronRdpError.cs | 42 +++ .../Generated/RawIronRdpErrorKind.cs | 21 ++ ...esultBoxBlockingTcpFrameBoxIronRdpError.cs | 46 ++++ ...BoxBlockingUpgradedFrameBoxIronRdpError.cs | 46 ++++ ...esultBoxConnectionResultBoxIronRdpError.cs | 46 ++++ ...fiResultBoxShouldUpgradeBoxIronRdpError.cs | 46 ++++ ...FfiResultBoxStdTcpStreamBoxIronRdpError.cs | 46 ++++ ...kingFfiResultBoxUpgradedBoxIronRdpError.cs | 46 ++++ .../Generated/RawKerberosConfig.cs | 21 ++ .../Generated/RawPduHintResult.cs | 28 ++ .../Generated/RawServerName.cs | 24 ++ .../Generated/RawShouldUpgrade.cs | 21 ++ .../Generated}/RawSocketAddr.cs | 6 +- .../Devolutions.IronRdp/Generated/RawState.cs | 31 +++ .../Generated}/RawStaticChannelSet.cs | 6 +- .../Generated/RawStdTcpStream.cs | 21 ++ .../Devolutions.IronRdp/Generated/RawTls.cs | 24 ++ ...esultBoxTlsUpgradeResultBoxIronRdpError.cs | 46 ++++ ...iResultBoxUpgradedStreamBoxIronRdpError.cs | 46 ++++ .../Generated/RawTlsUpgradeResult.cs | 27 ++ .../Generated/RawUpgraded.cs | 21 ++ .../Generated/RawUpgradedStream.cs | 21 ++ .../Devolutions.IronRdp/Generated/RawVecU8.cs | 30 ++ .../Generated/ServerName.cs | 80 ++++++ .../Generated/ShouldUpgrade.cs | 63 +++++ .../Generated}/SocketAddr.cs | 4 +- .../Devolutions.IronRdp/Generated/State.cs | 138 ++++++++++ .../Generated}/StaticChannelSet.cs | 4 +- .../Generated/StdTcpStream.cs | 63 +++++ .../Devolutions.IronRdp/Generated/Tls.cs | 92 +++++++ .../Generated/TlsUpgradeResult.cs | 117 ++++++++ .../Devolutions.IronRdp/Generated/Upgraded.cs | 63 +++++ .../Generated/UpgradedStream.cs | 63 +++++ .../Devolutions.IronRdp/Generated/VecU8.cs | 116 ++++++++ ffi/dotnet/RawClientConnector.cs | 30 -- ffi/justfile | 10 + ffi/src/{connector.rs => connector/mod.rs} | 101 ++++--- ffi/src/connector/result.rs | 64 +++++ ffi/src/credssp.rs | 7 + ffi/src/error.rs | 68 ++++- ffi/src/ironrdp_blocking.rs | 176 ++++++++++++ ffi/src/lib.rs | 6 +- ffi/src/pdu.rs | 0 ffi/src/svc.rs | 2 +- ffi/src/tls.rs | 145 ++++++++++ ffi/src/utils/mod.rs | 3 + 84 files changed, 4187 insertions(+), 207 deletions(-) create mode 100644 ffi/dotnet-interop-conf.toml delete mode 100644 ffi/dotnet/ClientConnector.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/.gitignore create mode 100644 ffi/dotnet/Devolutions.IronRdp/Class1.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Devolutions.IronRdp.csproj create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/Any.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/BlockingTcpFrame.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/BlockingUpgradedFrame.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs rename ffi/dotnet/{ => Devolutions.IronRdp/Generated}/ClientConnectorState.cs (95%) rename ffi/dotnet/{ => Devolutions.IronRdp/Generated}/Config.cs (95%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/ConnectionResult.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/DesktopSize.cs rename ffi/dotnet/{ => Devolutions.IronRdp/Generated}/DiplomatRuntime.cs (99%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/DrdynvcChannel.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/GraphicsConfig.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpBlocking.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpException.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/KerberosConfig.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/PduHintResult.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawAny.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingTcpFrame.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingUpgradedFrame.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs rename ffi/dotnet/{ => Devolutions.IronRdp/Generated}/RawClientConnectorState.cs (78%) rename ffi/dotnet/{ => Devolutions.IronRdp/Generated}/RawConfig.cs (76%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectionResult.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoolBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxUsizeBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultVoidBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawDesktopSize.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawDrdynvcChannel.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawGraphicsConfig.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpBlocking.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawKerberosConfig.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHintResult.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawServerName.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawShouldUpgrade.cs rename ffi/dotnet/{ => Devolutions.IronRdp/Generated}/RawSocketAddr.cs (76%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawState.cs rename ffi/dotnet/{ => Devolutions.IronRdp/Generated}/RawStaticChannelSet.cs (77%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawStdTcpStream.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawTls.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxTlsUpgradeResultBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxUpgradedStreamBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsUpgradeResult.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgraded.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgradedStream.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/ServerName.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/ShouldUpgrade.cs rename ffi/dotnet/{ => Devolutions.IronRdp/Generated}/SocketAddr.cs (95%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/State.cs rename ffi/dotnet/{ => Devolutions.IronRdp/Generated}/StaticChannelSet.cs (95%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/StdTcpStream.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/Tls.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/TlsUpgradeResult.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/Upgraded.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/UpgradedStream.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs delete mode 100644 ffi/dotnet/RawClientConnector.cs create mode 100644 ffi/justfile rename ffi/src/{connector.rs => connector/mod.rs} (55%) create mode 100644 ffi/src/connector/result.rs create mode 100644 ffi/src/credssp.rs create mode 100644 ffi/src/ironrdp_blocking.rs create mode 100644 ffi/src/pdu.rs create mode 100644 ffi/src/tls.rs diff --git a/Cargo.lock b/Cargo.lock index adbd3a54d..f5f420329 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "arbitrary" @@ -722,9 +722,9 @@ dependencies = [ [[package]] name = "crypto-mac" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" dependencies = [ "generic-array", "subtle", @@ -1110,10 +1110,17 @@ dependencies = [ name = "ffi" version = "0.1.0" dependencies = [ + "anyhow", "diplomat", "diplomat-runtime", "ironrdp", + "ironrdp-blocking", + "rustls", + "rustls-pemfile 2.1.0", + "sspi", + "thiserror", "tracing", + "x509-cert", ] [[package]] @@ -3831,9 +3838,9 @@ checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 8e1cd28a6..fc959717b 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -10,9 +10,15 @@ keywords.workspace = true categories.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] +anyhow = "1.0.81" diplomat = "0.7.0" diplomat-runtime = "0.7.0" -ironrdp = {workspace = true, features = ["connector","dvc","svc"]} +ironrdp = { workspace = true, features = ["connector", "dvc", "svc","rdpdr","rdpsnd"] } +ironrdp-blocking = { path = "../crates/ironrdp-blocking" } +rustls = "0.21" +x509-cert = { version = "0.2", default-features = false, features = ["std"] } +rustls-pemfile = "2.1" +sspi = { workspace = true, features = ["network_client"] } +thiserror.workspace = true tracing.workspace = true diff --git a/ffi/dotnet-interop-conf.toml b/ffi/dotnet-interop-conf.toml new file mode 100644 index 000000000..e2c76a3af --- /dev/null +++ b/ffi/dotnet-interop-conf.toml @@ -0,0 +1,10 @@ +namespace = "Devolutions.IronRdp" +native_lib = "DevolutionsIronRdp" + +[exceptions] +trim_suffix = "Error" +error_message_method = "ToDisplay" + +[properties] +setters_prefix = "set_" +getters_prefix = "get_" diff --git a/ffi/dotnet/ClientConnector.cs b/ffi/dotnet/ClientConnector.cs deleted file mode 100644 index f63ea931c..000000000 --- a/ffi/dotnet/ClientConnector.cs +++ /dev/null @@ -1,106 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Interop.Diplomat; -#pragma warning restore 0105 - -namespace Interop; - -#nullable enable - -public partial class ClientConnector: IDisposable -{ - private unsafe Raw.ClientConnector* _inner; - - /// - /// Creates a managed ClientConnector from a raw handle. - /// - /// - /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). - ///
- /// This constructor assumes the raw struct is allocated on Rust side. - /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. - ///
- public unsafe ClientConnector(Raw.ClientConnector* handle) - { - _inner = handle; - } - - /// - /// A ClientConnector allocated on Rust side. - /// - public static ClientConnector New(Config config) - { - unsafe - { - Raw.Config* configRaw; - configRaw = config.AsFFI(); - if (configRaw == null) - { - throw new ObjectDisposedException("Config"); - } - Raw.ClientConnector* retVal = Raw.ClientConnector.New(configRaw); - return new ClientConnector(retVal); - } - } - - /// - /// Must use - /// - /// - /// A ClientConnector allocated on Rust side. - /// - public ClientConnector WithServerAddr(SocketAddr serverAddr) - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("ClientConnector"); - } - Raw.SocketAddr* serverAddrRaw; - serverAddrRaw = serverAddr.AsFFI(); - if (serverAddrRaw == null) - { - throw new ObjectDisposedException("SocketAddr"); - } - Raw.ClientConnector* retVal = Raw.ClientConnector.WithServerAddr(_inner, serverAddrRaw); - return new ClientConnector(retVal); - } - } - - /// - /// Returns the underlying raw handle. - /// - public unsafe Raw.ClientConnector* AsFFI() - { - return _inner; - } - - /// - /// Destroys the underlying object immediately. - /// - public void Dispose() - { - unsafe - { - if (_inner == null) - { - return; - } - - Raw.ClientConnector.Destroy(_inner); - _inner = null; - - GC.SuppressFinalize(this); - } - } - - ~ClientConnector() - { - Dispose(); - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/.gitignore b/ffi/dotnet/Devolutions.IronRdp/.gitignore new file mode 100644 index 000000000..2e9693ed1 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/.gitignore @@ -0,0 +1,2 @@ +obj +bin \ No newline at end of file diff --git a/ffi/dotnet/Devolutions.IronRdp/Class1.cs b/ffi/dotnet/Devolutions.IronRdp/Class1.cs new file mode 100644 index 000000000..8f4ebe3c9 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Class1.cs @@ -0,0 +1,6 @@ +namespace Devolutions.IronRdp; + +public class Class1 +{ + +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Devolutions.IronRdp.csproj b/ffi/dotnet/Devolutions.IronRdp/Devolutions.IronRdp.csproj new file mode 100644 index 000000000..fa71b7ae6 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Devolutions.IronRdp.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/Any.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/Any.cs new file mode 100644 index 000000000..7c5c27863 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/Any.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class Any: IDisposable +{ + private unsafe Raw.Any* _inner; + + /// + /// Creates a managed Any from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe Any(Raw.Any* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.Any* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.Any.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~Any() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/BlockingTcpFrame.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/BlockingTcpFrame.cs new file mode 100644 index 000000000..28f1abaf3 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/BlockingTcpFrame.cs @@ -0,0 +1,109 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class BlockingTcpFrame: IDisposable +{ + private unsafe Raw.BlockingTcpFrame* _inner; + + /// + /// Creates a managed BlockingTcpFrame from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe BlockingTcpFrame(Raw.BlockingTcpFrame* handle) + { + _inner = handle; + } + + /// + /// + /// A BlockingTcpFrame allocated on Rust side. + /// + public static BlockingTcpFrame FromTcpStream(StdTcpStream stream) + { + unsafe + { + Raw.StdTcpStream* streamRaw; + streamRaw = stream.AsFFI(); + if (streamRaw == null) + { + throw new ObjectDisposedException("StdTcpStream"); + } + Raw.IronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError result = Raw.BlockingTcpFrame.FromTcpStream(streamRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.BlockingTcpFrame* retVal = result.Ok; + return new BlockingTcpFrame(retVal); + } + } + + /// + /// + /// A StdTcpStream allocated on Rust side. + /// + public StdTcpStream IntoTcpSteamNoLeftover() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("BlockingTcpFrame"); + } + Raw.IronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError result = Raw.BlockingTcpFrame.IntoTcpSteamNoLeftover(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.StdTcpStream* retVal = result.Ok; + return new StdTcpStream(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.BlockingTcpFrame* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.BlockingTcpFrame.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~BlockingTcpFrame() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/BlockingUpgradedFrame.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/BlockingUpgradedFrame.cs new file mode 100644 index 000000000..a4de2ed09 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/BlockingUpgradedFrame.cs @@ -0,0 +1,87 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class BlockingUpgradedFrame: IDisposable +{ + private unsafe Raw.BlockingUpgradedFrame* _inner; + + /// + /// Creates a managed BlockingUpgradedFrame from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe BlockingUpgradedFrame(Raw.BlockingUpgradedFrame* handle) + { + _inner = handle; + } + + /// + /// + /// A BlockingUpgradedFrame allocated on Rust side. + /// + public static BlockingUpgradedFrame FromUpgradedStream(UpgradedStream stream) + { + unsafe + { + Raw.UpgradedStream* streamRaw; + streamRaw = stream.AsFFI(); + if (streamRaw == null) + { + throw new ObjectDisposedException("UpgradedStream"); + } + Raw.IronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError result = Raw.BlockingUpgradedFrame.FromUpgradedStream(streamRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.BlockingUpgradedFrame* retVal = result.Ok; + return new BlockingUpgradedFrame(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.BlockingUpgradedFrame* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.BlockingUpgradedFrame.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~BlockingUpgradedFrame() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs new file mode 100644 index 000000000..7ee01a935 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs @@ -0,0 +1,256 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class ClientConnector: IDisposable +{ + private unsafe Raw.ClientConnector* _inner; + + /// + /// Creates a managed ClientConnector from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ClientConnector(Raw.ClientConnector* handle) + { + _inner = handle; + } + + /// + /// A ClientConnector allocated on Rust side. + /// + public static ClientConnector New(Config config) + { + unsafe + { + Raw.Config* configRaw; + configRaw = config.AsFFI(); + if (configRaw == null) + { + throw new ObjectDisposedException("Config"); + } + Raw.ClientConnector* retVal = Raw.ClientConnector.New(configRaw); + return new ClientConnector(retVal); + } + } + + /// + /// Must use + /// + /// + public void WithServerAddr(SocketAddr serverAddr) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.SocketAddr* serverAddrRaw; + serverAddrRaw = serverAddr.AsFFI(); + if (serverAddrRaw == null) + { + throw new ObjectDisposedException("SocketAddr"); + } + Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.ClientConnector.WithServerAddr(_inner, serverAddrRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + } + } + + /// + /// Must use + /// + /// + public void WithStaticChannelRdpSnd() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.ClientConnector.WithStaticChannelRdpSnd(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + } + } + + /// + /// Must use + /// + /// + public void WithStaticChannelRdpdr(string computerName, uint smartCardDeviceId) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + byte[] computerNameBuf = DiplomatUtils.StringToUtf8(computerName); + nuint computerNameBufLength = (nuint)computerNameBuf.Length; + fixed (byte* computerNameBufPtr = computerNameBuf) + { + Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.ClientConnector.WithStaticChannelRdpdr(_inner, computerNameBufPtr, computerNameBufLength, smartCardDeviceId); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + } + } + } + + /// + public bool ShouldPerformSecurityUpgrade() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.ConnectorFfiResultBoolBoxIronRdpError result = Raw.ClientConnector.ShouldPerformSecurityUpgrade(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + bool retVal = result.Ok; + return retVal; + } + } + + /// + public void MarkSecurityUpgradeAsDone() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.ClientConnector.MarkSecurityUpgradeAsDone(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + } + } + + /// + public bool ShouldPerformCredssp() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.ConnectorFfiResultBoolBoxIronRdpError result = Raw.ClientConnector.ShouldPerformCredssp(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + bool retVal = result.Ok; + return retVal; + } + } + + /// + public void MarkCredsspAsDone() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.ClientConnector.MarkCredsspAsDone(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + } + } + + /// + /// A PduHintResult allocated on Rust side. + /// + public PduHintResult NextPduHint() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.PduHintResult* retVal = Raw.ClientConnector.NextPduHint(_inner); + return new PduHintResult(retVal); + } + } + + /// + /// A State allocated on Rust side. + /// + public State State() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.State* retVal = Raw.ClientConnector.State(_inner); + return new State(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ClientConnector* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ClientConnector.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ClientConnector() + { + Dispose(); + } +} diff --git a/ffi/dotnet/ClientConnectorState.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnectorState.cs similarity index 95% rename from ffi/dotnet/ClientConnectorState.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnectorState.cs index e683b8a56..127bc83cb 100644 --- a/ffi/dotnet/ClientConnectorState.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnectorState.cs @@ -4,10 +4,10 @@ using System; using System.Runtime.InteropServices; -using Interop.Diplomat; +using Devolutions.IronRdp.Diplomat; #pragma warning restore 0105 -namespace Interop; +namespace Devolutions.IronRdp; #nullable enable diff --git a/ffi/dotnet/Config.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/Config.cs similarity index 95% rename from ffi/dotnet/Config.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/Config.cs index 7bfd13850..75b53cf6e 100644 --- a/ffi/dotnet/Config.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/Config.cs @@ -4,10 +4,10 @@ using System; using System.Runtime.InteropServices; -using Interop.Diplomat; +using Devolutions.IronRdp.Diplomat; #pragma warning restore 0105 -namespace Interop; +namespace Devolutions.IronRdp; #nullable enable diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ConnectionResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ConnectionResult.cs new file mode 100644 index 000000000..abafd7bef --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ConnectionResult.cs @@ -0,0 +1,223 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class ConnectionResult: IDisposable +{ + private unsafe Raw.ConnectionResult* _inner; + + public DesktopSize DesktopSize + { + get + { + return GetDesktopSize(); + } + } + + public GraphicsConfig? GraphicsConfig + { + get + { + return GetGraphicsConfig(); + } + } + + public ushort IoChannelId + { + get + { + return GetIoChannelId(); + } + } + + public bool NoServerPointer + { + get + { + return GetNoServerPointer(); + } + } + + public bool PointerSoftwareRendering + { + get + { + return GetPointerSoftwareRendering(); + } + } + + public StaticChannelSet StaticChannels + { + get + { + return GetStaticChannels(); + } + } + + public ushort UserChannelId + { + get + { + return GetUserChannelId(); + } + } + + /// + /// Creates a managed ConnectionResult from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ConnectionResult(Raw.ConnectionResult* handle) + { + _inner = handle; + } + + public ushort GetIoChannelId() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConnectionResult"); + } + ushort retVal = Raw.ConnectionResult.GetIoChannelId(_inner); + return retVal; + } + } + + public ushort GetUserChannelId() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConnectionResult"); + } + ushort retVal = Raw.ConnectionResult.GetUserChannelId(_inner); + return retVal; + } + } + + /// + /// A StaticChannelSet allocated on Rust side. + /// + public StaticChannelSet GetStaticChannels() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConnectionResult"); + } + Raw.StaticChannelSet* retVal = Raw.ConnectionResult.GetStaticChannels(_inner); + return new StaticChannelSet(retVal); + } + } + + /// + /// A DesktopSize allocated on Rust side. + /// + public DesktopSize GetDesktopSize() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConnectionResult"); + } + Raw.DesktopSize* retVal = Raw.ConnectionResult.GetDesktopSize(_inner); + return new DesktopSize(retVal); + } + } + + public bool GetNoServerPointer() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConnectionResult"); + } + bool retVal = Raw.ConnectionResult.GetNoServerPointer(_inner); + return retVal; + } + } + + public bool GetPointerSoftwareRendering() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConnectionResult"); + } + bool retVal = Raw.ConnectionResult.GetPointerSoftwareRendering(_inner); + return retVal; + } + } + + /// + /// A GraphicsConfig allocated on Rust side. + /// + public GraphicsConfig? GetGraphicsConfig() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConnectionResult"); + } + Raw.GraphicsConfig* retVal = Raw.ConnectionResult.GetGraphicsConfig(_inner); + if (retVal == null) + { + return null; + } + return new GraphicsConfig(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ConnectionResult* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ConnectionResult.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ConnectionResult() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/DesktopSize.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/DesktopSize.cs new file mode 100644 index 000000000..ecdb02070 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/DesktopSize.cs @@ -0,0 +1,105 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class DesktopSize: IDisposable +{ + private unsafe Raw.DesktopSize* _inner; + + public ushort Height + { + get + { + return GetHeight(); + } + } + + public ushort Width + { + get + { + return GetWidth(); + } + } + + /// + /// Creates a managed DesktopSize from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe DesktopSize(Raw.DesktopSize* handle) + { + _inner = handle; + } + + public ushort GetWidth() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("DesktopSize"); + } + ushort retVal = Raw.DesktopSize.GetWidth(_inner); + return retVal; + } + } + + public ushort GetHeight() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("DesktopSize"); + } + ushort retVal = Raw.DesktopSize.GetHeight(_inner); + return retVal; + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.DesktopSize* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.DesktopSize.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~DesktopSize() + { + Dispose(); + } +} diff --git a/ffi/dotnet/DiplomatRuntime.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/DiplomatRuntime.cs similarity index 99% rename from ffi/dotnet/DiplomatRuntime.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/DiplomatRuntime.cs index 059a9c571..18223e37b 100644 --- a/ffi/dotnet/DiplomatRuntime.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/DiplomatRuntime.cs @@ -6,7 +6,7 @@ using System.Runtime.InteropServices; using System.Text; -namespace Interop.Diplomat; +namespace Devolutions.IronRdp.Diplomat; #nullable enable diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/DrdynvcChannel.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/DrdynvcChannel.cs new file mode 100644 index 000000000..d42952916 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/DrdynvcChannel.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class DrdynvcChannel: IDisposable +{ + private unsafe Raw.DrdynvcChannel* _inner; + + /// + /// Creates a managed DrdynvcChannel from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe DrdynvcChannel(Raw.DrdynvcChannel* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.DrdynvcChannel* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.DrdynvcChannel.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~DrdynvcChannel() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/GraphicsConfig.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/GraphicsConfig.cs new file mode 100644 index 000000000..78329a72c --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/GraphicsConfig.cs @@ -0,0 +1,168 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class GraphicsConfig: IDisposable +{ + private unsafe Raw.GraphicsConfig* _inner; + + public bool Avc444 + { + get + { + return GetAvc444(); + } + } + + public uint Capabilities + { + get + { + return GetCapabilities(); + } + } + + public bool H264 + { + get + { + return GetH264(); + } + } + + public bool SmallCache + { + get + { + return GetSmallCache(); + } + } + + public bool ThinClient + { + get + { + return GetThinClient(); + } + } + + /// + /// Creates a managed GraphicsConfig from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe GraphicsConfig(Raw.GraphicsConfig* handle) + { + _inner = handle; + } + + public bool GetAvc444() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("GraphicsConfig"); + } + bool retVal = Raw.GraphicsConfig.GetAvc444(_inner); + return retVal; + } + } + + public bool GetH264() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("GraphicsConfig"); + } + bool retVal = Raw.GraphicsConfig.GetH264(_inner); + return retVal; + } + } + + public bool GetThinClient() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("GraphicsConfig"); + } + bool retVal = Raw.GraphicsConfig.GetThinClient(_inner); + return retVal; + } + } + + public bool GetSmallCache() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("GraphicsConfig"); + } + bool retVal = Raw.GraphicsConfig.GetSmallCache(_inner); + return retVal; + } + } + + public uint GetCapabilities() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("GraphicsConfig"); + } + uint retVal = Raw.GraphicsConfig.GetCapabilities(_inner); + return retVal; + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.GraphicsConfig* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.GraphicsConfig.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~GraphicsConfig() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpBlocking.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpBlocking.cs new file mode 100644 index 000000000..dae4610dc --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpBlocking.cs @@ -0,0 +1,220 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class IronRdpBlocking: IDisposable +{ + private unsafe Raw.IronRdpBlocking* _inner; + + /// + /// Creates a managed IronRdpBlocking from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe IronRdpBlocking(Raw.IronRdpBlocking* handle) + { + _inner = handle; + } + + /// + /// A IronRdpBlocking allocated on Rust side. + /// + public static IronRdpBlocking New() + { + unsafe + { + Raw.IronRdpBlocking* retVal = Raw.IronRdpBlocking.New(); + return new IronRdpBlocking(retVal); + } + } + + /// + /// + /// A ShouldUpgrade allocated on Rust side. + /// + public static ShouldUpgrade ConnectBegin(BlockingTcpFrame framed, ClientConnector connector) + { + unsafe + { + Raw.BlockingTcpFrame* framedRaw; + framedRaw = framed.AsFFI(); + if (framedRaw == null) + { + throw new ObjectDisposedException("BlockingTcpFrame"); + } + Raw.ClientConnector* connectorRaw; + connectorRaw = connector.AsFFI(); + if (connectorRaw == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.IronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError result = Raw.IronRdpBlocking.ConnectBegin(framedRaw, connectorRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.ShouldUpgrade* retVal = result.Ok; + return new ShouldUpgrade(retVal); + } + } + + /// + /// + /// A Upgraded allocated on Rust side. + /// + public static Upgraded MarkAsUpgraded(ShouldUpgrade shouldUpgrade, ClientConnector connector) + { + unsafe + { + Raw.ShouldUpgrade* shouldUpgradeRaw; + shouldUpgradeRaw = shouldUpgrade.AsFFI(); + if (shouldUpgradeRaw == null) + { + throw new ObjectDisposedException("ShouldUpgrade"); + } + Raw.ClientConnector* connectorRaw; + connectorRaw = connector.AsFFI(); + if (connectorRaw == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.IronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError result = Raw.IronRdpBlocking.MarkAsUpgraded(shouldUpgradeRaw, connectorRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.Upgraded* retVal = result.Ok; + return new Upgraded(retVal); + } + } + + /// + /// + /// A ShouldUpgrade allocated on Rust side. + /// + public static ShouldUpgrade SkipConnectBegin(ClientConnector connector) + { + unsafe + { + Raw.ClientConnector* connectorRaw; + connectorRaw = connector.AsFFI(); + if (connectorRaw == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.IronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError result = Raw.IronRdpBlocking.SkipConnectBegin(connectorRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.ShouldUpgrade* retVal = result.Ok; + return new ShouldUpgrade(retVal); + } + } + + /// + /// + /// A ConnectionResult allocated on Rust side. + /// + public static ConnectionResult ConnectFinalize(Upgraded upgraded, BlockingUpgradedFrame upgradedFramed, ClientConnector connector, ServerName serverName, VecU8 serverPublicKey, KerberosConfig? kerberosConfig) + { + unsafe + { + Raw.Upgraded* upgradedRaw; + upgradedRaw = upgraded.AsFFI(); + if (upgradedRaw == null) + { + throw new ObjectDisposedException("Upgraded"); + } + Raw.BlockingUpgradedFrame* upgradedFramedRaw; + upgradedFramedRaw = upgradedFramed.AsFFI(); + if (upgradedFramedRaw == null) + { + throw new ObjectDisposedException("BlockingUpgradedFrame"); + } + Raw.ClientConnector* connectorRaw; + connectorRaw = connector.AsFFI(); + if (connectorRaw == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.ServerName* serverNameRaw; + serverNameRaw = serverName.AsFFI(); + if (serverNameRaw == null) + { + throw new ObjectDisposedException("ServerName"); + } + Raw.VecU8* serverPublicKeyRaw; + serverPublicKeyRaw = serverPublicKey.AsFFI(); + if (serverPublicKeyRaw == null) + { + throw new ObjectDisposedException("VecU8"); + } + Raw.KerberosConfig* kerberosConfigRaw; + if (kerberosConfig == null) + { + kerberosConfigRaw = null; + } + else + { + kerberosConfigRaw = kerberosConfig.AsFFI(); + if (kerberosConfigRaw == null) + { + throw new ObjectDisposedException("KerberosConfig"); + } + } + Raw.IronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError result = Raw.IronRdpBlocking.ConnectFinalize(upgradedRaw, upgradedFramedRaw, connectorRaw, serverNameRaw, serverPublicKeyRaw, kerberosConfigRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.ConnectionResult* retVal = result.Ok; + return new ConnectionResult(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.IronRdpBlocking* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.IronRdpBlocking.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~IronRdpBlocking() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpError.cs new file mode 100644 index 000000000..763ee69ca --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpError.cs @@ -0,0 +1,142 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +/// +/// Stringified Picky error along with an error kind. +/// +public partial class IronRdpError: IDisposable +{ + private unsafe Raw.IronRdpError* _inner; + + public IronRdpErrorKind Kind + { + get + { + return GetKind(); + } + } + + /// + /// Creates a managed IronRdpError from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe IronRdpError(Raw.IronRdpError* handle) + { + _inner = handle; + } + + /// + /// Returns the error as a string. + /// + public void ToDisplay(DiplomatWriteable writeable) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("IronRdpError"); + } + Raw.IronRdpError.ToDisplay(_inner, &writeable); + } + } + + /// + /// Returns the error as a string. + /// + public string ToDisplay() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("IronRdpError"); + } + DiplomatWriteable writeable = new DiplomatWriteable(); + Raw.IronRdpError.ToDisplay(_inner, &writeable); + string retVal = writeable.ToUnicode(); + writeable.Dispose(); + return retVal; + } + } + + /// + /// Prints the error string. + /// + public void Print() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("IronRdpError"); + } + Raw.IronRdpError.Print(_inner); + } + } + + /// + /// Returns the error kind. + /// + /// + /// A IronRdpErrorKind allocated on C# side. + /// + public IronRdpErrorKind GetKind() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("IronRdpError"); + } + Raw.IronRdpErrorKind retVal = Raw.IronRdpError.GetKind(_inner); + return (IronRdpErrorKind)retVal; + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.IronRdpError* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.IronRdpError.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~IronRdpError() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs new file mode 100644 index 000000000..eeb056b7d --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public enum IronRdpErrorKind +{ + Generic = 0, + PduError = 1, + SspiError = 2, + NullPointer = 3, + IO = 4, +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpException.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpException.cs new file mode 100644 index 000000000..1dc629155 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpException.cs @@ -0,0 +1,30 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class IronRdpException : Exception +{ + private IronRdpError _inner; + + public IronRdpException(IronRdpError inner) : base(inner.ToDisplay()) + { + _inner = inner; + } + + public IronRdpError Inner + { + get + { + return _inner; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/KerberosConfig.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/KerberosConfig.cs new file mode 100644 index 000000000..0cb7f570b --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/KerberosConfig.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class KerberosConfig: IDisposable +{ + private unsafe Raw.KerberosConfig* _inner; + + /// + /// Creates a managed KerberosConfig from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe KerberosConfig(Raw.KerberosConfig* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.KerberosConfig* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.KerberosConfig.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~KerberosConfig() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/PduHintResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/PduHintResult.cs new file mode 100644 index 000000000..55b7d09ba --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/PduHintResult.cs @@ -0,0 +1,105 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class PduHintResult: IDisposable +{ + private unsafe Raw.PduHintResult* _inner; + + /// + /// Creates a managed PduHintResult from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe PduHintResult(Raw.PduHintResult* handle) + { + _inner = handle; + } + + public bool IsSome() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("PduHintResult"); + } + bool retVal = Raw.PduHintResult.IsSome(_inner); + return retVal; + } + } + + /// + public nuint FindSize(VecU8 buffer) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("PduHintResult"); + } + Raw.VecU8* bufferRaw; + bufferRaw = buffer.AsFFI(); + if (bufferRaw == null) + { + throw new ObjectDisposedException("VecU8"); + } + Raw.ConnectorFfiResultOptBoxUsizeBoxIronRdpError result = Raw.PduHintResult.FindSize(_inner, bufferRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + nuint* retVal = result.Ok; + if (retVal == null) + { + return null; + } + return retVal; + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.PduHintResult* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.PduHintResult.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~PduHintResult() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawAny.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawAny.cs new file mode 100644 index 000000000..ebd723664 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawAny.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct Any +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Any_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(Any* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingTcpFrame.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingTcpFrame.cs new file mode 100644 index 000000000..29a6f5fc0 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingTcpFrame.cs @@ -0,0 +1,27 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct BlockingTcpFrame +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "BlockingTcpFrame_from_tcp_stream", ExactSpelling = true)] + public static unsafe extern IronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError FromTcpStream(StdTcpStream* stream); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "BlockingTcpFrame_into_tcp_steam_no_leftover", ExactSpelling = true)] + public static unsafe extern IronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError IntoTcpSteamNoLeftover(BlockingTcpFrame* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "BlockingTcpFrame_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(BlockingTcpFrame* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingUpgradedFrame.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingUpgradedFrame.cs new file mode 100644 index 000000000..d223eee5a --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingUpgradedFrame.cs @@ -0,0 +1,24 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct BlockingUpgradedFrame +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "BlockingUpgradedFrame_from_upgraded_stream", ExactSpelling = true)] + public static unsafe extern IronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError FromUpgradedStream(UpgradedStream* stream); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "BlockingUpgradedFrame_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(BlockingUpgradedFrame* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs new file mode 100644 index 000000000..0b607ed4c --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs @@ -0,0 +1,60 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ClientConnector +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_new", ExactSpelling = true)] + public static unsafe extern ClientConnector* New(Config* config); + + /// + /// Must use + /// + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_with_server_addr", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError WithServerAddr(ClientConnector* self, SocketAddr* serverAddr); + + /// + /// Must use + /// + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_with_static_channel_rdp_snd", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError WithStaticChannelRdpSnd(ClientConnector* self); + + /// + /// Must use + /// + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_with_static_channel_rdpdr", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError WithStaticChannelRdpdr(ClientConnector* self, byte* computerName, nuint computerNameSz, uint smartCardDeviceId); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_should_perform_security_upgrade", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultBoolBoxIronRdpError ShouldPerformSecurityUpgrade(ClientConnector* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_mark_security_upgrade_as_done", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError MarkSecurityUpgradeAsDone(ClientConnector* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_should_perform_credssp", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultBoolBoxIronRdpError ShouldPerformCredssp(ClientConnector* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_mark_credssp_as_done", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError MarkCredsspAsDone(ClientConnector* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_next_pdu_hint", ExactSpelling = true)] + public static unsafe extern PduHintResult* NextPduHint(ClientConnector* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_state", ExactSpelling = true)] + public static unsafe extern State* State(ClientConnector* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ClientConnector* self); +} diff --git a/ffi/dotnet/RawClientConnectorState.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnectorState.cs similarity index 78% rename from ffi/dotnet/RawClientConnectorState.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnectorState.cs index ef7bb2702..669ae9c03 100644 --- a/ffi/dotnet/RawClientConnectorState.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnectorState.cs @@ -4,17 +4,17 @@ using System; using System.Runtime.InteropServices; -using Interop.Diplomat; +using Devolutions.IronRdp.Diplomat; #pragma warning restore 0105 -namespace Interop.Raw; +namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] public partial struct ClientConnectorState { - private const string NativeLib = "rust"; + private const string NativeLib = "DevolutionsIronRdp"; [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnectorState_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(ClientConnectorState* self); diff --git a/ffi/dotnet/RawConfig.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfig.cs similarity index 76% rename from ffi/dotnet/RawConfig.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawConfig.cs index 3e9d934c9..a32a8b2fb 100644 --- a/ffi/dotnet/RawConfig.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfig.cs @@ -4,17 +4,17 @@ using System; using System.Runtime.InteropServices; -using Interop.Diplomat; +using Devolutions.IronRdp.Diplomat; #pragma warning restore 0105 -namespace Interop.Raw; +namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] public partial struct Config { - private const string NativeLib = "rust"; + private const string NativeLib = "DevolutionsIronRdp"; [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Config_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(Config* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectionResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectionResult.cs new file mode 100644 index 000000000..fb3bbcd5c --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectionResult.cs @@ -0,0 +1,44 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ConnectionResult +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConnectionResult_get_io_channel_id", ExactSpelling = true)] + public static unsafe extern ushort GetIoChannelId(ConnectionResult* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConnectionResult_get_user_channel_id", ExactSpelling = true)] + public static unsafe extern ushort GetUserChannelId(ConnectionResult* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConnectionResult_get_static_channels", ExactSpelling = true)] + public static unsafe extern StaticChannelSet* GetStaticChannels(ConnectionResult* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConnectionResult_get_desktop_size", ExactSpelling = true)] + public static unsafe extern DesktopSize* GetDesktopSize(ConnectionResult* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConnectionResult_get_no_server_pointer", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool GetNoServerPointer(ConnectionResult* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConnectionResult_get_pointer_software_rendering", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool GetPointerSoftwareRendering(ConnectionResult* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConnectionResult_get_graphics_config", ExactSpelling = true)] + public static unsafe extern GraphicsConfig* GetGraphicsConfig(ConnectionResult* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConnectionResult_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ConnectionResult* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoolBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoolBoxIronRdpError.cs new file mode 100644 index 000000000..7fc158904 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoolBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ConnectorFfiResultBoolBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal bool ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe bool Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxUsizeBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxUsizeBoxIronRdpError.cs new file mode 100644 index 000000000..29db5b075 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxUsizeBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ConnectorFfiResultOptBoxUsizeBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal nuint* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe nuint* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultVoidBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultVoidBoxIronRdpError.cs new file mode 100644 index 000000000..5c652dc22 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultVoidBoxIronRdpError.cs @@ -0,0 +1,36 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ConnectorFfiResultVoidBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawDesktopSize.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawDesktopSize.cs new file mode 100644 index 000000000..e902c7ff7 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawDesktopSize.cs @@ -0,0 +1,27 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct DesktopSize +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "DesktopSize_get_width", ExactSpelling = true)] + public static unsafe extern ushort GetWidth(DesktopSize* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "DesktopSize_get_height", ExactSpelling = true)] + public static unsafe extern ushort GetHeight(DesktopSize* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "DesktopSize_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(DesktopSize* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawDrdynvcChannel.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawDrdynvcChannel.cs new file mode 100644 index 000000000..124d54dda --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawDrdynvcChannel.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct DrdynvcChannel +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "DrdynvcChannel_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(DrdynvcChannel* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawGraphicsConfig.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawGraphicsConfig.cs new file mode 100644 index 000000000..bd6a4749b --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawGraphicsConfig.cs @@ -0,0 +1,40 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct GraphicsConfig +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GraphicsConfig_get_avc444", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool GetAvc444(GraphicsConfig* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GraphicsConfig_get_h264", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool GetH264(GraphicsConfig* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GraphicsConfig_get_thin_client", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool GetThinClient(GraphicsConfig* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GraphicsConfig_get_small_cache", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool GetSmallCache(GraphicsConfig* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GraphicsConfig_get_capabilities", ExactSpelling = true)] + public static unsafe extern uint GetCapabilities(GraphicsConfig* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GraphicsConfig_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(GraphicsConfig* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpBlocking.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpBlocking.cs new file mode 100644 index 000000000..93141ee35 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpBlocking.cs @@ -0,0 +1,36 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct IronRdpBlocking +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpBlocking_new", ExactSpelling = true)] + public static unsafe extern IronRdpBlocking* New(); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpBlocking_connect_begin", ExactSpelling = true)] + public static unsafe extern IronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError ConnectBegin(BlockingTcpFrame* framed, ClientConnector* connector); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpBlocking_mark_as_upgraded", ExactSpelling = true)] + public static unsafe extern IronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError MarkAsUpgraded(ShouldUpgrade* shouldUpgrade, ClientConnector* connector); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpBlocking_skip_connect_begin", ExactSpelling = true)] + public static unsafe extern IronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError SkipConnectBegin(ClientConnector* connector); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpBlocking_connect_finalize", ExactSpelling = true)] + public static unsafe extern IronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError ConnectFinalize(Upgraded* upgraded, BlockingUpgradedFrame* upgradedFramed, ClientConnector* connector, ServerName* serverName, VecU8* serverPublicKey, KerberosConfig* kerberosConfig); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpBlocking_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(IronRdpBlocking* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpError.cs new file mode 100644 index 000000000..ffd7d40c2 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpError.cs @@ -0,0 +1,42 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +/// +/// Stringified Picky error along with an error kind. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct IronRdpError +{ + private const string NativeLib = "DevolutionsIronRdp"; + + /// + /// Returns the error as a string. + /// + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpError_to_display", ExactSpelling = true)] + public static unsafe extern void ToDisplay(IronRdpError* self, DiplomatWriteable* writeable); + + /// + /// Prints the error string. + /// + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpError_print", ExactSpelling = true)] + public static unsafe extern void Print(IronRdpError* self); + + /// + /// Returns the error kind. + /// + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpError_get_kind", ExactSpelling = true)] + public static unsafe extern IronRdpErrorKind GetKind(IronRdpError* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpError_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(IronRdpError* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs new file mode 100644 index 000000000..33416f5d3 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +public enum IronRdpErrorKind +{ + Generic = 0, + PduError = 1, + SspiError = 2, + NullPointer = 3, + IO = 4, +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError.cs new file mode 100644 index 000000000..72908ffee --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct IronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal BlockingTcpFrame* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe BlockingTcpFrame* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError.cs new file mode 100644 index 000000000..3d9b285aa --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct IronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal BlockingUpgradedFrame* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe BlockingUpgradedFrame* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError.cs new file mode 100644 index 000000000..be72b0c46 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct IronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal ConnectionResult* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe ConnectionResult* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError.cs new file mode 100644 index 000000000..b7110a17c --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct IronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal ShouldUpgrade* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe ShouldUpgrade* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError.cs new file mode 100644 index 000000000..ff2c2a9ba --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct IronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal StdTcpStream* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe StdTcpStream* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError.cs new file mode 100644 index 000000000..06b0ea3c9 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct IronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal Upgraded* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe Upgraded* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawKerberosConfig.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawKerberosConfig.cs new file mode 100644 index 000000000..410ff439b --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawKerberosConfig.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct KerberosConfig +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "KerberosConfig_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(KerberosConfig* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHintResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHintResult.cs new file mode 100644 index 000000000..953fdfd9b --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHintResult.cs @@ -0,0 +1,28 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct PduHintResult +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHintResult_is_some", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool IsSome(PduHintResult* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHintResult_find_size", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultOptBoxUsizeBoxIronRdpError FindSize(PduHintResult* self, VecU8* buffer); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHintResult_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(PduHintResult* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawServerName.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawServerName.cs new file mode 100644 index 000000000..7850dd8dc --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawServerName.cs @@ -0,0 +1,24 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ServerName +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ServerName_new", ExactSpelling = true)] + public static unsafe extern ServerName* New(byte* name, nuint nameSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ServerName_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ServerName* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawShouldUpgrade.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawShouldUpgrade.cs new file mode 100644 index 000000000..d08b4051c --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawShouldUpgrade.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ShouldUpgrade +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ShouldUpgrade_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ShouldUpgrade* self); +} diff --git a/ffi/dotnet/RawSocketAddr.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs similarity index 76% rename from ffi/dotnet/RawSocketAddr.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs index 3ddfcda06..f9ba151b3 100644 --- a/ffi/dotnet/RawSocketAddr.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs @@ -4,17 +4,17 @@ using System; using System.Runtime.InteropServices; -using Interop.Diplomat; +using Devolutions.IronRdp.Diplomat; #pragma warning restore 0105 -namespace Interop.Raw; +namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] public partial struct SocketAddr { - private const string NativeLib = "rust"; + private const string NativeLib = "DevolutionsIronRdp"; [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SocketAddr_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(SocketAddr* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawState.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawState.cs new file mode 100644 index 000000000..7ed952046 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawState.cs @@ -0,0 +1,31 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct State +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "State_get_name", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError GetName(State* self, DiplomatWriteable* writeable); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "State_is_terminal", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool IsTerminal(State* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "State_as_any", ExactSpelling = true)] + public static unsafe extern Any* AsAny(State* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "State_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(State* self); +} diff --git a/ffi/dotnet/RawStaticChannelSet.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawStaticChannelSet.cs similarity index 77% rename from ffi/dotnet/RawStaticChannelSet.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawStaticChannelSet.cs index 49f276e07..e7237ace8 100644 --- a/ffi/dotnet/RawStaticChannelSet.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawStaticChannelSet.cs @@ -4,17 +4,17 @@ using System; using System.Runtime.InteropServices; -using Interop.Diplomat; +using Devolutions.IronRdp.Diplomat; #pragma warning restore 0105 -namespace Interop.Raw; +namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] public partial struct StaticChannelSet { - private const string NativeLib = "rust"; + private const string NativeLib = "DevolutionsIronRdp"; [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "StaticChannelSet_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(StaticChannelSet* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawStdTcpStream.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawStdTcpStream.cs new file mode 100644 index 000000000..e67a2e4d3 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawStdTcpStream.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct StdTcpStream +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "StdTcpStream_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(StdTcpStream* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawTls.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTls.cs new file mode 100644 index 000000000..003a51359 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTls.cs @@ -0,0 +1,24 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct Tls +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Tls_tls_upgrade", ExactSpelling = true)] + public static unsafe extern TlsFfiResultBoxTlsUpgradeResultBoxIronRdpError TlsUpgrade(StdTcpStream* stream, byte* serverName, nuint serverNameSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Tls_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(Tls* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxTlsUpgradeResultBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxTlsUpgradeResultBoxIronRdpError.cs new file mode 100644 index 000000000..c46e5675f --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxTlsUpgradeResultBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct TlsFfiResultBoxTlsUpgradeResultBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal TlsUpgradeResult* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe TlsUpgradeResult* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxUpgradedStreamBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxUpgradedStreamBoxIronRdpError.cs new file mode 100644 index 000000000..66c6be871 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxUpgradedStreamBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct TlsFfiResultBoxUpgradedStreamBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal UpgradedStream* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe UpgradedStream* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsUpgradeResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsUpgradeResult.cs new file mode 100644 index 000000000..fa77aa9d6 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsUpgradeResult.cs @@ -0,0 +1,27 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct TlsUpgradeResult +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "TlsUpgradeResult_get_upgraded_stream", ExactSpelling = true)] + public static unsafe extern TlsFfiResultBoxUpgradedStreamBoxIronRdpError GetUpgradedStream(TlsUpgradeResult* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "TlsUpgradeResult_get_server_public_key", ExactSpelling = true)] + public static unsafe extern VecU8* GetServerPublicKey(TlsUpgradeResult* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "TlsUpgradeResult_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(TlsUpgradeResult* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgraded.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgraded.cs new file mode 100644 index 000000000..91bb63948 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgraded.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct Upgraded +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Upgraded_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(Upgraded* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgradedStream.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgradedStream.cs new file mode 100644 index 000000000..9ffe4d129 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgradedStream.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct UpgradedStream +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "UpgradedStream_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(UpgradedStream* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs new file mode 100644 index 000000000..653d0558e --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs @@ -0,0 +1,30 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct VecU8 +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "VecU8_from_byte", ExactSpelling = true)] + public static unsafe extern VecU8* FromByte(byte* bytes, nuint bytesSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "VecU8_get_size", ExactSpelling = true)] + public static unsafe extern nuint GetSize(VecU8* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "VecU8_fill", ExactSpelling = true)] + public static unsafe extern void Fill(VecU8* self, byte* buffer, nuint bufferSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "VecU8_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(VecU8* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ServerName.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ServerName.cs new file mode 100644 index 000000000..6a2439fa5 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ServerName.cs @@ -0,0 +1,80 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class ServerName: IDisposable +{ + private unsafe Raw.ServerName* _inner; + + /// + /// Creates a managed ServerName from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ServerName(Raw.ServerName* handle) + { + _inner = handle; + } + + /// + /// A ServerName allocated on Rust side. + /// + public static ServerName New(string name) + { + unsafe + { + byte[] nameBuf = DiplomatUtils.StringToUtf8(name); + nuint nameBufLength = (nuint)nameBuf.Length; + fixed (byte* nameBufPtr = nameBuf) + { + Raw.ServerName* retVal = Raw.ServerName.New(nameBufPtr, nameBufLength); + return new ServerName(retVal); + } + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ServerName* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ServerName.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ServerName() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ShouldUpgrade.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ShouldUpgrade.cs new file mode 100644 index 000000000..f0c533ce1 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ShouldUpgrade.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class ShouldUpgrade: IDisposable +{ + private unsafe Raw.ShouldUpgrade* _inner; + + /// + /// Creates a managed ShouldUpgrade from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ShouldUpgrade(Raw.ShouldUpgrade* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ShouldUpgrade* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ShouldUpgrade.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ShouldUpgrade() + { + Dispose(); + } +} diff --git a/ffi/dotnet/SocketAddr.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs similarity index 95% rename from ffi/dotnet/SocketAddr.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs index b765416d9..34b79a736 100644 --- a/ffi/dotnet/SocketAddr.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs @@ -4,10 +4,10 @@ using System; using System.Runtime.InteropServices; -using Interop.Diplomat; +using Devolutions.IronRdp.Diplomat; #pragma warning restore 0105 -namespace Interop; +namespace Devolutions.IronRdp; #nullable enable diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/State.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/State.cs new file mode 100644 index 000000000..8c76b38cf --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/State.cs @@ -0,0 +1,138 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class State: IDisposable +{ + private unsafe Raw.State* _inner; + + public string Name + { + get + { + return GetName(); + } + } + + /// + /// Creates a managed State from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe State(Raw.State* handle) + { + _inner = handle; + } + + /// + public void GetName(DiplomatWriteable writeable) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("State"); + } + Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.State.GetName(_inner, &writeable); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + } + } + + /// + public string GetName() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("State"); + } + DiplomatWriteable writeable = new DiplomatWriteable(); + Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.State.GetName(_inner, &writeable); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + string retVal = writeable.ToUnicode(); + writeable.Dispose(); + return retVal; + } + } + + public bool IsTerminal() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("State"); + } + bool retVal = Raw.State.IsTerminal(_inner); + return retVal; + } + } + + /// + /// A Any allocated on Rust side. + /// + public Any AsAny() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("State"); + } + Raw.Any* retVal = Raw.State.AsAny(_inner); + return new Any(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.State* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.State.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~State() + { + Dispose(); + } +} diff --git a/ffi/dotnet/StaticChannelSet.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/StaticChannelSet.cs similarity index 95% rename from ffi/dotnet/StaticChannelSet.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/StaticChannelSet.cs index 0041ba7d2..3ee5e0f2a 100644 --- a/ffi/dotnet/StaticChannelSet.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/StaticChannelSet.cs @@ -4,10 +4,10 @@ using System; using System.Runtime.InteropServices; -using Interop.Diplomat; +using Devolutions.IronRdp.Diplomat; #pragma warning restore 0105 -namespace Interop; +namespace Devolutions.IronRdp; #nullable enable diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/StdTcpStream.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/StdTcpStream.cs new file mode 100644 index 000000000..76bd147bc --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/StdTcpStream.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class StdTcpStream: IDisposable +{ + private unsafe Raw.StdTcpStream* _inner; + + /// + /// Creates a managed StdTcpStream from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe StdTcpStream(Raw.StdTcpStream* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.StdTcpStream* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.StdTcpStream.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~StdTcpStream() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/Tls.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/Tls.cs new file mode 100644 index 000000000..c1ffeff4d --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/Tls.cs @@ -0,0 +1,92 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class Tls: IDisposable +{ + private unsafe Raw.Tls* _inner; + + /// + /// Creates a managed Tls from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe Tls(Raw.Tls* handle) + { + _inner = handle; + } + + /// + /// + /// A TlsUpgradeResult allocated on Rust side. + /// + public static TlsUpgradeResult TlsUpgrade(StdTcpStream stream, string serverName) + { + unsafe + { + byte[] serverNameBuf = DiplomatUtils.StringToUtf8(serverName); + nuint serverNameBufLength = (nuint)serverNameBuf.Length; + Raw.StdTcpStream* streamRaw; + streamRaw = stream.AsFFI(); + if (streamRaw == null) + { + throw new ObjectDisposedException("StdTcpStream"); + } + fixed (byte* serverNameBufPtr = serverNameBuf) + { + Raw.TlsFfiResultBoxTlsUpgradeResultBoxIronRdpError result = Raw.Tls.TlsUpgrade(streamRaw, serverNameBufPtr, serverNameBufLength); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.TlsUpgradeResult* retVal = result.Ok; + return new TlsUpgradeResult(retVal); + } + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.Tls* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.Tls.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~Tls() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/TlsUpgradeResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/TlsUpgradeResult.cs new file mode 100644 index 000000000..6afb8b621 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/TlsUpgradeResult.cs @@ -0,0 +1,117 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class TlsUpgradeResult: IDisposable +{ + private unsafe Raw.TlsUpgradeResult* _inner; + + public VecU8 ServerPublicKey + { + get + { + return GetServerPublicKey(); + } + } + + public UpgradedStream UpgradedStream + { + get + { + return GetUpgradedStream(); + } + } + + /// + /// Creates a managed TlsUpgradeResult from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe TlsUpgradeResult(Raw.TlsUpgradeResult* handle) + { + _inner = handle; + } + + /// + /// + /// A UpgradedStream allocated on Rust side. + /// + public UpgradedStream GetUpgradedStream() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("TlsUpgradeResult"); + } + Raw.TlsFfiResultBoxUpgradedStreamBoxIronRdpError result = Raw.TlsUpgradeResult.GetUpgradedStream(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.UpgradedStream* retVal = result.Ok; + return new UpgradedStream(retVal); + } + } + + /// + /// A VecU8 allocated on Rust side. + /// + public VecU8 GetServerPublicKey() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("TlsUpgradeResult"); + } + Raw.VecU8* retVal = Raw.TlsUpgradeResult.GetServerPublicKey(_inner); + return new VecU8(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.TlsUpgradeResult* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.TlsUpgradeResult.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~TlsUpgradeResult() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/Upgraded.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/Upgraded.cs new file mode 100644 index 000000000..3055985e2 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/Upgraded.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class Upgraded: IDisposable +{ + private unsafe Raw.Upgraded* _inner; + + /// + /// Creates a managed Upgraded from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe Upgraded(Raw.Upgraded* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.Upgraded* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.Upgraded.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~Upgraded() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/UpgradedStream.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/UpgradedStream.cs new file mode 100644 index 000000000..ad2978433 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/UpgradedStream.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class UpgradedStream: IDisposable +{ + private unsafe Raw.UpgradedStream* _inner; + + /// + /// Creates a managed UpgradedStream from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe UpgradedStream(Raw.UpgradedStream* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.UpgradedStream* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.UpgradedStream.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~UpgradedStream() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs new file mode 100644 index 000000000..bd64f925f --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs @@ -0,0 +1,116 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class VecU8: IDisposable +{ + private unsafe Raw.VecU8* _inner; + + public nuint Size + { + get + { + return GetSize(); + } + } + + /// + /// Creates a managed VecU8 from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe VecU8(Raw.VecU8* handle) + { + _inner = handle; + } + + /// + /// A VecU8 allocated on Rust side. + /// + public static VecU8 FromByte(byte[] bytes) + { + unsafe + { + nuint bytesLength = (nuint)bytes.Length; + fixed (byte* bytesPtr = bytes) + { + Raw.VecU8* retVal = Raw.VecU8.FromByte(bytesPtr, bytesLength); + return new VecU8(retVal); + } + } + } + + public nuint GetSize() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("VecU8"); + } + nuint retVal = Raw.VecU8.GetSize(_inner); + return retVal; + } + } + + public void Fill(byte[] buffer) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("VecU8"); + } + nuint bufferLength = (nuint)buffer.Length; + fixed (byte* bufferPtr = buffer) + { + Raw.VecU8.Fill(_inner, bufferPtr, bufferLength); + } + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.VecU8* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.VecU8.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~VecU8() + { + Dispose(); + } +} diff --git a/ffi/dotnet/RawClientConnector.cs b/ffi/dotnet/RawClientConnector.cs deleted file mode 100644 index 6cb269a5a..000000000 --- a/ffi/dotnet/RawClientConnector.cs +++ /dev/null @@ -1,30 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Interop.Diplomat; -#pragma warning restore 0105 - -namespace Interop.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct ClientConnector -{ - private const string NativeLib = "rust"; - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_new", ExactSpelling = true)] - public static unsafe extern ClientConnector* New(Config* config); - - /// - /// Must use - /// - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_with_server_addr", ExactSpelling = true)] - public static unsafe extern ClientConnector* WithServerAddr(ClientConnector* self, SocketAddr* serverAddr); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(ClientConnector* self); -} diff --git a/ffi/justfile b/ffi/justfile new file mode 100644 index 000000000..f1449ff9e --- /dev/null +++ b/ffi/justfile @@ -0,0 +1,10 @@ + +defalt: bindings + +dotnet_generated_path := "./dotnet/Devolutions.IronRdp/Generated/" +dotnet_diplomat_config := "./dotnet-interop-conf.toml" + +bindings: + -rm {{dotnet_generated_path}}*.cs + diplomat-tool dotnet {{dotnet_generated_path}} -l {{dotnet_diplomat_config}} + @echo ">> .NET wrapper generated at {{dotnet_generated_path}}" \ No newline at end of file diff --git a/ffi/src/connector.rs b/ffi/src/connector/mod.rs similarity index 55% rename from ffi/src/connector.rs rename to ffi/src/connector/mod.rs index c58034dad..98e74fcd4 100644 --- a/ffi/src/connector.rs +++ b/ffi/src/connector/mod.rs @@ -1,12 +1,4 @@ -macro_rules! take_if_not_none { - ($expr:expr) => {{ - if let Some(value) = $expr.take() { - value - } else { - panic!("Inner value is None") - } - }}; -} +pub mod result; #[diplomat::bridge] pub mod ffi { @@ -15,7 +7,7 @@ pub mod ffi { use std::fmt::Write; use crate::{ - error::ffi::IronRdpError, + error::{ffi::{IronRdpError, IronRdpErrorKind}, NullPointerError}, utils::ffi::{SocketAddr, VecU8}, }; @@ -28,6 +20,9 @@ pub mod ffi { #[diplomat::opaque] pub struct ClientConnectorState(pub ironrdp::connector::ClientConnectorState); + #[diplomat::opaque] + pub struct ServerName(pub ironrdp::connector::ServerName); + // Basic Impl for ClientConnector impl ClientConnector { pub fn new(config: &Config) -> Box { @@ -37,23 +32,38 @@ pub mod ffi { } /// Must use - pub fn with_server_addr(&mut self, server_addr: &SocketAddr) { - let connector = take_if_not_none!(self.0); + pub fn with_server_addr(&mut self, server_addr: &SocketAddr) -> Result<(), Box> { + let Some(connector) = self.0.take() else { + return Err(IronRdpErrorKind::NullPointer.into()); + }; let server_addr = server_addr.0.clone(); self.0 = Some(connector.with_server_addr(server_addr)); + + Ok(()) } // FIXME: We need to create opaque for ironrdp::svc::StaticChannelSet /// Must use - pub fn with_static_channel_rdp_snd(&mut self) { - let connector = take_if_not_none!(self.0); + pub fn with_static_channel_rdp_snd(&mut self) -> Result<(), Box> { + let Some(connector) = self.0.take() else { + return Err(IronRdpErrorKind::NullPointer.into()); + }; + self.0 = Some(connector.with_static_channel(ironrdp::rdpsnd::Rdpsnd::new())); + + Ok(()) } // FIXME: We need to create opaque for ironrdp::rdpdr::Rdpdr /// Must use - pub fn with_static_channel_rdpdr(&mut self, computer_name: &str, smart_card_device_id: u32) { - let connector = take_if_not_none!(self.0); + pub fn with_static_channel_rdpdr( + &mut self, + computer_name: &str, + smart_card_device_id: u32, + ) -> Result<(), Box> { + let Some(connector) = self.0.take() else { + return Err(IronRdpErrorKind::NullPointer.into()); + }; self.0 = Some( connector.with_static_channel( ironrdp::rdpdr::Rdpdr::new( @@ -63,34 +73,37 @@ pub mod ffi { .with_smartcard(smart_card_device_id), ), ); + + Ok(()) } - pub fn should_perform_security_upgrade(&self) -> bool { + pub fn should_perform_security_upgrade(&self) -> Result> { let Some(connector) = self.0.as_ref() else { - panic!("Inner value is None") + return Err(NullPointerError::for_item("connector").into()); }; - connector.should_perform_security_upgrade() + Ok(connector.should_perform_security_upgrade()) } - pub fn mark_security_upgrade_as_done(&mut self) { + pub fn mark_security_upgrade_as_done(&mut self) -> Result<(), Box> { let Some(connector) = self.0.as_mut() else { - panic!("Inner value is None") + return Err(NullPointerError::for_item("connector").into()); }; - connector.mark_security_upgrade_as_done(); + Ok(connector.mark_security_upgrade_as_done()) } - pub fn should_perform_credssp(&self) -> bool { + pub fn should_perform_credssp(&self) -> Result> { let Some(connector) = self.0.as_ref() else { - panic!("Inner value is None") + return Err(NullPointerError::for_item("connector").into()); }; - connector.should_perform_credssp() + + Ok(connector.should_perform_credssp()) } - pub fn mark_credssp_as_done(&mut self) { + pub fn mark_credssp_as_done(&mut self) -> Result<(), Box> { let Some(connector) = self.0.as_mut() else { - panic!("Inner value is None") + return Err(NullPointerError::for_item("connector").into()); }; - connector.mark_credssp_as_done(); + Ok(connector.mark_credssp_as_done()) } } @@ -102,14 +115,14 @@ pub mod ffi { self.0.is_some() } - pub fn find_size(&'a self, buffer: &VecU8) -> Result, Box> { + pub fn find_size(&'a self, buffer: &VecU8) -> Result>, Box> { let Some(pdu_hint) = self.0 else { return Ok(None); }; let size = pdu_hint.find_size(buffer.0.as_slice())?; - Ok(size) + size.map(|size| Ok(Box::new(size))).transpose() } } @@ -117,7 +130,7 @@ pub mod ffi { pub struct State<'a>(pub &'a dyn ironrdp::connector::State); impl<'a> State<'a> { - pub fn get_name(&'a self,writeable:&'a mut DiplomatWriteable) -> Result<(), Box>{ + pub fn get_name(&'a self, writeable: &'a mut DiplomatWriteable) -> Result<(), Box> { let name = self.0.name(); write!(writeable, "{}", name)?; Ok(()) @@ -127,22 +140,20 @@ pub mod ffi { self.0.is_terminal() } - pub fn as_any(&'a self) -> Box { + pub fn as_any(&'a self) -> Box> { Box::new(crate::utils::ffi::Any(self.0.as_any())) } - } - impl ClientConnector { - pub fn next_pdu_hint(&self) -> Box { + pub fn next_pdu_hint<'a>(&'a self) -> Box> { let Some(connector) = self.0.as_ref() else { panic!("Inner value is None") }; Box::new(PduHintResult(connector.next_pdu_hint())) } - pub fn state(&self) -> Box { + pub fn state<'a>(&'a self) -> Box> { let Some(connector) = self.0.as_ref() else { panic!("Inner value is None") }; @@ -150,4 +161,22 @@ pub mod ffi { } } + impl ServerName { + pub fn new(name: &str) -> Box { + Box::new(ServerName(ironrdp::connector::ServerName::new(name))) + } + } + + #[diplomat::opaque] + pub struct DesktopSize(pub ironrdp::connector::DesktopSize); + + impl DesktopSize { + pub fn get_width(&self) -> u16 { + self.0.width + } + + pub fn get_height(&self) -> u16 { + self.0.height + } + } } diff --git a/ffi/src/connector/result.rs b/ffi/src/connector/result.rs new file mode 100644 index 000000000..e8fad2e91 --- /dev/null +++ b/ffi/src/connector/result.rs @@ -0,0 +1,64 @@ +#[diplomat::bridge] +pub mod ffi { + use crate::connector::ffi::DesktopSize; + + #[diplomat::opaque] + pub struct ConnectionResult(pub ironrdp::connector::ConnectionResult); + + impl ConnectionResult { + pub fn get_io_channel_id(&self) -> u16 { + self.0.io_channel_id + } + + pub fn get_user_channel_id(&self) -> u16 { + self.0.user_channel_id + } + + pub fn get_static_channels<'a>(&'a self) -> Box> { + Box::new(crate::svc::ffi::StaticChannelSet(&self.0.static_channels)) + } + + pub fn get_desktop_size(&self) -> Box { + Box::new(DesktopSize(self.0.desktop_size)) + } + + pub fn get_no_server_pointer(&self) -> bool { + self.0.no_server_pointer + } + + pub fn get_pointer_software_rendering(&self) -> bool { + self.0.pointer_software_rendering + } + + pub fn get_graphics_config(&self) -> Option> { + self.0.graphics_config.clone().map(GraphicsConfig).map(Box::new) + } + } + + #[diplomat::opaque] + pub struct GraphicsConfig(pub ironrdp::connector::GraphicsConfig); + + impl GraphicsConfig { + + pub fn get_avc444(&self) -> bool { + self.0.avc444 + } + + pub fn get_h264(&self) -> bool { + self.0.h264 + } + + pub fn get_thin_client(&self) -> bool { + self.0.thin_client + } + + pub fn get_small_cache(&self) -> bool { + self.0.small_cache + } + + pub fn get_capabilities(&self) -> u32 { + self.0.capabilities + } + + } +} diff --git a/ffi/src/credssp.rs b/ffi/src/credssp.rs new file mode 100644 index 000000000..a05d8e27d --- /dev/null +++ b/ffi/src/credssp.rs @@ -0,0 +1,7 @@ + +#[diplomat::bridge] +pub mod ffi { + + #[diplomat::opaque] + pub struct KerberosConfig(pub ironrdp::connector::credssp::KerberosConfig); +} \ No newline at end of file diff --git a/ffi/src/error.rs b/ffi/src/error.rs index 801f50423..7429a2f79 100644 --- a/ffi/src/error.rs +++ b/ffi/src/error.rs @@ -1,4 +1,26 @@ -use std::fmt; +use ironrdp::connector::ConnectorError; + +use self::ffi::IronRdpErrorKind; + +impl Into for ConnectorError { + fn into(self) -> ffi::IronRdpErrorKind { + match self.kind { + ironrdp::connector::ConnectorErrorKind::Pdu(_) => todo!(), + ironrdp::connector::ConnectorErrorKind::Credssp(_) => todo!(), + ironrdp::connector::ConnectorErrorKind::Reason(_) => todo!(), + ironrdp::connector::ConnectorErrorKind::AccessDenied => todo!(), + ironrdp::connector::ConnectorErrorKind::General => todo!(), + ironrdp::connector::ConnectorErrorKind::Custom => todo!(), + _ => todo!(), + } + } +} + +impl Into for &str { + fn into(self) -> ffi::IronRdpErrorKind { + ffi::IronRdpErrorKind::Generic + } +} impl Into for ironrdp::pdu::PduError { fn into(self) -> ffi::IronRdpErrorKind { @@ -43,12 +65,17 @@ pub mod ffi { use diplomat_runtime::DiplomatWriteable; use std::fmt::Write as _; - /// Kind associated to a Picky Error - #[derive(Clone, Copy)] + #[derive(Debug, Clone, Copy, thiserror::Error)] pub enum IronRdpErrorKind { - /// Generic Picky error + #[error("Generic error")] Generic, + #[error("PDU error")] PduError, + #[error("CredSSP error")] + SspiError, + #[error("Null pointer error")] + NullPointer, + #[error("IO error")] IO, } @@ -74,3 +101,36 @@ pub mod ffi { } } } + + +#[derive(Debug)] +pub struct NullPointerError{ + item: String, + reason: Option, +} + +impl NullPointerError { + pub fn for_item(item: &str) -> NullPointerError { + NullPointerError { + item: item.to_string(), + reason: None, + } + } + + pub fn reason(mut self, reason: &str) -> NullPointerError { + self.reason = Some(reason.to_string()); + self + } +} + +impl ToString for NullPointerError { + fn to_string(&self) -> String { + format!("{}: {:?}", self.item, self.reason) + } +} + +impl Into for NullPointerError { + fn into(self) -> IronRdpErrorKind { + IronRdpErrorKind::NullPointer + } +} \ No newline at end of file diff --git a/ffi/src/ironrdp_blocking.rs b/ffi/src/ironrdp_blocking.rs new file mode 100644 index 000000000..0de0ab8c4 --- /dev/null +++ b/ffi/src/ironrdp_blocking.rs @@ -0,0 +1,176 @@ +#[diplomat::bridge] +pub mod ffi { + use ironrdp::connector::sspi::network_client; + use ironrdp_blocking::connect_begin; + + use crate::{ + connector::result::ffi::ConnectionResult, + error::{ + ffi::{IronRdpError, IronRdpErrorKind}, + NullPointerError, + }, + tls::TlsStream, + utils::ffi::{StdTcpStream, VecU8}, + }; + + #[diplomat::opaque] + pub struct BlockingTcpFrame(pub Option>); + + impl BlockingTcpFrame { + pub fn from_tcp_stream(stream: &mut StdTcpStream) -> Result, Box> { + let Some(stream) = stream.0.take() else { + return Err(NullPointerError::for_item("tcp_stream") + .reason("tcp stream has been consumed") + .into()); + }; + + let framed = ironrdp_blocking::Framed::new(stream); + + Ok(Box::new(BlockingTcpFrame(Some(framed)))) + } + + pub fn into_tcp_steam_no_leftover(&mut self) -> Result, Box> { + let Some(stream) = self.0.take() else { + return Err(NullPointerError::for_item("BlockingTcpFrame") + .reason("BlockingTcpFrame has been consumed") + .into()); + }; + + let stream = stream.into_inner_no_leftover(); + + Ok(Box::new(StdTcpStream(Some(stream)))) + } + } + + #[diplomat::opaque] + pub struct BlockingUpgradedFrame(pub Option>); + + impl BlockingUpgradedFrame { + pub fn from_upgraded_stream( + stream: &mut crate::tls::ffi::UpgradedStream, + ) -> Result, Box> { + let Some(stream) = stream.0.take() else { + return Err(NullPointerError::for_item("upgraded_stream") + .reason("upgraded stream has been consumed") + .into()); + }; + + let framed = ironrdp_blocking::Framed::new(stream); + + Ok(Box::new(BlockingUpgradedFrame(Some(framed)))) + } + } + + #[diplomat::opaque] // Diplomat does not support direct function calls, so we need to wrap the function in a struct + pub struct IronRdpBlocking; + + #[diplomat::opaque] + pub struct ShouldUpgrade(pub Option); + + #[diplomat::opaque] + pub struct Upgraded(pub Option); + + impl IronRdpBlocking { + pub fn new() -> Box { + Box::new(IronRdpBlocking) + } + + pub fn connect_begin( + framed: &mut BlockingTcpFrame, + connector: &mut crate::connector::ffi::ClientConnector, + ) -> Result, Box> { + let Some(ref mut connector) = connector.0 else { + return Err(IronRdpErrorKind::NullPointer.into()); + }; + + let Some(framed) = framed.0.as_mut() else { + return Err(NullPointerError::for_item("framed") + .reason("framed has been consumed") + .into()); + }; + + let result = connect_begin(framed, connector)?; + + Ok(Box::new(ShouldUpgrade(Some(result)))) + } + + pub fn mark_as_upgraded( + should_upgrade: &mut ShouldUpgrade, + connector: &mut crate::connector::ffi::ClientConnector, + ) -> Result, Box> { + let Some(ref mut connector) = connector.0 else { + return Err(NullPointerError::for_item("connector") + .reason("inner connector is missing") + .into()); + }; + + let Some(should_upgrade) = should_upgrade.0.take() else { + return Err(NullPointerError::for_item("should_upgrade") + .reason("ShouldUpgrade is missing, Note: ShouldUpgrade should be used only once") + .into()); + }; + + let result = ironrdp_blocking::mark_as_upgraded(should_upgrade, connector); + + Ok(Box::new(Upgraded(Some(result)))) + } + + pub fn skip_connect_begin( + connector: &mut crate::connector::ffi::ClientConnector, + ) -> Result, Box> { + let Some(ref mut connector) = connector.0 else { + return Err(NullPointerError::for_item("connector") + .reason("inner connector is missing") + .into()); + }; + + let result = ironrdp_blocking::skip_connect_begin(connector); + + Ok(Box::new(ShouldUpgrade(Some(result)))) + } + + pub fn connect_finalize( + upgraded: &mut Upgraded, + upgraded_framed: &mut BlockingUpgradedFrame, + connector: &mut crate::connector::ffi::ClientConnector, + server_name: &crate::connector::ffi::ServerName, + server_public_key: &VecU8, + kerberos_config: Option<&crate::credssp::ffi::KerberosConfig>, + ) -> Result, Box> { + let Some(connector) = connector.0.take() else { + return Err(NullPointerError::for_item("connector") + .reason("inner connector is missing") + .into()); + }; + + let Some(upgraded) = upgraded.0.take() else { + return Err(NullPointerError::for_item("upgraded") + .reason("Upgraded inner is missing, Note: Upgraded should be used only once") + .into()); + }; + + let Some(framed) = upgraded_framed.0.as_mut() else { + return Err(NullPointerError::for_item("framed") + .reason("framed has been consumed") + .into()); + }; + + let server_name = server_name.0.clone(); + let mut network_client = network_client::reqwest_network_client::ReqwestNetworkClient::default(); + + let kerberos_config = kerberos_config.as_ref().map(|config| config.0.clone()); + + let result = ironrdp_blocking::connect_finalize( + upgraded, + framed, + connector, + server_name, + server_public_key.0.clone(), + &mut network_client, + kerberos_config, + )?; + + Ok(Box::new(ConnectionResult(result))) + } + } +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 619c58bbd..e3df24470 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -2,4 +2,8 @@ pub mod utils; pub mod connector; pub mod svc; pub mod dvc; -pub mod error; \ No newline at end of file +pub mod error; +pub mod pdu; +pub mod ironrdp_blocking; +pub mod credssp; +pub mod tls; \ No newline at end of file diff --git a/ffi/src/pdu.rs b/ffi/src/pdu.rs new file mode 100644 index 000000000..e69de29bb diff --git a/ffi/src/svc.rs b/ffi/src/svc.rs index b100ea17a..035978284 100644 --- a/ffi/src/svc.rs +++ b/ffi/src/svc.rs @@ -3,5 +3,5 @@ pub mod ffi { #[diplomat::opaque] - pub struct StaticChannelSet(pub ironrdp::svc::StaticChannelSet); + pub struct StaticChannelSet<'a>(pub &'a ironrdp::svc::StaticChannelSet); } \ No newline at end of file diff --git a/ffi/src/tls.rs b/ffi/src/tls.rs new file mode 100644 index 000000000..cf9decab4 --- /dev/null +++ b/ffi/src/tls.rs @@ -0,0 +1,145 @@ +use std::{io::Write, net::TcpStream}; + +use anyhow::Context; + +pub type TlsStream = rustls::StreamOwned; + +#[diplomat::bridge] +pub mod ffi { + use crate::{error::{ffi::IronRdpError, NullPointerError}, utils::ffi::VecU8}; + + use super::TlsStream; + + #[diplomat::opaque] + pub struct Tls; + + #[diplomat::opaque] + pub struct TlsUpgradeResult { + pub tls_stream: Option, + pub server_public_key: Vec, + } + + #[diplomat::opaque] + pub struct UpgradedStream(pub Option); + + impl TlsUpgradeResult { + + pub fn get_upgraded_stream(&mut self) -> Result, Box> { + let Some(tls_stream) = self.tls_stream.take() else { + return Err(NullPointerError::for_item("tls_stream") + .reason("tls_stream is missing") + .into()); + }; + + Ok(Box::new(UpgradedStream(Some(tls_stream)))) + } + + pub fn get_server_public_key(&self) -> Box { + VecU8::from_byte(&self.server_public_key) + } + + } + + impl Tls { + pub fn tls_upgrade( + stream: &mut crate::utils::ffi::StdTcpStream, + server_name: &str, + ) -> Result, Box> { + let Some(stream) = stream.0.take() else { + return Err(NullPointerError::for_item("stream") + .reason("inner stream is missing") + .into()); + }; + let (tls_stream, server_public_key) = super::tls_upgrade(stream, server_name).unwrap(); + + Ok(Box::new(TlsUpgradeResult { + tls_stream: Some(tls_stream), + server_public_key: server_public_key, + })) + } + } +} + +/// Copy and pased this code from the screenshot example, this is temporary and should be replaced with something more configurable +/// Need to put more thought into this +/// FIXME/TODO, Implement better TLS FFI interface +fn tls_upgrade( + stream: TcpStream, + server_name: &str, +) -> anyhow::Result<(rustls::StreamOwned, Vec)> { + let mut config = rustls::client::ClientConfig::builder() + .with_safe_defaults() + .with_custom_certificate_verifier(std::sync::Arc::new(danger::NoCertificateVerification)) + .with_no_client_auth(); + + // This adds support for the SSLKEYLOGFILE env variable (https://wiki.wireshark.org/TLS#using-the-pre-master-secret) + config.key_log = std::sync::Arc::new(rustls::KeyLogFile::new()); + + // Disable TLS resumption because it’s not supported by some services such as CredSSP. + // + // > The CredSSP Protocol does not extend the TLS wire protocol. TLS session resumption is not supported. + // + // source: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/385a7489-d46b-464c-b224-f7340e308a5c + config.resumption = rustls::client::Resumption::disabled(); + + let config = std::sync::Arc::new(config); + + let server_name = server_name.try_into().unwrap(); + + let client = rustls::ClientConnection::new(config, server_name)?; + + let mut tls_stream = rustls::StreamOwned::new(client, stream); + + // We need to flush in order to ensure the TLS handshake is moving forward. Without flushing, + // it’s likely the peer certificate is not yet received a this point. + tls_stream.flush()?; + + let cert = tls_stream + .conn + .peer_certificates() + .and_then(|certificates| certificates.first()) + .context("peer certificate is missing")?; + + let server_public_key = extract_tls_server_public_key(&cert.0)?; + + Ok((tls_stream, server_public_key)) +} + +fn extract_tls_server_public_key(cert: &[u8]) -> anyhow::Result> { + use x509_cert::der::Decode as _; + + let cert = x509_cert::Certificate::from_der(cert)?; + + let server_public_key = cert + .tbs_certificate + .subject_public_key_info + .subject_public_key + .as_bytes() + .context("subject public key BIT STRING is not aligned")? + .to_owned(); + + Ok(server_public_key) +} + +mod danger { + use std::time::SystemTime; + + use rustls::client::{ServerCertVerified, ServerCertVerifier}; + use rustls::{Certificate, Error, ServerName}; + + pub(super) struct NoCertificateVerification; + + impl ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _end_entity: &Certificate, + _intermediates: &[Certificate], + _server_name: &ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: SystemTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + } +} diff --git a/ffi/src/utils/mod.rs b/ffi/src/utils/mod.rs index 98fdabc20..b53270789 100644 --- a/ffi/src/utils/mod.rs +++ b/ffi/src/utils/mod.rs @@ -31,4 +31,7 @@ pub mod ffi { #[diplomat::opaque] pub struct Any<'a>(pub &'a dyn std::any::Any); + #[diplomat::opaque] + pub struct StdTcpStream(pub Option); + } \ No newline at end of file From 70f06fc765aac31a03337c5a462922165750b439 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 25 Mar 2024 16:35:43 -0400 Subject: [PATCH 03/44] WIP:Connector connects --- Cargo.lock | 95 +++- ffi/Cargo.toml | 10 + ffi/build.rs | 98 ++++ .../.gitignore | 2 + .../Devolutions.IronRdp.ConnectExample.csproj | 14 + .../Program.cs | 65 +++ ffi/dotnet/Devolutions.IronRdp/Class1.cs | 6 - .../Devolutions.IronRdp.csproj | 1 + .../Devolutions.IronRdp/Generated/Config.cs | 12 + .../Generated/ConfigBuilder.cs | 464 ++++++++++++++++++ .../Generated/KeyboardType.cs | 23 + .../Generated/OptionalUsize.cs | 95 ++++ .../Generated/PduHintResult.cs | 11 +- .../Generated/RawConfig.cs | 3 + .../Generated/RawConfigBuilder.cs | 78 +++ ...ConfigFfiResultBoxConfigBoxIronRdpError.cs | 46 ++ ...esultOptBoxOptionalUsizeBoxIronRdpError.cs | 46 ++ .../Generated/RawKeyboardType.cs | 23 + .../Generated/RawOptionalUsize.cs | 28 ++ .../Generated/RawPduHintResult.cs | 2 +- .../Generated/RawSocketAddr.cs | 3 + .../Generated/RawStdTcpStream.cs | 6 + ...lsFfiResultBoxSocketAddrBoxIronRdpError.cs | 46 ++ ...FfiResultBoxStdTcpStreamBoxIronRdpError.cs | 46 ++ ... RawUtilsFfiResultUsizeBoxIronRdpError.cs} | 6 +- .../RawUtilsFfiResultVoidBoxIronRdpError.cs | 36 ++ .../Generated/SocketAddr.cs | 23 + .../Generated/StdTcpStream.cs | 41 ++ ffi/justfile | 11 +- ffi/src/connector/config.rs | 196 ++++++++ ffi/src/connector/mod.rs | 26 +- ffi/src/connector/result.rs | 3 +- ffi/src/error.rs | 5 +- ffi/src/utils/mod.rs | 50 +- 34 files changed, 1579 insertions(+), 41 deletions(-) create mode 100644 ffi/build.rs create mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/.gitignore create mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/Devolutions.IronRdp.ConnectExample.csproj create mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Class1.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/ConfigBuilder.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/KeyboardType.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/OptionalUsize.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawConfigBuilder.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorConfigFfiResultBoxConfigBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawKeyboardType.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawOptionalUsize.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxSocketAddrBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxStdTcpStreamBoxIronRdpError.cs rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawConnectorFfiResultOptBoxUsizeBoxIronRdpError.cs => RawUtilsFfiResultUsizeBoxIronRdpError.cs} (85%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultVoidBoxIronRdpError.cs create mode 100644 ffi/src/connector/config.rs diff --git a/Cargo.lock b/Cargo.lock index f5f420329..c05ae95e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1034,6 +1034,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "embed-resource" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6985554d0688b687c5cb73898a34fbe3ad6c24c58c238a4d91d5e840670ee9d" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml", + "vswhom", + "winreg 0.52.0", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -1113,6 +1127,7 @@ dependencies = [ "anyhow", "diplomat", "diplomat-runtime", + "embed-resource", "ironrdp", "ironrdp-blocking", "rustls", @@ -2995,7 +3010,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] @@ -3557,6 +3572,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4127,11 +4151,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.9", +] + [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -4141,7 +4180,20 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.34", +] + +[[package]] +name = "toml_edit" +version = "0.22.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.5", ] [[package]] @@ -4392,6 +4444,26 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "wait-timeout" version = "0.2.0" @@ -4946,6 +5018,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -4966,6 +5047,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index fc959717b..133da7211 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -9,6 +9,13 @@ authors.workspace = true keywords.workspace = true categories.workspace = true +[lib] +name = "ironrdp" +crate-type = ["staticlib", "cdylib"] +doc = false +test = false +doctest = false + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] anyhow = "1.0.81" @@ -22,3 +29,6 @@ rustls-pemfile = "2.1" sspi = { workspace = true, features = ["network_client"] } thiserror.workspace = true tracing.workspace = true + +[target.'cfg(windows)'.build-dependencies] +embed-resource = "2.2.0" diff --git a/ffi/build.rs b/ffi/build.rs new file mode 100644 index 000000000..f07f82ba9 --- /dev/null +++ b/ffi/build.rs @@ -0,0 +1,98 @@ +#[cfg(not(target_os = "windows"))] +use other::main_stub; +#[cfg(target_os = "windows")] +use win::main_stub; + +fn main() { + main_stub(); +} + +#[cfg(target_os = "windows")] +mod win { + extern crate embed_resource; + + use std::env; + use std::fs::File; + use std::io::Write; + + fn generate_version_rc() -> String { + let output_name = "DevolutionsIronRdp"; + let filename = format!("{}.dll", output_name); + let company_name = "Devolutions Inc."; + let legal_copyright = format!("Copyright 2019-2022 {}", company_name); + + let version_number = env::var("CARGO_PKG_VERSION").unwrap() + ".0"; + let version_commas = version_number.replace('.', ","); + let file_description = output_name; + let file_version = version_number.clone(); + let internal_name = filename.clone(); + let original_filename = filename; + let product_name = output_name; + let product_version = version_number; + let vs_file_version = version_commas.clone(); + let vs_product_version = version_commas; + + let version_rc = format!( + r#"#include +VS_VERSION_INFO VERSIONINFO + FILEVERSION {vs_file_version} + PRODUCTVERSION {vs_product_version} + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "{company_name}" + VALUE "FileDescription", "{file_description}" + VALUE "FileVersion", "{file_version}" + VALUE "InternalName", "{internal_name}" + VALUE "LegalCopyright", "{legal_copyright}" + VALUE "OriginalFilename", "{original_filename}" + VALUE "ProductName", "{product_name}" + VALUE "ProductVersion", "{product_version}" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END +"#, + vs_file_version = vs_file_version, + vs_product_version = vs_product_version, + company_name = company_name, + file_description = file_description, + file_version = file_version, + internal_name = internal_name, + legal_copyright = legal_copyright, + original_filename = original_filename, + product_name = product_name, + product_version = product_version + ); + + version_rc + } + + pub fn main_stub() { + let out_dir = env::var("OUT_DIR").unwrap(); + let version_rc_file = format!("{}/version.rc", out_dir); + let version_rc_data = generate_version_rc(); + let mut file = File::create(&version_rc_file).expect("cannot create version.rc file"); + file.write_all(version_rc_data.as_bytes()).unwrap(); + embed_resource::compile(&version_rc_file, embed_resource::NONE); + } +} + +#[cfg(not(target_os = "windows"))] +mod other { + pub fn main_stub() {} +} diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.gitignore b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.gitignore new file mode 100644 index 000000000..2e9693ed1 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.gitignore @@ -0,0 +1,2 @@ +obj +bin \ No newline at end of file diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Devolutions.IronRdp.ConnectExample.csproj b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Devolutions.IronRdp.ConnectExample.csproj new file mode 100644 index 000000000..2e1eeb1b9 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Devolutions.IronRdp.ConnectExample.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs new file mode 100644 index 000000000..36aaabf0c --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -0,0 +1,65 @@ +using System; +using Devolutions.IronRdp; +namespace Devolutions.IronRdp.ConnectExample +{ + class Program + { + static void Main(string[] args) + { + try + { + var serverName = "IT-HELP-DC.ad.it-help.ninja"; + var username = "Administrator"; + var password = "DevoLabs123!"; + var domain = "ad.it-help.ninja"; + + Connect(serverName, username, password, domain); + } + catch (IronRdpException e) + { + var err = e.Inner.ToDisplay(); + Console.WriteLine(err); + } + } + + static void Connect(String servername, String username, String password, String domain) + { + SocketAddr serverAddr = SocketAddr.LookUp(servername, 3389); + + StdTcpStream stream = StdTcpStream.Connect(serverAddr); + + BlockingTcpFrame tcpFrame = BlockingTcpFrame.FromTcpStream(stream); + + ConfigBuilder configBuilder = ConfigBuilder.New(); + + // Password is wrong + configBuilder.WithUsernameAndPasswrord(username, password); + configBuilder.SetDomain(domain); + configBuilder.SetDesktopSize(800, 600); + configBuilder.SetClientName("IronRdp"); + configBuilder.SetClientDir("C:\\"); + + Config config = configBuilder.Build(); + + ClientConnector connector = ClientConnector.New(config); + connector.WithServerAddr(serverAddr); + + ShouldUpgrade shouldUpgrade = IronRdpBlocking.ConnectBegin(tcpFrame, connector); + + var tcpStream = tcpFrame.IntoTcpSteamNoLeftover(); + + var tlsUpgradeResult = Tls.TlsUpgrade(tcpStream, servername); + var upgraded = IronRdpBlocking.MarkAsUpgraded(shouldUpgrade, connector); + + var upgradedStream = tlsUpgradeResult.GetUpgradedStream(); + var serverPublicKey = tlsUpgradeResult.GetServerPublicKey(); + + var upgradedFrame = BlockingUpgradedFrame.FromUpgradedStream(upgradedStream); + + var serverName = ServerName.New(servername); + + var connectorResult = IronRdpBlocking.ConnectFinalize(upgraded, upgradedFrame, connector, serverName, serverPublicKey, null); + + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Class1.cs b/ffi/dotnet/Devolutions.IronRdp/Class1.cs deleted file mode 100644 index 8f4ebe3c9..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Class1.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Devolutions.IronRdp; - -public class Class1 -{ - -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Devolutions.IronRdp.csproj b/ffi/dotnet/Devolutions.IronRdp/Devolutions.IronRdp.csproj index fa71b7ae6..bd44ee5ae 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Devolutions.IronRdp.csproj +++ b/ffi/dotnet/Devolutions.IronRdp/Devolutions.IronRdp.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + true diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/Config.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/Config.cs index 75b53cf6e..e234f4823 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/Config.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/Config.cs @@ -29,6 +29,18 @@ public unsafe Config(Raw.Config* handle) _inner = handle; } + /// + /// A ConfigBuilder allocated on Rust side. + /// + public static ConfigBuilder GetBuilder() + { + unsafe + { + Raw.ConfigBuilder* retVal = Raw.Config.GetBuilder(); + return new ConfigBuilder(retVal); + } + } + /// /// Returns the underlying raw handle. /// diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ConfigBuilder.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ConfigBuilder.cs new file mode 100644 index 000000000..d8903664f --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ConfigBuilder.cs @@ -0,0 +1,464 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class ConfigBuilder: IDisposable +{ + private unsafe Raw.ConfigBuilder* _inner; + + public bool Autologon + { + set + { + SetAutologon(value); + } + } + + public uint ClientBuild + { + set + { + SetClientBuild(value); + } + } + + public string ClientDir + { + set + { + SetClientDir(value); + } + } + + public string ClientName + { + set + { + SetClientName(value); + } + } + + public string DigProductId + { + set + { + SetDigProductId(value); + } + } + + public string Domain + { + set + { + SetDomain(value); + } + } + + public bool EnableCredssp + { + set + { + SetEnableCredssp(value); + } + } + + public bool EnableTls + { + set + { + SetEnableTls(value); + } + } + + public GraphicsConfig Graphics + { + set + { + SetGraphics(value); + } + } + + public string ImeFileName + { + set + { + SetImeFileName(value); + } + } + + public uint KeyboardFunctionalKeysCount + { + set + { + SetKeyboardFunctionalKeysCount(value); + } + } + + public uint KeyboardSubtype + { + set + { + SetKeyboardSubtype(value); + } + } + + public KeyboardType KeyboardType + { + set + { + SetKeyboardType(value); + } + } + + public bool NoServerPointer + { + set + { + SetNoServerPointer(value); + } + } + + public bool PointerSoftwareRendering + { + set + { + SetPointerSoftwareRendering(value); + } + } + + /// + /// Creates a managed ConfigBuilder from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ConfigBuilder(Raw.ConfigBuilder* handle) + { + _inner = handle; + } + + /// + /// A ConfigBuilder allocated on Rust side. + /// + public static ConfigBuilder New() + { + unsafe + { + Raw.ConfigBuilder* retVal = Raw.ConfigBuilder.New(); + return new ConfigBuilder(retVal); + } + } + + public void WithUsernameAndPasswrord(string username, string password) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + byte[] usernameBuf = DiplomatUtils.StringToUtf8(username); + byte[] passwordBuf = DiplomatUtils.StringToUtf8(password); + nuint usernameBufLength = (nuint)usernameBuf.Length; + nuint passwordBufLength = (nuint)passwordBuf.Length; + fixed (byte* usernameBufPtr = usernameBuf) + { + fixed (byte* passwordBufPtr = passwordBuf) + { + Raw.ConfigBuilder.WithUsernameAndPasswrord(_inner, usernameBufPtr, usernameBufLength, passwordBufPtr, passwordBufLength); + } + } + } + } + + public void SetDomain(string domain) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + byte[] domainBuf = DiplomatUtils.StringToUtf8(domain); + nuint domainBufLength = (nuint)domainBuf.Length; + fixed (byte* domainBufPtr = domainBuf) + { + Raw.ConfigBuilder.SetDomain(_inner, domainBufPtr, domainBufLength); + } + } + } + + public void SetEnableTls(bool enableTls) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + Raw.ConfigBuilder.SetEnableTls(_inner, enableTls); + } + } + + public void SetEnableCredssp(bool enableCredssp) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + Raw.ConfigBuilder.SetEnableCredssp(_inner, enableCredssp); + } + } + + public void SetKeyboardType(KeyboardType keyboardType) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + Raw.KeyboardType keyboardTypeRaw; + keyboardTypeRaw = (Raw.KeyboardType)keyboardType; + Raw.ConfigBuilder.SetKeyboardType(_inner, keyboardTypeRaw); + } + } + + public void SetKeyboardSubtype(uint keyboardSubtype) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + Raw.ConfigBuilder.SetKeyboardSubtype(_inner, keyboardSubtype); + } + } + + public void SetKeyboardFunctionalKeysCount(uint keyboardFunctionalKeysCount) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + Raw.ConfigBuilder.SetKeyboardFunctionalKeysCount(_inner, keyboardFunctionalKeysCount); + } + } + + public void SetImeFileName(string imeFileName) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + byte[] imeFileNameBuf = DiplomatUtils.StringToUtf8(imeFileName); + nuint imeFileNameBufLength = (nuint)imeFileNameBuf.Length; + fixed (byte* imeFileNameBufPtr = imeFileNameBuf) + { + Raw.ConfigBuilder.SetImeFileName(_inner, imeFileNameBufPtr, imeFileNameBufLength); + } + } + } + + public void SetDigProductId(string digProductId) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + byte[] digProductIdBuf = DiplomatUtils.StringToUtf8(digProductId); + nuint digProductIdBufLength = (nuint)digProductIdBuf.Length; + fixed (byte* digProductIdBufPtr = digProductIdBuf) + { + Raw.ConfigBuilder.SetDigProductId(_inner, digProductIdBufPtr, digProductIdBufLength); + } + } + } + + public void SetDesktopSize(ushort height, ushort width) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + Raw.ConfigBuilder.SetDesktopSize(_inner, height, width); + } + } + + public void SetGraphics(GraphicsConfig graphics) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + Raw.GraphicsConfig* graphicsRaw; + graphicsRaw = graphics.AsFFI(); + if (graphicsRaw == null) + { + throw new ObjectDisposedException("GraphicsConfig"); + } + Raw.ConfigBuilder.SetGraphics(_inner, graphicsRaw); + } + } + + public void SetClientBuild(uint clientBuild) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + Raw.ConfigBuilder.SetClientBuild(_inner, clientBuild); + } + } + + public void SetClientName(string clientName) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + byte[] clientNameBuf = DiplomatUtils.StringToUtf8(clientName); + nuint clientNameBufLength = (nuint)clientNameBuf.Length; + fixed (byte* clientNameBufPtr = clientNameBuf) + { + Raw.ConfigBuilder.SetClientName(_inner, clientNameBufPtr, clientNameBufLength); + } + } + } + + public void SetClientDir(string clientDir) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + byte[] clientDirBuf = DiplomatUtils.StringToUtf8(clientDir); + nuint clientDirBufLength = (nuint)clientDirBuf.Length; + fixed (byte* clientDirBufPtr = clientDirBuf) + { + Raw.ConfigBuilder.SetClientDir(_inner, clientDirBufPtr, clientDirBufLength); + } + } + } + + public void SetNoServerPointer(bool noServerPointer) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + Raw.ConfigBuilder.SetNoServerPointer(_inner, noServerPointer); + } + } + + public void SetAutologon(bool autologon) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + Raw.ConfigBuilder.SetAutologon(_inner, autologon); + } + } + + public void SetPointerSoftwareRendering(bool pointerSoftwareRendering) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + Raw.ConfigBuilder.SetPointerSoftwareRendering(_inner, pointerSoftwareRendering); + } + } + + /// + /// + /// A Config allocated on Rust side. + /// + public Config Build() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ConfigBuilder"); + } + Raw.ConnectorConfigFfiResultBoxConfigBoxIronRdpError result = Raw.ConfigBuilder.Build(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.Config* retVal = result.Ok; + return new Config(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ConfigBuilder* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ConfigBuilder.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ConfigBuilder() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/KeyboardType.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/KeyboardType.cs new file mode 100644 index 000000000..fe13b4c60 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/KeyboardType.cs @@ -0,0 +1,23 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public enum KeyboardType +{ + IbmPcXt = 0, + OlivettiIco = 1, + IbmPcAt = 2, + IbmEnhanced = 3, + Nokia1050 = 4, + Nokia9140 = 5, + Japanese = 6, +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/OptionalUsize.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/OptionalUsize.cs new file mode 100644 index 000000000..1d8621a4c --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/OptionalUsize.cs @@ -0,0 +1,95 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class OptionalUsize: IDisposable +{ + private unsafe Raw.OptionalUsize* _inner; + + /// + /// Creates a managed OptionalUsize from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe OptionalUsize(Raw.OptionalUsize* handle) + { + _inner = handle; + } + + public bool IsSome() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("OptionalUsize"); + } + bool retVal = Raw.OptionalUsize.IsSome(_inner); + return retVal; + } + } + + /// + public nuint Get() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("OptionalUsize"); + } + Raw.UtilsFfiResultUsizeBoxIronRdpError result = Raw.OptionalUsize.Get(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + nuint retVal = result.Ok; + return retVal; + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.OptionalUsize* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.OptionalUsize.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~OptionalUsize() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/PduHintResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/PduHintResult.cs index 55b7d09ba..21fb58a02 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/PduHintResult.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/PduHintResult.cs @@ -43,7 +43,10 @@ public bool IsSome() } /// - public nuint FindSize(VecU8 buffer) + /// + /// A OptionalUsize allocated on Rust side. + /// + public OptionalUsize FindSize(VecU8 buffer) { unsafe { @@ -57,17 +60,17 @@ public nuint FindSize(VecU8 buffer) { throw new ObjectDisposedException("VecU8"); } - Raw.ConnectorFfiResultOptBoxUsizeBoxIronRdpError result = Raw.PduHintResult.FindSize(_inner, bufferRaw); + Raw.ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError result = Raw.PduHintResult.FindSize(_inner, bufferRaw); if (!result.isOk) { throw new IronRdpException(new IronRdpError(result.Err)); } - nuint* retVal = result.Ok; + Raw.OptionalUsize* retVal = result.Ok; if (retVal == null) { return null; } - return retVal; + return new OptionalUsize(retVal); } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfig.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfig.cs index a32a8b2fb..1eb7592e2 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfig.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfig.cs @@ -16,6 +16,9 @@ public partial struct Config { private const string NativeLib = "DevolutionsIronRdp"; + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Config_get_builder", ExactSpelling = true)] + public static unsafe extern ConfigBuilder* GetBuilder(); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Config_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(Config* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfigBuilder.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfigBuilder.cs new file mode 100644 index 000000000..829a257d3 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfigBuilder.cs @@ -0,0 +1,78 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ConfigBuilder +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_new", ExactSpelling = true)] + public static unsafe extern ConfigBuilder* New(); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_with_username_and_passwrord", ExactSpelling = true)] + public static unsafe extern void WithUsernameAndPasswrord(ConfigBuilder* self, byte* username, nuint usernameSz, byte* password, nuint passwordSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_domain", ExactSpelling = true)] + public static unsafe extern void SetDomain(ConfigBuilder* self, byte* domain, nuint domainSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_enable_tls", ExactSpelling = true)] + public static unsafe extern void SetEnableTls(ConfigBuilder* self, [MarshalAs(UnmanagedType.U1)] bool enableTls); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_enable_credssp", ExactSpelling = true)] + public static unsafe extern void SetEnableCredssp(ConfigBuilder* self, [MarshalAs(UnmanagedType.U1)] bool enableCredssp); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_keyboard_type", ExactSpelling = true)] + public static unsafe extern void SetKeyboardType(ConfigBuilder* self, KeyboardType keyboardType); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_keyboard_subtype", ExactSpelling = true)] + public static unsafe extern void SetKeyboardSubtype(ConfigBuilder* self, uint keyboardSubtype); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_keyboard_functional_keys_count", ExactSpelling = true)] + public static unsafe extern void SetKeyboardFunctionalKeysCount(ConfigBuilder* self, uint keyboardFunctionalKeysCount); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_ime_file_name", ExactSpelling = true)] + public static unsafe extern void SetImeFileName(ConfigBuilder* self, byte* imeFileName, nuint imeFileNameSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_dig_product_id", ExactSpelling = true)] + public static unsafe extern void SetDigProductId(ConfigBuilder* self, byte* digProductId, nuint digProductIdSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_desktop_size", ExactSpelling = true)] + public static unsafe extern void SetDesktopSize(ConfigBuilder* self, ushort height, ushort width); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_graphics", ExactSpelling = true)] + public static unsafe extern void SetGraphics(ConfigBuilder* self, GraphicsConfig* graphics); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_client_build", ExactSpelling = true)] + public static unsafe extern void SetClientBuild(ConfigBuilder* self, uint clientBuild); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_client_name", ExactSpelling = true)] + public static unsafe extern void SetClientName(ConfigBuilder* self, byte* clientName, nuint clientNameSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_client_dir", ExactSpelling = true)] + public static unsafe extern void SetClientDir(ConfigBuilder* self, byte* clientDir, nuint clientDirSz); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_no_server_pointer", ExactSpelling = true)] + public static unsafe extern void SetNoServerPointer(ConfigBuilder* self, [MarshalAs(UnmanagedType.U1)] bool noServerPointer); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_autologon", ExactSpelling = true)] + public static unsafe extern void SetAutologon(ConfigBuilder* self, [MarshalAs(UnmanagedType.U1)] bool autologon); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_pointer_software_rendering", ExactSpelling = true)] + public static unsafe extern void SetPointerSoftwareRendering(ConfigBuilder* self, [MarshalAs(UnmanagedType.U1)] bool pointerSoftwareRendering); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_build", ExactSpelling = true)] + public static unsafe extern ConnectorConfigFfiResultBoxConfigBoxIronRdpError Build(ConfigBuilder* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ConfigBuilder* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorConfigFfiResultBoxConfigBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorConfigFfiResultBoxConfigBoxIronRdpError.cs new file mode 100644 index 000000000..e2e20bf79 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorConfigFfiResultBoxConfigBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ConnectorConfigFfiResultBoxConfigBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal Config* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe Config* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError.cs new file mode 100644 index 000000000..5190f88d4 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal OptionalUsize* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe OptionalUsize* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawKeyboardType.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawKeyboardType.cs new file mode 100644 index 000000000..baf5eda99 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawKeyboardType.cs @@ -0,0 +1,23 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +public enum KeyboardType +{ + IbmPcXt = 0, + OlivettiIco = 1, + IbmPcAt = 2, + IbmEnhanced = 3, + Nokia1050 = 4, + Nokia9140 = 5, + Japanese = 6, +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawOptionalUsize.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawOptionalUsize.cs new file mode 100644 index 000000000..42f3cbd9e --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawOptionalUsize.cs @@ -0,0 +1,28 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct OptionalUsize +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "OptionalUsize_is_some", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool IsSome(OptionalUsize* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "OptionalUsize_get", ExactSpelling = true)] + public static unsafe extern UtilsFfiResultUsizeBoxIronRdpError Get(OptionalUsize* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "OptionalUsize_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(OptionalUsize* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHintResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHintResult.cs index 953fdfd9b..e7ac03e28 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHintResult.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHintResult.cs @@ -21,7 +21,7 @@ public partial struct PduHintResult public static unsafe extern bool IsSome(PduHintResult* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHintResult_find_size", ExactSpelling = true)] - public static unsafe extern ConnectorFfiResultOptBoxUsizeBoxIronRdpError FindSize(PduHintResult* self, VecU8* buffer); + public static unsafe extern ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError FindSize(PduHintResult* self, VecU8* buffer); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHintResult_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(PduHintResult* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs index f9ba151b3..d236be496 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs @@ -16,6 +16,9 @@ public partial struct SocketAddr { private const string NativeLib = "DevolutionsIronRdp"; + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SocketAddr_look_up", ExactSpelling = true)] + public static unsafe extern UtilsFfiResultBoxSocketAddrBoxIronRdpError LookUp(byte* host, nuint hostSz, ushort port); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SocketAddr_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(SocketAddr* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawStdTcpStream.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawStdTcpStream.cs index e67a2e4d3..423fc0ab5 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawStdTcpStream.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawStdTcpStream.cs @@ -16,6 +16,12 @@ public partial struct StdTcpStream { private const string NativeLib = "DevolutionsIronRdp"; + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "StdTcpStream_connect", ExactSpelling = true)] + public static unsafe extern UtilsFfiResultBoxStdTcpStreamBoxIronRdpError Connect(SocketAddr* addr); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "StdTcpStream_set_read_timeout", ExactSpelling = true)] + public static unsafe extern UtilsFfiResultVoidBoxIronRdpError SetReadTimeout(StdTcpStream* self); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "StdTcpStream_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(StdTcpStream* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxSocketAddrBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxSocketAddrBoxIronRdpError.cs new file mode 100644 index 000000000..2b5fe7c1c --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxSocketAddrBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct UtilsFfiResultBoxSocketAddrBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal SocketAddr* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe SocketAddr* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxStdTcpStreamBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxStdTcpStreamBoxIronRdpError.cs new file mode 100644 index 000000000..c3ca29c99 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxStdTcpStreamBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct UtilsFfiResultBoxStdTcpStreamBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal StdTcpStream* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe StdTcpStream* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxUsizeBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultUsizeBoxIronRdpError.cs similarity index 85% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxUsizeBoxIronRdpError.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultUsizeBoxIronRdpError.cs index 29db5b075..faaa2f53d 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxUsizeBoxIronRdpError.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultUsizeBoxIronRdpError.cs @@ -12,13 +12,13 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct ConnectorFfiResultOptBoxUsizeBoxIronRdpError +public partial struct UtilsFfiResultUsizeBoxIronRdpError { [StructLayout(LayoutKind.Explicit)] private unsafe struct InnerUnion { [FieldOffset(0)] - internal nuint* ok; + internal nuint ok; [FieldOffset(0)] internal IronRdpError* err; } @@ -28,7 +28,7 @@ private unsafe struct InnerUnion [MarshalAs(UnmanagedType.U1)] public bool isOk; - public unsafe nuint* Ok + public unsafe nuint Ok { get { diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultVoidBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultVoidBoxIronRdpError.cs new file mode 100644 index 000000000..686bbfb02 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultVoidBoxIronRdpError.cs @@ -0,0 +1,36 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct UtilsFfiResultVoidBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs index 34b79a736..dedf37e24 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs @@ -29,6 +29,29 @@ public unsafe SocketAddr(Raw.SocketAddr* handle) _inner = handle; } + /// + /// + /// A SocketAddr allocated on Rust side. + /// + public static SocketAddr LookUp(string host, ushort port) + { + unsafe + { + byte[] hostBuf = DiplomatUtils.StringToUtf8(host); + nuint hostBufLength = (nuint)hostBuf.Length; + fixed (byte* hostBufPtr = hostBuf) + { + Raw.UtilsFfiResultBoxSocketAddrBoxIronRdpError result = Raw.SocketAddr.LookUp(hostBufPtr, hostBufLength, port); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.SocketAddr* retVal = result.Ok; + return new SocketAddr(retVal); + } + } + } + /// /// Returns the underlying raw handle. /// diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/StdTcpStream.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/StdTcpStream.cs index 76bd147bc..96e2990b1 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/StdTcpStream.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/StdTcpStream.cs @@ -29,6 +29,47 @@ public unsafe StdTcpStream(Raw.StdTcpStream* handle) _inner = handle; } + /// + /// + /// A StdTcpStream allocated on Rust side. + /// + public static StdTcpStream Connect(SocketAddr addr) + { + unsafe + { + Raw.SocketAddr* addrRaw; + addrRaw = addr.AsFFI(); + if (addrRaw == null) + { + throw new ObjectDisposedException("SocketAddr"); + } + Raw.UtilsFfiResultBoxStdTcpStreamBoxIronRdpError result = Raw.StdTcpStream.Connect(addrRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.StdTcpStream* retVal = result.Ok; + return new StdTcpStream(retVal); + } + } + + /// + public void SetReadTimeout() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("StdTcpStream"); + } + Raw.UtilsFfiResultVoidBoxIronRdpError result = Raw.StdTcpStream.SetReadTimeout(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + } + } + /// /// Returns the underlying raw handle. /// diff --git a/ffi/justfile b/ffi/justfile index f1449ff9e..67ee324cd 100644 --- a/ffi/justfile +++ b/ffi/justfile @@ -4,7 +4,16 @@ defalt: bindings dotnet_generated_path := "./dotnet/Devolutions.IronRdp/Generated/" dotnet_diplomat_config := "./dotnet-interop-conf.toml" +dotnet_dll_name := "DevolutionsIronRdp.dll" +rust_built_dll_name := "ironrdp.dll" + +rust_build_result_diretory := "../target/debug/" + bindings: -rm {{dotnet_generated_path}}*.cs diplomat-tool dotnet {{dotnet_generated_path}} -l {{dotnet_diplomat_config}} - @echo ">> .NET wrapper generated at {{dotnet_generated_path}}" \ No newline at end of file + @echo ">> .NET wrapper generated at {{dotnet_generated_path}}" + +build-dll: + cargo build + cp {{rust_build_result_diretory}}{{rust_built_dll_name}} {{rust_build_result_diretory}}{{dotnet_dll_name}} \ No newline at end of file diff --git a/ffi/src/connector/config.rs b/ffi/src/connector/config.rs new file mode 100644 index 000000000..26775375e --- /dev/null +++ b/ffi/src/connector/config.rs @@ -0,0 +1,196 @@ +#[diplomat::bridge] +pub mod ffi { + use ironrdp::{ + connector::{BitmapConfig, Credentials}, + pdu::rdp::capability_sets::MajorPlatformType, + }; + + use crate::error::ffi::IronRdpError; + + #[diplomat::opaque] + pub struct Config(pub ironrdp::connector::Config); + + impl Config { + pub fn get_builder() -> Box { + Box::new(crate::connector::config::ffi::ConfigBuilder::default()) + } + } + + #[derive(Default)] + #[diplomat::opaque] + pub struct ConfigBuilder { + pub credentials: Option, + pub domain: Option, + pub enable_tls: Option, + pub enable_credssp: Option, + pub keyboard_type: Option, + pub keyboard_subtype: Option, + pub keyboard_functional_keys_count: Option, + pub ime_file_name: Option, + pub dig_product_id: Option, + pub desktop_size: Option, + pub graphics: Option, + pub bitmap: Option, + pub client_build: Option, + pub client_name: Option, + pub client_dir: Option, + pub platform: Option, + pub no_server_pointer: Option, + pub autologon: Option, + pub pointer_software_rendering: Option, + } + + #[diplomat::enum_convert(ironrdp::pdu::gcc::KeyboardType)] + pub enum KeyboardType { + IbmPcXt, + OlivettiIco, + IbmPcAt, + IbmEnhanced, + Nokia1050, + Nokia9140, + Japanese, + } + + #[diplomat::opaque] + pub struct DesktopSize(pub ironrdp::connector::DesktopSize); + + impl DesktopSize { + pub fn get_width(&self) -> u16 { + self.0.width + } + + pub fn get_height(&self) -> u16 { + self.0.height + } + } + + impl ConfigBuilder { + pub fn new() -> Box { + Box::new(Self::default()) + } + + pub fn with_username_and_passwrord(&mut self, username: &str, password: &str) { + self.credentials = Some(Credentials::UsernamePassword { + username: username.to_string(), + password: password.to_string(), + }); + } + + pub fn set_domain(&mut self, domain: &str) { + self.domain = Some(domain.to_string()); + } + + pub fn set_enable_tls(&mut self, enable_tls: bool) { + self.enable_tls = Some(enable_tls); + } + + pub fn set_enable_credssp(&mut self, enable_credssp: bool) { + self.enable_credssp = Some(enable_credssp); + } + + pub fn set_keyboard_type(&mut self, keyboard_type: KeyboardType) { + self.keyboard_type = Some(keyboard_type.into()); + } + + pub fn set_keyboard_subtype(&mut self, keyboard_subtype: u32) { + self.keyboard_subtype = Some(keyboard_subtype); + } + + pub fn set_keyboard_functional_keys_count(&mut self, keyboard_functional_keys_count: u32) { + self.keyboard_functional_keys_count = Some(keyboard_functional_keys_count); + } + + pub fn set_ime_file_name(&mut self, ime_file_name: &str) { + self.ime_file_name = Some(ime_file_name.to_string()); + } + + pub fn set_dig_product_id(&mut self, dig_product_id: &str) { + self.dig_product_id = Some(dig_product_id.to_string()); + } + + pub fn set_desktop_size(&mut self, height: u16, width: u16) { + self.desktop_size = Some(ironrdp::connector::DesktopSize { + width, + height, + }); + } + + pub fn set_graphics(&mut self, graphics: &crate::connector::result::ffi::GraphicsConfig) { + self.graphics = Some(graphics.0.clone()); + } + + // TODO: set bitmap + + pub fn set_client_build(&mut self, client_build: u32) { + self.client_build = Some(client_build); + } + + pub fn set_client_name(&mut self, client_name: &str) { + self.client_name = Some(client_name.to_string()); + } + + pub fn set_client_dir(&mut self, client_dir: &str) { + self.client_dir = Some(client_dir.to_string()); + } + + pub fn set_no_server_pointer(&mut self, no_server_pointer: bool) { + self.no_server_pointer = Some(no_server_pointer); + } + + pub fn set_autologon(&mut self, autologon: bool) { + self.autologon = Some(autologon); + } + + pub fn set_pointer_software_rendering(&mut self, pointer_software_rendering: bool) { + self.pointer_software_rendering = Some(pointer_software_rendering); + } + + pub fn build(&self) -> Result, Box> { + let inner_config = ironrdp::connector::Config { + credentials: self.credentials.clone().ok_or("Credentials not set")?, + domain: self.domain.clone(), + enable_tls: self.enable_tls.unwrap_or(false), + enable_credssp: self.enable_credssp.unwrap_or(true), + + keyboard_type: self + .keyboard_type + .unwrap_or(ironrdp::pdu::gcc::KeyboardType::IbmEnhanced), + keyboard_subtype: self.keyboard_subtype.unwrap_or(0), + keyboard_functional_keys_count: self.keyboard_functional_keys_count.unwrap_or(12), + ime_file_name: self.ime_file_name.clone().unwrap_or_default(), + dig_product_id: self.dig_product_id.clone().unwrap_or_default(), + desktop_size: self.desktop_size.clone().ok_or("Desktop size not set")?, + graphics: self.graphics.clone(), + bitmap: None, + client_build: self.client_build.unwrap_or(0), + client_name: self.client_name.clone().ok_or("Client name not set")?, + client_dir: self.client_dir.clone().ok_or("Client dir not set")?, + + #[cfg(windows)] + platform: MajorPlatformType::WINDOWS, + #[cfg(target_os = "macos")] + platform: MajorPlatformType::MACINTOSH, + #[cfg(target_os = "ios")] + platform: MajorPlatformType::IOS, + #[cfg(target_os = "linux")] + platform: MajorPlatformType::UNIX, + #[cfg(target_os = "android")] + platform: MajorPlatformType::ANDROID, + #[cfg(target_os = "freebsd")] + platform: MajorPlatformType::UNIX, + #[cfg(target_os = "dragonfly")] + platform: MajorPlatformType::UNIX, + #[cfg(target_os = "openbsd")] + platform: MajorPlatformType::UNIX, + #[cfg(target_os = "netbsd")] + platform: MajorPlatformType::UNIX, + + no_server_pointer: self.no_server_pointer.unwrap_or(false), + autologon: self.autologon.unwrap_or(false), + pointer_software_rendering: self.pointer_software_rendering.unwrap_or(false), + }; + + Ok(Box::new(Config(inner_config))) + } + } +} diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index 98e74fcd4..8426c4544 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -1,4 +1,5 @@ pub mod result; +pub mod config; #[diplomat::bridge] pub mod ffi { @@ -7,15 +8,18 @@ pub mod ffi { use std::fmt::Write; use crate::{ - error::{ffi::{IronRdpError, IronRdpErrorKind}, NullPointerError}, + error::{ + ffi::{IronRdpError, IronRdpErrorKind}, + NullPointerError, + }, utils::ffi::{SocketAddr, VecU8}, }; + use super::config::ffi::Config; + #[diplomat::opaque] // We must use Option here, as ClientConnector is not Clone and have functions that consume it pub struct ClientConnector(pub Option); - #[diplomat::opaque] - pub struct Config(pub ironrdp::connector::Config); #[diplomat::opaque] pub struct ClientConnectorState(pub ironrdp::connector::ClientConnectorState); @@ -115,14 +119,14 @@ pub mod ffi { self.0.is_some() } - pub fn find_size(&'a self, buffer: &VecU8) -> Result>, Box> { + pub fn find_size(&'a self, buffer: &VecU8) -> Result>, Box> { let Some(pdu_hint) = self.0 else { return Ok(None); }; let size = pdu_hint.find_size(buffer.0.as_slice())?; - size.map(|size| Ok(Box::new(size))).transpose() + Ok(Some(Box::new(crate::utils::ffi::OptionalUsize(size)))) } } @@ -167,16 +171,4 @@ pub mod ffi { } } - #[diplomat::opaque] - pub struct DesktopSize(pub ironrdp::connector::DesktopSize); - - impl DesktopSize { - pub fn get_width(&self) -> u16 { - self.0.width - } - - pub fn get_height(&self) -> u16 { - self.0.height - } - } } diff --git a/ffi/src/connector/result.rs b/ffi/src/connector/result.rs index e8fad2e91..9b5739828 100644 --- a/ffi/src/connector/result.rs +++ b/ffi/src/connector/result.rs @@ -1,6 +1,7 @@ #[diplomat::bridge] pub mod ffi { - use crate::connector::ffi::DesktopSize; + use crate::connector::config::ffi::DesktopSize; + #[diplomat::opaque] pub struct ConnectionResult(pub ironrdp::connector::ConnectionResult); diff --git a/ffi/src/error.rs b/ffi/src/error.rs index 7429a2f79..41a1cb1cd 100644 --- a/ffi/src/error.rs +++ b/ffi/src/error.rs @@ -125,7 +125,10 @@ impl NullPointerError { impl ToString for NullPointerError { fn to_string(&self) -> String { - format!("{}: {:?}", self.item, self.reason) + if let Some(reason) = &self.reason { + return format!("{}: {}", self.item, reason); + } + format!("{}: is consumed or never constructed", self.item) } } diff --git a/ffi/src/utils/mod.rs b/ffi/src/utils/mod.rs index b53270789..f30143504 100644 --- a/ffi/src/utils/mod.rs +++ b/ffi/src/utils/mod.rs @@ -1,24 +1,34 @@ - #[diplomat::bridge] pub mod ffi { + use crate::error::{ffi::IronRdpError, NullPointerError}; + #[diplomat::opaque] pub struct SocketAddr(pub std::net::SocketAddr); + impl SocketAddr { + pub fn look_up(host: &str, port: u16) -> Result, Box> { + use std::net::ToSocketAddrs as _; + let addr = (host, port) + .to_socket_addrs()? + .next() + .ok_or_else(|| "Failed to resolve address")?; + Ok(Box::new(SocketAddr(addr))) + } + } + #[diplomat::opaque] pub struct VecU8(pub Vec); impl VecU8 { - pub fn from_byte(bytes:&[u8]) -> Box { + pub fn from_byte(bytes: &[u8]) -> Box { Box::new(VecU8(bytes.to_vec())) } - pub fn get_size(&self) -> usize { self.0.len() } - pub fn fill(&self, buffer: &mut [u8]) { if buffer.len() < self.0.len() { //TODO: FIX: Should not panic, for prototype only @@ -34,4 +44,34 @@ pub mod ffi { #[diplomat::opaque] pub struct StdTcpStream(pub Option); -} \ No newline at end of file + impl StdTcpStream { + pub fn connect(addr: &SocketAddr) -> Result, Box> { + let stream = std::net::TcpStream::connect(addr.0.clone())?; + Ok(Box::new(StdTcpStream(Some(stream)))) + } + + pub fn set_read_timeout(&mut self) -> Result<(), Box> { + let stream = self + .0 + .as_ref() + .ok_or_else(|| NullPointerError::for_item("StdTcpStream"))?; + stream.set_read_timeout(Some(std::time::Duration::from_secs(5)))?; + Ok(()) + } + } + + + #[diplomat::opaque] + pub struct OptionalUsize(pub Option); + + impl OptionalUsize { + pub fn is_some(&self) -> bool { + self.0.is_some() + } + + pub fn get(&self) -> Result> { + self.0.ok_or_else(|| "Value is None".into()) + } + + } +} From 549b5d7bb14e275dbf75af893179e3ff4ddb713d Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 25 Mar 2024 16:39:21 -0400 Subject: [PATCH 04/44] CI --- ffi/src/connector/config.rs | 11 ++++------- ffi/src/connector/mod.rs | 21 +++++++++++--------- ffi/src/connector/result.rs | 5 +---- ffi/src/credssp.rs | 5 ++--- ffi/src/dvc.rs | 4 +--- ffi/src/error.rs | 39 ++++++++++++++++--------------------- ffi/src/ironrdp_blocking.rs | 2 +- ffi/src/lib.rs | 10 +++++----- ffi/src/pdu.rs | 1 + ffi/src/svc.rs | 3 +-- ffi/src/tls.rs | 9 +++++---- ffi/src/utils/mod.rs | 8 +++----- 12 files changed, 53 insertions(+), 65 deletions(-) diff --git a/ffi/src/connector/config.rs b/ffi/src/connector/config.rs index 26775375e..57e4eaea9 100644 --- a/ffi/src/connector/config.rs +++ b/ffi/src/connector/config.rs @@ -12,7 +12,7 @@ pub mod ffi { impl Config { pub fn get_builder() -> Box { - Box::new(crate::connector::config::ffi::ConfigBuilder::default()) + Box::::default() } } @@ -66,7 +66,7 @@ pub mod ffi { impl ConfigBuilder { pub fn new() -> Box { - Box::new(Self::default()) + Box::::default() } pub fn with_username_and_passwrord(&mut self, username: &str, password: &str) { @@ -109,10 +109,7 @@ pub mod ffi { } pub fn set_desktop_size(&mut self, height: u16, width: u16) { - self.desktop_size = Some(ironrdp::connector::DesktopSize { - width, - height, - }); + self.desktop_size = Some(ironrdp::connector::DesktopSize { width, height }); } pub fn set_graphics(&mut self, graphics: &crate::connector::result::ffi::GraphicsConfig) { @@ -159,7 +156,7 @@ pub mod ffi { keyboard_functional_keys_count: self.keyboard_functional_keys_count.unwrap_or(12), ime_file_name: self.ime_file_name.clone().unwrap_or_default(), dig_product_id: self.dig_product_id.clone().unwrap_or_default(), - desktop_size: self.desktop_size.clone().ok_or("Desktop size not set")?, + desktop_size: self.desktop_size.ok_or("Desktop size not set")?, graphics: self.graphics.clone(), bitmap: None, client_build: self.client_build.unwrap_or(0), diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index 8426c4544..fec0e19a4 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -1,5 +1,5 @@ -pub mod result; pub mod config; +pub mod result; #[diplomat::bridge] pub mod ffi { @@ -20,7 +20,6 @@ pub mod ffi { #[diplomat::opaque] // We must use Option here, as ClientConnector is not Clone and have functions that consume it pub struct ClientConnector(pub Option); - #[diplomat::opaque] pub struct ClientConnectorState(pub ironrdp::connector::ClientConnectorState); @@ -40,7 +39,7 @@ pub mod ffi { let Some(connector) = self.0.take() else { return Err(IronRdpErrorKind::NullPointer.into()); }; - let server_addr = server_addr.0.clone(); + let server_addr = server_addr.0; self.0 = Some(connector.with_server_addr(server_addr)); Ok(()) @@ -92,7 +91,8 @@ pub mod ffi { let Some(connector) = self.0.as_mut() else { return Err(NullPointerError::for_item("connector").into()); }; - Ok(connector.mark_security_upgrade_as_done()) + connector.mark_security_upgrade_as_done(); + Ok(()) } pub fn should_perform_credssp(&self) -> Result> { @@ -107,7 +107,8 @@ pub mod ffi { let Some(connector) = self.0.as_mut() else { return Err(NullPointerError::for_item("connector").into()); }; - Ok(connector.mark_credssp_as_done()) + connector.mark_credssp_as_done(); + Ok(()) } } @@ -119,7 +120,10 @@ pub mod ffi { self.0.is_some() } - pub fn find_size(&'a self, buffer: &VecU8) -> Result>, Box> { + pub fn find_size( + &'a self, + buffer: &VecU8, + ) -> Result>, Box> { let Some(pdu_hint) = self.0 else { return Ok(None); }; @@ -150,14 +154,14 @@ pub mod ffi { } impl ClientConnector { - pub fn next_pdu_hint<'a>(&'a self) -> Box> { + pub fn next_pdu_hint(&self) -> Box> { let Some(connector) = self.0.as_ref() else { panic!("Inner value is None") }; Box::new(PduHintResult(connector.next_pdu_hint())) } - pub fn state<'a>(&'a self) -> Box> { + pub fn state(&self) -> Box> { let Some(connector) = self.0.as_ref() else { panic!("Inner value is None") }; @@ -170,5 +174,4 @@ pub mod ffi { Box::new(ServerName(ironrdp::connector::ServerName::new(name))) } } - } diff --git a/ffi/src/connector/result.rs b/ffi/src/connector/result.rs index 9b5739828..4652029a0 100644 --- a/ffi/src/connector/result.rs +++ b/ffi/src/connector/result.rs @@ -2,7 +2,6 @@ pub mod ffi { use crate::connector::config::ffi::DesktopSize; - #[diplomat::opaque] pub struct ConnectionResult(pub ironrdp::connector::ConnectionResult); @@ -15,7 +14,7 @@ pub mod ffi { self.0.user_channel_id } - pub fn get_static_channels<'a>(&'a self) -> Box> { + pub fn get_static_channels(&self) -> Box> { Box::new(crate::svc::ffi::StaticChannelSet(&self.0.static_channels)) } @@ -40,7 +39,6 @@ pub mod ffi { pub struct GraphicsConfig(pub ironrdp::connector::GraphicsConfig); impl GraphicsConfig { - pub fn get_avc444(&self) -> bool { self.0.avc444 } @@ -60,6 +58,5 @@ pub mod ffi { pub fn get_capabilities(&self) -> u32 { self.0.capabilities } - } } diff --git a/ffi/src/credssp.rs b/ffi/src/credssp.rs index a05d8e27d..010d62aa1 100644 --- a/ffi/src/credssp.rs +++ b/ffi/src/credssp.rs @@ -1,7 +1,6 @@ - #[diplomat::bridge] pub mod ffi { - + #[diplomat::opaque] pub struct KerberosConfig(pub ironrdp::connector::credssp::KerberosConfig); -} \ No newline at end of file +} diff --git a/ffi/src/dvc.rs b/ffi/src/dvc.rs index 58e01dc50..1618237a1 100644 --- a/ffi/src/dvc.rs +++ b/ffi/src/dvc.rs @@ -1,8 +1,6 @@ - - #[diplomat::bridge] pub mod ffi { #[diplomat::opaque] pub struct DrdynvcChannel(pub ironrdp::dvc::DrdynvcClient); -} \ No newline at end of file +} diff --git a/ffi/src/error.rs b/ffi/src/error.rs index 41a1cb1cd..d5e0dbc66 100644 --- a/ffi/src/error.rs +++ b/ffi/src/error.rs @@ -2,9 +2,9 @@ use ironrdp::connector::ConnectorError; use self::ffi::IronRdpErrorKind; -impl Into for ConnectorError { - fn into(self) -> ffi::IronRdpErrorKind { - match self.kind { +impl From for ffi::IronRdpErrorKind { + fn from(val: ConnectorError) -> Self { + match val.kind { ironrdp::connector::ConnectorErrorKind::Pdu(_) => todo!(), ironrdp::connector::ConnectorErrorKind::Credssp(_) => todo!(), ironrdp::connector::ConnectorErrorKind::Reason(_) => todo!(), @@ -16,30 +16,26 @@ impl Into for ConnectorError { } } -impl Into for &str { - fn into(self) -> ffi::IronRdpErrorKind { +impl From<&str> for ffi::IronRdpErrorKind { + fn from(_val: &str) -> Self { ffi::IronRdpErrorKind::Generic } } -impl Into for ironrdp::pdu::PduError { - fn into(self) -> ffi::IronRdpErrorKind { - match self { - _ => ffi::IronRdpErrorKind::PduError, - } +impl From for ffi::IronRdpErrorKind { + fn from(_val: ironrdp::pdu::PduError) -> Self { + ffi::IronRdpErrorKind::PduError } } -impl Into for std::io::Error { - fn into(self) -> ffi::IronRdpErrorKind { - match self.kind() { - _ => ffi::IronRdpErrorKind::IO, - } +impl From for ffi::IronRdpErrorKind { + fn from(_: std::io::Error) -> Self { + ffi::IronRdpErrorKind::IO } } -impl Into for std::fmt::Error { - fn into(self) -> ffi::IronRdpErrorKind { +impl From for ffi::IronRdpErrorKind { + fn from(_val: std::fmt::Error) -> Self { ffi::IronRdpErrorKind::Generic } } @@ -102,9 +98,8 @@ pub mod ffi { } } - #[derive(Debug)] -pub struct NullPointerError{ +pub struct NullPointerError { item: String, reason: Option, } @@ -132,8 +127,8 @@ impl ToString for NullPointerError { } } -impl Into for NullPointerError { - fn into(self) -> IronRdpErrorKind { +impl From for IronRdpErrorKind { + fn from(_val: NullPointerError) -> Self { IronRdpErrorKind::NullPointer } -} \ No newline at end of file +} diff --git a/ffi/src/ironrdp_blocking.rs b/ffi/src/ironrdp_blocking.rs index 0de0ab8c4..1f81d3aef 100644 --- a/ffi/src/ironrdp_blocking.rs +++ b/ffi/src/ironrdp_blocking.rs @@ -156,7 +156,7 @@ pub mod ffi { }; let server_name = server_name.0.clone(); - let mut network_client = network_client::reqwest_network_client::ReqwestNetworkClient::default(); + let mut network_client = network_client::reqwest_network_client::ReqwestNetworkClient; let kerberos_config = kerberos_config.as_ref().map(|config| config.0.clone()); diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index e3df24470..73893dd45 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,9 +1,9 @@ -pub mod utils; pub mod connector; -pub mod svc; +pub mod credssp; pub mod dvc; pub mod error; -pub mod pdu; pub mod ironrdp_blocking; -pub mod credssp; -pub mod tls; \ No newline at end of file +pub mod pdu; +pub mod svc; +pub mod tls; +pub mod utils; diff --git a/ffi/src/pdu.rs b/ffi/src/pdu.rs index e69de29bb..8b1378917 100644 --- a/ffi/src/pdu.rs +++ b/ffi/src/pdu.rs @@ -0,0 +1 @@ + diff --git a/ffi/src/svc.rs b/ffi/src/svc.rs index 035978284..87e083bbd 100644 --- a/ffi/src/svc.rs +++ b/ffi/src/svc.rs @@ -1,7 +1,6 @@ - #[diplomat::bridge] pub mod ffi { #[diplomat::opaque] pub struct StaticChannelSet<'a>(pub &'a ironrdp::svc::StaticChannelSet); -} \ No newline at end of file +} diff --git a/ffi/src/tls.rs b/ffi/src/tls.rs index cf9decab4..5b6dc6125 100644 --- a/ffi/src/tls.rs +++ b/ffi/src/tls.rs @@ -6,7 +6,10 @@ pub type TlsStream = rustls::StreamOwned; #[diplomat::bridge] pub mod ffi { - use crate::{error::{ffi::IronRdpError, NullPointerError}, utils::ffi::VecU8}; + use crate::{ + error::{ffi::IronRdpError, NullPointerError}, + utils::ffi::VecU8, + }; use super::TlsStream; @@ -23,7 +26,6 @@ pub mod ffi { pub struct UpgradedStream(pub Option); impl TlsUpgradeResult { - pub fn get_upgraded_stream(&mut self) -> Result, Box> { let Some(tls_stream) = self.tls_stream.take() else { return Err(NullPointerError::for_item("tls_stream") @@ -37,7 +39,6 @@ pub mod ffi { pub fn get_server_public_key(&self) -> Box { VecU8::from_byte(&self.server_public_key) } - } impl Tls { @@ -54,7 +55,7 @@ pub mod ffi { Ok(Box::new(TlsUpgradeResult { tls_stream: Some(tls_stream), - server_public_key: server_public_key, + server_public_key, })) } } diff --git a/ffi/src/utils/mod.rs b/ffi/src/utils/mod.rs index f30143504..023611e40 100644 --- a/ffi/src/utils/mod.rs +++ b/ffi/src/utils/mod.rs @@ -12,7 +12,7 @@ pub mod ffi { let addr = (host, port) .to_socket_addrs()? .next() - .ok_or_else(|| "Failed to resolve address")?; + .ok_or("Failed to resolve address")?; Ok(Box::new(SocketAddr(addr))) } } @@ -46,7 +46,7 @@ pub mod ffi { impl StdTcpStream { pub fn connect(addr: &SocketAddr) -> Result, Box> { - let stream = std::net::TcpStream::connect(addr.0.clone())?; + let stream = std::net::TcpStream::connect(addr.0)?; Ok(Box::new(StdTcpStream(Some(stream)))) } @@ -60,7 +60,6 @@ pub mod ffi { } } - #[diplomat::opaque] pub struct OptionalUsize(pub Option); @@ -69,9 +68,8 @@ pub mod ffi { self.0.is_some() } - pub fn get(&self) -> Result> { + pub fn get(&self) -> Result> { self.0.ok_or_else(|| "Value is None".into()) } - } } From 5360b5861626ee6f7182979f140fcdb69eab6bf8 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 25 Mar 2024 16:42:40 -0400 Subject: [PATCH 05/44] CI:typos --- ffi/justfile | 6 +++--- ffi/src/tls.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ffi/justfile b/ffi/justfile index 67ee324cd..3c83d5a21 100644 --- a/ffi/justfile +++ b/ffi/justfile @@ -1,5 +1,5 @@ -defalt: bindings +default: bindings dotnet_generated_path := "./dotnet/Devolutions.IronRdp/Generated/" dotnet_diplomat_config := "./dotnet-interop-conf.toml" @@ -7,7 +7,7 @@ dotnet_diplomat_config := "./dotnet-interop-conf.toml" dotnet_dll_name := "DevolutionsIronRdp.dll" rust_built_dll_name := "ironrdp.dll" -rust_build_result_diretory := "../target/debug/" +rust_build_result_directory := "../target/debug/" bindings: -rm {{dotnet_generated_path}}*.cs @@ -16,4 +16,4 @@ bindings: build-dll: cargo build - cp {{rust_build_result_diretory}}{{rust_built_dll_name}} {{rust_build_result_diretory}}{{dotnet_dll_name}} \ No newline at end of file + cp {{rust_build_result_directory}}{{rust_built_dll_name}} {{rust_build_result_directory}}{{dotnet_dll_name}} \ No newline at end of file diff --git a/ffi/src/tls.rs b/ffi/src/tls.rs index 5b6dc6125..d08a2f750 100644 --- a/ffi/src/tls.rs +++ b/ffi/src/tls.rs @@ -61,7 +61,7 @@ pub mod ffi { } } -/// Copy and pased this code from the screenshot example, this is temporary and should be replaced with something more configurable +/// Copy and parsed this code from the screenshot example, this is temporary and should be replaced with something more configurable /// Need to put more thought into this /// FIXME/TODO, Implement better TLS FFI interface fn tls_upgrade( From 5e652476b9161db1b6461998f9dda27ee6f06c75 Mon Sep 17 00:00:00 2001 From: irving ou Date: Tue, 26 Mar 2024 12:55:28 -0400 Subject: [PATCH 06/44] Review fix: CI,add xtask,address review comments --- Cargo.lock | 1 - ffi/Cargo.toml | 1 - ffi/build.rs | 7 +- .../Program.cs | 1 - .../Generated/ClientConnector.cs | 16 ++++- .../Generated/IronRdpError.cs | 15 ---- .../Generated/IronRdpErrorKind.cs | 3 +- .../Generated/RawClientConnector.cs | 4 +- ...fiResultBoxPduHintResultBoxIronRdpError.cs | 46 ++++++++++++ ...nnectorFfiResultBoxStateBoxIronRdpError.cs | 46 ++++++++++++ .../Generated/RawIronRdpError.cs | 6 -- .../Generated/RawIronRdpErrorKind.cs | 3 +- .../Generated/RawSocketAddr.cs | 3 + .../Generated/SocketAddr.cs | 23 ++++++ ffi/justfile | 19 ----- ffi/src/connector/config.rs | 16 ++--- ffi/src/connector/mod.rs | 31 ++++---- ffi/src/error.rs | 72 +++++++++---------- ffi/src/ironrdp_blocking.rs | 24 +++---- ffi/src/lib.rs | 5 +- ffi/src/pdu.rs | 1 - ffi/src/tls.rs | 6 +- ffi/src/utils/mod.rs | 10 ++- xtask/src/cli.rs | 14 ++++ xtask/src/ffi.rs | 50 +++++++++++++ xtask/src/main.rs | 3 + 26 files changed, 295 insertions(+), 131 deletions(-) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintResultBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxStateBoxIronRdpError.cs delete mode 100644 ffi/justfile delete mode 100644 ffi/src/pdu.rs create mode 100644 xtask/src/ffi.rs diff --git a/Cargo.lock b/Cargo.lock index c05ae95e3..a7dfd93ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1131,7 +1131,6 @@ dependencies = [ "ironrdp", "ironrdp-blocking", "rustls", - "rustls-pemfile 2.1.0", "sspi", "thiserror", "tracing", diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 133da7211..40fcc57f8 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -25,7 +25,6 @@ ironrdp = { workspace = true, features = ["connector", "dvc", "svc","rdpdr","rdp ironrdp-blocking = { path = "../crates/ironrdp-blocking" } rustls = "0.21" x509-cert = { version = "0.2", default-features = false, features = ["std"] } -rustls-pemfile = "2.1" sspi = { workspace = true, features = ["network_client"] } thiserror.workspace = true tracing.workspace = true diff --git a/ffi/build.rs b/ffi/build.rs index f07f82ba9..946a2761b 100644 --- a/ffi/build.rs +++ b/ffi/build.rs @@ -21,7 +21,10 @@ mod win { let company_name = "Devolutions Inc."; let legal_copyright = format!("Copyright 2019-2022 {}", company_name); - let version_number = env::var("CARGO_PKG_VERSION").unwrap() + ".0"; + let mut cargo_version = env::var("CARGO_PKG_VERSION").unwrap(); + cargo_version.push_str(".0"); + + let version_number = cargo_version; let version_commas = version_number.replace('.', ","); let file_description = output_name; let file_version = version_number.clone(); @@ -82,7 +85,7 @@ END version_rc } - pub fn main_stub() { + pub(crate) fn main_stub() { let out_dir = env::var("OUT_DIR").unwrap(); let version_rc_file = format!("{}/version.rc", out_dir); let version_rc_data = generate_version_rc(); diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index 36aaabf0c..71c231bca 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -32,7 +32,6 @@ static void Connect(String servername, String username, String password, String ConfigBuilder configBuilder = ConfigBuilder.New(); - // Password is wrong configBuilder.WithUsernameAndPasswrord(username, password); configBuilder.SetDomain(domain); configBuilder.SetDesktopSize(800, 600); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs index 7ee01a935..9d3760695 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs @@ -190,6 +190,7 @@ public void MarkCredsspAsDone() } } + /// /// /// A PduHintResult allocated on Rust side. /// @@ -201,11 +202,17 @@ public PduHintResult NextPduHint() { throw new ObjectDisposedException("ClientConnector"); } - Raw.PduHintResult* retVal = Raw.ClientConnector.NextPduHint(_inner); + Raw.ConnectorFfiResultBoxPduHintResultBoxIronRdpError result = Raw.ClientConnector.NextPduHint(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.PduHintResult* retVal = result.Ok; return new PduHintResult(retVal); } } + /// /// /// A State allocated on Rust side. /// @@ -217,7 +224,12 @@ public State State() { throw new ObjectDisposedException("ClientConnector"); } - Raw.State* retVal = Raw.ClientConnector.State(_inner); + Raw.ConnectorFfiResultBoxStateBoxIronRdpError result = Raw.ClientConnector.State(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.State* retVal = result.Ok; return new State(retVal); } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpError.cs index 763ee69ca..d2c25abf9 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpError.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpError.cs @@ -74,21 +74,6 @@ public string ToDisplay() } } - /// - /// Prints the error string. - /// - public void Print() - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("IronRdpError"); - } - Raw.IronRdpError.Print(_inner); - } - } - /// /// Returns the error kind. /// diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs index eeb056b7d..87c8efb18 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs @@ -16,6 +16,7 @@ public enum IronRdpErrorKind Generic = 0, PduError = 1, SspiError = 2, - NullPointer = 3, + Consumed = 3, IO = 4, + AccessDenied = 5, } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs index 0b607ed4c..f802892c0 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs @@ -50,10 +50,10 @@ public partial struct ClientConnector public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError MarkCredsspAsDone(ClientConnector* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_next_pdu_hint", ExactSpelling = true)] - public static unsafe extern PduHintResult* NextPduHint(ClientConnector* self); + public static unsafe extern ConnectorFfiResultBoxPduHintResultBoxIronRdpError NextPduHint(ClientConnector* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_state", ExactSpelling = true)] - public static unsafe extern State* State(ClientConnector* self); + public static unsafe extern ConnectorFfiResultBoxStateBoxIronRdpError State(ClientConnector* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(ClientConnector* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintResultBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintResultBoxIronRdpError.cs new file mode 100644 index 000000000..85a17c69a --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintResultBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ConnectorFfiResultBoxPduHintResultBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal PduHintResult* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe PduHintResult* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxStateBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxStateBoxIronRdpError.cs new file mode 100644 index 000000000..52c4cae5f --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxStateBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ConnectorFfiResultBoxStateBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal State* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe State* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpError.cs index ffd7d40c2..680d5d9df 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpError.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpError.cs @@ -25,12 +25,6 @@ public partial struct IronRdpError [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpError_to_display", ExactSpelling = true)] public static unsafe extern void ToDisplay(IronRdpError* self, DiplomatWriteable* writeable); - /// - /// Prints the error string. - /// - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpError_print", ExactSpelling = true)] - public static unsafe extern void Print(IronRdpError* self); - /// /// Returns the error kind. /// diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs index 33416f5d3..8243a9d50 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs @@ -16,6 +16,7 @@ public enum IronRdpErrorKind Generic = 0, PduError = 1, SspiError = 2, - NullPointer = 3, + Consumed = 3, IO = 4, + AccessDenied = 5, } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs index d236be496..c5131a9a7 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs @@ -19,6 +19,9 @@ public partial struct SocketAddr [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SocketAddr_look_up", ExactSpelling = true)] public static unsafe extern UtilsFfiResultBoxSocketAddrBoxIronRdpError LookUp(byte* host, nuint hostSz, ushort port); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SocketAddr_from_ffi_str", ExactSpelling = true)] + public static unsafe extern UtilsFfiResultBoxSocketAddrBoxIronRdpError FromFfiStr(byte* addr, nuint addrSz); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SocketAddr_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(SocketAddr* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs index dedf37e24..8cca7360c 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs @@ -52,6 +52,29 @@ public static SocketAddr LookUp(string host, ushort port) } } + /// + /// + /// A SocketAddr allocated on Rust side. + /// + public static SocketAddr FromFfiStr(string addr) + { + unsafe + { + byte[] addrBuf = DiplomatUtils.StringToUtf8(addr); + nuint addrBufLength = (nuint)addrBuf.Length; + fixed (byte* addrBufPtr = addrBuf) + { + Raw.UtilsFfiResultBoxSocketAddrBoxIronRdpError result = Raw.SocketAddr.FromFfiStr(addrBufPtr, addrBufLength); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.SocketAddr* retVal = result.Ok; + return new SocketAddr(retVal); + } + } + } + /// /// Returns the underlying raw handle. /// diff --git a/ffi/justfile b/ffi/justfile deleted file mode 100644 index 3c83d5a21..000000000 --- a/ffi/justfile +++ /dev/null @@ -1,19 +0,0 @@ - -default: bindings - -dotnet_generated_path := "./dotnet/Devolutions.IronRdp/Generated/" -dotnet_diplomat_config := "./dotnet-interop-conf.toml" - -dotnet_dll_name := "DevolutionsIronRdp.dll" -rust_built_dll_name := "ironrdp.dll" - -rust_build_result_directory := "../target/debug/" - -bindings: - -rm {{dotnet_generated_path}}*.cs - diplomat-tool dotnet {{dotnet_generated_path}} -l {{dotnet_diplomat_config}} - @echo ">> .NET wrapper generated at {{dotnet_generated_path}}" - -build-dll: - cargo build - cp {{rust_build_result_directory}}{{rust_built_dll_name}} {{rust_build_result_directory}}{{dotnet_dll_name}} \ No newline at end of file diff --git a/ffi/src/connector/config.rs b/ffi/src/connector/config.rs index 57e4eaea9..ffccf746c 100644 --- a/ffi/src/connector/config.rs +++ b/ffi/src/connector/config.rs @@ -11,7 +11,7 @@ pub mod ffi { pub struct Config(pub ironrdp::connector::Config); impl Config { - pub fn get_builder() -> Box { + pub fn get_builder() -> Box { Box::::default() } } @@ -71,13 +71,13 @@ pub mod ffi { pub fn with_username_and_passwrord(&mut self, username: &str, password: &str) { self.credentials = Some(Credentials::UsernamePassword { - username: username.to_string(), - password: password.to_string(), + username: username.to_owned(), + password: password.to_owned(), }); } pub fn set_domain(&mut self, domain: &str) { - self.domain = Some(domain.to_string()); + self.domain = Some(domain.to_owned()); } pub fn set_enable_tls(&mut self, enable_tls: bool) { @@ -101,11 +101,11 @@ pub mod ffi { } pub fn set_ime_file_name(&mut self, ime_file_name: &str) { - self.ime_file_name = Some(ime_file_name.to_string()); + self.ime_file_name = Some(ime_file_name.to_owned()); } pub fn set_dig_product_id(&mut self, dig_product_id: &str) { - self.dig_product_id = Some(dig_product_id.to_string()); + self.dig_product_id = Some(dig_product_id.to_owned()); } pub fn set_desktop_size(&mut self, height: u16, width: u16) { @@ -123,11 +123,11 @@ pub mod ffi { } pub fn set_client_name(&mut self, client_name: &str) { - self.client_name = Some(client_name.to_string()); + self.client_name = Some(client_name.to_owned()); } pub fn set_client_dir(&mut self, client_dir: &str) { - self.client_dir = Some(client_dir.to_string()); + self.client_dir = Some(client_dir.to_owned()); } pub fn set_no_server_pointer(&mut self, no_server_pointer: bool) { diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index fec0e19a4..d9681b4c6 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -1,3 +1,4 @@ +#![allow(clippy::unnecessary_box_returns)] // Diplomat requires returning Boxed types pub mod config; pub mod result; @@ -10,7 +11,7 @@ pub mod ffi { use crate::{ error::{ ffi::{IronRdpError, IronRdpErrorKind}, - NullPointerError, + ValueConsumedError, }, utils::ffi::{SocketAddr, VecU8}, }; @@ -37,7 +38,7 @@ pub mod ffi { /// Must use pub fn with_server_addr(&mut self, server_addr: &SocketAddr) -> Result<(), Box> { let Some(connector) = self.0.take() else { - return Err(IronRdpErrorKind::NullPointer.into()); + return Err(IronRdpErrorKind::Consumed.into()); }; let server_addr = server_addr.0; self.0 = Some(connector.with_server_addr(server_addr)); @@ -49,7 +50,7 @@ pub mod ffi { /// Must use pub fn with_static_channel_rdp_snd(&mut self) -> Result<(), Box> { let Some(connector) = self.0.take() else { - return Err(IronRdpErrorKind::NullPointer.into()); + return Err(IronRdpErrorKind::Consumed.into()); }; self.0 = Some(connector.with_static_channel(ironrdp::rdpsnd::Rdpsnd::new())); @@ -65,13 +66,13 @@ pub mod ffi { smart_card_device_id: u32, ) -> Result<(), Box> { let Some(connector) = self.0.take() else { - return Err(IronRdpErrorKind::NullPointer.into()); + return Err(ValueConsumedError::for_item("connector").into()); }; self.0 = Some( connector.with_static_channel( ironrdp::rdpdr::Rdpdr::new( Box::new(ironrdp::rdpdr::NoopRdpdrBackend {}), - computer_name.to_string(), + computer_name.to_owned(), ) .with_smartcard(smart_card_device_id), ), @@ -82,14 +83,14 @@ pub mod ffi { pub fn should_perform_security_upgrade(&self) -> Result> { let Some(connector) = self.0.as_ref() else { - return Err(NullPointerError::for_item("connector").into()); + return Err(ValueConsumedError::for_item("connector").into()); }; Ok(connector.should_perform_security_upgrade()) } pub fn mark_security_upgrade_as_done(&mut self) -> Result<(), Box> { let Some(connector) = self.0.as_mut() else { - return Err(NullPointerError::for_item("connector").into()); + return Err(ValueConsumedError::for_item("connector").into()); }; connector.mark_security_upgrade_as_done(); Ok(()) @@ -97,7 +98,7 @@ pub mod ffi { pub fn should_perform_credssp(&self) -> Result> { let Some(connector) = self.0.as_ref() else { - return Err(NullPointerError::for_item("connector").into()); + return Err(ValueConsumedError::for_item("connector").into()); }; Ok(connector.should_perform_credssp()) @@ -105,7 +106,7 @@ pub mod ffi { pub fn mark_credssp_as_done(&mut self) -> Result<(), Box> { let Some(connector) = self.0.as_mut() else { - return Err(NullPointerError::for_item("connector").into()); + return Err(ValueConsumedError::for_item("connector").into()); }; connector.mark_credssp_as_done(); Ok(()) @@ -154,18 +155,18 @@ pub mod ffi { } impl ClientConnector { - pub fn next_pdu_hint(&self) -> Box> { + pub fn next_pdu_hint(&self) -> Result>, Box> { let Some(connector) = self.0.as_ref() else { - panic!("Inner value is None") + return Err(ValueConsumedError::for_item("connector").into()); }; - Box::new(PduHintResult(connector.next_pdu_hint())) + Ok(Box::new(PduHintResult(connector.next_pdu_hint()))) } - pub fn state(&self) -> Box> { + pub fn state(&self) -> Result>, Box> { let Some(connector) = self.0.as_ref() else { - panic!("Inner value is None") + return Err(ValueConsumedError::for_item("connector").into()); }; - Box::new(State(connector.state())) + Ok(Box::new(State(connector.state()))) } } diff --git a/ffi/src/error.rs b/ffi/src/error.rs index d5e0dbc66..ad18d649e 100644 --- a/ffi/src/error.rs +++ b/ffi/src/error.rs @@ -1,48 +1,47 @@ +#![allow(clippy::return_self_not_must_use)] use ironrdp::connector::ConnectorError; use self::ffi::IronRdpErrorKind; -impl From for ffi::IronRdpErrorKind { + +impl From for IronRdpErrorKind { fn from(val: ConnectorError) -> Self { match val.kind { - ironrdp::connector::ConnectorErrorKind::Pdu(_) => todo!(), - ironrdp::connector::ConnectorErrorKind::Credssp(_) => todo!(), - ironrdp::connector::ConnectorErrorKind::Reason(_) => todo!(), - ironrdp::connector::ConnectorErrorKind::AccessDenied => todo!(), - ironrdp::connector::ConnectorErrorKind::General => todo!(), - ironrdp::connector::ConnectorErrorKind::Custom => todo!(), - _ => todo!(), + ironrdp::connector::ConnectorErrorKind::Pdu(_) => IronRdpErrorKind::PduError, + ironrdp::connector::ConnectorErrorKind::Credssp(_) => IronRdpErrorKind::SspiError, + ironrdp::connector::ConnectorErrorKind::AccessDenied => IronRdpErrorKind::AccessDenied, + _ => IronRdpErrorKind::Generic, } } } -impl From<&str> for ffi::IronRdpErrorKind { +impl From<&str> for IronRdpErrorKind { fn from(_val: &str) -> Self { - ffi::IronRdpErrorKind::Generic + IronRdpErrorKind::Generic } } -impl From for ffi::IronRdpErrorKind { +impl From for IronRdpErrorKind { fn from(_val: ironrdp::pdu::PduError) -> Self { - ffi::IronRdpErrorKind::PduError + IronRdpErrorKind::PduError } } -impl From for ffi::IronRdpErrorKind { +impl From for IronRdpErrorKind { fn from(_: std::io::Error) -> Self { - ffi::IronRdpErrorKind::IO + IronRdpErrorKind::IO } } -impl From for ffi::IronRdpErrorKind { +impl From for IronRdpErrorKind { fn from(_val: std::fmt::Error) -> Self { - ffi::IronRdpErrorKind::Generic + IronRdpErrorKind::Generic } } impl From for Box where - T: Into + ToString, + T: Into + ToString, { fn from(value: T) -> Self { let repr = value.to_string(); @@ -52,8 +51,8 @@ where } struct IronRdpErrorInner { - pub repr: String, - pub kind: ffi::IronRdpErrorKind, + repr: String, + kind: IronRdpErrorKind, } #[diplomat::bridge] @@ -69,10 +68,12 @@ pub mod ffi { PduError, #[error("CredSSP error")] SspiError, - #[error("Null pointer error")] - NullPointer, + #[error("Value is consumed")] + Consumed, #[error("IO error")] IO, + #[error("Access denied")] + AccessDenied, } /// Stringified Picky error along with an error kind. @@ -86,11 +87,6 @@ pub mod ffi { writeable.flush(); } - /// Prints the error string. - pub fn print(&self) { - println!("{}", self.0.repr); - } - /// Returns the error kind. pub fn get_kind(&self) -> IronRdpErrorKind { self.0.kind @@ -99,26 +95,26 @@ pub mod ffi { } #[derive(Debug)] -pub struct NullPointerError { +pub struct ValueConsumedError { item: String, reason: Option, } -impl NullPointerError { - pub fn for_item(item: &str) -> NullPointerError { - NullPointerError { - item: item.to_string(), +impl ValueConsumedError { + pub fn for_item(item: &str) -> ValueConsumedError { + ValueConsumedError { + item: item.to_owned(), reason: None, } } - pub fn reason(mut self, reason: &str) -> NullPointerError { - self.reason = Some(reason.to_string()); + pub fn reason(mut self, reason: &str) -> ValueConsumedError { + self.reason = Some(reason.to_owned()); self } } -impl ToString for NullPointerError { +impl ToString for ValueConsumedError { fn to_string(&self) -> String { if let Some(reason) = &self.reason { return format!("{}: {}", self.item, reason); @@ -127,8 +123,8 @@ impl ToString for NullPointerError { } } -impl From for IronRdpErrorKind { - fn from(_val: NullPointerError) -> Self { - IronRdpErrorKind::NullPointer +impl From for IronRdpErrorKind { + fn from(_val: ValueConsumedError) -> Self { + IronRdpErrorKind::Consumed } -} +} \ No newline at end of file diff --git a/ffi/src/ironrdp_blocking.rs b/ffi/src/ironrdp_blocking.rs index 1f81d3aef..5863fe707 100644 --- a/ffi/src/ironrdp_blocking.rs +++ b/ffi/src/ironrdp_blocking.rs @@ -7,7 +7,7 @@ pub mod ffi { connector::result::ffi::ConnectionResult, error::{ ffi::{IronRdpError, IronRdpErrorKind}, - NullPointerError, + ValueConsumedError, }, tls::TlsStream, utils::ffi::{StdTcpStream, VecU8}, @@ -19,7 +19,7 @@ pub mod ffi { impl BlockingTcpFrame { pub fn from_tcp_stream(stream: &mut StdTcpStream) -> Result, Box> { let Some(stream) = stream.0.take() else { - return Err(NullPointerError::for_item("tcp_stream") + return Err(ValueConsumedError::for_item("tcp_stream") .reason("tcp stream has been consumed") .into()); }; @@ -31,7 +31,7 @@ pub mod ffi { pub fn into_tcp_steam_no_leftover(&mut self) -> Result, Box> { let Some(stream) = self.0.take() else { - return Err(NullPointerError::for_item("BlockingTcpFrame") + return Err(ValueConsumedError::for_item("BlockingTcpFrame") .reason("BlockingTcpFrame has been consumed") .into()); }; @@ -50,7 +50,7 @@ pub mod ffi { stream: &mut crate::tls::ffi::UpgradedStream, ) -> Result, Box> { let Some(stream) = stream.0.take() else { - return Err(NullPointerError::for_item("upgraded_stream") + return Err(ValueConsumedError::for_item("upgraded_stream") .reason("upgraded stream has been consumed") .into()); }; @@ -80,11 +80,11 @@ pub mod ffi { connector: &mut crate::connector::ffi::ClientConnector, ) -> Result, Box> { let Some(ref mut connector) = connector.0 else { - return Err(IronRdpErrorKind::NullPointer.into()); + return Err(IronRdpErrorKind::Consumed.into()); }; let Some(framed) = framed.0.as_mut() else { - return Err(NullPointerError::for_item("framed") + return Err(ValueConsumedError::for_item("framed") .reason("framed has been consumed") .into()); }; @@ -99,13 +99,13 @@ pub mod ffi { connector: &mut crate::connector::ffi::ClientConnector, ) -> Result, Box> { let Some(ref mut connector) = connector.0 else { - return Err(NullPointerError::for_item("connector") + return Err(ValueConsumedError::for_item("connector") .reason("inner connector is missing") .into()); }; let Some(should_upgrade) = should_upgrade.0.take() else { - return Err(NullPointerError::for_item("should_upgrade") + return Err(ValueConsumedError::for_item("should_upgrade") .reason("ShouldUpgrade is missing, Note: ShouldUpgrade should be used only once") .into()); }; @@ -119,7 +119,7 @@ pub mod ffi { connector: &mut crate::connector::ffi::ClientConnector, ) -> Result, Box> { let Some(ref mut connector) = connector.0 else { - return Err(NullPointerError::for_item("connector") + return Err(ValueConsumedError::for_item("connector") .reason("inner connector is missing") .into()); }; @@ -138,19 +138,19 @@ pub mod ffi { kerberos_config: Option<&crate::credssp::ffi::KerberosConfig>, ) -> Result, Box> { let Some(connector) = connector.0.take() else { - return Err(NullPointerError::for_item("connector") + return Err(ValueConsumedError::for_item("connector") .reason("inner connector is missing") .into()); }; let Some(upgraded) = upgraded.0.take() else { - return Err(NullPointerError::for_item("upgraded") + return Err(ValueConsumedError::for_item("upgraded") .reason("Upgraded inner is missing, Note: Upgraded should be used only once") .into()); }; let Some(framed) = upgraded_framed.0.as_mut() else { - return Err(NullPointerError::for_item("framed") + return Err(ValueConsumedError::for_item("framed") .reason("framed has been consumed") .into()); }; diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 73893dd45..52e9a6c13 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,9 +1,12 @@ +#![allow(clippy::unnecessary_box_returns)] // Diplomat requires returning Boxed types pub mod connector; pub mod credssp; pub mod dvc; pub mod error; pub mod ironrdp_blocking; -pub mod pdu; pub mod svc; pub mod tls; pub mod utils; + +use sspi as _; // we need this for network_client and avoid CI failure +use tracing as _; // need this in the future \ No newline at end of file diff --git a/ffi/src/pdu.rs b/ffi/src/pdu.rs deleted file mode 100644 index 8b1378917..000000000 --- a/ffi/src/pdu.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ffi/src/tls.rs b/ffi/src/tls.rs index d08a2f750..c960b7fd4 100644 --- a/ffi/src/tls.rs +++ b/ffi/src/tls.rs @@ -7,7 +7,7 @@ pub type TlsStream = rustls::StreamOwned; #[diplomat::bridge] pub mod ffi { use crate::{ - error::{ffi::IronRdpError, NullPointerError}, + error::{ffi::IronRdpError, ValueConsumedError}, utils::ffi::VecU8, }; @@ -28,7 +28,7 @@ pub mod ffi { impl TlsUpgradeResult { pub fn get_upgraded_stream(&mut self) -> Result, Box> { let Some(tls_stream) = self.tls_stream.take() else { - return Err(NullPointerError::for_item("tls_stream") + return Err(ValueConsumedError::for_item("tls_stream") .reason("tls_stream is missing") .into()); }; @@ -47,7 +47,7 @@ pub mod ffi { server_name: &str, ) -> Result, Box> { let Some(stream) = stream.0.take() else { - return Err(NullPointerError::for_item("stream") + return Err(ValueConsumedError::for_item("stream") .reason("inner stream is missing") .into()); }; diff --git a/ffi/src/utils/mod.rs b/ffi/src/utils/mod.rs index 023611e40..c63868e20 100644 --- a/ffi/src/utils/mod.rs +++ b/ffi/src/utils/mod.rs @@ -1,7 +1,7 @@ #[diplomat::bridge] pub mod ffi { - use crate::error::{ffi::IronRdpError, NullPointerError}; + use crate::error::{ffi::IronRdpError, ValueConsumedError}; #[diplomat::opaque] pub struct SocketAddr(pub std::net::SocketAddr); @@ -15,6 +15,12 @@ pub mod ffi { .ok_or("Failed to resolve address")?; Ok(Box::new(SocketAddr(addr))) } + + // named from_ffi_str to avoid conflict with std::net::SocketAddr::from_str + pub fn from_ffi_str(addr: &str) -> Result, Box> { + let addr = addr.parse().map_err(|_| "Failed to parse address")?; + Ok(Box::new(SocketAddr(addr))) + } } #[diplomat::opaque] @@ -54,7 +60,7 @@ pub mod ffi { let stream = self .0 .as_ref() - .ok_or_else(|| NullPointerError::for_item("StdTcpStream"))?; + .ok_or_else(|| ValueConsumedError::for_item("StdTcpStream"))?; stream.set_read_timeout(Some(std::time::Duration::from_secs(5)))?; Ok(()) } diff --git a/xtask/src/cli.rs b/xtask/src/cli.rs index 331eba208..b027ec141 100644 --- a/xtask/src/cli.rs +++ b/xtask/src/cli.rs @@ -36,6 +36,8 @@ TASKS: web check Ensure Web Client is building without error web install Install dependencies required to build and run Web Client web run Run SvelteKit-based standalone Web Client + ffi build [--release] Build DLL for FFI (default is debug) + ffi bindings Generate C# bindings for FFI "; pub fn print_help() { @@ -85,6 +87,10 @@ pub enum Action { WebCheck, WebInstall, WebRun, + FfiBuildDll { + debug: bool, + }, + FfiBuildBindings, } pub fn parse_args() -> anyhow::Result { @@ -152,6 +158,14 @@ pub fn parse_args() -> anyhow::Result { Some(unknown) => anyhow::bail!("unknown web action: {unknown}"), None => Action::ShowHelp, }, + Some("ffi") => match args.subcommand()?.as_deref() { + Some("build") => Action::FfiBuildDll { + debug: !args.contains("--release"), + }, + Some("bindings") => Action::FfiBuildBindings, + Some(unknown) => anyhow::bail!("unknown ffi action: {unknown}"), + None => Action::ShowHelp, + }, None | Some(_) => Action::ShowHelp, } }; diff --git a/xtask/src/ffi.rs b/xtask/src/ffi.rs new file mode 100644 index 000000000..84a0bdb15 --- /dev/null +++ b/xtask/src/ffi.rs @@ -0,0 +1,50 @@ +pub(crate) fn build_dll(sh: &xshell::Shell, debug: bool) -> anyhow::Result<()> { + let mut args = vec!["build", "--package", "ffi"]; + if !debug { + args.push("--release"); + } + sh.cmd("cargo").args(&args).run()?; + + Ok(()) +} + +use std::fs; +use std::path::Path; + +pub(crate) fn build_bindings(sh: &xshell::Shell) -> anyhow::Result<()> { + let dotnet_generated_path = "./dotnet/Devolutions.IronRdp/Generated/"; + let diplomat_config = "./dotnet-interop-conf.toml"; + + // Check if diplomat tool is installed + sh.change_dir("./ffi"); + let cwd = sh.current_dir(); + let generated_code_dir = cwd.join(dotnet_generated_path); + if !generated_code_dir.exists() { + anyhow::bail!("The directory {:?} does not exist", generated_code_dir); + } + remove_cs_files(&generated_code_dir)?; + + sh.cmd("diplomat-tool") + .arg("dotnet") + .arg(dotnet_generated_path) + .arg("-l") + .arg(diplomat_config) + .run()?; + + Ok(()) +} + +/// Removes all `.cs` files in the given directory. +fn remove_cs_files(dir: &Path) -> anyhow::Result<()> { + if dir.is_dir() { + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("cs") { + println!("Removing file: {:?}", path); + fs::remove_file(path)?; + } + } + } + Ok(()) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index d420de90b..a1695ab14 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -12,6 +12,7 @@ mod check; mod clean; mod cli; mod cov; +mod ffi; mod fuzz; mod prelude; mod section; @@ -111,6 +112,8 @@ fn main() -> anyhow::Result<()> { Action::WebCheck => web::check(&sh)?, Action::WebInstall => web::install(&sh)?, Action::WebRun => web::run(&sh)?, + Action::FfiBuildDll { debug } => ffi::build_dll(&sh, debug)?, + Action::FfiBuildBindings => ffi::build_bindings(&sh)?, } Ok(()) From 798758a6ca19e1226719bcc4fda1002075835ca4 Mon Sep 17 00:00:00 2001 From: irving ou Date: Tue, 26 Mar 2024 12:56:48 -0400 Subject: [PATCH 07/44] CI:fmt --- crates/ironrdp-testsuite-core/tests/pcb.rs | 6 ++---- ffi/src/connector/mod.rs | 7 ++----- ffi/src/error.rs | 3 +-- ffi/src/lib.rs | 2 +- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/ironrdp-testsuite-core/tests/pcb.rs b/crates/ironrdp-testsuite-core/tests/pcb.rs index 796b9c0a4..199118572 100644 --- a/crates/ironrdp-testsuite-core/tests/pcb.rs +++ b/crates/ironrdp-testsuite-core/tests/pcb.rs @@ -99,8 +99,7 @@ fn null_size() { .err() .unwrap(); - expect![ - [r#" + expect![[r#" Error { context: "PreconnectionBlob", kind: InvalidMessage { @@ -109,8 +108,7 @@ fn null_size() { }, source: None, } - "#] - ] + "#]] .assert_debug_eq(&e); } diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index d9681b4c6..5fee37b58 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -70,11 +70,8 @@ pub mod ffi { }; self.0 = Some( connector.with_static_channel( - ironrdp::rdpdr::Rdpdr::new( - Box::new(ironrdp::rdpdr::NoopRdpdrBackend {}), - computer_name.to_owned(), - ) - .with_smartcard(smart_card_device_id), + ironrdp::rdpdr::Rdpdr::new(Box::new(ironrdp::rdpdr::NoopRdpdrBackend {}), computer_name.to_owned()) + .with_smartcard(smart_card_device_id), ), ); diff --git a/ffi/src/error.rs b/ffi/src/error.rs index ad18d649e..7007d791a 100644 --- a/ffi/src/error.rs +++ b/ffi/src/error.rs @@ -3,7 +3,6 @@ use ironrdp::connector::ConnectorError; use self::ffi::IronRdpErrorKind; - impl From for IronRdpErrorKind { fn from(val: ConnectorError) -> Self { match val.kind { @@ -127,4 +126,4 @@ impl From for IronRdpErrorKind { fn from(_val: ValueConsumedError) -> Self { IronRdpErrorKind::Consumed } -} \ No newline at end of file +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 52e9a6c13..43a3562f7 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -9,4 +9,4 @@ pub mod tls; pub mod utils; use sspi as _; // we need this for network_client and avoid CI failure -use tracing as _; // need this in the future \ No newline at end of file +use tracing as _; // need this in the future From a8360240216a527ae5cab0e4014e18f07d2a5c0b Mon Sep 17 00:00:00 2001 From: irving ou Date: Tue, 26 Mar 2024 13:03:36 -0400 Subject: [PATCH 08/44] CI --- crates/ironrdp-testsuite-core/tests/pcb.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/ironrdp-testsuite-core/tests/pcb.rs b/crates/ironrdp-testsuite-core/tests/pcb.rs index 199118572..796b9c0a4 100644 --- a/crates/ironrdp-testsuite-core/tests/pcb.rs +++ b/crates/ironrdp-testsuite-core/tests/pcb.rs @@ -99,7 +99,8 @@ fn null_size() { .err() .unwrap(); - expect![[r#" + expect![ + [r#" Error { context: "PreconnectionBlob", kind: InvalidMessage { @@ -108,7 +109,8 @@ fn null_size() { }, source: None, } - "#]] + "#] + ] .assert_debug_eq(&e); } From f55ec36096c0414c6292635bc773cb08a66edb6e Mon Sep 17 00:00:00 2001 From: irving ou Date: Tue, 26 Mar 2024 13:15:19 -0400 Subject: [PATCH 09/44] CI --- ffi/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/build.rs b/ffi/build.rs index 946a2761b..655b2821b 100644 --- a/ffi/build.rs +++ b/ffi/build.rs @@ -97,5 +97,5 @@ END #[cfg(not(target_os = "windows"))] mod other { - pub fn main_stub() {} + pub(crate) fn main_stub() {} } From e978f7fb2ccf952d886a1bc50c25ca27cec151ca Mon Sep 17 00:00:00 2001 From: irving ou Date: Wed, 27 Mar 2024 14:58:03 -0400 Subject: [PATCH 10/44] WIP:review fix,expose sens-io instead --- .../Program.cs | 51 ++- .../Generated/ClientConnector.cs | 39 +- .../Generated/ClientState.cs | 119 ++++++ ...TcpFrame.cs => CredsspProcessGenerator.cs} | 48 +-- .../Generated/CredsspSequence.cs | 226 +++++++++++ .../Generated/CredsspSequenceInitResult.cs | 123 ++++++ .../Generated/GeneratorState.cs | 147 ++++++++ .../Generated/IronRdpBlocking.cs | 220 ----------- .../Generated/IronRdpErrorKind.cs | 2 +- .../{ShouldUpgrade.cs => NetworkRequest.cs} | 14 +- .../{PduHintResult.cs => PduHint.cs} | 22 +- .../Generated/RawBlockingTcpFrame.cs | 27 -- .../Generated/RawClientConnector.cs | 5 +- .../Generated/RawClientState.cs | 32 ++ ...ctorFfiResultBoxPduHintBoxIronRdpError.cs} | 6 +- ...CredsspProcessGeneratorBoxIronRdpError.cs} | 6 +- ...esultBoxCredsspSequenceBoxIronRdpError.cs} | 6 +- ...edsspSequenceInitResultBoxIronRdpError.cs} | 6 +- ...sspFfiResultBoxTsRequestBoxIronRdpError.cs | 46 +++ ...edsspFfiResultBoxWrittenBoxIronRdpError.cs | 46 +++ ...FfiResultOptBoxTsRequestBoxIronRdpError.cs | 46 +++ ...kFfiResultBoxClientStateBoxIronRdpError.cs | 46 +++ ...ResultBoxGeneratorStateBoxIronRdpError.cs} | 6 +- ...rkFfiResultBoxTsRequestBoxIronRdpError.cs} | 6 +- .../Generated/RawCredsspProcessGenerator.cs | 27 ++ .../Generated/RawCredsspSequence.cs | 36 ++ .../Generated/RawCredsspSequenceInitResult.cs | 27 ++ .../Generated/RawGeneratorState.cs | 35 ++ .../Generated/RawIronRdpBlocking.cs | 36 -- .../Generated/RawIronRdpErrorKind.cs | 2 +- ...esultBoxBlockingTcpFrameBoxIronRdpError.cs | 46 --- ...wShouldUpgrade.cs => RawNetworkRequest.cs} | 6 +- .../{RawPduHintResult.cs => RawPduHint.cs} | 14 +- .../{RawUpgraded.cs => RawTsRequest.cs} | 6 +- .../Devolutions.IronRdp/Generated/RawVecU8.cs | 3 + ...lockingUpgradedFrame.cs => RawWriteBuf.cs} | 13 +- .../Generated/RawWritten.cs | 28 ++ .../Generated/{Upgraded.cs => TsRequest.cs} | 14 +- .../Devolutions.IronRdp/Generated/VecU8.cs | 12 + .../Devolutions.IronRdp/Generated/WriteBuf.cs | 87 +++++ .../{BlockingUpgradedFrame.cs => Written.cs} | 55 +-- ffi/src/connector/mod.rs | 17 +- ffi/src/connector/result.rs | 18 +- ffi/src/credssp.rs | 6 - ffi/src/credssp/mod.rs | 102 +++++ ffi/src/credssp/network.rs | 76 ++++ ffi/src/error.rs | 10 +- ffi/src/ironrdp_blocking.rs | 352 +++++++++--------- ffi/src/lib.rs | 3 +- ffi/src/pdu.rs | 18 + ffi/src/utils/mod.rs | 4 + xtask/src/cli.rs | 15 +- xtask/src/ffi.rs | 17 +- xtask/src/main.rs | 4 +- 54 files changed, 1728 insertions(+), 656 deletions(-) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/ClientState.cs rename ffi/dotnet/Devolutions.IronRdp/Generated/{BlockingTcpFrame.cs => CredsspProcessGenerator.cs} (54%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequence.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequenceInitResult.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/GeneratorState.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpBlocking.cs rename ffi/dotnet/Devolutions.IronRdp/Generated/{ShouldUpgrade.cs => NetworkRequest.cs} (76%) rename ffi/dotnet/Devolutions.IronRdp/Generated/{PduHintResult.cs => PduHint.cs} (78%) delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingTcpFrame.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawClientState.cs rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawIronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError.cs => RawConnectorFfiResultBoxPduHintBoxIronRdpError.cs} (83%) rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawIronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError.cs => RawCredsspFfiResultBoxCredsspProcessGeneratorBoxIronRdpError.cs} (80%) rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawConnectorFfiResultBoxPduHintResultBoxIronRdpError.cs => RawCredsspFfiResultBoxCredsspSequenceBoxIronRdpError.cs} (84%) rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawIronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError.cs => RawCredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError.cs} (80%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxTsRequestBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxWrittenBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultOptBoxTsRequestBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultBoxClientStateBoxIronRdpError.cs rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawIronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError.cs => RawCredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError.cs} (84%) rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawIronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError.cs => RawCredsspNetworkFfiResultBoxTsRequestBoxIronRdpError.cs} (85%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspProcessGenerator.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequence.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequenceInitResult.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawGeneratorState.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpBlocking.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError.cs rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawShouldUpgrade.cs => RawNetworkRequest.cs} (69%) rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawPduHintResult.cs => RawPduHint.cs} (59%) rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawUpgraded.cs => RawTsRequest.cs} (71%) rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawBlockingUpgradedFrame.cs => RawWriteBuf.cs} (50%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawWritten.cs rename ffi/dotnet/Devolutions.IronRdp/Generated/{Upgraded.cs => TsRequest.cs} (78%) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/WriteBuf.cs rename ffi/dotnet/Devolutions.IronRdp/Generated/{BlockingUpgradedFrame.cs => Written.cs} (52%) delete mode 100644 ffi/src/credssp.rs create mode 100644 ffi/src/credssp/mod.rs create mode 100644 ffi/src/credssp/network.rs create mode 100644 ffi/src/pdu.rs diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index 71c231bca..7df95ac38 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -1,4 +1,7 @@ using System; +using System.IO.Compression; +using System.Net; +using System.Net.Sockets; using Devolutions.IronRdp; namespace Devolutions.IronRdp.ConnectExample { @@ -22,14 +25,10 @@ static void Main(string[] args) } } - static void Connect(String servername, String username, String password, String domain) + static async void Connect(String servername, String username, String password, String domain) { SocketAddr serverAddr = SocketAddr.LookUp(servername, 3389); - StdTcpStream stream = StdTcpStream.Connect(serverAddr); - - BlockingTcpFrame tcpFrame = BlockingTcpFrame.FromTcpStream(stream); - ConfigBuilder configBuilder = ConfigBuilder.New(); configBuilder.WithUsernameAndPasswrord(username, password); @@ -43,22 +42,46 @@ static void Connect(String servername, String username, String password, String ClientConnector connector = ClientConnector.New(config); connector.WithServerAddr(serverAddr); - ShouldUpgrade shouldUpgrade = IronRdpBlocking.ConnectBegin(tcpFrame, connector); + var writeBuf = WriteBuf.New(); - var tcpStream = tcpFrame.IntoTcpSteamNoLeftover(); + var stream = await CreateTcpConnection(servername, 3389); - var tlsUpgradeResult = Tls.TlsUpgrade(tcpStream, servername); - var upgraded = IronRdpBlocking.MarkAsUpgraded(shouldUpgrade, connector); + while (!connector.ShouldPerformSecurityUpgrade()) + { + SingleConnectStep(connector, writeBuf,stream); + } + } - var upgradedStream = tlsUpgradeResult.GetUpgradedStream(); - var serverPublicKey = tlsUpgradeResult.GetServerPublicKey(); + static void SingleConnectStep(ClientConnector connector, WriteBuf writeBuf, NetworkStream stream) + { + var pduHint = connector.NextPduHint(); - var upgradedFrame = BlockingUpgradedFrame.FromUpgradedStream(upgradedStream); + if (pduHint.IsSome()) { + var pdu = ReadByHints(stream, pduHint); + connector.Step(pdu, writeBuf); + } else { + // connector.Setp + } - var serverName = ServerName.New(servername); - var connectorResult = IronRdpBlocking.ConnectFinalize(upgraded, upgradedFrame, connector, serverName, serverPublicKey, null); + } + static async Task CreateTcpConnection(String servername, int port) + { + IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync(servername); + IPAddress ipAddress = ipHostInfo.AddressList[0]; + IPEndPoint ipEndPoint = new(ipAddress, port); + + using TcpClient client = new(); + + await client.ConnectAsync(ipEndPoint); + using NetworkStream stream = client.GetStream(); + + return stream; + } + static VecU8 ReadByHints(NetworkStream stream ,PduHint pduHint) { + // TODO: Implement ReadByHints + return VecU8.NewEmpty(); } } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs index 9d3760695..25dd35107 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs @@ -190,11 +190,40 @@ public void MarkCredsspAsDone() } } + /// + public void Step(VecU8 input, WriteBuf writeBuf) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.VecU8* inputRaw; + inputRaw = input.AsFFI(); + if (inputRaw == null) + { + throw new ObjectDisposedException("VecU8"); + } + Raw.WriteBuf* writeBufRaw; + writeBufRaw = writeBuf.AsFFI(); + if (writeBufRaw == null) + { + throw new ObjectDisposedException("WriteBuf"); + } + Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.ClientConnector.Step(_inner, inputRaw, writeBufRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + } + } + /// /// - /// A PduHintResult allocated on Rust side. + /// A PduHint allocated on Rust side. /// - public PduHintResult NextPduHint() + public PduHint NextPduHint() { unsafe { @@ -202,13 +231,13 @@ public PduHintResult NextPduHint() { throw new ObjectDisposedException("ClientConnector"); } - Raw.ConnectorFfiResultBoxPduHintResultBoxIronRdpError result = Raw.ClientConnector.NextPduHint(_inner); + Raw.ConnectorFfiResultBoxPduHintBoxIronRdpError result = Raw.ClientConnector.NextPduHint(_inner); if (!result.isOk) { throw new IronRdpException(new IronRdpError(result.Err)); } - Raw.PduHintResult* retVal = result.Ok; - return new PduHintResult(retVal); + Raw.PduHint* retVal = result.Ok; + return new PduHint(retVal); } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientState.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientState.cs new file mode 100644 index 000000000..7b52b51e5 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientState.cs @@ -0,0 +1,119 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class ClientState: IDisposable +{ + private unsafe Raw.ClientState* _inner; + + public TsRequest TsRequest + { + get + { + return GetTsRequest(); + } + } + + /// + /// Creates a managed ClientState from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ClientState(Raw.ClientState* handle) + { + _inner = handle; + } + + public bool IsReplyNeeded() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientState"); + } + bool retVal = Raw.ClientState.IsReplyNeeded(_inner); + return retVal; + } + } + + public bool IsFinalMessage() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientState"); + } + bool retVal = Raw.ClientState.IsFinalMessage(_inner); + return retVal; + } + } + + /// + /// + /// A TsRequest allocated on Rust side. + /// + public TsRequest GetTsRequest() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientState"); + } + Raw.CredsspNetworkFfiResultBoxTsRequestBoxIronRdpError result = Raw.ClientState.GetTsRequest(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.TsRequest* retVal = result.Ok; + return new TsRequest(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ClientState* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ClientState.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ClientState() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/BlockingTcpFrame.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspProcessGenerator.cs similarity index 54% rename from ffi/dotnet/Devolutions.IronRdp/Generated/BlockingTcpFrame.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/CredsspProcessGenerator.cs index 28f1abaf3..84ee6523c 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/BlockingTcpFrame.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspProcessGenerator.cs @@ -11,12 +11,12 @@ namespace Devolutions.IronRdp; #nullable enable -public partial class BlockingTcpFrame: IDisposable +public partial class CredsspProcessGenerator: IDisposable { - private unsafe Raw.BlockingTcpFrame* _inner; + private unsafe Raw.CredsspProcessGenerator* _inner; /// - /// Creates a managed BlockingTcpFrame from a raw handle. + /// Creates a managed CredsspProcessGenerator from a raw handle. /// /// /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). @@ -24,61 +24,65 @@ public partial class BlockingTcpFrame: IDisposable /// This constructor assumes the raw struct is allocated on Rust side. /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. /// - public unsafe BlockingTcpFrame(Raw.BlockingTcpFrame* handle) + public unsafe CredsspProcessGenerator(Raw.CredsspProcessGenerator* handle) { _inner = handle; } /// /// - /// A BlockingTcpFrame allocated on Rust side. + /// A GeneratorState allocated on Rust side. /// - public static BlockingTcpFrame FromTcpStream(StdTcpStream stream) + public GeneratorState Start() { unsafe { - Raw.StdTcpStream* streamRaw; - streamRaw = stream.AsFFI(); - if (streamRaw == null) + if (_inner == null) { - throw new ObjectDisposedException("StdTcpStream"); + throw new ObjectDisposedException("CredsspProcessGenerator"); } - Raw.IronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError result = Raw.BlockingTcpFrame.FromTcpStream(streamRaw); + Raw.CredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError result = Raw.CredsspProcessGenerator.Start(_inner); if (!result.isOk) { throw new IronRdpException(new IronRdpError(result.Err)); } - Raw.BlockingTcpFrame* retVal = result.Ok; - return new BlockingTcpFrame(retVal); + Raw.GeneratorState* retVal = result.Ok; + return new GeneratorState(retVal); } } /// /// - /// A StdTcpStream allocated on Rust side. + /// A GeneratorState allocated on Rust side. /// - public StdTcpStream IntoTcpSteamNoLeftover() + public GeneratorState Resume(VecU8 response) { unsafe { if (_inner == null) { - throw new ObjectDisposedException("BlockingTcpFrame"); + throw new ObjectDisposedException("CredsspProcessGenerator"); + } + Raw.VecU8* responseRaw; + responseRaw = response.AsFFI(); + if (responseRaw == null) + { + throw new ObjectDisposedException("VecU8"); } - Raw.IronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError result = Raw.BlockingTcpFrame.IntoTcpSteamNoLeftover(_inner); + Raw.CredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError result = Raw.CredsspProcessGenerator.Resume(_inner, responseRaw); if (!result.isOk) { throw new IronRdpException(new IronRdpError(result.Err)); } - Raw.StdTcpStream* retVal = result.Ok; - return new StdTcpStream(retVal); + Raw.GeneratorState* retVal = result.Ok; + return new GeneratorState(retVal); } } /// /// Returns the underlying raw handle. /// - public unsafe Raw.BlockingTcpFrame* AsFFI() + public unsafe Raw.CredsspProcessGenerator* AsFFI() { return _inner; } @@ -95,14 +99,14 @@ public void Dispose() return; } - Raw.BlockingTcpFrame.Destroy(_inner); + Raw.CredsspProcessGenerator.Destroy(_inner); _inner = null; GC.SuppressFinalize(this); } } - ~BlockingTcpFrame() + ~CredsspProcessGenerator() { Dispose(); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequence.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequence.cs new file mode 100644 index 000000000..717d8de08 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequence.cs @@ -0,0 +1,226 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class CredsspSequence: IDisposable +{ + private unsafe Raw.CredsspSequence* _inner; + + /// + /// Creates a managed CredsspSequence from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe CredsspSequence(Raw.CredsspSequence* handle) + { + _inner = handle; + } + + /// + /// A PduHint allocated on Rust side. + /// + public PduHint? NextPduHint() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("CredsspSequence"); + } + Raw.PduHint* retVal = Raw.CredsspSequence.NextPduHint(_inner); + if (retVal == null) + { + return null; + } + return new PduHint(retVal); + } + } + + /// + /// + /// A CredsspSequenceInitResult allocated on Rust side. + /// + public static CredsspSequenceInitResult Init(ClientConnector connector, ServerName serverName, VecU8 serverPublicKey, KerberosConfig? kerberoConfigs) + { + unsafe + { + Raw.ClientConnector* connectorRaw; + connectorRaw = connector.AsFFI(); + if (connectorRaw == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.ServerName* serverNameRaw; + serverNameRaw = serverName.AsFFI(); + if (serverNameRaw == null) + { + throw new ObjectDisposedException("ServerName"); + } + Raw.VecU8* serverPublicKeyRaw; + serverPublicKeyRaw = serverPublicKey.AsFFI(); + if (serverPublicKeyRaw == null) + { + throw new ObjectDisposedException("VecU8"); + } + Raw.KerberosConfig* kerberoConfigsRaw; + if (kerberoConfigs == null) + { + kerberoConfigsRaw = null; + } + else + { + kerberoConfigsRaw = kerberoConfigs.AsFFI(); + if (kerberoConfigsRaw == null) + { + throw new ObjectDisposedException("KerberosConfig"); + } + } + Raw.CredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError result = Raw.CredsspSequence.Init(connectorRaw, serverNameRaw, serverPublicKeyRaw, kerberoConfigsRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.CredsspSequenceInitResult* retVal = result.Ok; + return new CredsspSequenceInitResult(retVal); + } + } + + /// + /// + /// A TsRequest allocated on Rust side. + /// + public TsRequest DecodeServerMessage(VecU8 pdu) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("CredsspSequence"); + } + Raw.VecU8* pduRaw; + pduRaw = pdu.AsFFI(); + if (pduRaw == null) + { + throw new ObjectDisposedException("VecU8"); + } + Raw.CredsspFfiResultOptBoxTsRequestBoxIronRdpError result = Raw.CredsspSequence.DecodeServerMessage(_inner, pduRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.TsRequest* retVal = result.Ok; + if (retVal == null) + { + return null; + } + return new TsRequest(retVal); + } + } + + /// + /// + /// A CredsspProcessGenerator allocated on Rust side. + /// + public CredsspProcessGenerator ProcessTsRequest(TsRequest tsRequest) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("CredsspSequence"); + } + Raw.TsRequest* tsRequestRaw; + tsRequestRaw = tsRequest.AsFFI(); + if (tsRequestRaw == null) + { + throw new ObjectDisposedException("TsRequest"); + } + Raw.CredsspFfiResultBoxCredsspProcessGeneratorBoxIronRdpError result = Raw.CredsspSequence.ProcessTsRequest(_inner, tsRequestRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.CredsspProcessGenerator* retVal = result.Ok; + return new CredsspProcessGenerator(retVal); + } + } + + /// + /// + /// A Written allocated on Rust side. + /// + public Written HandleProcessResult(ClientState clientState, WriteBuf buf) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("CredsspSequence"); + } + Raw.ClientState* clientStateRaw; + clientStateRaw = clientState.AsFFI(); + if (clientStateRaw == null) + { + throw new ObjectDisposedException("ClientState"); + } + Raw.WriteBuf* bufRaw; + bufRaw = buf.AsFFI(); + if (bufRaw == null) + { + throw new ObjectDisposedException("WriteBuf"); + } + Raw.CredsspFfiResultBoxWrittenBoxIronRdpError result = Raw.CredsspSequence.HandleProcessResult(_inner, clientStateRaw, bufRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.Written* retVal = result.Ok; + return new Written(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.CredsspSequence* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.CredsspSequence.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~CredsspSequence() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequenceInitResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequenceInitResult.cs new file mode 100644 index 000000000..658a8c620 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequenceInitResult.cs @@ -0,0 +1,123 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class CredsspSequenceInitResult: IDisposable +{ + private unsafe Raw.CredsspSequenceInitResult* _inner; + + public CredsspSequence CredsspSequence + { + get + { + return GetCredsspSequence(); + } + } + + public TsRequest TsRequest + { + get + { + return GetTsRequest(); + } + } + + /// + /// Creates a managed CredsspSequenceInitResult from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe CredsspSequenceInitResult(Raw.CredsspSequenceInitResult* handle) + { + _inner = handle; + } + + /// + /// + /// A CredsspSequence allocated on Rust side. + /// + public CredsspSequence GetCredsspSequence() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("CredsspSequenceInitResult"); + } + Raw.CredsspFfiResultBoxCredsspSequenceBoxIronRdpError result = Raw.CredsspSequenceInitResult.GetCredsspSequence(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.CredsspSequence* retVal = result.Ok; + return new CredsspSequence(retVal); + } + } + + /// + /// + /// A TsRequest allocated on Rust side. + /// + public TsRequest GetTsRequest() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("CredsspSequenceInitResult"); + } + Raw.CredsspFfiResultBoxTsRequestBoxIronRdpError result = Raw.CredsspSequenceInitResult.GetTsRequest(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.TsRequest* retVal = result.Ok; + return new TsRequest(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.CredsspSequenceInitResult* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.CredsspSequenceInitResult.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~CredsspSequenceInitResult() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/GeneratorState.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/GeneratorState.cs new file mode 100644 index 000000000..50a5c7d75 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/GeneratorState.cs @@ -0,0 +1,147 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class GeneratorState: IDisposable +{ + private unsafe Raw.GeneratorState* _inner; + + public ClientState ClientStateIfCompleted + { + get + { + return GetClientStateIfCompleted(); + } + } + + public NetworkRequest? NetworkRequestIfSuspended + { + get + { + return GetNetworkRequestIfSuspended(); + } + } + + /// + /// Creates a managed GeneratorState from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe GeneratorState(Raw.GeneratorState* handle) + { + _inner = handle; + } + + public bool IsSuspended() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("GeneratorState"); + } + bool retVal = Raw.GeneratorState.IsSuspended(_inner); + return retVal; + } + } + + public bool IsCompleted() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("GeneratorState"); + } + bool retVal = Raw.GeneratorState.IsCompleted(_inner); + return retVal; + } + } + + /// + /// A NetworkRequest allocated on Rust side. + /// + public NetworkRequest? GetNetworkRequestIfSuspended() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("GeneratorState"); + } + Raw.NetworkRequest* retVal = Raw.GeneratorState.GetNetworkRequestIfSuspended(_inner); + if (retVal == null) + { + return null; + } + return new NetworkRequest(retVal); + } + } + + /// + /// + /// A ClientState allocated on Rust side. + /// + public ClientState GetClientStateIfCompleted() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("GeneratorState"); + } + Raw.CredsspNetworkFfiResultBoxClientStateBoxIronRdpError result = Raw.GeneratorState.GetClientStateIfCompleted(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.ClientState* retVal = result.Ok; + return new ClientState(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.GeneratorState* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.GeneratorState.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~GeneratorState() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpBlocking.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpBlocking.cs deleted file mode 100644 index dae4610dc..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpBlocking.cs +++ /dev/null @@ -1,220 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp; - -#nullable enable - -public partial class IronRdpBlocking: IDisposable -{ - private unsafe Raw.IronRdpBlocking* _inner; - - /// - /// Creates a managed IronRdpBlocking from a raw handle. - /// - /// - /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). - ///
- /// This constructor assumes the raw struct is allocated on Rust side. - /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. - ///
- public unsafe IronRdpBlocking(Raw.IronRdpBlocking* handle) - { - _inner = handle; - } - - /// - /// A IronRdpBlocking allocated on Rust side. - /// - public static IronRdpBlocking New() - { - unsafe - { - Raw.IronRdpBlocking* retVal = Raw.IronRdpBlocking.New(); - return new IronRdpBlocking(retVal); - } - } - - /// - /// - /// A ShouldUpgrade allocated on Rust side. - /// - public static ShouldUpgrade ConnectBegin(BlockingTcpFrame framed, ClientConnector connector) - { - unsafe - { - Raw.BlockingTcpFrame* framedRaw; - framedRaw = framed.AsFFI(); - if (framedRaw == null) - { - throw new ObjectDisposedException("BlockingTcpFrame"); - } - Raw.ClientConnector* connectorRaw; - connectorRaw = connector.AsFFI(); - if (connectorRaw == null) - { - throw new ObjectDisposedException("ClientConnector"); - } - Raw.IronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError result = Raw.IronRdpBlocking.ConnectBegin(framedRaw, connectorRaw); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - Raw.ShouldUpgrade* retVal = result.Ok; - return new ShouldUpgrade(retVal); - } - } - - /// - /// - /// A Upgraded allocated on Rust side. - /// - public static Upgraded MarkAsUpgraded(ShouldUpgrade shouldUpgrade, ClientConnector connector) - { - unsafe - { - Raw.ShouldUpgrade* shouldUpgradeRaw; - shouldUpgradeRaw = shouldUpgrade.AsFFI(); - if (shouldUpgradeRaw == null) - { - throw new ObjectDisposedException("ShouldUpgrade"); - } - Raw.ClientConnector* connectorRaw; - connectorRaw = connector.AsFFI(); - if (connectorRaw == null) - { - throw new ObjectDisposedException("ClientConnector"); - } - Raw.IronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError result = Raw.IronRdpBlocking.MarkAsUpgraded(shouldUpgradeRaw, connectorRaw); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - Raw.Upgraded* retVal = result.Ok; - return new Upgraded(retVal); - } - } - - /// - /// - /// A ShouldUpgrade allocated on Rust side. - /// - public static ShouldUpgrade SkipConnectBegin(ClientConnector connector) - { - unsafe - { - Raw.ClientConnector* connectorRaw; - connectorRaw = connector.AsFFI(); - if (connectorRaw == null) - { - throw new ObjectDisposedException("ClientConnector"); - } - Raw.IronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError result = Raw.IronRdpBlocking.SkipConnectBegin(connectorRaw); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - Raw.ShouldUpgrade* retVal = result.Ok; - return new ShouldUpgrade(retVal); - } - } - - /// - /// - /// A ConnectionResult allocated on Rust side. - /// - public static ConnectionResult ConnectFinalize(Upgraded upgraded, BlockingUpgradedFrame upgradedFramed, ClientConnector connector, ServerName serverName, VecU8 serverPublicKey, KerberosConfig? kerberosConfig) - { - unsafe - { - Raw.Upgraded* upgradedRaw; - upgradedRaw = upgraded.AsFFI(); - if (upgradedRaw == null) - { - throw new ObjectDisposedException("Upgraded"); - } - Raw.BlockingUpgradedFrame* upgradedFramedRaw; - upgradedFramedRaw = upgradedFramed.AsFFI(); - if (upgradedFramedRaw == null) - { - throw new ObjectDisposedException("BlockingUpgradedFrame"); - } - Raw.ClientConnector* connectorRaw; - connectorRaw = connector.AsFFI(); - if (connectorRaw == null) - { - throw new ObjectDisposedException("ClientConnector"); - } - Raw.ServerName* serverNameRaw; - serverNameRaw = serverName.AsFFI(); - if (serverNameRaw == null) - { - throw new ObjectDisposedException("ServerName"); - } - Raw.VecU8* serverPublicKeyRaw; - serverPublicKeyRaw = serverPublicKey.AsFFI(); - if (serverPublicKeyRaw == null) - { - throw new ObjectDisposedException("VecU8"); - } - Raw.KerberosConfig* kerberosConfigRaw; - if (kerberosConfig == null) - { - kerberosConfigRaw = null; - } - else - { - kerberosConfigRaw = kerberosConfig.AsFFI(); - if (kerberosConfigRaw == null) - { - throw new ObjectDisposedException("KerberosConfig"); - } - } - Raw.IronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError result = Raw.IronRdpBlocking.ConnectFinalize(upgradedRaw, upgradedFramedRaw, connectorRaw, serverNameRaw, serverPublicKeyRaw, kerberosConfigRaw); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - Raw.ConnectionResult* retVal = result.Ok; - return new ConnectionResult(retVal); - } - } - - /// - /// Returns the underlying raw handle. - /// - public unsafe Raw.IronRdpBlocking* AsFFI() - { - return _inner; - } - - /// - /// Destroys the underlying object immediately. - /// - public void Dispose() - { - unsafe - { - if (_inner == null) - { - return; - } - - Raw.IronRdpBlocking.Destroy(_inner); - _inner = null; - - GC.SuppressFinalize(this); - } - } - - ~IronRdpBlocking() - { - Dispose(); - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs index 87c8efb18..029758eac 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs @@ -15,7 +15,7 @@ public enum IronRdpErrorKind { Generic = 0, PduError = 1, - SspiError = 2, + CredsspError = 2, Consumed = 3, IO = 4, AccessDenied = 5, diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ShouldUpgrade.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/NetworkRequest.cs similarity index 76% rename from ffi/dotnet/Devolutions.IronRdp/Generated/ShouldUpgrade.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/NetworkRequest.cs index f0c533ce1..099a17d99 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/ShouldUpgrade.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/NetworkRequest.cs @@ -11,12 +11,12 @@ namespace Devolutions.IronRdp; #nullable enable -public partial class ShouldUpgrade: IDisposable +public partial class NetworkRequest: IDisposable { - private unsafe Raw.ShouldUpgrade* _inner; + private unsafe Raw.NetworkRequest* _inner; /// - /// Creates a managed ShouldUpgrade from a raw handle. + /// Creates a managed NetworkRequest from a raw handle. /// /// /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). @@ -24,7 +24,7 @@ public partial class ShouldUpgrade: IDisposable /// This constructor assumes the raw struct is allocated on Rust side. /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. /// - public unsafe ShouldUpgrade(Raw.ShouldUpgrade* handle) + public unsafe NetworkRequest(Raw.NetworkRequest* handle) { _inner = handle; } @@ -32,7 +32,7 @@ public unsafe ShouldUpgrade(Raw.ShouldUpgrade* handle) /// /// Returns the underlying raw handle. /// - public unsafe Raw.ShouldUpgrade* AsFFI() + public unsafe Raw.NetworkRequest* AsFFI() { return _inner; } @@ -49,14 +49,14 @@ public void Dispose() return; } - Raw.ShouldUpgrade.Destroy(_inner); + Raw.NetworkRequest.Destroy(_inner); _inner = null; GC.SuppressFinalize(this); } } - ~ShouldUpgrade() + ~NetworkRequest() { Dispose(); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/PduHintResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/PduHint.cs similarity index 78% rename from ffi/dotnet/Devolutions.IronRdp/Generated/PduHintResult.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/PduHint.cs index 21fb58a02..ade90356a 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/PduHintResult.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/PduHint.cs @@ -11,12 +11,12 @@ namespace Devolutions.IronRdp; #nullable enable -public partial class PduHintResult: IDisposable +public partial class PduHint: IDisposable { - private unsafe Raw.PduHintResult* _inner; + private unsafe Raw.PduHint* _inner; /// - /// Creates a managed PduHintResult from a raw handle. + /// Creates a managed PduHint from a raw handle. /// /// /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). @@ -24,7 +24,7 @@ public partial class PduHintResult: IDisposable /// This constructor assumes the raw struct is allocated on Rust side. /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. /// - public unsafe PduHintResult(Raw.PduHintResult* handle) + public unsafe PduHint(Raw.PduHint* handle) { _inner = handle; } @@ -35,9 +35,9 @@ public bool IsSome() { if (_inner == null) { - throw new ObjectDisposedException("PduHintResult"); + throw new ObjectDisposedException("PduHint"); } - bool retVal = Raw.PduHintResult.IsSome(_inner); + bool retVal = Raw.PduHint.IsSome(_inner); return retVal; } } @@ -52,7 +52,7 @@ public OptionalUsize FindSize(VecU8 buffer) { if (_inner == null) { - throw new ObjectDisposedException("PduHintResult"); + throw new ObjectDisposedException("PduHint"); } Raw.VecU8* bufferRaw; bufferRaw = buffer.AsFFI(); @@ -60,7 +60,7 @@ public OptionalUsize FindSize(VecU8 buffer) { throw new ObjectDisposedException("VecU8"); } - Raw.ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError result = Raw.PduHintResult.FindSize(_inner, bufferRaw); + Raw.ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError result = Raw.PduHint.FindSize(_inner, bufferRaw); if (!result.isOk) { throw new IronRdpException(new IronRdpError(result.Err)); @@ -77,7 +77,7 @@ public OptionalUsize FindSize(VecU8 buffer) /// /// Returns the underlying raw handle. /// - public unsafe Raw.PduHintResult* AsFFI() + public unsafe Raw.PduHint* AsFFI() { return _inner; } @@ -94,14 +94,14 @@ public void Dispose() return; } - Raw.PduHintResult.Destroy(_inner); + Raw.PduHint.Destroy(_inner); _inner = null; GC.SuppressFinalize(this); } } - ~PduHintResult() + ~PduHint() { Dispose(); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingTcpFrame.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingTcpFrame.cs deleted file mode 100644 index 29a6f5fc0..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingTcpFrame.cs +++ /dev/null @@ -1,27 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct BlockingTcpFrame -{ - private const string NativeLib = "DevolutionsIronRdp"; - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "BlockingTcpFrame_from_tcp_stream", ExactSpelling = true)] - public static unsafe extern IronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError FromTcpStream(StdTcpStream* stream); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "BlockingTcpFrame_into_tcp_steam_no_leftover", ExactSpelling = true)] - public static unsafe extern IronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError IntoTcpSteamNoLeftover(BlockingTcpFrame* self); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "BlockingTcpFrame_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(BlockingTcpFrame* self); -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs index f802892c0..8fc718b33 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs @@ -49,8 +49,11 @@ public partial struct ClientConnector [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_mark_credssp_as_done", ExactSpelling = true)] public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError MarkCredsspAsDone(ClientConnector* self); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_step", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError Step(ClientConnector* self, VecU8* input, WriteBuf* writeBuf); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_next_pdu_hint", ExactSpelling = true)] - public static unsafe extern ConnectorFfiResultBoxPduHintResultBoxIronRdpError NextPduHint(ClientConnector* self); + public static unsafe extern ConnectorFfiResultBoxPduHintBoxIronRdpError NextPduHint(ClientConnector* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_state", ExactSpelling = true)] public static unsafe extern ConnectorFfiResultBoxStateBoxIronRdpError State(ClientConnector* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientState.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientState.cs new file mode 100644 index 000000000..5a694bc2a --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientState.cs @@ -0,0 +1,32 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ClientState +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientState_is_reply_needed", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool IsReplyNeeded(ClientState* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientState_is_final_message", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool IsFinalMessage(ClientState* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientState_get_ts_request", ExactSpelling = true)] + public static unsafe extern CredsspNetworkFfiResultBoxTsRequestBoxIronRdpError GetTsRequest(ClientState* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientState_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ClientState* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintBoxIronRdpError.cs similarity index 83% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintBoxIronRdpError.cs index ff2c2a9ba..a4b4959e2 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintBoxIronRdpError.cs @@ -12,13 +12,13 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct IronrdpBlockingFfiResultBoxStdTcpStreamBoxIronRdpError +public partial struct ConnectorFfiResultBoxPduHintBoxIronRdpError { [StructLayout(LayoutKind.Explicit)] private unsafe struct InnerUnion { [FieldOffset(0)] - internal StdTcpStream* ok; + internal PduHint* ok; [FieldOffset(0)] internal IronRdpError* err; } @@ -28,7 +28,7 @@ private unsafe struct InnerUnion [MarshalAs(UnmanagedType.U1)] public bool isOk; - public unsafe StdTcpStream* Ok + public unsafe PduHint* Ok { get { diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxCredsspProcessGeneratorBoxIronRdpError.cs similarity index 80% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxCredsspProcessGeneratorBoxIronRdpError.cs index be72b0c46..ce13d18a3 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxCredsspProcessGeneratorBoxIronRdpError.cs @@ -12,13 +12,13 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct IronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError +public partial struct CredsspFfiResultBoxCredsspProcessGeneratorBoxIronRdpError { [StructLayout(LayoutKind.Explicit)] private unsafe struct InnerUnion { [FieldOffset(0)] - internal ConnectionResult* ok; + internal CredsspProcessGenerator* ok; [FieldOffset(0)] internal IronRdpError* err; } @@ -28,7 +28,7 @@ private unsafe struct InnerUnion [MarshalAs(UnmanagedType.U1)] public bool isOk; - public unsafe ConnectionResult* Ok + public unsafe CredsspProcessGenerator* Ok { get { diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintResultBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxCredsspSequenceBoxIronRdpError.cs similarity index 84% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintResultBoxIronRdpError.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxCredsspSequenceBoxIronRdpError.cs index 85a17c69a..57da8cd7b 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintResultBoxIronRdpError.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxCredsspSequenceBoxIronRdpError.cs @@ -12,13 +12,13 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct ConnectorFfiResultBoxPduHintResultBoxIronRdpError +public partial struct CredsspFfiResultBoxCredsspSequenceBoxIronRdpError { [StructLayout(LayoutKind.Explicit)] private unsafe struct InnerUnion { [FieldOffset(0)] - internal PduHintResult* ok; + internal CredsspSequence* ok; [FieldOffset(0)] internal IronRdpError* err; } @@ -28,7 +28,7 @@ private unsafe struct InnerUnion [MarshalAs(UnmanagedType.U1)] public bool isOk; - public unsafe PduHintResult* Ok + public unsafe CredsspSequence* Ok { get { diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError.cs similarity index 80% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError.cs index 3d9b285aa..d8602e290 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError.cs @@ -12,13 +12,13 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct IronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError +public partial struct CredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError { [StructLayout(LayoutKind.Explicit)] private unsafe struct InnerUnion { [FieldOffset(0)] - internal BlockingUpgradedFrame* ok; + internal CredsspSequenceInitResult* ok; [FieldOffset(0)] internal IronRdpError* err; } @@ -28,7 +28,7 @@ private unsafe struct InnerUnion [MarshalAs(UnmanagedType.U1)] public bool isOk; - public unsafe BlockingUpgradedFrame* Ok + public unsafe CredsspSequenceInitResult* Ok { get { diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxTsRequestBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxTsRequestBoxIronRdpError.cs new file mode 100644 index 000000000..9da0daa76 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxTsRequestBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct CredsspFfiResultBoxTsRequestBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal TsRequest* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe TsRequest* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxWrittenBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxWrittenBoxIronRdpError.cs new file mode 100644 index 000000000..b7e2a675d --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultBoxWrittenBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct CredsspFfiResultBoxWrittenBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal Written* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe Written* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultOptBoxTsRequestBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultOptBoxTsRequestBoxIronRdpError.cs new file mode 100644 index 000000000..6c104178f --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspFfiResultOptBoxTsRequestBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct CredsspFfiResultOptBoxTsRequestBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal TsRequest* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe TsRequest* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultBoxClientStateBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultBoxClientStateBoxIronRdpError.cs new file mode 100644 index 000000000..ba55c276c --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultBoxClientStateBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct CredsspNetworkFfiResultBoxClientStateBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal ClientState* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe ClientState* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError.cs similarity index 84% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError.cs index b7110a17c..2b7fb20a7 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError.cs @@ -12,13 +12,13 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct IronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError +public partial struct CredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError { [StructLayout(LayoutKind.Explicit)] private unsafe struct InnerUnion { [FieldOffset(0)] - internal ShouldUpgrade* ok; + internal GeneratorState* ok; [FieldOffset(0)] internal IronRdpError* err; } @@ -28,7 +28,7 @@ private unsafe struct InnerUnion [MarshalAs(UnmanagedType.U1)] public bool isOk; - public unsafe ShouldUpgrade* Ok + public unsafe GeneratorState* Ok { get { diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultBoxTsRequestBoxIronRdpError.cs similarity index 85% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultBoxTsRequestBoxIronRdpError.cs index 06b0ea3c9..e79b9b37f 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultBoxTsRequestBoxIronRdpError.cs @@ -12,13 +12,13 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct IronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError +public partial struct CredsspNetworkFfiResultBoxTsRequestBoxIronRdpError { [StructLayout(LayoutKind.Explicit)] private unsafe struct InnerUnion { [FieldOffset(0)] - internal Upgraded* ok; + internal TsRequest* ok; [FieldOffset(0)] internal IronRdpError* err; } @@ -28,7 +28,7 @@ private unsafe struct InnerUnion [MarshalAs(UnmanagedType.U1)] public bool isOk; - public unsafe Upgraded* Ok + public unsafe TsRequest* Ok { get { diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspProcessGenerator.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspProcessGenerator.cs new file mode 100644 index 000000000..48be7d0e9 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspProcessGenerator.cs @@ -0,0 +1,27 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct CredsspProcessGenerator +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspProcessGenerator_start", ExactSpelling = true)] + public static unsafe extern CredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError Start(CredsspProcessGenerator* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspProcessGenerator_resume", ExactSpelling = true)] + public static unsafe extern CredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError Resume(CredsspProcessGenerator* self, VecU8* response); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspProcessGenerator_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(CredsspProcessGenerator* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequence.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequence.cs new file mode 100644 index 000000000..7cd366041 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequence.cs @@ -0,0 +1,36 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct CredsspSequence +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequence_next_pdu_hint", ExactSpelling = true)] + public static unsafe extern PduHint* NextPduHint(CredsspSequence* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequence_init", ExactSpelling = true)] + public static unsafe extern CredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError Init(ClientConnector* connector, ServerName* serverName, VecU8* serverPublicKey, KerberosConfig* kerberoConfigs); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequence_decode_server_message", ExactSpelling = true)] + public static unsafe extern CredsspFfiResultOptBoxTsRequestBoxIronRdpError DecodeServerMessage(CredsspSequence* self, VecU8* pdu); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequence_process_ts_request", ExactSpelling = true)] + public static unsafe extern CredsspFfiResultBoxCredsspProcessGeneratorBoxIronRdpError ProcessTsRequest(CredsspSequence* self, TsRequest* tsRequest); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequence_handle_process_result", ExactSpelling = true)] + public static unsafe extern CredsspFfiResultBoxWrittenBoxIronRdpError HandleProcessResult(CredsspSequence* self, ClientState* clientState, WriteBuf* buf); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequence_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(CredsspSequence* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequenceInitResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequenceInitResult.cs new file mode 100644 index 000000000..fa340166b --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequenceInitResult.cs @@ -0,0 +1,27 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct CredsspSequenceInitResult +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequenceInitResult_get_credssp_sequence", ExactSpelling = true)] + public static unsafe extern CredsspFfiResultBoxCredsspSequenceBoxIronRdpError GetCredsspSequence(CredsspSequenceInitResult* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequenceInitResult_get_ts_request", ExactSpelling = true)] + public static unsafe extern CredsspFfiResultBoxTsRequestBoxIronRdpError GetTsRequest(CredsspSequenceInitResult* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequenceInitResult_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(CredsspSequenceInitResult* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawGeneratorState.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawGeneratorState.cs new file mode 100644 index 000000000..29bf6ba7c --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawGeneratorState.cs @@ -0,0 +1,35 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct GeneratorState +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GeneratorState_is_suspended", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool IsSuspended(GeneratorState* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GeneratorState_is_completed", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool IsCompleted(GeneratorState* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GeneratorState_get_network_request_if_suspended", ExactSpelling = true)] + public static unsafe extern NetworkRequest* GetNetworkRequestIfSuspended(GeneratorState* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GeneratorState_get_client_state_if_completed", ExactSpelling = true)] + public static unsafe extern CredsspNetworkFfiResultBoxClientStateBoxIronRdpError GetClientStateIfCompleted(GeneratorState* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GeneratorState_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(GeneratorState* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpBlocking.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpBlocking.cs deleted file mode 100644 index 93141ee35..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpBlocking.cs +++ /dev/null @@ -1,36 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct IronRdpBlocking -{ - private const string NativeLib = "DevolutionsIronRdp"; - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpBlocking_new", ExactSpelling = true)] - public static unsafe extern IronRdpBlocking* New(); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpBlocking_connect_begin", ExactSpelling = true)] - public static unsafe extern IronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError ConnectBegin(BlockingTcpFrame* framed, ClientConnector* connector); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpBlocking_mark_as_upgraded", ExactSpelling = true)] - public static unsafe extern IronrdpBlockingFfiResultBoxUpgradedBoxIronRdpError MarkAsUpgraded(ShouldUpgrade* shouldUpgrade, ClientConnector* connector); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpBlocking_skip_connect_begin", ExactSpelling = true)] - public static unsafe extern IronrdpBlockingFfiResultBoxShouldUpgradeBoxIronRdpError SkipConnectBegin(ClientConnector* connector); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpBlocking_connect_finalize", ExactSpelling = true)] - public static unsafe extern IronrdpBlockingFfiResultBoxConnectionResultBoxIronRdpError ConnectFinalize(Upgraded* upgraded, BlockingUpgradedFrame* upgradedFramed, ClientConnector* connector, ServerName* serverName, VecU8* serverPublicKey, KerberosConfig* kerberosConfig); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "IronRdpBlocking_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(IronRdpBlocking* self); -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs index 8243a9d50..83c1d0e1b 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs @@ -15,7 +15,7 @@ public enum IronRdpErrorKind { Generic = 0, PduError = 1, - SspiError = 2, + CredsspError = 2, Consumed = 3, IO = 4, AccessDenied = 5, diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError.cs deleted file mode 100644 index 72908ffee..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError.cs +++ /dev/null @@ -1,46 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct IronrdpBlockingFfiResultBoxBlockingTcpFrameBoxIronRdpError -{ - [StructLayout(LayoutKind.Explicit)] - private unsafe struct InnerUnion - { - [FieldOffset(0)] - internal BlockingTcpFrame* ok; - [FieldOffset(0)] - internal IronRdpError* err; - } - - private InnerUnion _inner; - - [MarshalAs(UnmanagedType.U1)] - public bool isOk; - - public unsafe BlockingTcpFrame* Ok - { - get - { - return _inner.ok; - } - } - - public unsafe IronRdpError* Err - { - get - { - return _inner.err; - } - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawShouldUpgrade.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawNetworkRequest.cs similarity index 69% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawShouldUpgrade.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawNetworkRequest.cs index d08b4051c..41e48e1e2 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawShouldUpgrade.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawNetworkRequest.cs @@ -12,10 +12,10 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct ShouldUpgrade +public partial struct NetworkRequest { private const string NativeLib = "DevolutionsIronRdp"; - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ShouldUpgrade_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(ShouldUpgrade* self); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "NetworkRequest_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(NetworkRequest* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHintResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHint.cs similarity index 59% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHintResult.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHint.cs index e7ac03e28..44e3e08fb 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHintResult.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHint.cs @@ -12,17 +12,17 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct PduHintResult +public partial struct PduHint { private const string NativeLib = "DevolutionsIronRdp"; - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHintResult_is_some", ExactSpelling = true)] + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHint_is_some", ExactSpelling = true)] [return: MarshalAs(UnmanagedType.U1)] - public static unsafe extern bool IsSome(PduHintResult* self); + public static unsafe extern bool IsSome(PduHint* self); - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHintResult_find_size", ExactSpelling = true)] - public static unsafe extern ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError FindSize(PduHintResult* self, VecU8* buffer); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHint_find_size", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError FindSize(PduHint* self, VecU8* buffer); - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHintResult_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(PduHintResult* self); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHint_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(PduHint* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgraded.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTsRequest.cs similarity index 71% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgraded.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawTsRequest.cs index 91bb63948..e5b8f200a 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgraded.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTsRequest.cs @@ -12,10 +12,10 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct Upgraded +public partial struct TsRequest { private const string NativeLib = "DevolutionsIronRdp"; - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Upgraded_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(Upgraded* self); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "TsRequest_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(TsRequest* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs index 653d0558e..7c3d6776a 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs @@ -25,6 +25,9 @@ public partial struct VecU8 [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "VecU8_fill", ExactSpelling = true)] public static unsafe extern void Fill(VecU8* self, byte* buffer, nuint bufferSz); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "VecU8_new_empty", ExactSpelling = true)] + public static unsafe extern VecU8* NewEmpty(); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "VecU8_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(VecU8* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingUpgradedFrame.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawWriteBuf.cs similarity index 50% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingUpgradedFrame.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawWriteBuf.cs index d223eee5a..6ac0aa26c 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawBlockingUpgradedFrame.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawWriteBuf.cs @@ -12,13 +12,16 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct BlockingUpgradedFrame +public partial struct WriteBuf { private const string NativeLib = "DevolutionsIronRdp"; - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "BlockingUpgradedFrame_from_upgraded_stream", ExactSpelling = true)] - public static unsafe extern IronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError FromUpgradedStream(UpgradedStream* stream); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WriteBuf_new", ExactSpelling = true)] + public static unsafe extern WriteBuf* New(); - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "BlockingUpgradedFrame_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(BlockingUpgradedFrame* self); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WriteBuf_clear", ExactSpelling = true)] + public static unsafe extern void Clear(WriteBuf* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WriteBuf_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(WriteBuf* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawWritten.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawWritten.cs new file mode 100644 index 000000000..b59615605 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawWritten.cs @@ -0,0 +1,28 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct Written +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Written_is_nothing", ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + public static unsafe extern bool IsNothing(Written* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Written_get_size", ExactSpelling = true)] + public static unsafe extern OptionalUsize* GetSize(Written* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Written_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(Written* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/Upgraded.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/TsRequest.cs similarity index 78% rename from ffi/dotnet/Devolutions.IronRdp/Generated/Upgraded.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/TsRequest.cs index 3055985e2..6a3972a5d 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/Upgraded.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/TsRequest.cs @@ -11,12 +11,12 @@ namespace Devolutions.IronRdp; #nullable enable -public partial class Upgraded: IDisposable +public partial class TsRequest: IDisposable { - private unsafe Raw.Upgraded* _inner; + private unsafe Raw.TsRequest* _inner; /// - /// Creates a managed Upgraded from a raw handle. + /// Creates a managed TsRequest from a raw handle. /// /// /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). @@ -24,7 +24,7 @@ public partial class Upgraded: IDisposable /// This constructor assumes the raw struct is allocated on Rust side. /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. /// - public unsafe Upgraded(Raw.Upgraded* handle) + public unsafe TsRequest(Raw.TsRequest* handle) { _inner = handle; } @@ -32,7 +32,7 @@ public unsafe Upgraded(Raw.Upgraded* handle) /// /// Returns the underlying raw handle. /// - public unsafe Raw.Upgraded* AsFFI() + public unsafe Raw.TsRequest* AsFFI() { return _inner; } @@ -49,14 +49,14 @@ public void Dispose() return; } - Raw.Upgraded.Destroy(_inner); + Raw.TsRequest.Destroy(_inner); _inner = null; GC.SuppressFinalize(this); } } - ~Upgraded() + ~TsRequest() { Dispose(); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs index bd64f925f..47e965c6b 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs @@ -82,6 +82,18 @@ public void Fill(byte[] buffer) } } + /// + /// A VecU8 allocated on Rust side. + /// + public static VecU8 NewEmpty() + { + unsafe + { + Raw.VecU8* retVal = Raw.VecU8.NewEmpty(); + return new VecU8(retVal); + } + } + /// /// Returns the underlying raw handle. /// diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/WriteBuf.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/WriteBuf.cs new file mode 100644 index 000000000..f1198bae1 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/WriteBuf.cs @@ -0,0 +1,87 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class WriteBuf: IDisposable +{ + private unsafe Raw.WriteBuf* _inner; + + /// + /// Creates a managed WriteBuf from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe WriteBuf(Raw.WriteBuf* handle) + { + _inner = handle; + } + + /// + /// A WriteBuf allocated on Rust side. + /// + public static WriteBuf New() + { + unsafe + { + Raw.WriteBuf* retVal = Raw.WriteBuf.New(); + return new WriteBuf(retVal); + } + } + + public void Clear() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("WriteBuf"); + } + Raw.WriteBuf.Clear(_inner); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.WriteBuf* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.WriteBuf.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~WriteBuf() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/BlockingUpgradedFrame.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/Written.cs similarity index 52% rename from ffi/dotnet/Devolutions.IronRdp/Generated/BlockingUpgradedFrame.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/Written.cs index a4de2ed09..df50bba24 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/BlockingUpgradedFrame.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/Written.cs @@ -11,12 +11,20 @@ namespace Devolutions.IronRdp; #nullable enable -public partial class BlockingUpgradedFrame: IDisposable +public partial class Written: IDisposable { - private unsafe Raw.BlockingUpgradedFrame* _inner; + private unsafe Raw.Written* _inner; + + public OptionalUsize Size + { + get + { + return GetSize(); + } + } /// - /// Creates a managed BlockingUpgradedFrame from a raw handle. + /// Creates a managed Written from a raw handle. /// /// /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). @@ -24,39 +32,44 @@ public partial class BlockingUpgradedFrame: IDisposable /// This constructor assumes the raw struct is allocated on Rust side. /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. /// - public unsafe BlockingUpgradedFrame(Raw.BlockingUpgradedFrame* handle) + public unsafe Written(Raw.Written* handle) { _inner = handle; } - /// - /// - /// A BlockingUpgradedFrame allocated on Rust side. - /// - public static BlockingUpgradedFrame FromUpgradedStream(UpgradedStream stream) + public bool IsNothing() { unsafe { - Raw.UpgradedStream* streamRaw; - streamRaw = stream.AsFFI(); - if (streamRaw == null) + if (_inner == null) { - throw new ObjectDisposedException("UpgradedStream"); + throw new ObjectDisposedException("Written"); } - Raw.IronrdpBlockingFfiResultBoxBlockingUpgradedFrameBoxIronRdpError result = Raw.BlockingUpgradedFrame.FromUpgradedStream(streamRaw); - if (!result.isOk) + bool retVal = Raw.Written.IsNothing(_inner); + return retVal; + } + } + + /// + /// A OptionalUsize allocated on Rust side. + /// + public OptionalUsize GetSize() + { + unsafe + { + if (_inner == null) { - throw new IronRdpException(new IronRdpError(result.Err)); + throw new ObjectDisposedException("Written"); } - Raw.BlockingUpgradedFrame* retVal = result.Ok; - return new BlockingUpgradedFrame(retVal); + Raw.OptionalUsize* retVal = Raw.Written.GetSize(_inner); + return new OptionalUsize(retVal); } } /// /// Returns the underlying raw handle. /// - public unsafe Raw.BlockingUpgradedFrame* AsFFI() + public unsafe Raw.Written* AsFFI() { return _inner; } @@ -73,14 +86,14 @@ public void Dispose() return; } - Raw.BlockingUpgradedFrame.Destroy(_inner); + Raw.Written.Destroy(_inner); _inner = null; GC.SuppressFinalize(this); } } - ~BlockingUpgradedFrame() + ~Written() { Dispose(); } diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index 5fee37b58..fa27ad235 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -13,6 +13,7 @@ pub mod ffi { ffi::{IronRdpError, IronRdpErrorKind}, ValueConsumedError, }, + pdu::ffi::WriteBuf, utils::ffi::{SocketAddr, VecU8}, }; @@ -108,12 +109,20 @@ pub mod ffi { connector.mark_credssp_as_done(); Ok(()) } + + pub fn step(&mut self, input: &VecU8, write_buf: &mut WriteBuf) -> Result<(), Box> { + let Some(connector) = self.0.as_mut() else { + return Err(ValueConsumedError::for_item("connector").into()); + }; + connector.step(input.0.as_ref(), &mut write_buf.0)?; + Ok(()) + } } #[diplomat::opaque] - pub struct PduHintResult<'a>(pub Option<&'a dyn ironrdp::pdu::PduHint>); + pub struct PduHint<'a>(pub Option<&'a dyn ironrdp::pdu::PduHint>); - impl<'a> PduHintResult<'a> { + impl<'a> PduHint<'a> { pub fn is_some(&'a self) -> bool { self.0.is_some() } @@ -152,11 +161,11 @@ pub mod ffi { } impl ClientConnector { - pub fn next_pdu_hint(&self) -> Result>, Box> { + pub fn next_pdu_hint(&self) -> Result>, Box> { let Some(connector) = self.0.as_ref() else { return Err(ValueConsumedError::for_item("connector").into()); }; - Ok(Box::new(PduHintResult(connector.next_pdu_hint()))) + Ok(Box::new(PduHint(connector.next_pdu_hint()))) } pub fn state(&self) -> Result>, Box> { diff --git a/ffi/src/connector/result.rs b/ffi/src/connector/result.rs index 4652029a0..f3190d64a 100644 --- a/ffi/src/connector/result.rs +++ b/ffi/src/connector/result.rs @@ -1,6 +1,22 @@ #[diplomat::bridge] pub mod ffi { - use crate::connector::config::ffi::DesktopSize; + use crate::{connector::config::ffi::DesktopSize, utils::ffi::OptionalUsize}; + + #[diplomat::opaque] + pub struct Written(pub ironrdp::connector::Written); + + impl Written { + pub fn is_nothing(&self) -> bool { + matches!(self.0, ironrdp::connector::Written::Nothing) + } + + pub fn get_size(&self) -> Box { + match &self.0 { + ironrdp::connector::Written::Size(size) => Box::new(OptionalUsize(Some(size.get()))), + ironrdp::connector::Written::Nothing => Box::new(OptionalUsize(None)), + } + } + } #[diplomat::opaque] pub struct ConnectionResult(pub ironrdp::connector::ConnectionResult); diff --git a/ffi/src/credssp.rs b/ffi/src/credssp.rs deleted file mode 100644 index 010d62aa1..000000000 --- a/ffi/src/credssp.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[diplomat::bridge] -pub mod ffi { - - #[diplomat::opaque] - pub struct KerberosConfig(pub ironrdp::connector::credssp::KerberosConfig); -} diff --git a/ffi/src/credssp/mod.rs b/ffi/src/credssp/mod.rs new file mode 100644 index 000000000..da3549443 --- /dev/null +++ b/ffi/src/credssp/mod.rs @@ -0,0 +1,102 @@ +pub mod network; + +#[diplomat::bridge] +pub mod ffi { + + use crate::{ + connector::{ + ffi::{ClientConnector, PduHint, ServerName}, + result::ffi::Written, + }, + error::{ffi::IronRdpError, ValueConsumedError}, + pdu::ffi::WriteBuf, + utils::ffi::VecU8, + }; + + use super::network::ffi::{ClientState, CredsspProcessGenerator}; + + #[diplomat::opaque] + pub struct KerberosConfig(pub ironrdp::connector::credssp::KerberosConfig); + + #[diplomat::opaque] + pub struct CredsspSequence(pub ironrdp::connector::credssp::CredsspSequence); + + #[diplomat::opaque] + pub struct TsRequest(pub sspi::credssp::TsRequest); + + #[diplomat::opaque] + pub struct CredsspSequenceInitResult { + pub credssp_sequence: Option>, + pub ts_request: Option>, + } + + impl CredsspSequenceInitResult { + pub fn get_credssp_sequence(&mut self) -> Result, Box> { + let Some(credssp_sequence) = self.credssp_sequence.take() else { + return Err(ValueConsumedError::for_item("credssp_sequence").into()); + }; + Ok(credssp_sequence) + } + + pub fn get_ts_request(&mut self) -> Result, Box> { + let Some(ts_request) = self.ts_request.take() else { + return Err(ValueConsumedError::for_item("ts_request").into()); + }; + Ok(ts_request) + } + } + + impl CredsspSequence { + pub fn next_pdu_hint<'a>(&'a self) -> Option>> { + self.0.next_pdu_hint().map(|hint| Box::new(PduHint(Some(hint)))) + } + + pub fn init( + connector: &ClientConnector, + server_name: &ServerName, + server_public_key: &VecU8, + kerbero_configs: Option<&KerberosConfig>, + ) -> Result, Box> { + let Some(connector) = connector.0.as_ref() else { + return Err(ValueConsumedError::for_item("connector").into()); + }; + + let (credssp_sequence, ts_request) = ironrdp::connector::credssp::CredsspSequence::init( + connector, + server_name.0.clone(), + server_public_key.0.clone(), + kerbero_configs.map(|config| config.0.clone()), + )?; + + return Ok(CredsspSequenceInitResult { + credssp_sequence: Some(Box::new(CredsspSequence(credssp_sequence))), + ts_request: Some(Box::new(TsRequest(ts_request))), + }) + .map(Box::new); + } + + pub fn decode_server_message(&mut self, pdu: &VecU8) -> Result>, Box> { + let ts_request = self.0.decode_server_message(&pdu.0)?; + Ok(ts_request.map(|ts_request| Box::new(TsRequest(ts_request)))) + } + + pub fn process_ts_request<'a>( + &'a mut self, + ts_request: Box, + ) -> Result>, Box> { + let ts_request = ts_request.0; + let generator = self.0.process_ts_request(ts_request); + Ok(Box::new(CredsspProcessGenerator(generator))) + } + + pub fn handle_process_result( + &mut self, + client_state: &ClientState, + buf: &mut WriteBuf, + ) -> Result, Box> { + let client_state = client_state.0.clone(); + let written = self.0.handle_process_result(client_state, &mut buf.0)?; + Ok(Box::new(Written(written))) + } + } +} diff --git a/ffi/src/credssp/network.rs b/ffi/src/credssp/network.rs new file mode 100644 index 000000000..c9b25dbb6 --- /dev/null +++ b/ffi/src/credssp/network.rs @@ -0,0 +1,76 @@ +pub type CredsspGeneratorState = + sspi::generator::GeneratorState>; + +#[diplomat::bridge] +pub mod ffi { + + use crate::{credssp::ffi::TsRequest, error::ffi::IronRdpError, utils::ffi::VecU8}; + + use super::CredsspGeneratorState; + + #[diplomat::opaque] + pub struct CredsspProcessGenerator<'a>(pub ironrdp::connector::credssp::CredsspProcessGenerator<'a>); + + #[diplomat::opaque] + pub struct GeneratorState(pub CredsspGeneratorState); + + #[diplomat::opaque] + pub struct NetworkRequest<'a>(pub &'a sspi::generator::NetworkRequest); + + #[diplomat::opaque] + pub struct ClientState(pub sspi::credssp::ClientState); + + impl<'a> CredsspProcessGenerator<'a> { + pub fn start(&mut self) -> Result, Box> { + let state = self.0.start(); + Ok(Box::new(GeneratorState(state))) + } + + pub fn resume(&mut self, response: &VecU8) -> Result, Box> { + let state = self.0.resume(Ok(response.0.clone())); + Ok(Box::new(GeneratorState(state))) + } + } + + impl GeneratorState { + pub fn is_suspended(&self) -> bool { + matches!(self.0, CredsspGeneratorState::Suspended(_)) + } + + pub fn is_completed(&self) -> bool { + matches!(self.0, CredsspGeneratorState::Completed(_)) + } + + pub fn get_network_request_if_suspended<'a>(&'a self) -> Option>> { + match &self.0 { + CredsspGeneratorState::Suspended(request) => Some(Box::new(NetworkRequest(request))), + _ => None, + } + } + + pub fn get_client_state_if_completed(&self) -> Result, Box> { + match &self.0 { + CredsspGeneratorState::Completed(Ok(res)) => Ok(res.clone()).map(ClientState).map(Box::new), + CredsspGeneratorState::Completed(Err(e)) => Err(e.to_owned().into()), + _ => Err("Generator is not completed".into()), + } + } + } + + impl ClientState { + pub fn is_reply_needed(&self) -> bool { + matches!(self.0, sspi::credssp::ClientState::ReplyNeeded(_)) + } + + pub fn is_final_message(&self) -> bool { + matches!(self.0, sspi::credssp::ClientState::FinalMessage(_)) + } + + pub fn get_ts_request(&self) -> Result, Box> { + match &self.0 { + sspi::credssp::ClientState::ReplyNeeded(ts_request) => Ok(Box::new(TsRequest(ts_request.clone()))), + sspi::credssp::ClientState::FinalMessage(ts_request) => Ok(Box::new(TsRequest(ts_request.clone()))), + } + } + } +} diff --git a/ffi/src/error.rs b/ffi/src/error.rs index 7007d791a..5f6ea9303 100644 --- a/ffi/src/error.rs +++ b/ffi/src/error.rs @@ -7,7 +7,7 @@ impl From for IronRdpErrorKind { fn from(val: ConnectorError) -> Self { match val.kind { ironrdp::connector::ConnectorErrorKind::Pdu(_) => IronRdpErrorKind::PduError, - ironrdp::connector::ConnectorErrorKind::Credssp(_) => IronRdpErrorKind::SspiError, + ironrdp::connector::ConnectorErrorKind::Credssp(_) => IronRdpErrorKind::CredsspError, ironrdp::connector::ConnectorErrorKind::AccessDenied => IronRdpErrorKind::AccessDenied, _ => IronRdpErrorKind::Generic, } @@ -20,6 +20,12 @@ impl From<&str> for IronRdpErrorKind { } } +impl From for IronRdpErrorKind { + fn from(_val: sspi::Error) -> Self { + IronRdpErrorKind::CredsspError + } +} + impl From for IronRdpErrorKind { fn from(_val: ironrdp::pdu::PduError) -> Self { IronRdpErrorKind::PduError @@ -66,7 +72,7 @@ pub mod ffi { #[error("PDU error")] PduError, #[error("CredSSP error")] - SspiError, + CredsspError, #[error("Value is consumed")] Consumed, #[error("IO error")] diff --git a/ffi/src/ironrdp_blocking.rs b/ffi/src/ironrdp_blocking.rs index 5863fe707..9cceb5965 100644 --- a/ffi/src/ironrdp_blocking.rs +++ b/ffi/src/ironrdp_blocking.rs @@ -1,176 +1,176 @@ -#[diplomat::bridge] -pub mod ffi { - use ironrdp::connector::sspi::network_client; - use ironrdp_blocking::connect_begin; - - use crate::{ - connector::result::ffi::ConnectionResult, - error::{ - ffi::{IronRdpError, IronRdpErrorKind}, - ValueConsumedError, - }, - tls::TlsStream, - utils::ffi::{StdTcpStream, VecU8}, - }; - - #[diplomat::opaque] - pub struct BlockingTcpFrame(pub Option>); - - impl BlockingTcpFrame { - pub fn from_tcp_stream(stream: &mut StdTcpStream) -> Result, Box> { - let Some(stream) = stream.0.take() else { - return Err(ValueConsumedError::for_item("tcp_stream") - .reason("tcp stream has been consumed") - .into()); - }; - - let framed = ironrdp_blocking::Framed::new(stream); - - Ok(Box::new(BlockingTcpFrame(Some(framed)))) - } - - pub fn into_tcp_steam_no_leftover(&mut self) -> Result, Box> { - let Some(stream) = self.0.take() else { - return Err(ValueConsumedError::for_item("BlockingTcpFrame") - .reason("BlockingTcpFrame has been consumed") - .into()); - }; - - let stream = stream.into_inner_no_leftover(); - - Ok(Box::new(StdTcpStream(Some(stream)))) - } - } - - #[diplomat::opaque] - pub struct BlockingUpgradedFrame(pub Option>); - - impl BlockingUpgradedFrame { - pub fn from_upgraded_stream( - stream: &mut crate::tls::ffi::UpgradedStream, - ) -> Result, Box> { - let Some(stream) = stream.0.take() else { - return Err(ValueConsumedError::for_item("upgraded_stream") - .reason("upgraded stream has been consumed") - .into()); - }; - - let framed = ironrdp_blocking::Framed::new(stream); - - Ok(Box::new(BlockingUpgradedFrame(Some(framed)))) - } - } - - #[diplomat::opaque] // Diplomat does not support direct function calls, so we need to wrap the function in a struct - pub struct IronRdpBlocking; - - #[diplomat::opaque] - pub struct ShouldUpgrade(pub Option); - - #[diplomat::opaque] - pub struct Upgraded(pub Option); - - impl IronRdpBlocking { - pub fn new() -> Box { - Box::new(IronRdpBlocking) - } - - pub fn connect_begin( - framed: &mut BlockingTcpFrame, - connector: &mut crate::connector::ffi::ClientConnector, - ) -> Result, Box> { - let Some(ref mut connector) = connector.0 else { - return Err(IronRdpErrorKind::Consumed.into()); - }; - - let Some(framed) = framed.0.as_mut() else { - return Err(ValueConsumedError::for_item("framed") - .reason("framed has been consumed") - .into()); - }; - - let result = connect_begin(framed, connector)?; - - Ok(Box::new(ShouldUpgrade(Some(result)))) - } - - pub fn mark_as_upgraded( - should_upgrade: &mut ShouldUpgrade, - connector: &mut crate::connector::ffi::ClientConnector, - ) -> Result, Box> { - let Some(ref mut connector) = connector.0 else { - return Err(ValueConsumedError::for_item("connector") - .reason("inner connector is missing") - .into()); - }; - - let Some(should_upgrade) = should_upgrade.0.take() else { - return Err(ValueConsumedError::for_item("should_upgrade") - .reason("ShouldUpgrade is missing, Note: ShouldUpgrade should be used only once") - .into()); - }; - - let result = ironrdp_blocking::mark_as_upgraded(should_upgrade, connector); - - Ok(Box::new(Upgraded(Some(result)))) - } - - pub fn skip_connect_begin( - connector: &mut crate::connector::ffi::ClientConnector, - ) -> Result, Box> { - let Some(ref mut connector) = connector.0 else { - return Err(ValueConsumedError::for_item("connector") - .reason("inner connector is missing") - .into()); - }; - - let result = ironrdp_blocking::skip_connect_begin(connector); - - Ok(Box::new(ShouldUpgrade(Some(result)))) - } - - pub fn connect_finalize( - upgraded: &mut Upgraded, - upgraded_framed: &mut BlockingUpgradedFrame, - connector: &mut crate::connector::ffi::ClientConnector, - server_name: &crate::connector::ffi::ServerName, - server_public_key: &VecU8, - kerberos_config: Option<&crate::credssp::ffi::KerberosConfig>, - ) -> Result, Box> { - let Some(connector) = connector.0.take() else { - return Err(ValueConsumedError::for_item("connector") - .reason("inner connector is missing") - .into()); - }; - - let Some(upgraded) = upgraded.0.take() else { - return Err(ValueConsumedError::for_item("upgraded") - .reason("Upgraded inner is missing, Note: Upgraded should be used only once") - .into()); - }; - - let Some(framed) = upgraded_framed.0.as_mut() else { - return Err(ValueConsumedError::for_item("framed") - .reason("framed has been consumed") - .into()); - }; - - let server_name = server_name.0.clone(); - let mut network_client = network_client::reqwest_network_client::ReqwestNetworkClient; - - let kerberos_config = kerberos_config.as_ref().map(|config| config.0.clone()); - - let result = ironrdp_blocking::connect_finalize( - upgraded, - framed, - connector, - server_name, - server_public_key.0.clone(), - &mut network_client, - kerberos_config, - )?; - - Ok(Box::new(ConnectionResult(result))) - } - } -} +// #[diplomat::bridge] +// pub mod ffi { +// use ironrdp::connector::sspi::network_client; +// use ironrdp_blocking::connect_begin; + +// use crate::{ +// connector::result::ffi::ConnectionResult, +// error::{ +// ffi::{IronRdpError, IronRdpErrorKind}, +// ValueConsumedError, +// }, +// tls::TlsStream, +// utils::ffi::{StdTcpStream, VecU8}, +// }; + +// #[diplomat::opaque] +// pub struct BlockingTcpFrame(pub Option>); + +// impl BlockingTcpFrame { +// pub fn from_tcp_stream(stream: &mut StdTcpStream) -> Result, Box> { +// let Some(stream) = stream.0.take() else { +// return Err(ValueConsumedError::for_item("tcp_stream") +// .reason("tcp stream has been consumed") +// .into()); +// }; + +// let framed = ironrdp_blocking::Framed::new(stream); + +// Ok(Box::new(BlockingTcpFrame(Some(framed)))) +// } + +// pub fn into_tcp_steam_no_leftover(&mut self) -> Result, Box> { +// let Some(stream) = self.0.take() else { +// return Err(ValueConsumedError::for_item("BlockingTcpFrame") +// .reason("BlockingTcpFrame has been consumed") +// .into()); +// }; + +// let stream = stream.into_inner_no_leftover(); + +// Ok(Box::new(StdTcpStream(Some(stream)))) +// } +// } + +// #[diplomat::opaque] +// pub struct BlockingUpgradedFrame(pub Option>); + +// impl BlockingUpgradedFrame { +// pub fn from_upgraded_stream( +// stream: &mut crate::tls::ffi::UpgradedStream, +// ) -> Result, Box> { +// let Some(stream) = stream.0.take() else { +// return Err(ValueConsumedError::for_item("upgraded_stream") +// .reason("upgraded stream has been consumed") +// .into()); +// }; + +// let framed = ironrdp_blocking::Framed::new(stream); + +// Ok(Box::new(BlockingUpgradedFrame(Some(framed)))) +// } +// } + +// #[diplomat::opaque] // Diplomat does not support direct function calls, so we need to wrap the function in a struct +// pub struct IronRdpBlocking; + +// #[diplomat::opaque] +// pub struct ShouldUpgrade(pub Option); + +// #[diplomat::opaque] +// pub struct Upgraded(pub Option); + +// impl IronRdpBlocking { +// pub fn new() -> Box { +// Box::new(IronRdpBlocking) +// } + +// pub fn connect_begin( +// framed: &mut BlockingTcpFrame, +// connector: &mut crate::connector::ffi::ClientConnector, +// ) -> Result, Box> { +// let Some(ref mut connector) = connector.0 else { +// return Err(IronRdpErrorKind::Consumed.into()); +// }; + +// let Some(framed) = framed.0.as_mut() else { +// return Err(ValueConsumedError::for_item("framed") +// .reason("framed has been consumed") +// .into()); +// }; + +// let result = connect_begin(framed, connector)?; + +// Ok(Box::new(ShouldUpgrade(Some(result)))) +// } + +// pub fn mark_as_upgraded( +// should_upgrade: &mut ShouldUpgrade, +// connector: &mut crate::connector::ffi::ClientConnector, +// ) -> Result, Box> { +// let Some(ref mut connector) = connector.0 else { +// return Err(ValueConsumedError::for_item("connector") +// .reason("inner connector is missing") +// .into()); +// }; + +// let Some(should_upgrade) = should_upgrade.0.take() else { +// return Err(ValueConsumedError::for_item("should_upgrade") +// .reason("ShouldUpgrade is missing, Note: ShouldUpgrade should be used only once") +// .into()); +// }; + +// let result = ironrdp_blocking::mark_as_upgraded(should_upgrade, connector); + +// Ok(Box::new(Upgraded(Some(result)))) +// } + +// pub fn skip_connect_begin( +// connector: &mut crate::connector::ffi::ClientConnector, +// ) -> Result, Box> { +// let Some(ref mut connector) = connector.0 else { +// return Err(ValueConsumedError::for_item("connector") +// .reason("inner connector is missing") +// .into()); +// }; + +// let result = ironrdp_blocking::skip_connect_begin(connector); + +// Ok(Box::new(ShouldUpgrade(Some(result)))) +// } + +// pub fn connect_finalize( +// upgraded: &mut Upgraded, +// upgraded_framed: &mut BlockingUpgradedFrame, +// connector: &mut crate::connector::ffi::ClientConnector, +// server_name: &crate::connector::ffi::ServerName, +// server_public_key: &VecU8, +// kerberos_config: Option<&crate::credssp::ffi::KerberosConfig>, +// ) -> Result, Box> { +// let Some(connector) = connector.0.take() else { +// return Err(ValueConsumedError::for_item("connector") +// .reason("inner connector is missing") +// .into()); +// }; + +// let Some(upgraded) = upgraded.0.take() else { +// return Err(ValueConsumedError::for_item("upgraded") +// .reason("Upgraded inner is missing, Note: Upgraded should be used only once") +// .into()); +// }; + +// let Some(framed) = upgraded_framed.0.as_mut() else { +// return Err(ValueConsumedError::for_item("framed") +// .reason("framed has been consumed") +// .into()); +// }; + +// let server_name = server_name.0.clone(); +// let mut network_client = network_client::reqwest_network_client::ReqwestNetworkClient; + +// let kerberos_config = kerberos_config.as_ref().map(|config| config.0.clone()); + +// let result = ironrdp_blocking::connect_finalize( +// upgraded, +// framed, +// connector, +// server_name, +// server_public_key.0.clone(), +// &mut network_client, +// kerberos_config, +// )?; + +// Ok(Box::new(ConnectionResult(result))) +// } +// } +// } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 43a3562f7..fde993563 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -3,10 +3,9 @@ pub mod connector; pub mod credssp; pub mod dvc; pub mod error; -pub mod ironrdp_blocking; pub mod svc; pub mod tls; pub mod utils; +pub mod pdu; -use sspi as _; // we need this for network_client and avoid CI failure use tracing as _; // need this in the future diff --git a/ffi/src/pdu.rs b/ffi/src/pdu.rs new file mode 100644 index 000000000..2fc3cfcc2 --- /dev/null +++ b/ffi/src/pdu.rs @@ -0,0 +1,18 @@ + + +#[diplomat::bridge] +pub mod ffi { + + #[diplomat::opaque] + pub struct WriteBuf(pub ironrdp::pdu::write_buf::WriteBuf); + + impl WriteBuf { + pub fn new() -> Box { + Box::new(WriteBuf(ironrdp::pdu::write_buf::WriteBuf::new())) + } + + pub fn clear(&mut self) { + self.0.clear(); + } + } +} \ No newline at end of file diff --git a/ffi/src/utils/mod.rs b/ffi/src/utils/mod.rs index c63868e20..78cb15c63 100644 --- a/ffi/src/utils/mod.rs +++ b/ffi/src/utils/mod.rs @@ -42,6 +42,10 @@ pub mod ffi { } buffer.copy_from_slice(&self.0) } + + pub fn new_empty() -> Box { + Box::new(VecU8(Vec::new())) + } } #[diplomat::opaque] diff --git a/xtask/src/cli.rs b/xtask/src/cli.rs index b027ec141..fc8a0f854 100644 --- a/xtask/src/cli.rs +++ b/xtask/src/cli.rs @@ -37,7 +37,8 @@ TASKS: web install Install dependencies required to build and run Web Client web run Run SvelteKit-based standalone Web Client ffi build [--release] Build DLL for FFI (default is debug) - ffi bindings Generate C# bindings for FFI + ffi bindings [--skip-dotnet-build] + Generate C# bindings for FFI, optionally skipping the .NET build "; pub fn print_help() { @@ -88,9 +89,11 @@ pub enum Action { WebInstall, WebRun, FfiBuildDll { - debug: bool, + release: bool, + }, + FfiBuildBindings { + skip_dotnet_build: bool, }, - FfiBuildBindings, } pub fn parse_args() -> anyhow::Result { @@ -160,9 +163,11 @@ pub fn parse_args() -> anyhow::Result { }, Some("ffi") => match args.subcommand()?.as_deref() { Some("build") => Action::FfiBuildDll { - debug: !args.contains("--release"), + release: args.contains("--release"), + }, + Some("bindings") => Action::FfiBuildBindings { + skip_dotnet_build: args.contains("--skip-dotnet-build"), }, - Some("bindings") => Action::FfiBuildBindings, Some(unknown) => anyhow::bail!("unknown ffi action: {unknown}"), None => Action::ShowHelp, }, diff --git a/xtask/src/ffi.rs b/xtask/src/ffi.rs index 84a0bdb15..2595f3749 100644 --- a/xtask/src/ffi.rs +++ b/xtask/src/ffi.rs @@ -1,6 +1,6 @@ -pub(crate) fn build_dll(sh: &xshell::Shell, debug: bool) -> anyhow::Result<()> { +pub(crate) fn build_dll(sh: &xshell::Shell, release: bool) -> anyhow::Result<()> { let mut args = vec!["build", "--package", "ffi"]; - if !debug { + if release { args.push("--release"); } sh.cmd("cargo").args(&args).run()?; @@ -11,7 +11,7 @@ pub(crate) fn build_dll(sh: &xshell::Shell, debug: bool) -> anyhow::Result<()> { use std::fs; use std::path::Path; -pub(crate) fn build_bindings(sh: &xshell::Shell) -> anyhow::Result<()> { +pub(crate) fn build_bindings(sh: &xshell::Shell, skip_dotnet_build: bool) -> anyhow::Result<()> { let dotnet_generated_path = "./dotnet/Devolutions.IronRdp/Generated/"; let diplomat_config = "./dotnet-interop-conf.toml"; @@ -31,6 +31,17 @@ pub(crate) fn build_bindings(sh: &xshell::Shell) -> anyhow::Result<()> { .arg(diplomat_config) .run()?; + if skip_dotnet_build { + return Ok(()); + } + + sh.change_dir("./dotnet"); + sh.change_dir("./Devolutions.IronRdp"); + + sh.cmd("dotnet") + .arg("build") + .run()?; + Ok(()) } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index a1695ab14..03162e4e1 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -112,8 +112,8 @@ fn main() -> anyhow::Result<()> { Action::WebCheck => web::check(&sh)?, Action::WebInstall => web::install(&sh)?, Action::WebRun => web::run(&sh)?, - Action::FfiBuildDll { debug } => ffi::build_dll(&sh, debug)?, - Action::FfiBuildBindings => ffi::build_bindings(&sh)?, + Action::FfiBuildDll { release: debug } => ffi::build_dll(&sh, debug)?, + Action::FfiBuildBindings { skip_dotnet_build } => ffi::build_bindings(&sh,skip_dotnet_build)?, } Ok(()) From 8f9f4582ac0e9fb95ed88a3bad0b9f57331c2dc7 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 28 Mar 2024 12:06:39 -0400 Subject: [PATCH 11/44] WIP:Trying to get core protocol work with C# stream --- .../Program.cs | 63 ++++++++++++------- .../Generated/ClientConnector.cs | 43 +++++++++++-- .../Devolutions.IronRdp/Generated/PduHint.cs | 30 +++++---- .../Generated/RawClientConnector.cs | 5 +- .../Generated/RawPduHint.cs | 2 +- .../Generated/RawWriteBuf.cs | 3 + .../Devolutions.IronRdp/Generated/WriteBuf.cs | 21 +++++++ ffi/src/connector/mod.rs | 22 ++++--- ffi/src/pdu.rs | 12 +++- xtask/src/ffi.rs | 23 +++++++ 10 files changed, 168 insertions(+), 56 deletions(-) diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index 7df95ac38..16b1963f0 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -1,13 +1,10 @@ -using System; -using System.IO.Compression; -using System.Net; +using System.Net; using System.Net.Sockets; -using Devolutions.IronRdp; namespace Devolutions.IronRdp.ConnectExample { class Program { - static void Main(string[] args) + static async Task Main(string[] args) { try { @@ -16,7 +13,7 @@ static void Main(string[] args) var password = "DevoLabs123!"; var domain = "ad.it-help.ninja"; - Connect(serverName, username, password, domain); + await Connect(serverName, username, password, domain); } catch (IronRdpException e) { @@ -25,7 +22,7 @@ static void Main(string[] args) } } - static async void Connect(String servername, String username, String password, String domain) + static async Task Connect(String servername, String username, String password, String domain) { SocketAddr serverAddr = SocketAddr.LookUp(servername, 3389); @@ -43,45 +40,67 @@ static async void Connect(String servername, String username, String password, S connector.WithServerAddr(serverAddr); var writeBuf = WriteBuf.New(); - var stream = await CreateTcpConnection(servername, 3389); - + Console.WriteLine("Connected to server"); + var framed = new Framed(stream); while (!connector.ShouldPerformSecurityUpgrade()) { - SingleConnectStep(connector, writeBuf,stream); + await SingleConnectStep(connector, writeBuf, framed); } + + Console.WriteLine("need to perform security upgrade"); } - static void SingleConnectStep(ClientConnector connector, WriteBuf writeBuf, NetworkStream stream) + static async Task SingleConnectStep(ClientConnector connector, WriteBuf buf, Framed framed) { + buf.Clear(); + var pduHint = connector.NextPduHint(); + Written written; + if (pduHint.IsSome()) + { + byte[] pdu = await framed.ReadByHint(pduHint); + written = connector.Step(pdu,buf); + } + else + { + written = connector.StepNoInput(buf); + } - if (pduHint.IsSome()) { - var pdu = ReadByHints(stream, pduHint); - connector.Step(pdu, writeBuf); - } else { - // connector.Setp + if (written.IsNothing()) + { + Console.WriteLine("Written is nothing"); + return; } + var size = written.GetSize(); + + if (!size.IsSome()) + { + Console.WriteLine("Size is nothing"); + return; + } + var actualSize = size.Get(); + var response = new byte[actualSize]; + buf.ReadIntoBuf(response); + + await framed.Write(response); } + static async Task CreateTcpConnection(String servername, int port) { IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync(servername); IPAddress ipAddress = ipHostInfo.AddressList[0]; IPEndPoint ipEndPoint = new(ipAddress, port); - using TcpClient client = new(); + TcpClient client = new TcpClient(); await client.ConnectAsync(ipEndPoint); - using NetworkStream stream = client.GetStream(); + NetworkStream stream = client.GetStream(); return stream; } - static VecU8 ReadByHints(NetworkStream stream ,PduHint pduHint) { - // TODO: Implement ReadByHints - return VecU8.NewEmpty(); - } } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs index 25dd35107..4ce24ea9c 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs @@ -191,7 +191,10 @@ public void MarkCredsspAsDone() } /// - public void Step(VecU8 input, WriteBuf writeBuf) + /// + /// A Written allocated on Rust side. + /// + public Written Step(byte[] input, WriteBuf writeBuf) { unsafe { @@ -199,11 +202,37 @@ public void Step(VecU8 input, WriteBuf writeBuf) { throw new ObjectDisposedException("ClientConnector"); } - Raw.VecU8* inputRaw; - inputRaw = input.AsFFI(); - if (inputRaw == null) + nuint inputLength = (nuint)input.Length; + Raw.WriteBuf* writeBufRaw; + writeBufRaw = writeBuf.AsFFI(); + if (writeBufRaw == null) { - throw new ObjectDisposedException("VecU8"); + throw new ObjectDisposedException("WriteBuf"); + } + fixed (byte* inputPtr = input) + { + Raw.ConnectorFfiResultBoxWrittenBoxIronRdpError result = Raw.ClientConnector.Step(_inner, inputPtr, inputLength, writeBufRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.Written* retVal = result.Ok; + return new Written(retVal); + } + } + } + + /// + /// + /// A Written allocated on Rust side. + /// + public Written StepNoInput(WriteBuf writeBuf) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); } Raw.WriteBuf* writeBufRaw; writeBufRaw = writeBuf.AsFFI(); @@ -211,11 +240,13 @@ public void Step(VecU8 input, WriteBuf writeBuf) { throw new ObjectDisposedException("WriteBuf"); } - Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.ClientConnector.Step(_inner, inputRaw, writeBufRaw); + Raw.ConnectorFfiResultBoxWrittenBoxIronRdpError result = Raw.ClientConnector.StepNoInput(_inner, writeBufRaw); if (!result.isOk) { throw new IronRdpException(new IronRdpError(result.Err)); } + Raw.Written* retVal = result.Ok; + return new Written(retVal); } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/PduHint.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/PduHint.cs index ade90356a..6045939c7 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/PduHint.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/PduHint.cs @@ -46,7 +46,7 @@ public bool IsSome() /// /// A OptionalUsize allocated on Rust side. /// - public OptionalUsize FindSize(VecU8 buffer) + public OptionalUsize FindSize(byte[] bytes) { unsafe { @@ -54,23 +54,21 @@ public OptionalUsize FindSize(VecU8 buffer) { throw new ObjectDisposedException("PduHint"); } - Raw.VecU8* bufferRaw; - bufferRaw = buffer.AsFFI(); - if (bufferRaw == null) + nuint bytesLength = (nuint)bytes.Length; + fixed (byte* bytesPtr = bytes) { - throw new ObjectDisposedException("VecU8"); + Raw.ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError result = Raw.PduHint.FindSize(_inner, bytesPtr, bytesLength); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.OptionalUsize* retVal = result.Ok; + if (retVal == null) + { + return null; + } + return new OptionalUsize(retVal); } - Raw.ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError result = Raw.PduHint.FindSize(_inner, bufferRaw); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - Raw.OptionalUsize* retVal = result.Ok; - if (retVal == null) - { - return null; - } - return new OptionalUsize(retVal); } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs index 8fc718b33..022cfb880 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs @@ -50,7 +50,10 @@ public partial struct ClientConnector public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError MarkCredsspAsDone(ClientConnector* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_step", ExactSpelling = true)] - public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError Step(ClientConnector* self, VecU8* input, WriteBuf* writeBuf); + public static unsafe extern ConnectorFfiResultBoxWrittenBoxIronRdpError Step(ClientConnector* self, byte* input, nuint inputSz, WriteBuf* writeBuf); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_step_no_input", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultBoxWrittenBoxIronRdpError StepNoInput(ClientConnector* self, WriteBuf* writeBuf); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_next_pdu_hint", ExactSpelling = true)] public static unsafe extern ConnectorFfiResultBoxPduHintBoxIronRdpError NextPduHint(ClientConnector* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHint.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHint.cs index 44e3e08fb..1240286b5 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHint.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHint.cs @@ -21,7 +21,7 @@ public partial struct PduHint public static unsafe extern bool IsSome(PduHint* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHint_find_size", ExactSpelling = true)] - public static unsafe extern ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError FindSize(PduHint* self, VecU8* buffer); + public static unsafe extern ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError FindSize(PduHint* self, byte* bytes, nuint bytesSz); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHint_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(PduHint* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawWriteBuf.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawWriteBuf.cs index 6ac0aa26c..11af66f1b 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawWriteBuf.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawWriteBuf.cs @@ -22,6 +22,9 @@ public partial struct WriteBuf [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WriteBuf_clear", ExactSpelling = true)] public static unsafe extern void Clear(WriteBuf* self); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WriteBuf_read_into_buf", ExactSpelling = true)] + public static unsafe extern PduFfiResultVoidBoxIronRdpError ReadIntoBuf(WriteBuf* self, byte* buf, nuint bufSz); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WriteBuf_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(WriteBuf* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/WriteBuf.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/WriteBuf.cs index f1198bae1..2d3244053 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/WriteBuf.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/WriteBuf.cs @@ -53,6 +53,27 @@ public void Clear() } } + /// + public void ReadIntoBuf(byte[] buf) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("WriteBuf"); + } + nuint bufLength = (nuint)buf.Length; + fixed (byte* bufPtr = buf) + { + Raw.PduFfiResultVoidBoxIronRdpError result = Raw.WriteBuf.ReadIntoBuf(_inner, bufPtr, bufLength); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + } + } + } + /// /// Returns the underlying raw handle. /// diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index fa27ad235..065f83cd1 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -14,10 +14,10 @@ pub mod ffi { ValueConsumedError, }, pdu::ffi::WriteBuf, - utils::ffi::{SocketAddr, VecU8}, + utils::ffi::SocketAddr, }; - use super::config::ffi::Config; + use super::{config::ffi::Config, result::ffi::Written}; #[diplomat::opaque] // We must use Option here, as ClientConnector is not Clone and have functions that consume it pub struct ClientConnector(pub Option); @@ -110,12 +110,20 @@ pub mod ffi { Ok(()) } - pub fn step(&mut self, input: &VecU8, write_buf: &mut WriteBuf) -> Result<(), Box> { + pub fn step(&mut self, input: &[u8], write_buf: &mut WriteBuf) -> Result, Box> { let Some(connector) = self.0.as_mut() else { return Err(ValueConsumedError::for_item("connector").into()); }; - connector.step(input.0.as_ref(), &mut write_buf.0)?; - Ok(()) + let written = connector.step(input, &mut write_buf.0)?; + Ok(Written(written)).map(Box::new) + } + + pub fn step_no_input(&mut self, write_buf: &mut WriteBuf) -> Result, Box> { + let Some(connector) = self.0.as_mut() else { + return Err(ValueConsumedError::for_item("connector").into()); + }; + let written = connector.step_no_input(&mut write_buf.0)?; + Ok(Written(written)).map(Box::new) } } @@ -129,13 +137,13 @@ pub mod ffi { pub fn find_size( &'a self, - buffer: &VecU8, + bytes: &[u8], ) -> Result>, Box> { let Some(pdu_hint) = self.0 else { return Ok(None); }; - let size = pdu_hint.find_size(buffer.0.as_slice())?; + let size = pdu_hint.find_size(bytes)?; Ok(Some(Box::new(crate::utils::ffi::OptionalUsize(size)))) } diff --git a/ffi/src/pdu.rs b/ffi/src/pdu.rs index 2fc3cfcc2..3a8e267d0 100644 --- a/ffi/src/pdu.rs +++ b/ffi/src/pdu.rs @@ -1,8 +1,9 @@ - - #[diplomat::bridge] pub mod ffi { + use crate::error::ffi::IronRdpError; + + #[diplomat::opaque] pub struct WriteBuf(pub ironrdp::pdu::write_buf::WriteBuf); @@ -14,5 +15,10 @@ pub mod ffi { pub fn clear(&mut self) { self.0.clear(); } + + pub fn read_into_buf(&mut self, buf: &mut [u8]) -> Result<(),Box> { + buf.copy_from_slice(&self.0[..buf.len()]); + Ok(()) + } } -} \ No newline at end of file +} diff --git a/xtask/src/ffi.rs b/xtask/src/ffi.rs index 2595f3749..cb338766f 100644 --- a/xtask/src/ffi.rs +++ b/xtask/src/ffi.rs @@ -5,6 +5,29 @@ pub(crate) fn build_dll(sh: &xshell::Shell, release: bool) -> anyhow::Result<()> } sh.cmd("cargo").args(&args).run()?; + let target_dir = if release { + "release" + } else { + "debug" + }; + + let mut path = sh.current_dir().clone(); + path.push("target"); + path.push(target_dir); + + let dll_name = "ironrdp.dll"; + let devolution_dll_name = "DevolutionsIronRdp.dll"; + + let mut dll_path = path.clone(); + dll_path.push(dll_name); + + let mut devolution_dll_path = path.clone(); + devolution_dll_path.push(devolution_dll_name); + + // copy dll_path to devolution_dll_path + std::fs::copy(&dll_path, &devolution_dll_path)?; + println!("Copied {:?} to {:?}", dll_path, devolution_dll_path); + Ok(()) } From c016f36d9265b331fec061873ed236cd720f3331 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 28 Mar 2024 12:06:59 -0400 Subject: [PATCH 12/44] WIP:Trying to get core protocol work with C# stream --- .../Frame.cs | 64 +++++++++++++++++++ ...ectorFfiResultBoxWrittenBoxIronRdpError.cs | 46 +++++++++++++ .../RawPduFfiResultVoidBoxIronRdpError.cs | 36 +++++++++++ 3 files changed, 146 insertions(+) create mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxWrittenBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawPduFfiResultVoidBoxIronRdpError.cs diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs new file mode 100644 index 000000000..16f47b5ad --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs @@ -0,0 +1,64 @@ +using Devolutions.IronRdp; + +public class Framed where S : Stream +{ + private S stream; + private List buffer; + + public Framed(S stream) + { + this.stream = stream; + this.buffer = new List(); + } + + public (S, List) GetInner() + { + return (this.stream, this.buffer); + } + + public byte[] Peek() + { + return this.buffer.ToArray(); + } + + public async Task ReadExact(nuint size) + { + var buffer = new byte[size]; + Memory memory = buffer; + await this.stream.ReadExactlyAsync(memory); + this.buffer.AddRange(buffer); + } + + async Task Read() { + var buffer = new byte[1024]; + Memory memory = buffer; + var size = await this.stream.ReadAsync(memory); + this.buffer.AddRange(buffer.Take(size)); + return size; + } + + public async Task Write(byte[] data) + { + ReadOnlyMemory memory = data; + await this.stream.WriteAsync(memory); + } + + + public async Task ReadByHint(PduHint pduHint) { + while(true) { + + var size = pduHint.FindSize(this.buffer.ToArray()); + + if (size.IsSome()) { + await this.ReadExact(size.Get()); + return this.buffer.ToArray(); + }else { + var len = await this.Read(); + if (len == 0) { + throw new Exception("EOF"); + } + } + + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxWrittenBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxWrittenBoxIronRdpError.cs new file mode 100644 index 000000000..efb13c479 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxWrittenBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ConnectorFfiResultBoxWrittenBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal Written* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe Written* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduFfiResultVoidBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduFfiResultVoidBoxIronRdpError.cs new file mode 100644 index 000000000..90fcc2e61 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduFfiResultVoidBoxIronRdpError.cs @@ -0,0 +1,36 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct PduFfiResultVoidBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} From 69079e401e2b40cf48193312d9030154de2013a2 Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 28 Mar 2024 15:25:29 -0400 Subject: [PATCH 13/44] WIP:trying to fix asn1 encoding issue --- .../DesignTimeBuild/.dtbcache.v2 | Bin 0 -> 85479 bytes ...aff8b35a-9ba5-4e52-88a0-2944d404bb88.vsidx | Bin 0 -> 22164 bytes .../v17/.futdcache.v2 | Bin 0 -> 178 bytes ...ons.ironrdp.connectexample.metadata.v7.bin | Bin 0 -> 175628 bytes ...ons.ironrdp.connectexample.projects.v7.bin | Bin 0 -> 99693 bytes .../Frame.cs | 16 +- .../Program.cs | 138 +++++++++++++++++- .../Generated/CredsspProcessGenerator.cs | 22 ++- .../Generated/CredsspSequence.cs | 52 +++---- .../Generated/NetworkRequest.cs | 94 ++++++++++++ .../Generated/NetworkRequestProtocol.cs | 20 +++ ...dsspNetworkFfiResultVoidBoxIronRdpError.cs | 36 +++++ .../Generated/RawCredsspProcessGenerator.cs | 2 +- .../Generated/RawCredsspSequence.cs | 4 +- .../Generated/RawNetworkRequest.cs | 9 ++ .../Generated/RawNetworkRequestProtocol.cs | 20 +++ ffi/src/connector/mod.rs | 4 +- ffi/src/connector/state.rs | 8 + ffi/src/credssp/mod.rs | 8 +- ffi/src/credssp/network.rs | 29 +++- 20 files changed, 403 insertions(+), 59 deletions(-) create mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/DesignTimeBuild/.dtbcache.v2 create mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/FileContentIndex/aff8b35a-9ba5-4e52-88a0-2944d404bb88.vsidx create mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/v17/.futdcache.v2 create mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/ProjectEvaluation/devolutions.ironrdp.connectexample.metadata.v7.bin create mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/ProjectEvaluation/devolutions.ironrdp.connectexample.projects.v7.bin create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/NetworkRequestProtocol.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultVoidBoxIronRdpError.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawNetworkRequestProtocol.cs create mode 100644 ffi/src/connector/state.rs diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/DesignTimeBuild/.dtbcache.v2 b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/DesignTimeBuild/.dtbcache.v2 new file mode 100644 index 0000000000000000000000000000000000000000..a78cbb646430d78552a35ebbec77ba4c72f3d91a GIT binary patch literal 85479 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 Mx95oQ;G-Y?KQUZm?EnA( literal 0 HcmV?d00001 diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/FileContentIndex/aff8b35a-9ba5-4e52-88a0-2944d404bb88.vsidx b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/FileContentIndex/aff8b35a-9ba5-4e52-88a0-2944d404bb88.vsidx new file mode 100644 index 0000000000000000000000000000000000000000..978d3bbb8b3c7de41cd2a41a9f9a26fd90379aa6 GIT binary patch literal 22164 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 literal 0 HcmV?d00001 diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/v17/.futdcache.v2 b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/v17/.futdcache.v2 new file mode 100644 index 0000000000000000000000000000000000000000..7dca3450edc2400c7cf833c6a46c25ab7fe5e543 GIT binary patch literal 178 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 I#^Lz?2i$~l^#A|> literal 0 HcmV?d00001 diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/ProjectEvaluation/devolutions.ironrdp.connectexample.projects.v7.bin b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/ProjectEvaluation/devolutions.ironrdp.connectexample.projects.v7.bin new file mode 100644 index 0000000000000000000000000000000000000000..b42b918fd6187363cf13069efd50cc9e1bd3fdbe GIT binary patch literal 99693 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 literal 0 HcmV?d00001 diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs index 16f47b5ad..0ade2370c 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs @@ -21,12 +21,18 @@ public byte[] Peek() return this.buffer.ToArray(); } - public async Task ReadExact(nuint size) + public async Task ReadExact(nuint size) { - var buffer = new byte[size]; - Memory memory = buffer; - await this.stream.ReadExactlyAsync(memory); - this.buffer.AddRange(buffer); + while (true) { + if (buffer.Count >= (int)size) { + return this.buffer.Skip((int)size).ToArray(); + } + + var len = await this.Read(); + if (len == 0) { + throw new Exception("EOF"); + } + } } async Task Read() { diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index 16b1963f0..6084c31fe 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -1,5 +1,7 @@ using System.Net; +using System.Net.Security; using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; namespace Devolutions.IronRdp.ConnectExample { class Program @@ -48,10 +50,129 @@ static async Task Connect(String servername, String username, String password, S await SingleConnectStep(connector, writeBuf, framed); } + Console.WriteLine("need to perform security upgrade"); + var (streamRequireUpgrade, _) = framed.GetInner(); + byte[] serverpubkey = new byte[0]; + + var promise = new TaskCompletionSource(); + var sslStream = new SslStream(streamRequireUpgrade, false, (sender, certificate, chain, sslPolicyErrors) => + { + serverpubkey = certificate!.GetPublicKey(); + promise.SetResult(true); + return true; + }); + await sslStream.AuthenticateAsClientAsync(servername); + await promise.Task; + + var framedSsl = new Framed(sslStream); + connector.MarkSecurityUpgradeAsDone(); + Console.WriteLine("Security upgrade done"); + if (connector.ShouldPerformCredssp()) + { + Console.WriteLine("Performing CredSSP"); + await PerformCredsspSteps(connector, ServerName.New(servername), writeBuf, framedSsl, serverpubkey); + } + + Console.WriteLine("Performing RDP"); + while (!connector.State().IsTerminal()) + { + await SingleConnectStep(connector, writeBuf, framedSsl); + } + + } - static async Task SingleConnectStep(ClientConnector connector, WriteBuf buf, Framed framed) + private static async Task PerformCredsspSteps(ClientConnector connector, ServerName serverName, WriteBuf writeBuf, Framed framedSsl, byte[] serverpubkey) + { + var credsspSequenceInitResult = CredsspSequence.Init(connector, serverName, serverpubkey, null); + var credsspSequence = credsspSequenceInitResult.GetCredsspSequence(); + var tsRequest = credsspSequenceInitResult.GetTsRequest(); + TcpClient tcpClient = new TcpClient(); + while (true) + { + var generator = credsspSequence.ProcessTsRequest(tsRequest); + Console.WriteLine("Resolving generator"); + var clientState = await ResolveGenerator(generator,tcpClient); + Console.WriteLine("Generator resolved"); + writeBuf.Clear(); + var written = credsspSequence.HandleProcessResult(clientState, writeBuf); + + if (written.GetSize().IsSome()) + { + var actualSize = (int)written.GetSize().Get(); + var response = new byte[actualSize]; + writeBuf.ReadIntoBuf(response); + await framedSsl.Write(response); + } + + var pduHint = credsspSequence.NextPduHint()!; + if (!pduHint.IsSome()) + { + break; + } + + var pdu = await framedSsl.ReadByHint(pduHint); + var decoded = credsspSequence.DecodeServerMessage(pdu); + + if (null == decoded) + { + break; + } + + tsRequest = decoded; + } + } + + private static async Task ResolveGenerator(CredsspProcessGenerator generator, TcpClient tcpClient) + { + var state = generator.Start(); + Console.WriteLine("Starting generator"); + NetworkStream stream = null; + while (true) + { + if (state.IsSuspended()) + { + Console.WriteLine("Generator is suspended"); + var request = state.GetNetworkRequestIfSuspended()!; + var protocol = request.GetProtocol(); + var url = request.GetUrl(); + var data = request.GetData(); + Console.WriteLine("Sending request to " + url); + if (null == stream) + { + url = url.Replace("tcp://", ""); + var split = url.Split(":"); + Console.WriteLine("Connecting to " + split[0] + " on port " + split[1]); + await tcpClient.ConnectAsync(split[0], int.Parse(split[1])); + stream = tcpClient.GetStream(); + + } + if (protocol == NetworkRequestProtocol.Tcp) + { + stream.Write(Utils.Vecu8ToByte(data)); + var readBuf = new byte[8096]; + var readlen = await stream.ReadAsync(readBuf, 0, readBuf.Length); + var actuallyRead = new byte[readlen]; + Array.Copy(readBuf, actuallyRead, readlen); + state = generator.Resume(actuallyRead); + } + else + { + throw new Exception("Unimplemented protocol"); + } + } + else + { + Console.WriteLine("Generator is done"); + var client_state = state.GetClientStateIfCompleted(); + return client_state; + } + } + } + + static async Task SingleConnectStep(ClientConnector connector, WriteBuf buf, Framed framed) + where T : Stream { buf.Clear(); @@ -60,7 +181,7 @@ static async Task SingleConnectStep(ClientConnector connector, WriteBuf buf, Fra if (pduHint.IsSome()) { byte[] pdu = await framed.ReadByHint(pduHint); - written = connector.Step(pdu,buf); + written = connector.Step(pdu, buf); } else { @@ -103,4 +224,17 @@ static async Task CreateTcpConnection(String servername, int port } } + + public static class Utils + { + public static byte[] Vecu8ToByte(VecU8 vecU8) + { + var len = vecU8.GetSize(); + byte[] buffer = new byte[len]; + vecU8.Fill(buffer); + return buffer; + } + } } + + diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspProcessGenerator.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspProcessGenerator.cs index 84ee6523c..a5d0f1024 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspProcessGenerator.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspProcessGenerator.cs @@ -55,7 +55,7 @@ public GeneratorState Start() /// /// A GeneratorState allocated on Rust side. /// - public GeneratorState Resume(VecU8 response) + public GeneratorState Resume(byte[] response) { unsafe { @@ -63,19 +63,17 @@ public GeneratorState Resume(VecU8 response) { throw new ObjectDisposedException("CredsspProcessGenerator"); } - Raw.VecU8* responseRaw; - responseRaw = response.AsFFI(); - if (responseRaw == null) + nuint responseLength = (nuint)response.Length; + fixed (byte* responsePtr = response) { - throw new ObjectDisposedException("VecU8"); + Raw.CredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError result = Raw.CredsspProcessGenerator.Resume(_inner, responsePtr, responseLength); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.GeneratorState* retVal = result.Ok; + return new GeneratorState(retVal); } - Raw.CredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError result = Raw.CredsspProcessGenerator.Resume(_inner, responseRaw); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - Raw.GeneratorState* retVal = result.Ok; - return new GeneratorState(retVal); } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequence.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequence.cs index 717d8de08..d8ec33149 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequence.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequence.cs @@ -53,10 +53,11 @@ public unsafe CredsspSequence(Raw.CredsspSequence* handle) /// /// A CredsspSequenceInitResult allocated on Rust side. /// - public static CredsspSequenceInitResult Init(ClientConnector connector, ServerName serverName, VecU8 serverPublicKey, KerberosConfig? kerberoConfigs) + public static CredsspSequenceInitResult Init(ClientConnector connector, ServerName serverName, byte[] serverPublicKey, KerberosConfig? kerberoConfigs) { unsafe { + nuint serverPublicKeyLength = (nuint)serverPublicKey.Length; Raw.ClientConnector* connectorRaw; connectorRaw = connector.AsFFI(); if (connectorRaw == null) @@ -69,12 +70,6 @@ public static CredsspSequenceInitResult Init(ClientConnector connector, ServerNa { throw new ObjectDisposedException("ServerName"); } - Raw.VecU8* serverPublicKeyRaw; - serverPublicKeyRaw = serverPublicKey.AsFFI(); - if (serverPublicKeyRaw == null) - { - throw new ObjectDisposedException("VecU8"); - } Raw.KerberosConfig* kerberoConfigsRaw; if (kerberoConfigs == null) { @@ -88,13 +83,16 @@ public static CredsspSequenceInitResult Init(ClientConnector connector, ServerNa throw new ObjectDisposedException("KerberosConfig"); } } - Raw.CredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError result = Raw.CredsspSequence.Init(connectorRaw, serverNameRaw, serverPublicKeyRaw, kerberoConfigsRaw); - if (!result.isOk) + fixed (byte* serverPublicKeyPtr = serverPublicKey) { - throw new IronRdpException(new IronRdpError(result.Err)); + Raw.CredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError result = Raw.CredsspSequence.Init(connectorRaw, serverNameRaw, serverPublicKeyPtr, serverPublicKeyLength, kerberoConfigsRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.CredsspSequenceInitResult* retVal = result.Ok; + return new CredsspSequenceInitResult(retVal); } - Raw.CredsspSequenceInitResult* retVal = result.Ok; - return new CredsspSequenceInitResult(retVal); } } @@ -102,7 +100,7 @@ public static CredsspSequenceInitResult Init(ClientConnector connector, ServerNa /// /// A TsRequest allocated on Rust side. /// - public TsRequest DecodeServerMessage(VecU8 pdu) + public TsRequest DecodeServerMessage(byte[] pdu) { unsafe { @@ -110,23 +108,21 @@ public TsRequest DecodeServerMessage(VecU8 pdu) { throw new ObjectDisposedException("CredsspSequence"); } - Raw.VecU8* pduRaw; - pduRaw = pdu.AsFFI(); - if (pduRaw == null) - { - throw new ObjectDisposedException("VecU8"); - } - Raw.CredsspFfiResultOptBoxTsRequestBoxIronRdpError result = Raw.CredsspSequence.DecodeServerMessage(_inner, pduRaw); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - Raw.TsRequest* retVal = result.Ok; - if (retVal == null) + nuint pduLength = (nuint)pdu.Length; + fixed (byte* pduPtr = pdu) { - return null; + Raw.CredsspFfiResultOptBoxTsRequestBoxIronRdpError result = Raw.CredsspSequence.DecodeServerMessage(_inner, pduPtr, pduLength); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.TsRequest* retVal = result.Ok; + if (retVal == null) + { + return null; + } + return new TsRequest(retVal); } - return new TsRequest(retVal); } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/NetworkRequest.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/NetworkRequest.cs index 099a17d99..88044c8a1 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/NetworkRequest.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/NetworkRequest.cs @@ -15,6 +15,30 @@ public partial class NetworkRequest: IDisposable { private unsafe Raw.NetworkRequest* _inner; + public VecU8 Data + { + get + { + return GetData(); + } + } + + public NetworkRequestProtocol Protocol + { + get + { + return GetProtocol(); + } + } + + public string Url + { + get + { + return GetUrl(); + } + } + ///

/// Creates a managed NetworkRequest from a raw handle. /// @@ -29,6 +53,76 @@ public unsafe NetworkRequest(Raw.NetworkRequest* handle) _inner = handle; } + /// + /// A VecU8 allocated on Rust side. + /// + public VecU8 GetData() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("NetworkRequest"); + } + Raw.VecU8* retVal = Raw.NetworkRequest.GetData(_inner); + return new VecU8(retVal); + } + } + + /// + /// A NetworkRequestProtocol allocated on C# side. + /// + public NetworkRequestProtocol GetProtocol() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("NetworkRequest"); + } + Raw.NetworkRequestProtocol retVal = Raw.NetworkRequest.GetProtocol(_inner); + return (NetworkRequestProtocol)retVal; + } + } + + /// + public void GetUrl(DiplomatWriteable writeable) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("NetworkRequest"); + } + Raw.CredsspNetworkFfiResultVoidBoxIronRdpError result = Raw.NetworkRequest.GetUrl(_inner, &writeable); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + } + } + + /// + public string GetUrl() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("NetworkRequest"); + } + DiplomatWriteable writeable = new DiplomatWriteable(); + Raw.CredsspNetworkFfiResultVoidBoxIronRdpError result = Raw.NetworkRequest.GetUrl(_inner, &writeable); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + string retVal = writeable.ToUnicode(); + writeable.Dispose(); + return retVal; + } + } + /// /// Returns the underlying raw handle. /// diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/NetworkRequestProtocol.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/NetworkRequestProtocol.cs new file mode 100644 index 000000000..1dd231125 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/NetworkRequestProtocol.cs @@ -0,0 +1,20 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public enum NetworkRequestProtocol +{ + Tcp = 0, + Udp = 1, + Http = 2, + Https = 3, +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultVoidBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultVoidBoxIronRdpError.cs new file mode 100644 index 000000000..61de7b3e6 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspNetworkFfiResultVoidBoxIronRdpError.cs @@ -0,0 +1,36 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct CredsspNetworkFfiResultVoidBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspProcessGenerator.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspProcessGenerator.cs index 48be7d0e9..919fed6d2 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspProcessGenerator.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspProcessGenerator.cs @@ -20,7 +20,7 @@ public partial struct CredsspProcessGenerator public static unsafe extern CredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError Start(CredsspProcessGenerator* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspProcessGenerator_resume", ExactSpelling = true)] - public static unsafe extern CredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError Resume(CredsspProcessGenerator* self, VecU8* response); + public static unsafe extern CredsspNetworkFfiResultBoxGeneratorStateBoxIronRdpError Resume(CredsspProcessGenerator* self, byte* response, nuint responseSz); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspProcessGenerator_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(CredsspProcessGenerator* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequence.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequence.cs index 7cd366041..800bedf30 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequence.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequence.cs @@ -20,10 +20,10 @@ public partial struct CredsspSequence public static unsafe extern PduHint* NextPduHint(CredsspSequence* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequence_init", ExactSpelling = true)] - public static unsafe extern CredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError Init(ClientConnector* connector, ServerName* serverName, VecU8* serverPublicKey, KerberosConfig* kerberoConfigs); + public static unsafe extern CredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError Init(ClientConnector* connector, ServerName* serverName, byte* serverPublicKey, nuint serverPublicKeySz, KerberosConfig* kerberoConfigs); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequence_decode_server_message", ExactSpelling = true)] - public static unsafe extern CredsspFfiResultOptBoxTsRequestBoxIronRdpError DecodeServerMessage(CredsspSequence* self, VecU8* pdu); + public static unsafe extern CredsspFfiResultOptBoxTsRequestBoxIronRdpError DecodeServerMessage(CredsspSequence* self, byte* pdu, nuint pduSz); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequence_process_ts_request", ExactSpelling = true)] public static unsafe extern CredsspFfiResultBoxCredsspProcessGeneratorBoxIronRdpError ProcessTsRequest(CredsspSequence* self, TsRequest* tsRequest); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawNetworkRequest.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawNetworkRequest.cs index 41e48e1e2..5fa5bc27a 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawNetworkRequest.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawNetworkRequest.cs @@ -16,6 +16,15 @@ public partial struct NetworkRequest { private const string NativeLib = "DevolutionsIronRdp"; + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "NetworkRequest_get_data", ExactSpelling = true)] + public static unsafe extern VecU8* GetData(NetworkRequest* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "NetworkRequest_get_protocol", ExactSpelling = true)] + public static unsafe extern NetworkRequestProtocol GetProtocol(NetworkRequest* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "NetworkRequest_get_url", ExactSpelling = true)] + public static unsafe extern CredsspNetworkFfiResultVoidBoxIronRdpError GetUrl(NetworkRequest* self, DiplomatWriteable* writeable); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "NetworkRequest_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(NetworkRequest* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawNetworkRequestProtocol.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawNetworkRequestProtocol.cs new file mode 100644 index 000000000..4c15f64e9 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawNetworkRequestProtocol.cs @@ -0,0 +1,20 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +public enum NetworkRequestProtocol +{ + Tcp = 0, + Udp = 1, + Http = 2, + Https = 3, +} diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index 065f83cd1..e7925cb47 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -1,6 +1,7 @@ #![allow(clippy::unnecessary_box_returns)] // Diplomat requires returning Boxed types pub mod config; pub mod result; +pub mod state; #[diplomat::bridge] pub mod ffi { @@ -22,9 +23,6 @@ pub mod ffi { #[diplomat::opaque] // We must use Option here, as ClientConnector is not Clone and have functions that consume it pub struct ClientConnector(pub Option); - #[diplomat::opaque] - pub struct ClientConnectorState(pub ironrdp::connector::ClientConnectorState); - #[diplomat::opaque] pub struct ServerName(pub ironrdp::connector::ServerName); diff --git a/ffi/src/connector/state.rs b/ffi/src/connector/state.rs new file mode 100644 index 000000000..9890de82b --- /dev/null +++ b/ffi/src/connector/state.rs @@ -0,0 +1,8 @@ + +#[diplomat::bridge] +pub mod ffi { + + #[diplomat::opaque] + pub struct ClientConnectorState(pub ironrdp::connector::ClientConnectorState); + +} \ No newline at end of file diff --git a/ffi/src/credssp/mod.rs b/ffi/src/credssp/mod.rs index da3549443..adadaf56a 100644 --- a/ffi/src/credssp/mod.rs +++ b/ffi/src/credssp/mod.rs @@ -54,7 +54,7 @@ pub mod ffi { pub fn init( connector: &ClientConnector, server_name: &ServerName, - server_public_key: &VecU8, + server_public_key: &[u8], kerbero_configs: Option<&KerberosConfig>, ) -> Result, Box> { let Some(connector) = connector.0.as_ref() else { @@ -64,7 +64,7 @@ pub mod ffi { let (credssp_sequence, ts_request) = ironrdp::connector::credssp::CredsspSequence::init( connector, server_name.0.clone(), - server_public_key.0.clone(), + server_public_key.to_owned(), kerbero_configs.map(|config| config.0.clone()), )?; @@ -75,8 +75,8 @@ pub mod ffi { .map(Box::new); } - pub fn decode_server_message(&mut self, pdu: &VecU8) -> Result>, Box> { - let ts_request = self.0.decode_server_message(&pdu.0)?; + pub fn decode_server_message(&mut self, pdu: &[u8]) -> Result>, Box> { + let ts_request = self.0.decode_server_message(&pdu)?; Ok(ts_request.map(|ts_request| Box::new(TsRequest(ts_request)))) } diff --git a/ffi/src/credssp/network.rs b/ffi/src/credssp/network.rs index c9b25dbb6..6b2f80b38 100644 --- a/ffi/src/credssp/network.rs +++ b/ffi/src/credssp/network.rs @@ -17,6 +17,14 @@ pub mod ffi { #[diplomat::opaque] pub struct NetworkRequest<'a>(pub &'a sspi::generator::NetworkRequest); + #[diplomat::enum_convert(sspi::network_client::NetworkProtocol)] + pub enum NetworkRequestProtocol { + Tcp, + Udp, + Http, + Https, + } + #[diplomat::opaque] pub struct ClientState(pub sspi::credssp::ClientState); @@ -26,8 +34,8 @@ pub mod ffi { Ok(Box::new(GeneratorState(state))) } - pub fn resume(&mut self, response: &VecU8) -> Result, Box> { - let state = self.0.resume(Ok(response.0.clone())); + pub fn resume(&mut self, response: &[u8]) -> Result, Box> { + let state = self.0.resume(Ok(response.to_owned())); Ok(Box::new(GeneratorState(state))) } } @@ -73,4 +81,21 @@ pub mod ffi { } } } + + impl<'a> NetworkRequest<'a> { + pub fn get_data(&self) -> Box { + Box::new(VecU8(self.0.data.to_vec())) + } + + pub fn get_protocol(&self) -> NetworkRequestProtocol { + self.0.protocol.into() + } + + pub fn get_url(&self, writeable: &mut diplomat_runtime::DiplomatWriteable) -> Result<(), Box> { + use std::fmt::Write; + let url: &str = self.0.url.as_ref(); + write!(writeable, "{}", url)?; + Ok(()) + } + } } From b488061dc649844e00bbac12ebf0d602e7d44bd2 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 12:52:33 -0400 Subject: [PATCH 14/44] WIP:fixed credssp, now fixing panic on pdu --- .../Frame.cs | 38 ++++++++++++------- .../Program.cs | 7 ++-- .../Generated/ClientConnector.cs | 6 ++- .../Devolutions.IronRdp/Generated/PduHint.cs | 19 +--------- .../Generated/RawClientConnector.cs | 2 +- ...iResultBoxOptionalUsizeBoxIronRdpError.cs} | 2 +- ...rFfiResultOptBoxPduHintBoxIronRdpError.cs} | 2 +- .../Generated/RawPduHint.cs | 6 +-- ffi/src/connector/mod.rs | 21 ++++------ ffi/src/credssp/mod.rs | 2 +- 10 files changed, 45 insertions(+), 60 deletions(-) rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError.cs => RawConnectorFfiResultBoxOptionalUsizeBoxIronRdpError.cs} (91%) rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawConnectorFfiResultBoxPduHintBoxIronRdpError.cs => RawConnectorFfiResultOptBoxPduHintBoxIronRdpError.cs} (91%) diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs index 0ade2370c..a020e0b7b 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs @@ -21,21 +21,28 @@ public byte[] Peek() return this.buffer.ToArray(); } + // read from 0 to size bytes from the front of the buffer, and remove them from the buffer,keep the rest public async Task ReadExact(nuint size) { - while (true) { - if (buffer.Count >= (int)size) { - return this.buffer.Skip((int)size).ToArray(); + while (true) + { + if (buffer.Count >= (int)size) + { + var res = this.buffer.Take((int)size).ToArray(); + this.buffer = this.buffer.Skip((int)size).ToList(); + return res; } var len = await this.Read(); - if (len == 0) { + if (len == 0) + { throw new Exception("EOF"); } } } - async Task Read() { + async Task Read() + { var buffer = new byte[1024]; Memory memory = buffer; var size = await this.stream.ReadAsync(memory); @@ -50,17 +57,20 @@ public async Task Write(byte[] data) } - public async Task ReadByHint(PduHint pduHint) { - while(true) { - + public async Task ReadByHint(PduHint pduHint) + { + while (true) + { var size = pduHint.FindSize(this.buffer.ToArray()); - - if (size.IsSome()) { - await this.ReadExact(size.Get()); - return this.buffer.ToArray(); - }else { + if (size.IsSome()) + { + return await this.ReadExact(size.Get()); + } + else + { var len = await this.Read(); - if (len == 0) { + if (len == 0) + { throw new Exception("EOF"); } } diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index 6084c31fe..be1b9fab1 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -107,12 +107,12 @@ private static async Task PerformCredsspSteps(ClientConnector connector, ServerN } var pduHint = credsspSequence.NextPduHint()!; - if (!pduHint.IsSome()) + if (pduHint != null) { break; } - var pdu = await framedSsl.ReadByHint(pduHint); + var pdu = await framedSsl.ReadByHint(pduHint!); var decoded = credsspSequence.DecodeServerMessage(pdu); if (null == decoded) @@ -178,7 +178,7 @@ static async Task SingleConnectStep(ClientConnector connector, WriteBuf buf, var pduHint = connector.NextPduHint(); Written written; - if (pduHint.IsSome()) + if (pduHint != null) { byte[] pdu = await framed.ReadByHint(pduHint); written = connector.Step(pdu, buf); @@ -190,7 +190,6 @@ static async Task SingleConnectStep(ClientConnector connector, WriteBuf buf, if (written.IsNothing()) { - Console.WriteLine("Written is nothing"); return; } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs index 4ce24ea9c..3c5865bc4 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs @@ -262,12 +262,16 @@ public PduHint NextPduHint() { throw new ObjectDisposedException("ClientConnector"); } - Raw.ConnectorFfiResultBoxPduHintBoxIronRdpError result = Raw.ClientConnector.NextPduHint(_inner); + Raw.ConnectorFfiResultOptBoxPduHintBoxIronRdpError result = Raw.ClientConnector.NextPduHint(_inner); if (!result.isOk) { throw new IronRdpException(new IronRdpError(result.Err)); } Raw.PduHint* retVal = result.Ok; + if (retVal == null) + { + return null; + } return new PduHint(retVal); } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/PduHint.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/PduHint.cs index 6045939c7..6969ca84c 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/PduHint.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/PduHint.cs @@ -29,19 +29,6 @@ public unsafe PduHint(Raw.PduHint* handle) _inner = handle; } - public bool IsSome() - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("PduHint"); - } - bool retVal = Raw.PduHint.IsSome(_inner); - return retVal; - } - } - /// /// /// A OptionalUsize allocated on Rust side. @@ -57,16 +44,12 @@ public OptionalUsize FindSize(byte[] bytes) nuint bytesLength = (nuint)bytes.Length; fixed (byte* bytesPtr = bytes) { - Raw.ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError result = Raw.PduHint.FindSize(_inner, bytesPtr, bytesLength); + Raw.ConnectorFfiResultBoxOptionalUsizeBoxIronRdpError result = Raw.PduHint.FindSize(_inner, bytesPtr, bytesLength); if (!result.isOk) { throw new IronRdpException(new IronRdpError(result.Err)); } Raw.OptionalUsize* retVal = result.Ok; - if (retVal == null) - { - return null; - } return new OptionalUsize(retVal); } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs index 022cfb880..099a27114 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs @@ -56,7 +56,7 @@ public partial struct ClientConnector public static unsafe extern ConnectorFfiResultBoxWrittenBoxIronRdpError StepNoInput(ClientConnector* self, WriteBuf* writeBuf); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_next_pdu_hint", ExactSpelling = true)] - public static unsafe extern ConnectorFfiResultBoxPduHintBoxIronRdpError NextPduHint(ClientConnector* self); + public static unsafe extern ConnectorFfiResultOptBoxPduHintBoxIronRdpError NextPduHint(ClientConnector* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_state", ExactSpelling = true)] public static unsafe extern ConnectorFfiResultBoxStateBoxIronRdpError State(ClientConnector* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxOptionalUsizeBoxIronRdpError.cs similarity index 91% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxOptionalUsizeBoxIronRdpError.cs index 5190f88d4..ab190029d 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxOptionalUsizeBoxIronRdpError.cs @@ -12,7 +12,7 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError +public partial struct ConnectorFfiResultBoxOptionalUsizeBoxIronRdpError { [StructLayout(LayoutKind.Explicit)] private unsafe struct InnerUnion diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxPduHintBoxIronRdpError.cs similarity index 91% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintBoxIronRdpError.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxPduHintBoxIronRdpError.cs index a4b4959e2..c5fdf932e 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultBoxPduHintBoxIronRdpError.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectorFfiResultOptBoxPduHintBoxIronRdpError.cs @@ -12,7 +12,7 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct ConnectorFfiResultBoxPduHintBoxIronRdpError +public partial struct ConnectorFfiResultOptBoxPduHintBoxIronRdpError { [StructLayout(LayoutKind.Explicit)] private unsafe struct InnerUnion diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHint.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHint.cs index 1240286b5..6252967e3 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHint.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPduHint.cs @@ -16,12 +16,8 @@ public partial struct PduHint { private const string NativeLib = "DevolutionsIronRdp"; - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHint_is_some", ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.U1)] - public static unsafe extern bool IsSome(PduHint* self); - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHint_find_size", ExactSpelling = true)] - public static unsafe extern ConnectorFfiResultOptBoxOptionalUsizeBoxIronRdpError FindSize(PduHint* self, byte* bytes, nuint bytesSz); + public static unsafe extern ConnectorFfiResultBoxOptionalUsizeBoxIronRdpError FindSize(PduHint* self, byte* bytes, nuint bytesSz); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PduHint_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(PduHint* self); diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index e7925cb47..16e95f6e3 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -109,6 +109,7 @@ pub mod ffi { } pub fn step(&mut self, input: &[u8], write_buf: &mut WriteBuf) -> Result, Box> { + println!("==========step input = {:#X?} =========", input); let Some(connector) = self.0.as_mut() else { return Err(ValueConsumedError::for_item("connector").into()); }; @@ -126,24 +127,16 @@ pub mod ffi { } #[diplomat::opaque] - pub struct PduHint<'a>(pub Option<&'a dyn ironrdp::pdu::PduHint>); + pub struct PduHint<'a>(pub &'a dyn ironrdp::pdu::PduHint); impl<'a> PduHint<'a> { - pub fn is_some(&'a self) -> bool { - self.0.is_some() - } - pub fn find_size( &'a self, bytes: &[u8], - ) -> Result>, Box> { - let Some(pdu_hint) = self.0 else { - return Ok(None); - }; - + ) -> Result, Box> { + let pdu_hint = self.0; let size = pdu_hint.find_size(bytes)?; - - Ok(Some(Box::new(crate::utils::ffi::OptionalUsize(size)))) + Ok(Box::new(crate::utils::ffi::OptionalUsize(size))) } } @@ -167,11 +160,11 @@ pub mod ffi { } impl ClientConnector { - pub fn next_pdu_hint(&self) -> Result>, Box> { + pub fn next_pdu_hint(&self) -> Result>>, Box> { let Some(connector) = self.0.as_ref() else { return Err(ValueConsumedError::for_item("connector").into()); }; - Ok(Box::new(PduHint(connector.next_pdu_hint()))) + Ok(connector.next_pdu_hint().map(|hint| Box::new(PduHint(hint)))) } pub fn state(&self) -> Result>, Box> { diff --git a/ffi/src/credssp/mod.rs b/ffi/src/credssp/mod.rs index adadaf56a..33aa7a72d 100644 --- a/ffi/src/credssp/mod.rs +++ b/ffi/src/credssp/mod.rs @@ -48,7 +48,7 @@ pub mod ffi { impl CredsspSequence { pub fn next_pdu_hint<'a>(&'a self) -> Option>> { - self.0.next_pdu_hint().map(|hint| Box::new(PduHint(Some(hint)))) + self.0.next_pdu_hint().map(|hint| Box::new(PduHint(hint))) } pub fn init( From 4cda2ad38a9ad8a3b903ad8118b1a51e06e03268 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 14:32:13 -0400 Subject: [PATCH 15/44] WIP: connector works! --- .../Program.cs | 110 +++++++++--------- .../Generated/RawWritten.cs | 5 +- .../Generated/RawWrittenType.cs | 18 +++ .../Devolutions.IronRdp/Generated/Written.cs | 17 ++- .../Generated/WrittenType.cs | 18 +++ ffi/src/connector/mod.rs | 8 +- ffi/src/connector/result.rs | 12 +- ffi/src/credssp/mod.rs | 1 - 8 files changed, 121 insertions(+), 68 deletions(-) create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawWrittenType.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/WrittenType.cs diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index be1b9fab1..cdbeee195 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -26,61 +26,75 @@ static async Task Main(string[] args) static async Task Connect(String servername, String username, String password, String domain) { - SocketAddr serverAddr = SocketAddr.LookUp(servername, 3389); + SocketAddr serverAddr; + Config config = buildConfig(servername, username, password, domain, out serverAddr); - ConfigBuilder configBuilder = ConfigBuilder.New(); - - configBuilder.WithUsernameAndPasswrord(username, password); - configBuilder.SetDomain(domain); - configBuilder.SetDesktopSize(800, 600); - configBuilder.SetClientName("IronRdp"); - configBuilder.SetClientDir("C:\\"); - - Config config = configBuilder.Build(); + var stream = await CreateTcpConnection(servername, 3389); + var framed = new Framed(stream); ClientConnector connector = ClientConnector.New(config); connector.WithServerAddr(serverAddr); + await connect_begin(framed, connector); + var (serverPublicKey, framedSsl) = await sercurityUpgrade(servername, framed, connector); + await ConnectFinalize(servername, connector, serverPublicKey, framedSsl); + } + + private static async Task<(byte[], Framed)> sercurityUpgrade(string servername, Framed framed, ClientConnector connector) + { + byte[] serverPublicKey; + Framed framedSsl; + var (streamRequireUpgrade, _) = framed.GetInner(); + var promise = new TaskCompletionSource(); + var sslStream = new SslStream(streamRequireUpgrade, false, (sender, certificate, chain, sslPolicyErrors) => + { + promise.SetResult(certificate!.GetPublicKey()); + return true; + }); + await sslStream.AuthenticateAsClientAsync(servername); + serverPublicKey = await promise.Task; + framedSsl = new Framed(sslStream); + connector.MarkSecurityUpgradeAsDone(); + + return (serverPublicKey, framedSsl); + } + + private static async Task connect_begin(Framed framed, ClientConnector connector) + { var writeBuf = WriteBuf.New(); - var stream = await CreateTcpConnection(servername, 3389); - Console.WriteLine("Connected to server"); - var framed = new Framed(stream); while (!connector.ShouldPerformSecurityUpgrade()) { await SingleConnectStep(connector, writeBuf, framed); } + } + private static Config buildConfig(string servername, string username, string password, string domain, out SocketAddr serverAddr) + { + serverAddr = SocketAddr.LookUp(servername, 3389); + ConfigBuilder configBuilder = ConfigBuilder.New(); - Console.WriteLine("need to perform security upgrade"); - var (streamRequireUpgrade, _) = framed.GetInner(); - byte[] serverpubkey = new byte[0]; + configBuilder.WithUsernameAndPasswrord(username, password); + configBuilder.SetDomain(domain); + configBuilder.SetDesktopSize(800, 600); + configBuilder.SetClientName("IronRdp"); + configBuilder.SetClientDir("C:\\"); - var promise = new TaskCompletionSource(); - var sslStream = new SslStream(streamRequireUpgrade, false, (sender, certificate, chain, sslPolicyErrors) => - { - serverpubkey = certificate!.GetPublicKey(); - promise.SetResult(true); - return true; - }); - await sslStream.AuthenticateAsClientAsync(servername); - await promise.Task; + return configBuilder.Build(); + } - var framedSsl = new Framed(sslStream); - connector.MarkSecurityUpgradeAsDone(); - Console.WriteLine("Security upgrade done"); + private static async Task ConnectFinalize(string servername, ClientConnector connector, byte[] serverpubkey, Framed framedSsl) + { + Console.WriteLine("============ Connect Finalize Start ============"); + var writeBuf2 = WriteBuf.New(); if (connector.ShouldPerformCredssp()) { - Console.WriteLine("Performing CredSSP"); - await PerformCredsspSteps(connector, ServerName.New(servername), writeBuf, framedSsl, serverpubkey); + await PerformCredsspSteps(connector, ServerName.New(servername), writeBuf2, framedSsl, serverpubkey); } - - Console.WriteLine("Performing RDP"); + Console.WriteLine("============ Connect Finalize: CredSSP Is Done ============"); while (!connector.State().IsTerminal()) { - await SingleConnectStep(connector, writeBuf, framedSsl); + await SingleConnectStep(connector, writeBuf2, framedSsl); } - - } private static async Task PerformCredsspSteps(ClientConnector connector, ServerName serverName, WriteBuf writeBuf, Framed framedSsl, byte[] serverpubkey) @@ -92,9 +106,7 @@ private static async Task PerformCredsspSteps(ClientConnector connector, ServerN while (true) { var generator = credsspSequence.ProcessTsRequest(tsRequest); - Console.WriteLine("Resolving generator"); - var clientState = await ResolveGenerator(generator,tcpClient); - Console.WriteLine("Generator resolved"); + var clientState = await ResolveGenerator(generator, tcpClient); writeBuf.Clear(); var written = credsspSequence.HandleProcessResult(clientState, writeBuf); @@ -107,12 +119,12 @@ private static async Task PerformCredsspSteps(ClientConnector connector, ServerN } var pduHint = credsspSequence.NextPduHint()!; - if (pduHint != null) + if (pduHint == null) { break; } - var pdu = await framedSsl.ReadByHint(pduHint!); + var pdu = await framedSsl.ReadByHint(pduHint); var decoded = credsspSequence.DecodeServerMessage(pdu); if (null == decoded) @@ -127,7 +139,6 @@ private static async Task PerformCredsspSteps(ClientConnector connector, ServerN private static async Task ResolveGenerator(CredsspProcessGenerator generator, TcpClient tcpClient) { var state = generator.Start(); - Console.WriteLine("Starting generator"); NetworkStream stream = null; while (true) { @@ -146,7 +157,7 @@ private static async Task ResolveGenerator(CredsspProcessGenerator Console.WriteLine("Connecting to " + split[0] + " on port " + split[1]); await tcpClient.ConnectAsync(split[0], int.Parse(split[1])); stream = tcpClient.GetStream(); - + } if (protocol == NetworkRequestProtocol.Tcp) { @@ -164,7 +175,6 @@ private static async Task ResolveGenerator(CredsspProcessGenerator } else { - Console.WriteLine("Generator is done"); var client_state = state.GetClientStateIfCompleted(); return client_state; } @@ -188,21 +198,15 @@ static async Task SingleConnectStep(ClientConnector connector, WriteBuf buf, written = connector.StepNoInput(buf); } - if (written.IsNothing()) + if (written.GetWrittenType() == WrittenType.Nothing) { return; } - var size = written.GetSize(); - - if (!size.IsSome()) - { - Console.WriteLine("Size is nothing"); - return; - } - var actualSize = size.Get(); + // will throw if size is not set + var size = written.GetSize().Get(); - var response = new byte[actualSize]; + var response = new byte[size]; buf.ReadIntoBuf(response); await framed.Write(response); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawWritten.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawWritten.cs index b59615605..6792ddb39 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawWritten.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawWritten.cs @@ -16,9 +16,8 @@ public partial struct Written { private const string NativeLib = "DevolutionsIronRdp"; - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Written_is_nothing", ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.U1)] - public static unsafe extern bool IsNothing(Written* self); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Written_get_written_type", ExactSpelling = true)] + public static unsafe extern WrittenType GetWrittenType(Written* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Written_get_size", ExactSpelling = true)] public static unsafe extern OptionalUsize* GetSize(Written* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawWrittenType.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawWrittenType.cs new file mode 100644 index 000000000..5878798fc --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawWrittenType.cs @@ -0,0 +1,18 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +public enum WrittenType +{ + Size = 0, + Nothing = 1, +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/Written.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/Written.cs index df50bba24..c02d6cde3 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/Written.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/Written.cs @@ -23,6 +23,14 @@ public OptionalUsize Size } } + public WrittenType WrittenType + { + get + { + return GetWrittenType(); + } + } + /// /// Creates a managed Written from a raw handle. /// @@ -37,7 +45,10 @@ public unsafe Written(Raw.Written* handle) _inner = handle; } - public bool IsNothing() + /// + /// A WrittenType allocated on C# side. + /// + public WrittenType GetWrittenType() { unsafe { @@ -45,8 +56,8 @@ public bool IsNothing() { throw new ObjectDisposedException("Written"); } - bool retVal = Raw.Written.IsNothing(_inner); - return retVal; + Raw.WrittenType retVal = Raw.Written.GetWrittenType(_inner); + return (WrittenType)retVal; } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/WrittenType.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/WrittenType.cs new file mode 100644 index 000000000..72c949257 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/WrittenType.cs @@ -0,0 +1,18 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public enum WrittenType +{ + Size = 0, + Nothing = 1, +} diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index 16e95f6e3..a0bf15980 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -109,7 +109,6 @@ pub mod ffi { } pub fn step(&mut self, input: &[u8], write_buf: &mut WriteBuf) -> Result, Box> { - println!("==========step input = {:#X?} =========", input); let Some(connector) = self.0.as_mut() else { return Err(ValueConsumedError::for_item("connector").into()); }; @@ -130,10 +129,7 @@ pub mod ffi { pub struct PduHint<'a>(pub &'a dyn ironrdp::pdu::PduHint); impl<'a> PduHint<'a> { - pub fn find_size( - &'a self, - bytes: &[u8], - ) -> Result, Box> { + pub fn find_size(&'a self, bytes: &[u8]) -> Result, Box> { let pdu_hint = self.0; let size = pdu_hint.find_size(bytes)?; Ok(Box::new(crate::utils::ffi::OptionalUsize(size))) @@ -164,7 +160,7 @@ pub mod ffi { let Some(connector) = self.0.as_ref() else { return Err(ValueConsumedError::for_item("connector").into()); }; - Ok(connector.next_pdu_hint().map(|hint| Box::new(PduHint(hint)))) + Ok(connector.next_pdu_hint().map(PduHint).map(Box::new)) } pub fn state(&self) -> Result>, Box> { diff --git a/ffi/src/connector/result.rs b/ffi/src/connector/result.rs index f3190d64a..85fc7c48e 100644 --- a/ffi/src/connector/result.rs +++ b/ffi/src/connector/result.rs @@ -5,9 +5,17 @@ pub mod ffi { #[diplomat::opaque] pub struct Written(pub ironrdp::connector::Written); + pub enum WrittenType { + Size, + Nothing, + } + impl Written { - pub fn is_nothing(&self) -> bool { - matches!(self.0, ironrdp::connector::Written::Nothing) + pub fn get_written_type(&self) -> WrittenType { + match &self.0 { + ironrdp::connector::Written::Size(_) => WrittenType::Size, + ironrdp::connector::Written::Nothing => WrittenType::Nothing, + } } pub fn get_size(&self) -> Box { diff --git a/ffi/src/credssp/mod.rs b/ffi/src/credssp/mod.rs index 33aa7a72d..1bd23b581 100644 --- a/ffi/src/credssp/mod.rs +++ b/ffi/src/credssp/mod.rs @@ -10,7 +10,6 @@ pub mod ffi { }, error::{ffi::IronRdpError, ValueConsumedError}, pdu::ffi::WriteBuf, - utils::ffi::VecU8, }; use super::network::ffi::{ClientState, CredsspProcessGenerator}; From f9d76bfc3624e459d5ce80d3c56f1e802b258153 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 14:36:04 -0400 Subject: [PATCH 16/44] WIP: house keeping --- ffi/src/connector/mod.rs | 4 ++-- ffi/src/connector/state.rs | 4 +--- ffi/src/credssp/mod.rs | 8 ++++---- ffi/src/lib.rs | 2 +- ffi/src/pdu.rs | 3 +-- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index a0bf15980..70e0aae64 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -113,7 +113,7 @@ pub mod ffi { return Err(ValueConsumedError::for_item("connector").into()); }; let written = connector.step(input, &mut write_buf.0)?; - Ok(Written(written)).map(Box::new) + Ok(Box::new(Written(written))) } pub fn step_no_input(&mut self, write_buf: &mut WriteBuf) -> Result, Box> { @@ -121,7 +121,7 @@ pub mod ffi { return Err(ValueConsumedError::for_item("connector").into()); }; let written = connector.step_no_input(&mut write_buf.0)?; - Ok(Written(written)).map(Box::new) + Ok(Box::new(Written(written))) } } diff --git a/ffi/src/connector/state.rs b/ffi/src/connector/state.rs index 9890de82b..de892368c 100644 --- a/ffi/src/connector/state.rs +++ b/ffi/src/connector/state.rs @@ -1,8 +1,6 @@ - #[diplomat::bridge] pub mod ffi { #[diplomat::opaque] pub struct ClientConnectorState(pub ironrdp::connector::ClientConnectorState); - -} \ No newline at end of file +} diff --git a/ffi/src/credssp/mod.rs b/ffi/src/credssp/mod.rs index 1bd23b581..b9b0070f3 100644 --- a/ffi/src/credssp/mod.rs +++ b/ffi/src/credssp/mod.rs @@ -1,3 +1,4 @@ +#![allow(clippy::needless_lifetimes)] // Diplomat requires lifetimes pub mod network; #[diplomat::bridge] @@ -67,15 +68,14 @@ pub mod ffi { kerbero_configs.map(|config| config.0.clone()), )?; - return Ok(CredsspSequenceInitResult { + Ok(Box::new(CredsspSequenceInitResult { credssp_sequence: Some(Box::new(CredsspSequence(credssp_sequence))), ts_request: Some(Box::new(TsRequest(ts_request))), - }) - .map(Box::new); + })) } pub fn decode_server_message(&mut self, pdu: &[u8]) -> Result>, Box> { - let ts_request = self.0.decode_server_message(&pdu)?; + let ts_request = self.0.decode_server_message(pdu)?; Ok(ts_request.map(|ts_request| Box::new(TsRequest(ts_request)))) } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index fde993563..8e889fe42 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -3,9 +3,9 @@ pub mod connector; pub mod credssp; pub mod dvc; pub mod error; +pub mod pdu; pub mod svc; pub mod tls; pub mod utils; -pub mod pdu; use tracing as _; // need this in the future diff --git a/ffi/src/pdu.rs b/ffi/src/pdu.rs index 3a8e267d0..a7f887e6f 100644 --- a/ffi/src/pdu.rs +++ b/ffi/src/pdu.rs @@ -3,7 +3,6 @@ pub mod ffi { use crate::error::ffi::IronRdpError; - #[diplomat::opaque] pub struct WriteBuf(pub ironrdp::pdu::write_buf::WriteBuf); @@ -16,7 +15,7 @@ pub mod ffi { self.0.clear(); } - pub fn read_into_buf(&mut self, buf: &mut [u8]) -> Result<(),Box> { + pub fn read_into_buf(&mut self, buf: &mut [u8]) -> Result<(), Box> { buf.copy_from_slice(&self.0[..buf.len()]); Ok(()) } From a6b89fe6b6e49e4d71de41be7c2f5f82b8311e86 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 14:37:47 -0400 Subject: [PATCH 17/44] WIP:House keeping --- ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index cdbeee195..005c58dfe 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -84,13 +84,11 @@ private static Config buildConfig(string servername, string username, string pas private static async Task ConnectFinalize(string servername, ClientConnector connector, byte[] serverpubkey, Framed framedSsl) { - Console.WriteLine("============ Connect Finalize Start ============"); var writeBuf2 = WriteBuf.New(); if (connector.ShouldPerformCredssp()) { await PerformCredsspSteps(connector, ServerName.New(servername), writeBuf2, framedSsl, serverpubkey); } - Console.WriteLine("============ Connect Finalize: CredSSP Is Done ============"); while (!connector.State().IsTerminal()) { await SingleConnectStep(connector, writeBuf2, framedSsl); @@ -144,17 +142,14 @@ private static async Task ResolveGenerator(CredsspProcessGenerator { if (state.IsSuspended()) { - Console.WriteLine("Generator is suspended"); var request = state.GetNetworkRequestIfSuspended()!; var protocol = request.GetProtocol(); var url = request.GetUrl(); var data = request.GetData(); - Console.WriteLine("Sending request to " + url); if (null == stream) { url = url.Replace("tcp://", ""); var split = url.Split(":"); - Console.WriteLine("Connecting to " + split[0] + " on port " + split[1]); await tcpClient.ConnectAsync(split[0], int.Parse(split[1])); stream = tcpClient.GetStream(); From fadee45f4f413449e747fcfb61bd740f91700284 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 14:50:22 -0400 Subject: [PATCH 18/44] CI:fmt CI:fmt --- xtask/src/ffi.rs | 10 ++-------- xtask/src/main.rs | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/xtask/src/ffi.rs b/xtask/src/ffi.rs index cb338766f..86fb2e7b8 100644 --- a/xtask/src/ffi.rs +++ b/xtask/src/ffi.rs @@ -5,11 +5,7 @@ pub(crate) fn build_dll(sh: &xshell::Shell, release: bool) -> anyhow::Result<()> } sh.cmd("cargo").args(&args).run()?; - let target_dir = if release { - "release" - } else { - "debug" - }; + let target_dir = if release { "release" } else { "debug" }; let mut path = sh.current_dir().clone(); path.push("target"); @@ -61,9 +57,7 @@ pub(crate) fn build_bindings(sh: &xshell::Shell, skip_dotnet_build: bool) -> any sh.change_dir("./dotnet"); sh.change_dir("./Devolutions.IronRdp"); - sh.cmd("dotnet") - .arg("build") - .run()?; + sh.cmd("dotnet").arg("build").run()?; Ok(()) } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 03162e4e1..781700f94 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -113,7 +113,7 @@ fn main() -> anyhow::Result<()> { Action::WebInstall => web::install(&sh)?, Action::WebRun => web::run(&sh)?, Action::FfiBuildDll { release: debug } => ffi::build_dll(&sh, debug)?, - Action::FfiBuildBindings { skip_dotnet_build } => ffi::build_bindings(&sh,skip_dotnet_build)?, + Action::FfiBuildBindings { skip_dotnet_build } => ffi::build_bindings(&sh, skip_dotnet_build)?, } Ok(()) From 7d1d06f08d884357699bf6ffd25ac5af87d8cc29 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 14:57:17 -0400 Subject: [PATCH 19/44] CI:typos --- ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index 005c58dfe..d674113dc 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -36,11 +36,11 @@ static async Task Connect(String servername, String username, String password, S connector.WithServerAddr(serverAddr); await connect_begin(framed, connector); - var (serverPublicKey, framedSsl) = await sercurityUpgrade(servername, framed, connector); + var (serverPublicKey, framedSsl) = await securityUpgrade(servername, framed, connector); await ConnectFinalize(servername, connector, serverPublicKey, framedSsl); } - private static async Task<(byte[], Framed)> sercurityUpgrade(string servername, Framed framed, ClientConnector connector) + private static async Task<(byte[], Framed)> securityUpgrade(string servername, Framed framed, ClientConnector connector) { byte[] serverPublicKey; Framed framedSsl; From 3dffd367aa43b0e17e0316522dbdd5e1007524ef Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 14:59:33 -0400 Subject: [PATCH 20/44] Housekeeping: remove unused code --- ffi/src/credssp/mod.rs | 4 +- ffi/src/credssp/network.rs | 2 +- ffi/src/lib.rs | 1 - ffi/src/tls.rs | 146 ------------------------------------- ffi/src/utils/mod.rs | 21 +----- 5 files changed, 4 insertions(+), 170 deletions(-) delete mode 100644 ffi/src/tls.rs diff --git a/ffi/src/credssp/mod.rs b/ffi/src/credssp/mod.rs index b9b0070f3..31da1fed1 100644 --- a/ffi/src/credssp/mod.rs +++ b/ffi/src/credssp/mod.rs @@ -81,9 +81,9 @@ pub mod ffi { pub fn process_ts_request<'a>( &'a mut self, - ts_request: Box, + ts_request: &TsRequest, ) -> Result>, Box> { - let ts_request = ts_request.0; + let ts_request = ts_request.0.clone(); let generator = self.0.process_ts_request(ts_request); Ok(Box::new(CredsspProcessGenerator(generator))) } diff --git a/ffi/src/credssp/network.rs b/ffi/src/credssp/network.rs index 6b2f80b38..170dfcd9a 100644 --- a/ffi/src/credssp/network.rs +++ b/ffi/src/credssp/network.rs @@ -58,7 +58,7 @@ pub mod ffi { pub fn get_client_state_if_completed(&self) -> Result, Box> { match &self.0 { - CredsspGeneratorState::Completed(Ok(res)) => Ok(res.clone()).map(ClientState).map(Box::new), + CredsspGeneratorState::Completed(Ok(res)) => Ok(Box::new(ClientState(res.clone()))), CredsspGeneratorState::Completed(Err(e)) => Err(e.to_owned().into()), _ => Err("Generator is not completed".into()), } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 8e889fe42..28aae1655 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -5,7 +5,6 @@ pub mod dvc; pub mod error; pub mod pdu; pub mod svc; -pub mod tls; pub mod utils; use tracing as _; // need this in the future diff --git a/ffi/src/tls.rs b/ffi/src/tls.rs deleted file mode 100644 index c960b7fd4..000000000 --- a/ffi/src/tls.rs +++ /dev/null @@ -1,146 +0,0 @@ -use std::{io::Write, net::TcpStream}; - -use anyhow::Context; - -pub type TlsStream = rustls::StreamOwned; - -#[diplomat::bridge] -pub mod ffi { - use crate::{ - error::{ffi::IronRdpError, ValueConsumedError}, - utils::ffi::VecU8, - }; - - use super::TlsStream; - - #[diplomat::opaque] - pub struct Tls; - - #[diplomat::opaque] - pub struct TlsUpgradeResult { - pub tls_stream: Option, - pub server_public_key: Vec, - } - - #[diplomat::opaque] - pub struct UpgradedStream(pub Option); - - impl TlsUpgradeResult { - pub fn get_upgraded_stream(&mut self) -> Result, Box> { - let Some(tls_stream) = self.tls_stream.take() else { - return Err(ValueConsumedError::for_item("tls_stream") - .reason("tls_stream is missing") - .into()); - }; - - Ok(Box::new(UpgradedStream(Some(tls_stream)))) - } - - pub fn get_server_public_key(&self) -> Box { - VecU8::from_byte(&self.server_public_key) - } - } - - impl Tls { - pub fn tls_upgrade( - stream: &mut crate::utils::ffi::StdTcpStream, - server_name: &str, - ) -> Result, Box> { - let Some(stream) = stream.0.take() else { - return Err(ValueConsumedError::for_item("stream") - .reason("inner stream is missing") - .into()); - }; - let (tls_stream, server_public_key) = super::tls_upgrade(stream, server_name).unwrap(); - - Ok(Box::new(TlsUpgradeResult { - tls_stream: Some(tls_stream), - server_public_key, - })) - } - } -} - -/// Copy and parsed this code from the screenshot example, this is temporary and should be replaced with something more configurable -/// Need to put more thought into this -/// FIXME/TODO, Implement better TLS FFI interface -fn tls_upgrade( - stream: TcpStream, - server_name: &str, -) -> anyhow::Result<(rustls::StreamOwned, Vec)> { - let mut config = rustls::client::ClientConfig::builder() - .with_safe_defaults() - .with_custom_certificate_verifier(std::sync::Arc::new(danger::NoCertificateVerification)) - .with_no_client_auth(); - - // This adds support for the SSLKEYLOGFILE env variable (https://wiki.wireshark.org/TLS#using-the-pre-master-secret) - config.key_log = std::sync::Arc::new(rustls::KeyLogFile::new()); - - // Disable TLS resumption because it’s not supported by some services such as CredSSP. - // - // > The CredSSP Protocol does not extend the TLS wire protocol. TLS session resumption is not supported. - // - // source: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/385a7489-d46b-464c-b224-f7340e308a5c - config.resumption = rustls::client::Resumption::disabled(); - - let config = std::sync::Arc::new(config); - - let server_name = server_name.try_into().unwrap(); - - let client = rustls::ClientConnection::new(config, server_name)?; - - let mut tls_stream = rustls::StreamOwned::new(client, stream); - - // We need to flush in order to ensure the TLS handshake is moving forward. Without flushing, - // it’s likely the peer certificate is not yet received a this point. - tls_stream.flush()?; - - let cert = tls_stream - .conn - .peer_certificates() - .and_then(|certificates| certificates.first()) - .context("peer certificate is missing")?; - - let server_public_key = extract_tls_server_public_key(&cert.0)?; - - Ok((tls_stream, server_public_key)) -} - -fn extract_tls_server_public_key(cert: &[u8]) -> anyhow::Result> { - use x509_cert::der::Decode as _; - - let cert = x509_cert::Certificate::from_der(cert)?; - - let server_public_key = cert - .tbs_certificate - .subject_public_key_info - .subject_public_key - .as_bytes() - .context("subject public key BIT STRING is not aligned")? - .to_owned(); - - Ok(server_public_key) -} - -mod danger { - use std::time::SystemTime; - - use rustls::client::{ServerCertVerified, ServerCertVerifier}; - use rustls::{Certificate, Error, ServerName}; - - pub(super) struct NoCertificateVerification; - - impl ServerCertVerifier for NoCertificateVerification { - fn verify_server_cert( - &self, - _end_entity: &Certificate, - _intermediates: &[Certificate], - _server_name: &ServerName, - _scts: &mut dyn Iterator, - _ocsp_response: &[u8], - _now: SystemTime, - ) -> Result { - Ok(ServerCertVerified::assertion()) - } - } -} diff --git a/ffi/src/utils/mod.rs b/ffi/src/utils/mod.rs index 78cb15c63..e265f086b 100644 --- a/ffi/src/utils/mod.rs +++ b/ffi/src/utils/mod.rs @@ -1,7 +1,7 @@ #[diplomat::bridge] pub mod ffi { - use crate::error::{ffi::IronRdpError, ValueConsumedError}; + use crate::error::ffi::IronRdpError; #[diplomat::opaque] pub struct SocketAddr(pub std::net::SocketAddr); @@ -51,25 +51,6 @@ pub mod ffi { #[diplomat::opaque] pub struct Any<'a>(pub &'a dyn std::any::Any); - #[diplomat::opaque] - pub struct StdTcpStream(pub Option); - - impl StdTcpStream { - pub fn connect(addr: &SocketAddr) -> Result, Box> { - let stream = std::net::TcpStream::connect(addr.0)?; - Ok(Box::new(StdTcpStream(Some(stream)))) - } - - pub fn set_read_timeout(&mut self) -> Result<(), Box> { - let stream = self - .0 - .as_ref() - .ok_or_else(|| ValueConsumedError::for_item("StdTcpStream"))?; - stream.set_read_timeout(Some(std::time::Duration::from_secs(5)))?; - Ok(()) - } - } - #[diplomat::opaque] pub struct OptionalUsize(pub Option); From 8a4f83229a826d735e429235078a9f78bc3885a3 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 15:00:17 -0400 Subject: [PATCH 21/44] Housekeeping: rebuild ffi --- .../Generated/RawStdTcpStream.cs | 27 ---- .../Devolutions.IronRdp/Generated/RawTls.cs | 24 ---- ...esultBoxTlsUpgradeResultBoxIronRdpError.cs | 46 ------- ...iResultBoxUpgradedStreamBoxIronRdpError.cs | 46 ------- .../Generated/RawTlsUpgradeResult.cs | 27 ---- .../Generated/RawUpgradedStream.cs | 21 ---- ...FfiResultBoxStdTcpStreamBoxIronRdpError.cs | 46 ------- .../RawUtilsFfiResultVoidBoxIronRdpError.cs | 36 ------ .../Generated/StdTcpStream.cs | 104 ---------------- .../Devolutions.IronRdp/Generated/Tls.cs | 92 -------------- .../Generated/TlsUpgradeResult.cs | 117 ------------------ .../Generated/UpgradedStream.cs | 63 ---------- 12 files changed, 649 deletions(-) delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawStdTcpStream.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawTls.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxTlsUpgradeResultBoxIronRdpError.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxUpgradedStreamBoxIronRdpError.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsUpgradeResult.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgradedStream.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxStdTcpStreamBoxIronRdpError.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultVoidBoxIronRdpError.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/StdTcpStream.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/Tls.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/TlsUpgradeResult.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/UpgradedStream.cs diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawStdTcpStream.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawStdTcpStream.cs deleted file mode 100644 index 423fc0ab5..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawStdTcpStream.cs +++ /dev/null @@ -1,27 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct StdTcpStream -{ - private const string NativeLib = "DevolutionsIronRdp"; - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "StdTcpStream_connect", ExactSpelling = true)] - public static unsafe extern UtilsFfiResultBoxStdTcpStreamBoxIronRdpError Connect(SocketAddr* addr); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "StdTcpStream_set_read_timeout", ExactSpelling = true)] - public static unsafe extern UtilsFfiResultVoidBoxIronRdpError SetReadTimeout(StdTcpStream* self); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "StdTcpStream_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(StdTcpStream* self); -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawTls.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTls.cs deleted file mode 100644 index 003a51359..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawTls.cs +++ /dev/null @@ -1,24 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct Tls -{ - private const string NativeLib = "DevolutionsIronRdp"; - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Tls_tls_upgrade", ExactSpelling = true)] - public static unsafe extern TlsFfiResultBoxTlsUpgradeResultBoxIronRdpError TlsUpgrade(StdTcpStream* stream, byte* serverName, nuint serverNameSz); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Tls_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(Tls* self); -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxTlsUpgradeResultBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxTlsUpgradeResultBoxIronRdpError.cs deleted file mode 100644 index c46e5675f..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxTlsUpgradeResultBoxIronRdpError.cs +++ /dev/null @@ -1,46 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct TlsFfiResultBoxTlsUpgradeResultBoxIronRdpError -{ - [StructLayout(LayoutKind.Explicit)] - private unsafe struct InnerUnion - { - [FieldOffset(0)] - internal TlsUpgradeResult* ok; - [FieldOffset(0)] - internal IronRdpError* err; - } - - private InnerUnion _inner; - - [MarshalAs(UnmanagedType.U1)] - public bool isOk; - - public unsafe TlsUpgradeResult* Ok - { - get - { - return _inner.ok; - } - } - - public unsafe IronRdpError* Err - { - get - { - return _inner.err; - } - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxUpgradedStreamBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxUpgradedStreamBoxIronRdpError.cs deleted file mode 100644 index 66c6be871..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsFfiResultBoxUpgradedStreamBoxIronRdpError.cs +++ /dev/null @@ -1,46 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct TlsFfiResultBoxUpgradedStreamBoxIronRdpError -{ - [StructLayout(LayoutKind.Explicit)] - private unsafe struct InnerUnion - { - [FieldOffset(0)] - internal UpgradedStream* ok; - [FieldOffset(0)] - internal IronRdpError* err; - } - - private InnerUnion _inner; - - [MarshalAs(UnmanagedType.U1)] - public bool isOk; - - public unsafe UpgradedStream* Ok - { - get - { - return _inner.ok; - } - } - - public unsafe IronRdpError* Err - { - get - { - return _inner.err; - } - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsUpgradeResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsUpgradeResult.cs deleted file mode 100644 index fa77aa9d6..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawTlsUpgradeResult.cs +++ /dev/null @@ -1,27 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct TlsUpgradeResult -{ - private const string NativeLib = "DevolutionsIronRdp"; - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "TlsUpgradeResult_get_upgraded_stream", ExactSpelling = true)] - public static unsafe extern TlsFfiResultBoxUpgradedStreamBoxIronRdpError GetUpgradedStream(TlsUpgradeResult* self); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "TlsUpgradeResult_get_server_public_key", ExactSpelling = true)] - public static unsafe extern VecU8* GetServerPublicKey(TlsUpgradeResult* self); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "TlsUpgradeResult_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(TlsUpgradeResult* self); -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgradedStream.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgradedStream.cs deleted file mode 100644 index 9ffe4d129..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUpgradedStream.cs +++ /dev/null @@ -1,21 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct UpgradedStream -{ - private const string NativeLib = "DevolutionsIronRdp"; - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "UpgradedStream_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(UpgradedStream* self); -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxStdTcpStreamBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxStdTcpStreamBoxIronRdpError.cs deleted file mode 100644 index c3ca29c99..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxStdTcpStreamBoxIronRdpError.cs +++ /dev/null @@ -1,46 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct UtilsFfiResultBoxStdTcpStreamBoxIronRdpError -{ - [StructLayout(LayoutKind.Explicit)] - private unsafe struct InnerUnion - { - [FieldOffset(0)] - internal StdTcpStream* ok; - [FieldOffset(0)] - internal IronRdpError* err; - } - - private InnerUnion _inner; - - [MarshalAs(UnmanagedType.U1)] - public bool isOk; - - public unsafe StdTcpStream* Ok - { - get - { - return _inner.ok; - } - } - - public unsafe IronRdpError* Err - { - get - { - return _inner.err; - } - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultVoidBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultVoidBoxIronRdpError.cs deleted file mode 100644 index 686bbfb02..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultVoidBoxIronRdpError.cs +++ /dev/null @@ -1,36 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct UtilsFfiResultVoidBoxIronRdpError -{ - [StructLayout(LayoutKind.Explicit)] - private unsafe struct InnerUnion - { - [FieldOffset(0)] - internal IronRdpError* err; - } - - private InnerUnion _inner; - - [MarshalAs(UnmanagedType.U1)] - public bool isOk; - - public unsafe IronRdpError* Err - { - get - { - return _inner.err; - } - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/StdTcpStream.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/StdTcpStream.cs deleted file mode 100644 index 96e2990b1..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/StdTcpStream.cs +++ /dev/null @@ -1,104 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp; - -#nullable enable - -public partial class StdTcpStream: IDisposable -{ - private unsafe Raw.StdTcpStream* _inner; - - /// - /// Creates a managed StdTcpStream from a raw handle. - /// - /// - /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). - ///
- /// This constructor assumes the raw struct is allocated on Rust side. - /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. - ///
- public unsafe StdTcpStream(Raw.StdTcpStream* handle) - { - _inner = handle; - } - - /// - /// - /// A StdTcpStream allocated on Rust side. - /// - public static StdTcpStream Connect(SocketAddr addr) - { - unsafe - { - Raw.SocketAddr* addrRaw; - addrRaw = addr.AsFFI(); - if (addrRaw == null) - { - throw new ObjectDisposedException("SocketAddr"); - } - Raw.UtilsFfiResultBoxStdTcpStreamBoxIronRdpError result = Raw.StdTcpStream.Connect(addrRaw); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - Raw.StdTcpStream* retVal = result.Ok; - return new StdTcpStream(retVal); - } - } - - /// - public void SetReadTimeout() - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("StdTcpStream"); - } - Raw.UtilsFfiResultVoidBoxIronRdpError result = Raw.StdTcpStream.SetReadTimeout(_inner); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - } - } - - /// - /// Returns the underlying raw handle. - /// - public unsafe Raw.StdTcpStream* AsFFI() - { - return _inner; - } - - /// - /// Destroys the underlying object immediately. - /// - public void Dispose() - { - unsafe - { - if (_inner == null) - { - return; - } - - Raw.StdTcpStream.Destroy(_inner); - _inner = null; - - GC.SuppressFinalize(this); - } - } - - ~StdTcpStream() - { - Dispose(); - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/Tls.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/Tls.cs deleted file mode 100644 index c1ffeff4d..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/Tls.cs +++ /dev/null @@ -1,92 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp; - -#nullable enable - -public partial class Tls: IDisposable -{ - private unsafe Raw.Tls* _inner; - - /// - /// Creates a managed Tls from a raw handle. - /// - /// - /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). - ///
- /// This constructor assumes the raw struct is allocated on Rust side. - /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. - ///
- public unsafe Tls(Raw.Tls* handle) - { - _inner = handle; - } - - /// - /// - /// A TlsUpgradeResult allocated on Rust side. - /// - public static TlsUpgradeResult TlsUpgrade(StdTcpStream stream, string serverName) - { - unsafe - { - byte[] serverNameBuf = DiplomatUtils.StringToUtf8(serverName); - nuint serverNameBufLength = (nuint)serverNameBuf.Length; - Raw.StdTcpStream* streamRaw; - streamRaw = stream.AsFFI(); - if (streamRaw == null) - { - throw new ObjectDisposedException("StdTcpStream"); - } - fixed (byte* serverNameBufPtr = serverNameBuf) - { - Raw.TlsFfiResultBoxTlsUpgradeResultBoxIronRdpError result = Raw.Tls.TlsUpgrade(streamRaw, serverNameBufPtr, serverNameBufLength); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - Raw.TlsUpgradeResult* retVal = result.Ok; - return new TlsUpgradeResult(retVal); - } - } - } - - /// - /// Returns the underlying raw handle. - /// - public unsafe Raw.Tls* AsFFI() - { - return _inner; - } - - /// - /// Destroys the underlying object immediately. - /// - public void Dispose() - { - unsafe - { - if (_inner == null) - { - return; - } - - Raw.Tls.Destroy(_inner); - _inner = null; - - GC.SuppressFinalize(this); - } - } - - ~Tls() - { - Dispose(); - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/TlsUpgradeResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/TlsUpgradeResult.cs deleted file mode 100644 index 6afb8b621..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/TlsUpgradeResult.cs +++ /dev/null @@ -1,117 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp; - -#nullable enable - -public partial class TlsUpgradeResult: IDisposable -{ - private unsafe Raw.TlsUpgradeResult* _inner; - - public VecU8 ServerPublicKey - { - get - { - return GetServerPublicKey(); - } - } - - public UpgradedStream UpgradedStream - { - get - { - return GetUpgradedStream(); - } - } - - /// - /// Creates a managed TlsUpgradeResult from a raw handle. - /// - /// - /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). - ///
- /// This constructor assumes the raw struct is allocated on Rust side. - /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. - ///
- public unsafe TlsUpgradeResult(Raw.TlsUpgradeResult* handle) - { - _inner = handle; - } - - /// - /// - /// A UpgradedStream allocated on Rust side. - /// - public UpgradedStream GetUpgradedStream() - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("TlsUpgradeResult"); - } - Raw.TlsFfiResultBoxUpgradedStreamBoxIronRdpError result = Raw.TlsUpgradeResult.GetUpgradedStream(_inner); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - Raw.UpgradedStream* retVal = result.Ok; - return new UpgradedStream(retVal); - } - } - - /// - /// A VecU8 allocated on Rust side. - /// - public VecU8 GetServerPublicKey() - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("TlsUpgradeResult"); - } - Raw.VecU8* retVal = Raw.TlsUpgradeResult.GetServerPublicKey(_inner); - return new VecU8(retVal); - } - } - - /// - /// Returns the underlying raw handle. - /// - public unsafe Raw.TlsUpgradeResult* AsFFI() - { - return _inner; - } - - /// - /// Destroys the underlying object immediately. - /// - public void Dispose() - { - unsafe - { - if (_inner == null) - { - return; - } - - Raw.TlsUpgradeResult.Destroy(_inner); - _inner = null; - - GC.SuppressFinalize(this); - } - } - - ~TlsUpgradeResult() - { - Dispose(); - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/UpgradedStream.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/UpgradedStream.cs deleted file mode 100644 index ad2978433..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/UpgradedStream.cs +++ /dev/null @@ -1,63 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp; - -#nullable enable - -public partial class UpgradedStream: IDisposable -{ - private unsafe Raw.UpgradedStream* _inner; - - /// - /// Creates a managed UpgradedStream from a raw handle. - /// - /// - /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). - ///
- /// This constructor assumes the raw struct is allocated on Rust side. - /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. - ///
- public unsafe UpgradedStream(Raw.UpgradedStream* handle) - { - _inner = handle; - } - - /// - /// Returns the underlying raw handle. - /// - public unsafe Raw.UpgradedStream* AsFFI() - { - return _inner; - } - - /// - /// Destroys the underlying object immediately. - /// - public void Dispose() - { - unsafe - { - if (_inner == null) - { - return; - } - - Raw.UpgradedStream.Destroy(_inner); - _inner = null; - - GC.SuppressFinalize(this); - } - } - - ~UpgradedStream() - { - Dispose(); - } -} From cf9a701ee39e3ef38ea02a3479ff4ee5df53a7cf Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 15:03:43 -0400 Subject: [PATCH 22/44] add git attribute --- .gitattribute | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .gitattribute diff --git a/.gitattribute b/.gitattribute new file mode 100644 index 000000000..abc994967 --- /dev/null +++ b/.gitattribute @@ -0,0 +1,8 @@ +*.rs text eol=lf +*.toml text eol=lf +*.cs text eol=lf +*.js text eol=lf +*.ps1 text eol=lf +*.sln text eol=crlf + +ffi/dotnet/Devolutions.IronRdp/Generated/** linguist-generated merge=binary \ No newline at end of file From 25cc57134651e339a4b77a1faa6c777e73456397 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 15:04:50 -0400 Subject: [PATCH 23/44] Review fix: update legal_copy right date --- ffi/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/build.rs b/ffi/build.rs index 655b2821b..9e13504fc 100644 --- a/ffi/build.rs +++ b/ffi/build.rs @@ -19,7 +19,7 @@ mod win { let output_name = "DevolutionsIronRdp"; let filename = format!("{}.dll", output_name); let company_name = "Devolutions Inc."; - let legal_copyright = format!("Copyright 2019-2022 {}", company_name); + let legal_copyright = format!("Copyright 2019-2024 {}", company_name); let mut cargo_version = env::var("CARGO_PKG_VERSION").unwrap(); cargo_version.push_str(".0"); From 4f8ce4a13047209b5472048f3b20372f1fb3d0ee Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 15:08:52 -0400 Subject: [PATCH 24/44] CI: xtask check --- Cargo.lock | 4 ---- ffi/Cargo.toml | 4 ---- ffi/src/credssp/network.rs | 4 +++- xtask/src/ffi.rs | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7dfd93ae..a8ca4c62f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1124,17 +1124,13 @@ dependencies = [ name = "ffi" version = "0.1.0" dependencies = [ - "anyhow", "diplomat", "diplomat-runtime", "embed-resource", "ironrdp", - "ironrdp-blocking", - "rustls", "sspi", "thiserror", "tracing", - "x509-cert", ] [[package]] diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 40fcc57f8..7961cf388 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -18,13 +18,9 @@ doctest = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.81" diplomat = "0.7.0" diplomat-runtime = "0.7.0" ironrdp = { workspace = true, features = ["connector", "dvc", "svc","rdpdr","rdpsnd"] } -ironrdp-blocking = { path = "../crates/ironrdp-blocking" } -rustls = "0.21" -x509-cert = { version = "0.2", default-features = false, features = ["std"] } sspi = { workspace = true, features = ["network_client"] } thiserror.workspace = true tracing.workspace = true diff --git a/ffi/src/credssp/network.rs b/ffi/src/credssp/network.rs index 170dfcd9a..ea9f972dd 100644 --- a/ffi/src/credssp/network.rs +++ b/ffi/src/credssp/network.rs @@ -1,3 +1,5 @@ +#![allow(single_use_lifetimes)]// Diplomat requires lifetimes + pub type CredsspGeneratorState = sspi::generator::GeneratorState>; @@ -84,7 +86,7 @@ pub mod ffi { impl<'a> NetworkRequest<'a> { pub fn get_data(&self) -> Box { - Box::new(VecU8(self.0.data.to_vec())) + Box::new(VecU8(self.0.data.clone())) } pub fn get_protocol(&self) -> NetworkRequestProtocol { diff --git a/xtask/src/ffi.rs b/xtask/src/ffi.rs index 86fb2e7b8..8406dd624 100644 --- a/xtask/src/ffi.rs +++ b/xtask/src/ffi.rs @@ -7,7 +7,7 @@ pub(crate) fn build_dll(sh: &xshell::Shell, release: bool) -> anyhow::Result<()> let target_dir = if release { "release" } else { "debug" }; - let mut path = sh.current_dir().clone(); + let mut path = sh.current_dir(); path.push("target"); path.push(target_dir); From 6677c543403d65d6fdd73d11f0e258a509619b4a Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 15:13:06 -0400 Subject: [PATCH 25/44] CI, fmt --- ffi/src/credssp/network.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/src/credssp/network.rs b/ffi/src/credssp/network.rs index ea9f972dd..2eb6efd79 100644 --- a/ffi/src/credssp/network.rs +++ b/ffi/src/credssp/network.rs @@ -1,4 +1,4 @@ -#![allow(single_use_lifetimes)]// Diplomat requires lifetimes +#![allow(single_use_lifetimes)] // Diplomat requires lifetimes pub type CredsspGeneratorState = sspi::generator::GeneratorState>; From 2d8b849765761072b451adc2fb240bcc8f28219c Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 15:22:51 -0400 Subject: [PATCH 26/44] update exmaple so it takes commandline args --- .../Program.cs | 90 +++++++++++++++++-- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index d674113dc..45ac487eb 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -1,27 +1,99 @@ using System.Net; using System.Net.Security; using System.Net.Sockets; -using System.Security.Cryptography.X509Certificates; namespace Devolutions.IronRdp.ConnectExample { class Program { static async Task Main(string[] args) { - try + var arguments = ParseArguments(args); + + if (arguments == null) { - var serverName = "IT-HELP-DC.ad.it-help.ninja"; - var username = "Administrator"; - var password = "DevoLabs123!"; - var domain = "ad.it-help.ninja"; + return; + } + var serverName = arguments["--serverName"]; + var username = arguments["--username"]; + var password = arguments["--password"]; + var domain = arguments["--domain"]; + try + { await Connect(serverName, username, password, domain); } - catch (IronRdpException e) + catch (Exception e) + { + Console.WriteLine($"An error occurred: {e.Message}"); + } + } + + static Dictionary ParseArguments(string[] args) + { + if (args.Length == 0 || Array.Exists(args, arg => arg == "--help")) + { + PrintHelp(); + return null; + } + + var arguments = new Dictionary(); + string lastKey = null; + foreach (var arg in args) + { + if (arg.StartsWith("--")) + { + if (lastKey != null) + { + Console.WriteLine($"Error: Missing value for {lastKey}."); + PrintHelp(); + return null; + } + if (!IsValidArgument(arg)) + { + Console.WriteLine($"Error: Unknown argument {arg}."); + PrintHelp(); + return null; + } + lastKey = arg; + } + else + { + if (lastKey == null) + { + Console.WriteLine("Error: Value without a preceding flag."); + PrintHelp(); + return null; + } + arguments[lastKey] = arg; + lastKey = null; + } + } + + if (lastKey != null) { - var err = e.Inner.ToDisplay(); - Console.WriteLine(err); + Console.WriteLine($"Error: Missing value for {lastKey}."); + PrintHelp(); + return null; } + + return arguments; + } + + static bool IsValidArgument(string argument) + { + var validArguments = new List { "--serverName", "--username", "--password", "--domain" }; + return validArguments.Contains(argument); + } + + static void PrintHelp() + { + Console.WriteLine("Usage: dotnet run -- [OPTIONS]"); + Console.WriteLine("Options:"); + Console.WriteLine(" --serverName The name of the server to connect to."); + Console.WriteLine(" --username The username for connection."); + Console.WriteLine(" --password The password for connection."); + Console.WriteLine(" --domain The domain of the server."); + Console.WriteLine(" --help Show this message and exit."); } static async Task Connect(String servername, String username, String password, String domain) From 0614f704a2a2d9552b1b179560a87d073bcd9776 Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 15:47:31 -0400 Subject: [PATCH 27/44] CI, try to fix --- ffi/src/connector/config.rs | 27 ++++++++++++++++++++++++++- ffi/src/connector/result.rs | 30 ++++-------------------------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/ffi/src/connector/config.rs b/ffi/src/connector/config.rs index ffccf746c..ad670a3b5 100644 --- a/ffi/src/connector/config.rs +++ b/ffi/src/connector/config.rs @@ -112,7 +112,7 @@ pub mod ffi { self.desktop_size = Some(ironrdp::connector::DesktopSize { width, height }); } - pub fn set_graphics(&mut self, graphics: &crate::connector::result::ffi::GraphicsConfig) { + pub fn set_graphics(&mut self, graphics: &GraphicsConfig) { self.graphics = Some(graphics.0.clone()); } @@ -190,4 +190,29 @@ pub mod ffi { Ok(Box::new(Config(inner_config))) } } + + #[diplomat::opaque] + pub struct GraphicsConfig(pub ironrdp::connector::GraphicsConfig); + + impl GraphicsConfig { + pub fn get_avc444(&self) -> bool { + self.0.avc444 + } + + pub fn get_h264(&self) -> bool { + self.0.h264 + } + + pub fn get_thin_client(&self) -> bool { + self.0.thin_client + } + + pub fn get_small_cache(&self) -> bool { + self.0.small_cache + } + + pub fn get_capabilities(&self) -> u32 { + self.0.capabilities + } + } } diff --git a/ffi/src/connector/result.rs b/ffi/src/connector/result.rs index 85fc7c48e..286d73264 100644 --- a/ffi/src/connector/result.rs +++ b/ffi/src/connector/result.rs @@ -1,6 +1,9 @@ #[diplomat::bridge] pub mod ffi { - use crate::{connector::config::ffi::DesktopSize, utils::ffi::OptionalUsize}; + use crate::{ + connector::config::ffi::{DesktopSize, GraphicsConfig}, + utils::ffi::OptionalUsize, + }; #[diplomat::opaque] pub struct Written(pub ironrdp::connector::Written); @@ -58,29 +61,4 @@ pub mod ffi { self.0.graphics_config.clone().map(GraphicsConfig).map(Box::new) } } - - #[diplomat::opaque] - pub struct GraphicsConfig(pub ironrdp::connector::GraphicsConfig); - - impl GraphicsConfig { - pub fn get_avc444(&self) -> bool { - self.0.avc444 - } - - pub fn get_h264(&self) -> bool { - self.0.h264 - } - - pub fn get_thin_client(&self) -> bool { - self.0.thin_client - } - - pub fn get_small_cache(&self) -> bool { - self.0.small_cache - } - - pub fn get_capabilities(&self) -> u32 { - self.0.capabilities - } - } } From 06721824153f3ebe7c997003975747d98149aa2e Mon Sep 17 00:00:00 2001 From: irving ou Date: Mon, 1 Apr 2024 16:45:44 -0400 Subject: [PATCH 28/44] Merge master, fix CI --- .../Generated/ConfigBuilder.cs | 28 +-- .../Generated/ConnectionResult.cs | 28 --- .../Generated/GraphicsConfig.cs | 168 ----------------- .../Generated/PerformanceFlags.cs | 101 ++++++++++ .../Generated/PerformanceFlagsType.cs | 26 +++ .../Generated/RawConfigBuilder.cs | 4 +- .../Generated/RawConnectionResult.cs | 3 - .../Generated/RawGraphicsConfig.cs | 40 ---- .../Generated/RawPerformanceFlags.cs | 30 +++ .../Generated/RawPerformanceFlagsType.cs | 26 +++ ffi/src/connector/config.rs | 65 +++++-- ffi/src/connector/result.rs | 9 +- ffi/src/ironrdp_blocking.rs | 176 ------------------ 13 files changed, 247 insertions(+), 457 deletions(-) delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/GraphicsConfig.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/PerformanceFlags.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/PerformanceFlagsType.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawGraphicsConfig.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawPerformanceFlags.cs create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawPerformanceFlagsType.cs delete mode 100644 ffi/src/ironrdp_blocking.rs diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ConfigBuilder.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ConfigBuilder.cs index d8903664f..b3f46e34f 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/ConfigBuilder.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ConfigBuilder.cs @@ -79,14 +79,6 @@ public bool EnableTls } } - public GraphicsConfig Graphics - { - set - { - SetGraphics(value); - } - } - public string ImeFileName { set @@ -127,6 +119,14 @@ public bool NoServerPointer } } + public PerformanceFlags PerformanceFlags + { + set + { + SetPerformanceFlags(value); + } + } + public bool PointerSoftwareRendering { set @@ -308,7 +308,7 @@ public void SetDesktopSize(ushort height, ushort width) } } - public void SetGraphics(GraphicsConfig graphics) + public void SetPerformanceFlags(PerformanceFlags performanceFlags) { unsafe { @@ -316,13 +316,13 @@ public void SetGraphics(GraphicsConfig graphics) { throw new ObjectDisposedException("ConfigBuilder"); } - Raw.GraphicsConfig* graphicsRaw; - graphicsRaw = graphics.AsFFI(); - if (graphicsRaw == null) + Raw.PerformanceFlags* performanceFlagsRaw; + performanceFlagsRaw = performanceFlags.AsFFI(); + if (performanceFlagsRaw == null) { - throw new ObjectDisposedException("GraphicsConfig"); + throw new ObjectDisposedException("PerformanceFlags"); } - Raw.ConfigBuilder.SetGraphics(_inner, graphicsRaw); + Raw.ConfigBuilder.SetPerformanceFlags(_inner, performanceFlagsRaw); } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ConnectionResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ConnectionResult.cs index abafd7bef..d1d84d4b8 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/ConnectionResult.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ConnectionResult.cs @@ -23,14 +23,6 @@ public DesktopSize DesktopSize } } - public GraphicsConfig? GraphicsConfig - { - get - { - return GetGraphicsConfig(); - } - } - public ushort IoChannelId { get @@ -169,26 +161,6 @@ public bool GetPointerSoftwareRendering() } } - /// - /// A GraphicsConfig allocated on Rust side. - /// - public GraphicsConfig? GetGraphicsConfig() - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("ConnectionResult"); - } - Raw.GraphicsConfig* retVal = Raw.ConnectionResult.GetGraphicsConfig(_inner); - if (retVal == null) - { - return null; - } - return new GraphicsConfig(retVal); - } - } - /// /// Returns the underlying raw handle. /// diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/GraphicsConfig.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/GraphicsConfig.cs deleted file mode 100644 index 78329a72c..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/GraphicsConfig.cs +++ /dev/null @@ -1,168 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp; - -#nullable enable - -public partial class GraphicsConfig: IDisposable -{ - private unsafe Raw.GraphicsConfig* _inner; - - public bool Avc444 - { - get - { - return GetAvc444(); - } - } - - public uint Capabilities - { - get - { - return GetCapabilities(); - } - } - - public bool H264 - { - get - { - return GetH264(); - } - } - - public bool SmallCache - { - get - { - return GetSmallCache(); - } - } - - public bool ThinClient - { - get - { - return GetThinClient(); - } - } - - /// - /// Creates a managed GraphicsConfig from a raw handle. - /// - /// - /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). - ///
- /// This constructor assumes the raw struct is allocated on Rust side. - /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. - ///
- public unsafe GraphicsConfig(Raw.GraphicsConfig* handle) - { - _inner = handle; - } - - public bool GetAvc444() - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("GraphicsConfig"); - } - bool retVal = Raw.GraphicsConfig.GetAvc444(_inner); - return retVal; - } - } - - public bool GetH264() - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("GraphicsConfig"); - } - bool retVal = Raw.GraphicsConfig.GetH264(_inner); - return retVal; - } - } - - public bool GetThinClient() - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("GraphicsConfig"); - } - bool retVal = Raw.GraphicsConfig.GetThinClient(_inner); - return retVal; - } - } - - public bool GetSmallCache() - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("GraphicsConfig"); - } - bool retVal = Raw.GraphicsConfig.GetSmallCache(_inner); - return retVal; - } - } - - public uint GetCapabilities() - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("GraphicsConfig"); - } - uint retVal = Raw.GraphicsConfig.GetCapabilities(_inner); - return retVal; - } - } - - /// - /// Returns the underlying raw handle. - /// - public unsafe Raw.GraphicsConfig* AsFFI() - { - return _inner; - } - - /// - /// Destroys the underlying object immediately. - /// - public void Dispose() - { - unsafe - { - if (_inner == null) - { - return; - } - - Raw.GraphicsConfig.Destroy(_inner); - _inner = null; - - GC.SuppressFinalize(this); - } - } - - ~GraphicsConfig() - { - Dispose(); - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/PerformanceFlags.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/PerformanceFlags.cs new file mode 100644 index 000000000..e49b7e57f --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/PerformanceFlags.cs @@ -0,0 +1,101 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class PerformanceFlags: IDisposable +{ + private unsafe Raw.PerformanceFlags* _inner; + + /// + /// Creates a managed PerformanceFlags from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe PerformanceFlags(Raw.PerformanceFlags* handle) + { + _inner = handle; + } + + /// + /// A PerformanceFlags allocated on Rust side. + /// + public static PerformanceFlags NewDefault() + { + unsafe + { + Raw.PerformanceFlags* retVal = Raw.PerformanceFlags.NewDefault(); + return new PerformanceFlags(retVal); + } + } + + /// + /// A PerformanceFlags allocated on Rust side. + /// + public static PerformanceFlags NewEmpty() + { + unsafe + { + Raw.PerformanceFlags* retVal = Raw.PerformanceFlags.NewEmpty(); + return new PerformanceFlags(retVal); + } + } + + public void AddFlag(PerformanceFlagsType flag) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("PerformanceFlags"); + } + Raw.PerformanceFlagsType flagRaw; + flagRaw = (Raw.PerformanceFlagsType)flag; + Raw.PerformanceFlags.AddFlag(_inner, flagRaw); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.PerformanceFlags* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.PerformanceFlags.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~PerformanceFlags() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/PerformanceFlagsType.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/PerformanceFlagsType.cs new file mode 100644 index 000000000..cab76e289 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/PerformanceFlagsType.cs @@ -0,0 +1,26 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public enum PerformanceFlagsType +{ + DisableWallpaper = 0, + DisableFullWindowDrag = 1, + DisableMenuAnimations = 2, + DisableTheming = 3, + Reserved1 = 4, + DisableCursorShadow = 5, + DisableCursorSettings = 6, + EnableFontSmoothing = 7, + EnableDesktopComposition = 8, + Reserved2 = 9, +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfigBuilder.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfigBuilder.cs index 829a257d3..ac96aefb7 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfigBuilder.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConfigBuilder.cs @@ -49,8 +49,8 @@ public partial struct ConfigBuilder [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_desktop_size", ExactSpelling = true)] public static unsafe extern void SetDesktopSize(ConfigBuilder* self, ushort height, ushort width); - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_graphics", ExactSpelling = true)] - public static unsafe extern void SetGraphics(ConfigBuilder* self, GraphicsConfig* graphics); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_performance_flags", ExactSpelling = true)] + public static unsafe extern void SetPerformanceFlags(ConfigBuilder* self, PerformanceFlags* performanceFlags); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConfigBuilder_set_client_build", ExactSpelling = true)] public static unsafe extern void SetClientBuild(ConfigBuilder* self, uint clientBuild); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectionResult.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectionResult.cs index fb3bbcd5c..c8f51e3d1 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectionResult.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawConnectionResult.cs @@ -36,9 +36,6 @@ public partial struct ConnectionResult [return: MarshalAs(UnmanagedType.U1)] public static unsafe extern bool GetPointerSoftwareRendering(ConnectionResult* self); - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConnectionResult_get_graphics_config", ExactSpelling = true)] - public static unsafe extern GraphicsConfig* GetGraphicsConfig(ConnectionResult* self); - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ConnectionResult_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(ConnectionResult* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawGraphicsConfig.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawGraphicsConfig.cs deleted file mode 100644 index bd6a4749b..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawGraphicsConfig.cs +++ /dev/null @@ -1,40 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct GraphicsConfig -{ - private const string NativeLib = "DevolutionsIronRdp"; - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GraphicsConfig_get_avc444", ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.U1)] - public static unsafe extern bool GetAvc444(GraphicsConfig* self); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GraphicsConfig_get_h264", ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.U1)] - public static unsafe extern bool GetH264(GraphicsConfig* self); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GraphicsConfig_get_thin_client", ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.U1)] - public static unsafe extern bool GetThinClient(GraphicsConfig* self); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GraphicsConfig_get_small_cache", ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.U1)] - public static unsafe extern bool GetSmallCache(GraphicsConfig* self); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GraphicsConfig_get_capabilities", ExactSpelling = true)] - public static unsafe extern uint GetCapabilities(GraphicsConfig* self); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "GraphicsConfig_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(GraphicsConfig* self); -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawPerformanceFlags.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPerformanceFlags.cs new file mode 100644 index 000000000..fc5205801 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPerformanceFlags.cs @@ -0,0 +1,30 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct PerformanceFlags +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PerformanceFlags_new_default", ExactSpelling = true)] + public static unsafe extern PerformanceFlags* NewDefault(); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PerformanceFlags_new_empty", ExactSpelling = true)] + public static unsafe extern PerformanceFlags* NewEmpty(); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PerformanceFlags_add_flag", ExactSpelling = true)] + public static unsafe extern void AddFlag(PerformanceFlags* self, PerformanceFlagsType flag); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PerformanceFlags_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(PerformanceFlags* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawPerformanceFlagsType.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPerformanceFlagsType.cs new file mode 100644 index 000000000..05f5cd574 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawPerformanceFlagsType.cs @@ -0,0 +1,26 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +public enum PerformanceFlagsType +{ + DisableWallpaper = 0, + DisableFullWindowDrag = 1, + DisableMenuAnimations = 2, + DisableTheming = 3, + Reserved1 = 4, + DisableCursorShadow = 5, + DisableCursorSettings = 6, + EnableFontSmoothing = 7, + EnableDesktopComposition = 8, + Reserved2 = 9, +} diff --git a/ffi/src/connector/config.rs b/ffi/src/connector/config.rs index ad670a3b5..d164604d3 100644 --- a/ffi/src/connector/config.rs +++ b/ffi/src/connector/config.rs @@ -1,3 +1,7 @@ +use ironrdp::pdu::rdp::client_info::PerformanceFlags; + +use self::ffi::PerformanceFlagsType; + #[diplomat::bridge] pub mod ffi { use ironrdp::{ @@ -29,7 +33,6 @@ pub mod ffi { pub ime_file_name: Option, pub dig_product_id: Option, pub desktop_size: Option, - pub graphics: Option, pub bitmap: Option, pub client_build: Option, pub client_name: Option, @@ -38,6 +41,7 @@ pub mod ffi { pub no_server_pointer: Option, pub autologon: Option, pub pointer_software_rendering: Option, + pub performance_flags: Option, } #[diplomat::enum_convert(ironrdp::pdu::gcc::KeyboardType)] @@ -112,8 +116,8 @@ pub mod ffi { self.desktop_size = Some(ironrdp::connector::DesktopSize { width, height }); } - pub fn set_graphics(&mut self, graphics: &GraphicsConfig) { - self.graphics = Some(graphics.0.clone()); + pub fn set_performance_flags(&mut self, performance_flags: &PerformanceFlags) { + self.performance_flags = Some(performance_flags.0); } // TODO: set bitmap @@ -157,7 +161,6 @@ pub mod ffi { ime_file_name: self.ime_file_name.clone().unwrap_or_default(), dig_product_id: self.dig_product_id.clone().unwrap_or_default(), desktop_size: self.desktop_size.ok_or("Desktop size not set")?, - graphics: self.graphics.clone(), bitmap: None, client_build: self.client_build.unwrap_or(0), client_name: self.client_name.clone().ok_or("Client name not set")?, @@ -185,6 +188,7 @@ pub mod ffi { no_server_pointer: self.no_server_pointer.unwrap_or(false), autologon: self.autologon.unwrap_or(false), pointer_software_rendering: self.pointer_software_rendering.unwrap_or(false), + performance_flags: self.performance_flags.ok_or("Performance flag is missing")?, }; Ok(Box::new(Config(inner_config))) @@ -192,27 +196,52 @@ pub mod ffi { } #[diplomat::opaque] - pub struct GraphicsConfig(pub ironrdp::connector::GraphicsConfig); - - impl GraphicsConfig { - pub fn get_avc444(&self) -> bool { - self.0.avc444 - } + #[derive(Default)] + pub struct PerformanceFlags(pub ironrdp::pdu::rdp::client_info::PerformanceFlags); + + pub enum PerformanceFlagsType { + DisableWallpaper, + DisableFullWindowDrag, + DisableMenuAnimations, + DisableTheming, + Reserved1, + DisableCursorShadow, + DisableCursorSettings, + EnableFontSmoothing, + EnableDesktopComposition, + Reserved2, + } - pub fn get_h264(&self) -> bool { - self.0.h264 + impl PerformanceFlags { + pub fn new_default() -> Box { + Box::::default() } - pub fn get_thin_client(&self) -> bool { - self.0.thin_client + pub fn new_empty() -> Box { + Box::new(PerformanceFlags( + ironrdp::pdu::rdp::client_info::PerformanceFlags::empty(), + )) } - pub fn get_small_cache(&self) -> bool { - self.0.small_cache + pub fn add_flag(&mut self, flag: PerformanceFlagsType) { + self.0.insert(flag.into()); } + } +} - pub fn get_capabilities(&self) -> u32 { - self.0.capabilities +impl From for PerformanceFlags { + fn from(val: PerformanceFlagsType) -> Self { + match val { + PerformanceFlagsType::DisableCursorSettings => PerformanceFlags::DISABLE_CURSORSETTINGS, + PerformanceFlagsType::DisableCursorShadow => PerformanceFlags::DISABLE_CURSOR_SHADOW, + PerformanceFlagsType::DisableFullWindowDrag => PerformanceFlags::DISABLE_FULLWINDOWDRAG, + PerformanceFlagsType::DisableMenuAnimations => PerformanceFlags::DISABLE_MENUANIMATIONS, + PerformanceFlagsType::DisableTheming => PerformanceFlags::DISABLE_THEMING, + PerformanceFlagsType::DisableWallpaper => PerformanceFlags::DISABLE_WALLPAPER, + PerformanceFlagsType::EnableDesktopComposition => PerformanceFlags::ENABLE_DESKTOP_COMPOSITION, + PerformanceFlagsType::EnableFontSmoothing => PerformanceFlags::ENABLE_DESKTOP_COMPOSITION, + PerformanceFlagsType::Reserved1 => PerformanceFlags::RESERVED1, + PerformanceFlagsType::Reserved2 => PerformanceFlags::RESERVED2, } } } diff --git a/ffi/src/connector/result.rs b/ffi/src/connector/result.rs index 286d73264..cffea1a7b 100644 --- a/ffi/src/connector/result.rs +++ b/ffi/src/connector/result.rs @@ -1,9 +1,6 @@ #[diplomat::bridge] pub mod ffi { - use crate::{ - connector::config::ffi::{DesktopSize, GraphicsConfig}, - utils::ffi::OptionalUsize, - }; + use crate::{connector::config::ffi::DesktopSize, utils::ffi::OptionalUsize}; #[diplomat::opaque] pub struct Written(pub ironrdp::connector::Written); @@ -56,9 +53,5 @@ pub mod ffi { pub fn get_pointer_software_rendering(&self) -> bool { self.0.pointer_software_rendering } - - pub fn get_graphics_config(&self) -> Option> { - self.0.graphics_config.clone().map(GraphicsConfig).map(Box::new) - } } } diff --git a/ffi/src/ironrdp_blocking.rs b/ffi/src/ironrdp_blocking.rs deleted file mode 100644 index 9cceb5965..000000000 --- a/ffi/src/ironrdp_blocking.rs +++ /dev/null @@ -1,176 +0,0 @@ -// #[diplomat::bridge] -// pub mod ffi { -// use ironrdp::connector::sspi::network_client; -// use ironrdp_blocking::connect_begin; - -// use crate::{ -// connector::result::ffi::ConnectionResult, -// error::{ -// ffi::{IronRdpError, IronRdpErrorKind}, -// ValueConsumedError, -// }, -// tls::TlsStream, -// utils::ffi::{StdTcpStream, VecU8}, -// }; - -// #[diplomat::opaque] -// pub struct BlockingTcpFrame(pub Option>); - -// impl BlockingTcpFrame { -// pub fn from_tcp_stream(stream: &mut StdTcpStream) -> Result, Box> { -// let Some(stream) = stream.0.take() else { -// return Err(ValueConsumedError::for_item("tcp_stream") -// .reason("tcp stream has been consumed") -// .into()); -// }; - -// let framed = ironrdp_blocking::Framed::new(stream); - -// Ok(Box::new(BlockingTcpFrame(Some(framed)))) -// } - -// pub fn into_tcp_steam_no_leftover(&mut self) -> Result, Box> { -// let Some(stream) = self.0.take() else { -// return Err(ValueConsumedError::for_item("BlockingTcpFrame") -// .reason("BlockingTcpFrame has been consumed") -// .into()); -// }; - -// let stream = stream.into_inner_no_leftover(); - -// Ok(Box::new(StdTcpStream(Some(stream)))) -// } -// } - -// #[diplomat::opaque] -// pub struct BlockingUpgradedFrame(pub Option>); - -// impl BlockingUpgradedFrame { -// pub fn from_upgraded_stream( -// stream: &mut crate::tls::ffi::UpgradedStream, -// ) -> Result, Box> { -// let Some(stream) = stream.0.take() else { -// return Err(ValueConsumedError::for_item("upgraded_stream") -// .reason("upgraded stream has been consumed") -// .into()); -// }; - -// let framed = ironrdp_blocking::Framed::new(stream); - -// Ok(Box::new(BlockingUpgradedFrame(Some(framed)))) -// } -// } - -// #[diplomat::opaque] // Diplomat does not support direct function calls, so we need to wrap the function in a struct -// pub struct IronRdpBlocking; - -// #[diplomat::opaque] -// pub struct ShouldUpgrade(pub Option); - -// #[diplomat::opaque] -// pub struct Upgraded(pub Option); - -// impl IronRdpBlocking { -// pub fn new() -> Box { -// Box::new(IronRdpBlocking) -// } - -// pub fn connect_begin( -// framed: &mut BlockingTcpFrame, -// connector: &mut crate::connector::ffi::ClientConnector, -// ) -> Result, Box> { -// let Some(ref mut connector) = connector.0 else { -// return Err(IronRdpErrorKind::Consumed.into()); -// }; - -// let Some(framed) = framed.0.as_mut() else { -// return Err(ValueConsumedError::for_item("framed") -// .reason("framed has been consumed") -// .into()); -// }; - -// let result = connect_begin(framed, connector)?; - -// Ok(Box::new(ShouldUpgrade(Some(result)))) -// } - -// pub fn mark_as_upgraded( -// should_upgrade: &mut ShouldUpgrade, -// connector: &mut crate::connector::ffi::ClientConnector, -// ) -> Result, Box> { -// let Some(ref mut connector) = connector.0 else { -// return Err(ValueConsumedError::for_item("connector") -// .reason("inner connector is missing") -// .into()); -// }; - -// let Some(should_upgrade) = should_upgrade.0.take() else { -// return Err(ValueConsumedError::for_item("should_upgrade") -// .reason("ShouldUpgrade is missing, Note: ShouldUpgrade should be used only once") -// .into()); -// }; - -// let result = ironrdp_blocking::mark_as_upgraded(should_upgrade, connector); - -// Ok(Box::new(Upgraded(Some(result)))) -// } - -// pub fn skip_connect_begin( -// connector: &mut crate::connector::ffi::ClientConnector, -// ) -> Result, Box> { -// let Some(ref mut connector) = connector.0 else { -// return Err(ValueConsumedError::for_item("connector") -// .reason("inner connector is missing") -// .into()); -// }; - -// let result = ironrdp_blocking::skip_connect_begin(connector); - -// Ok(Box::new(ShouldUpgrade(Some(result)))) -// } - -// pub fn connect_finalize( -// upgraded: &mut Upgraded, -// upgraded_framed: &mut BlockingUpgradedFrame, -// connector: &mut crate::connector::ffi::ClientConnector, -// server_name: &crate::connector::ffi::ServerName, -// server_public_key: &VecU8, -// kerberos_config: Option<&crate::credssp::ffi::KerberosConfig>, -// ) -> Result, Box> { -// let Some(connector) = connector.0.take() else { -// return Err(ValueConsumedError::for_item("connector") -// .reason("inner connector is missing") -// .into()); -// }; - -// let Some(upgraded) = upgraded.0.take() else { -// return Err(ValueConsumedError::for_item("upgraded") -// .reason("Upgraded inner is missing, Note: Upgraded should be used only once") -// .into()); -// }; - -// let Some(framed) = upgraded_framed.0.as_mut() else { -// return Err(ValueConsumedError::for_item("framed") -// .reason("framed has been consumed") -// .into()); -// }; - -// let server_name = server_name.0.clone(); -// let mut network_client = network_client::reqwest_network_client::ReqwestNetworkClient; - -// let kerberos_config = kerberos_config.as_ref().map(|config| config.0.clone()); - -// let result = ironrdp_blocking::connect_finalize( -// upgraded, -// framed, -// connector, -// server_name, -// server_public_key.0.clone(), -// &mut network_client, -// kerberos_config, -// )?; - -// Ok(Box::new(ConnectionResult(result))) -// } -// } -// } From 832651addd7d6f88f8291f2063d04343feea64a5 Mon Sep 17 00:00:00 2001 From: "irvingouj @ Devolutions" <139169536+irvingoujAtDevolution@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:31:54 -0400 Subject: [PATCH 29/44] Update ffi/Cargo.toml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benoît Cortier --- ffi/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 7961cf388..51d2ee68c 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -16,7 +16,6 @@ doc = false test = false doctest = false -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diplomat = "0.7.0" diplomat-runtime = "0.7.0" From 9b3734300329a68947eaf9300fb38ff9b48f8c24 Mon Sep 17 00:00:00 2001 From: irving ou Date: Tue, 2 Apr 2024 16:42:36 -0400 Subject: [PATCH 30/44] Review fixes --- Cargo.lock | 1 - ffi/Cargo.toml | 1 - .../Program.cs | 8 +- .../Devolutions.IronRdp/Generated/Any.cs | 63 --------------- .../Generated/CredsspSequence.cs | 23 +++--- .../Devolutions.IronRdp/Generated/RawAny.cs | 21 ----- .../Generated/RawCredsspSequence.cs | 2 +- .../Generated/RawServerName.cs | 24 ------ .../Devolutions.IronRdp/Generated/RawState.cs | 3 - .../Devolutions.IronRdp/Generated/RawVecU8.cs | 4 +- .../Generated/ServerName.cs | 80 ------------------- .../Devolutions.IronRdp/Generated/State.cs | 16 ---- .../Devolutions.IronRdp/Generated/VecU8.cs | 4 +- .../src/Framed.cs} | 0 ffi/src/connector/mod.rs | 13 --- ffi/src/credssp/mod.rs | 6 +- ffi/src/error.rs | 11 ++- ffi/src/lib.rs | 2 - ffi/src/utils/mod.rs | 5 +- 19 files changed, 31 insertions(+), 256 deletions(-) delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/Any.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawAny.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawServerName.cs delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/ServerName.cs rename ffi/dotnet/{Devolutions.IronRdp.ConnectExample/Frame.cs => Devolutions.IronRdp/src/Framed.cs} (100%) diff --git a/Cargo.lock b/Cargo.lock index a3710b1d3..332229bb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1130,7 +1130,6 @@ dependencies = [ "ironrdp", "sspi", "thiserror", - "tracing", ] [[package]] diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 7961cf388..702dd77e7 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -23,7 +23,6 @@ diplomat-runtime = "0.7.0" ironrdp = { workspace = true, features = ["connector", "dvc", "svc","rdpdr","rdpsnd"] } sspi = { workspace = true, features = ["network_client"] } thiserror.workspace = true -tracing.workspace = true [target.'cfg(windows)'.build-dependencies] embed-resource = "2.2.0" diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index 45ac487eb..189ddba71 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -107,7 +107,7 @@ static async Task Connect(String servername, String username, String password, S ClientConnector connector = ClientConnector.New(config); connector.WithServerAddr(serverAddr); - await connect_begin(framed, connector); + await connectBegin(framed, connector); var (serverPublicKey, framedSsl) = await securityUpgrade(servername, framed, connector); await ConnectFinalize(servername, connector, serverPublicKey, framedSsl); } @@ -131,7 +131,7 @@ static async Task Connect(String servername, String username, String password, S return (serverPublicKey, framedSsl); } - private static async Task connect_begin(Framed framed, ClientConnector connector) + private static async Task connectBegin(Framed framed, ClientConnector connector) { var writeBuf = WriteBuf.New(); while (!connector.ShouldPerformSecurityUpgrade()) @@ -159,7 +159,7 @@ private static async Task ConnectFinalize(string servername, ClientConnector con var writeBuf2 = WriteBuf.New(); if (connector.ShouldPerformCredssp()) { - await PerformCredsspSteps(connector, ServerName.New(servername), writeBuf2, framedSsl, serverpubkey); + await PerformCredsspSteps(connector, servername, writeBuf2, framedSsl, serverpubkey); } while (!connector.State().IsTerminal()) { @@ -167,7 +167,7 @@ private static async Task ConnectFinalize(string servername, ClientConnector con } } - private static async Task PerformCredsspSteps(ClientConnector connector, ServerName serverName, WriteBuf writeBuf, Framed framedSsl, byte[] serverpubkey) + private static async Task PerformCredsspSteps(ClientConnector connector, string serverName, WriteBuf writeBuf, Framed framedSsl, byte[] serverpubkey) { var credsspSequenceInitResult = CredsspSequence.Init(connector, serverName, serverpubkey, null); var credsspSequence = credsspSequenceInitResult.GetCredsspSequence(); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/Any.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/Any.cs deleted file mode 100644 index 7c5c27863..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/Any.cs +++ /dev/null @@ -1,63 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp; - -#nullable enable - -public partial class Any: IDisposable -{ - private unsafe Raw.Any* _inner; - - /// - /// Creates a managed Any from a raw handle. - /// - /// - /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). - ///
- /// This constructor assumes the raw struct is allocated on Rust side. - /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. - ///
- public unsafe Any(Raw.Any* handle) - { - _inner = handle; - } - - /// - /// Returns the underlying raw handle. - /// - public unsafe Raw.Any* AsFFI() - { - return _inner; - } - - /// - /// Destroys the underlying object immediately. - /// - public void Dispose() - { - unsafe - { - if (_inner == null) - { - return; - } - - Raw.Any.Destroy(_inner); - _inner = null; - - GC.SuppressFinalize(this); - } - } - - ~Any() - { - Dispose(); - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequence.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequence.cs index d8ec33149..e750795ca 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequence.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/CredsspSequence.cs @@ -53,23 +53,19 @@ public unsafe CredsspSequence(Raw.CredsspSequence* handle) /// /// A CredsspSequenceInitResult allocated on Rust side. /// - public static CredsspSequenceInitResult Init(ClientConnector connector, ServerName serverName, byte[] serverPublicKey, KerberosConfig? kerberoConfigs) + public static CredsspSequenceInitResult Init(ClientConnector connector, string serverName, byte[] serverPublicKey, KerberosConfig? kerberoConfigs) { unsafe { + byte[] serverNameBuf = DiplomatUtils.StringToUtf8(serverName); nuint serverPublicKeyLength = (nuint)serverPublicKey.Length; + nuint serverNameBufLength = (nuint)serverNameBuf.Length; Raw.ClientConnector* connectorRaw; connectorRaw = connector.AsFFI(); if (connectorRaw == null) { throw new ObjectDisposedException("ClientConnector"); } - Raw.ServerName* serverNameRaw; - serverNameRaw = serverName.AsFFI(); - if (serverNameRaw == null) - { - throw new ObjectDisposedException("ServerName"); - } Raw.KerberosConfig* kerberoConfigsRaw; if (kerberoConfigs == null) { @@ -85,13 +81,16 @@ public static CredsspSequenceInitResult Init(ClientConnector connector, ServerNa } fixed (byte* serverPublicKeyPtr = serverPublicKey) { - Raw.CredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError result = Raw.CredsspSequence.Init(connectorRaw, serverNameRaw, serverPublicKeyPtr, serverPublicKeyLength, kerberoConfigsRaw); - if (!result.isOk) + fixed (byte* serverNameBufPtr = serverNameBuf) { - throw new IronRdpException(new IronRdpError(result.Err)); + Raw.CredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError result = Raw.CredsspSequence.Init(connectorRaw, serverNameBufPtr, serverNameBufLength, serverPublicKeyPtr, serverPublicKeyLength, kerberoConfigsRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.CredsspSequenceInitResult* retVal = result.Ok; + return new CredsspSequenceInitResult(retVal); } - Raw.CredsspSequenceInitResult* retVal = result.Ok; - return new CredsspSequenceInitResult(retVal); } } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawAny.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawAny.cs deleted file mode 100644 index ebd723664..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawAny.cs +++ /dev/null @@ -1,21 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct Any -{ - private const string NativeLib = "DevolutionsIronRdp"; - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Any_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(Any* self); -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequence.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequence.cs index 800bedf30..0029d7f23 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequence.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCredsspSequence.cs @@ -20,7 +20,7 @@ public partial struct CredsspSequence public static unsafe extern PduHint* NextPduHint(CredsspSequence* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequence_init", ExactSpelling = true)] - public static unsafe extern CredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError Init(ClientConnector* connector, ServerName* serverName, byte* serverPublicKey, nuint serverPublicKeySz, KerberosConfig* kerberoConfigs); + public static unsafe extern CredsspFfiResultBoxCredsspSequenceInitResultBoxIronRdpError Init(ClientConnector* connector, byte* serverName, nuint serverNameSz, byte* serverPublicKey, nuint serverPublicKeySz, KerberosConfig* kerberoConfigs); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CredsspSequence_decode_server_message", ExactSpelling = true)] public static unsafe extern CredsspFfiResultOptBoxTsRequestBoxIronRdpError DecodeServerMessage(CredsspSequence* self, byte* pdu, nuint pduSz); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawServerName.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawServerName.cs deleted file mode 100644 index 7850dd8dc..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawServerName.cs +++ /dev/null @@ -1,24 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct ServerName -{ - private const string NativeLib = "DevolutionsIronRdp"; - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ServerName_new", ExactSpelling = true)] - public static unsafe extern ServerName* New(byte* name, nuint nameSz); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ServerName_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(ServerName* self); -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawState.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawState.cs index 7ed952046..99ea3e78f 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawState.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawState.cs @@ -23,9 +23,6 @@ public partial struct State [return: MarshalAs(UnmanagedType.U1)] public static unsafe extern bool IsTerminal(State* self); - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "State_as_any", ExactSpelling = true)] - public static unsafe extern Any* AsAny(State* self); - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "State_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(State* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs index 7c3d6776a..3a3ab325c 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs @@ -16,8 +16,8 @@ public partial struct VecU8 { private const string NativeLib = "DevolutionsIronRdp"; - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "VecU8_from_byte", ExactSpelling = true)] - public static unsafe extern VecU8* FromByte(byte* bytes, nuint bytesSz); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "VecU8_from_bytes", ExactSpelling = true)] + public static unsafe extern VecU8* FromBytes(byte* bytes, nuint bytesSz); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "VecU8_get_size", ExactSpelling = true)] public static unsafe extern nuint GetSize(VecU8* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ServerName.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ServerName.cs deleted file mode 100644 index 6a2439fa5..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/ServerName.cs +++ /dev/null @@ -1,80 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp; - -#nullable enable - -public partial class ServerName: IDisposable -{ - private unsafe Raw.ServerName* _inner; - - /// - /// Creates a managed ServerName from a raw handle. - /// - /// - /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). - ///
- /// This constructor assumes the raw struct is allocated on Rust side. - /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. - ///
- public unsafe ServerName(Raw.ServerName* handle) - { - _inner = handle; - } - - /// - /// A ServerName allocated on Rust side. - /// - public static ServerName New(string name) - { - unsafe - { - byte[] nameBuf = DiplomatUtils.StringToUtf8(name); - nuint nameBufLength = (nuint)nameBuf.Length; - fixed (byte* nameBufPtr = nameBuf) - { - Raw.ServerName* retVal = Raw.ServerName.New(nameBufPtr, nameBufLength); - return new ServerName(retVal); - } - } - } - - /// - /// Returns the underlying raw handle. - /// - public unsafe Raw.ServerName* AsFFI() - { - return _inner; - } - - /// - /// Destroys the underlying object immediately. - /// - public void Dispose() - { - unsafe - { - if (_inner == null) - { - return; - } - - Raw.ServerName.Destroy(_inner); - _inner = null; - - GC.SuppressFinalize(this); - } - } - - ~ServerName() - { - Dispose(); - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/State.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/State.cs index 8c76b38cf..7d9875390 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/State.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/State.cs @@ -88,22 +88,6 @@ public bool IsTerminal() } } - /// - /// A Any allocated on Rust side. - /// - public Any AsAny() - { - unsafe - { - if (_inner == null) - { - throw new ObjectDisposedException("State"); - } - Raw.Any* retVal = Raw.State.AsAny(_inner); - return new Any(retVal); - } - } - /// /// Returns the underlying raw handle. /// diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs index 47e965c6b..7dea00410 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs @@ -40,14 +40,14 @@ public unsafe VecU8(Raw.VecU8* handle) /// /// A VecU8 allocated on Rust side. /// - public static VecU8 FromByte(byte[] bytes) + public static VecU8 FromBytes(byte[] bytes) { unsafe { nuint bytesLength = (nuint)bytes.Length; fixed (byte* bytesPtr = bytes) { - Raw.VecU8* retVal = Raw.VecU8.FromByte(bytesPtr, bytesLength); + Raw.VecU8* retVal = Raw.VecU8.FromBytes(bytesPtr, bytesLength); return new VecU8(retVal); } } diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs similarity index 100% rename from ffi/dotnet/Devolutions.IronRdp.ConnectExample/Frame.cs rename to ffi/dotnet/Devolutions.IronRdp/src/Framed.cs diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index 70e0aae64..df30c71b8 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -23,9 +23,6 @@ pub mod ffi { #[diplomat::opaque] // We must use Option here, as ClientConnector is not Clone and have functions that consume it pub struct ClientConnector(pub Option); - #[diplomat::opaque] - pub struct ServerName(pub ironrdp::connector::ServerName); - // Basic Impl for ClientConnector impl ClientConnector { pub fn new(config: &Config) -> Box { @@ -149,10 +146,6 @@ pub mod ffi { pub fn is_terminal(&'a self) -> bool { self.0.is_terminal() } - - pub fn as_any(&'a self) -> Box> { - Box::new(crate::utils::ffi::Any(self.0.as_any())) - } } impl ClientConnector { @@ -170,10 +163,4 @@ pub mod ffi { Ok(Box::new(State(connector.state()))) } } - - impl ServerName { - pub fn new(name: &str) -> Box { - Box::new(ServerName(ironrdp::connector::ServerName::new(name))) - } - } } diff --git a/ffi/src/credssp/mod.rs b/ffi/src/credssp/mod.rs index 31da1fed1..42762e195 100644 --- a/ffi/src/credssp/mod.rs +++ b/ffi/src/credssp/mod.rs @@ -6,7 +6,7 @@ pub mod ffi { use crate::{ connector::{ - ffi::{ClientConnector, PduHint, ServerName}, + ffi::{ClientConnector, PduHint}, result::ffi::Written, }, error::{ffi::IronRdpError, ValueConsumedError}, @@ -53,7 +53,7 @@ pub mod ffi { pub fn init( connector: &ClientConnector, - server_name: &ServerName, + server_name: &str, server_public_key: &[u8], kerbero_configs: Option<&KerberosConfig>, ) -> Result, Box> { @@ -63,7 +63,7 @@ pub mod ffi { let (credssp_sequence, ts_request) = ironrdp::connector::credssp::CredsspSequence::init( connector, - server_name.0.clone(), + server_name.into(), server_public_key.to_owned(), kerbero_configs.map(|config| config.0.clone()), )?; diff --git a/ffi/src/error.rs b/ffi/src/error.rs index 5f6ea9303..3e0de3219 100644 --- a/ffi/src/error.rs +++ b/ffi/src/error.rs @@ -1,4 +1,6 @@ #![allow(clippy::return_self_not_must_use)] +use std::fmt::Display; + use ironrdp::connector::ConnectorError; use self::ffi::IronRdpErrorKind; @@ -119,12 +121,13 @@ impl ValueConsumedError { } } -impl ToString for ValueConsumedError { - fn to_string(&self) -> String { +impl Display for ValueConsumedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(reason) = &self.reason { - return format!("{}: {}", self.item, reason); + write!(f, "{}: {}", self.item, reason) + } else { + write!(f, "{}: is consumed or never constructed", self.item) } - format!("{}: is consumed or never constructed", self.item) } } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 28aae1655..9a9858c8f 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -6,5 +6,3 @@ pub mod error; pub mod pdu; pub mod svc; pub mod utils; - -use tracing as _; // need this in the future diff --git a/ffi/src/utils/mod.rs b/ffi/src/utils/mod.rs index e265f086b..b324dacfb 100644 --- a/ffi/src/utils/mod.rs +++ b/ffi/src/utils/mod.rs @@ -27,7 +27,7 @@ pub mod ffi { pub struct VecU8(pub Vec); impl VecU8 { - pub fn from_byte(bytes: &[u8]) -> Box { + pub fn from_bytes(bytes: &[u8]) -> Box { Box::new(VecU8(bytes.to_vec())) } @@ -48,9 +48,6 @@ pub mod ffi { } } - #[diplomat::opaque] - pub struct Any<'a>(pub &'a dyn std::any::Any); - #[diplomat::opaque] pub struct OptionalUsize(pub Option); From 5471ff7d07e8cdc9636dc7dd3ab48fe1a75296aa Mon Sep 17 00:00:00 2001 From: irving ou Date: Tue, 2 Apr 2024 16:46:15 -0400 Subject: [PATCH 31/44] removed unnecessary files --- .../.gitignore | 3 ++- .../DesignTimeBuild/.dtbcache.v2 | Bin 85479 -> 0 bytes .../aff8b35a-9ba5-4e52-88a0-2944d404bb88.vsidx | Bin 22164 -> 0 bytes .../v17/.futdcache.v2 | Bin 178 -> 0 bytes ...ions.ironrdp.connectexample.metadata.v7.bin | Bin 175628 -> 0 bytes ...ions.ironrdp.connectexample.projects.v7.bin | Bin 99693 -> 0 bytes 6 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/DesignTimeBuild/.dtbcache.v2 delete mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/FileContentIndex/aff8b35a-9ba5-4e52-88a0-2944d404bb88.vsidx delete mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/v17/.futdcache.v2 delete mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/ProjectEvaluation/devolutions.ironrdp.connectexample.metadata.v7.bin delete mode 100644 ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/ProjectEvaluation/devolutions.ironrdp.connectexample.projects.v7.bin diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.gitignore b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.gitignore index 2e9693ed1..501bb1289 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.gitignore +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.gitignore @@ -1,2 +1,3 @@ obj -bin \ No newline at end of file +bin +.vs \ No newline at end of file diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/DesignTimeBuild/.dtbcache.v2 b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/DesignTimeBuild/.dtbcache.v2 deleted file mode 100644 index a78cbb646430d78552a35ebbec77ba4c72f3d91a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 85479 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 Mx95oQ;G-Y?KQUZm?EnA( diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/FileContentIndex/aff8b35a-9ba5-4e52-88a0-2944d404bb88.vsidx b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/FileContentIndex/aff8b35a-9ba5-4e52-88a0-2944d404bb88.vsidx deleted file mode 100644 index 978d3bbb8b3c7de41cd2a41a9f9a26fd90379aa6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22164 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 diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/v17/.futdcache.v2 b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/Devolutions.IronRdp.ConnectExample/v17/.futdcache.v2 deleted file mode 100644 index 7dca3450edc2400c7cf833c6a46c25ab7fe5e543..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 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 I#^Lz?2i$~l^#A|> diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/ProjectEvaluation/devolutions.ironrdp.connectexample.projects.v7.bin b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/.vs/ProjectEvaluation/devolutions.ironrdp.connectexample.projects.v7.bin deleted file mode 100644 index b42b918fd6187363cf13069efd50cc9e1bd3fdbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99693 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 From 629f3743a9419734da6256f2e371b36bacab6d79 Mon Sep 17 00:00:00 2001 From: irving ou Date: Wed, 3 Apr 2024 15:32:21 -0400 Subject: [PATCH 32/44] Review fix --- .../Program.cs | 17 ++- .../Generated/ClientConnector.cs | 19 ++- .../Generated/RawClientConnector.cs | 2 +- .../Generated/RawSocketAddr.cs | 27 ----- ...> RawUtilsFfiResultVoidBoxIronRdpError.cs} | 12 +- .../Devolutions.IronRdp/Generated/RawVecU8.cs | 2 +- .../Generated/SocketAddr.cs | 109 ------------------ .../Devolutions.IronRdp/Generated/VecU8.cs | 7 +- ffi/src/connector/mod.rs | 5 +- ffi/src/utils/mod.rs | 28 +---- 10 files changed, 36 insertions(+), 192 deletions(-) delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs rename ffi/dotnet/Devolutions.IronRdp/Generated/{RawUtilsFfiResultBoxSocketAddrBoxIronRdpError.cs => RawUtilsFfiResultVoidBoxIronRdpError.cs} (73%) delete mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index 189ddba71..19bb4d86a 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -98,14 +98,21 @@ static void PrintHelp() static async Task Connect(String servername, String username, String password, String domain) { - SocketAddr serverAddr; - Config config = buildConfig(servername, username, password, domain, out serverAddr); + Config config = buildConfig(servername, username, password, domain); var stream = await CreateTcpConnection(servername, 3389); var framed = new Framed(stream); ClientConnector connector = ClientConnector.New(config); - connector.WithServerAddr(serverAddr); + + var ip = await Dns.GetHostAddressesAsync(servername); + if (ip.Length == 0) + { + throw new Exception("Could not resolve server address"); + } + + var socketAddrString = ip[0].ToString()+":3389"; + connector.WithServerAddr(socketAddrString); await connectBegin(framed, connector); var (serverPublicKey, framedSsl) = await securityUpgrade(servername, framed, connector); @@ -140,9 +147,8 @@ private static async Task connectBegin(Framed framed, ClientConne } } - private static Config buildConfig(string servername, string username, string password, string domain, out SocketAddr serverAddr) + private static Config buildConfig(string servername, string username, string password, string domain) { - serverAddr = SocketAddr.LookUp(servername, 3389); ConfigBuilder configBuilder = ConfigBuilder.New(); configBuilder.WithUsernameAndPasswrord(username, password); @@ -150,6 +156,7 @@ private static Config buildConfig(string servername, string username, string pas configBuilder.SetDesktopSize(800, 600); configBuilder.SetClientName("IronRdp"); configBuilder.SetClientDir("C:\\"); + configBuilder.SetPerformanceFlags(PerformanceFlags.NewDefault()); return configBuilder.Build(); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs index 3c5865bc4..b87c0fce1 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs @@ -51,7 +51,7 @@ public static ClientConnector New(Config config) /// Must use /// /// - public void WithServerAddr(SocketAddr serverAddr) + public void WithServerAddr(string serverAddr) { unsafe { @@ -59,16 +59,15 @@ public void WithServerAddr(SocketAddr serverAddr) { throw new ObjectDisposedException("ClientConnector"); } - Raw.SocketAddr* serverAddrRaw; - serverAddrRaw = serverAddr.AsFFI(); - if (serverAddrRaw == null) + byte[] serverAddrBuf = DiplomatUtils.StringToUtf8(serverAddr); + nuint serverAddrBufLength = (nuint)serverAddrBuf.Length; + fixed (byte* serverAddrBufPtr = serverAddrBuf) { - throw new ObjectDisposedException("SocketAddr"); - } - Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.ClientConnector.WithServerAddr(_inner, serverAddrRaw); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); + Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.ClientConnector.WithServerAddr(_inner, serverAddrBufPtr, serverAddrBufLength); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } } } } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs index 099a27114..ce46a8ddc 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs @@ -23,7 +23,7 @@ public partial struct ClientConnector /// Must use /// [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_with_server_addr", ExactSpelling = true)] - public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError WithServerAddr(ClientConnector* self, SocketAddr* serverAddr); + public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError WithServerAddr(ClientConnector* self, byte* serverAddr, nuint serverAddrSz); ///

/// Must use diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs deleted file mode 100644 index c5131a9a7..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawSocketAddr.cs +++ /dev/null @@ -1,27 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp.Raw; - -#nullable enable - -[StructLayout(LayoutKind.Sequential)] -public partial struct SocketAddr -{ - private const string NativeLib = "DevolutionsIronRdp"; - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SocketAddr_look_up", ExactSpelling = true)] - public static unsafe extern UtilsFfiResultBoxSocketAddrBoxIronRdpError LookUp(byte* host, nuint hostSz, ushort port); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SocketAddr_from_ffi_str", ExactSpelling = true)] - public static unsafe extern UtilsFfiResultBoxSocketAddrBoxIronRdpError FromFfiStr(byte* addr, nuint addrSz); - - [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SocketAddr_destroy", ExactSpelling = true)] - public static unsafe extern void Destroy(SocketAddr* self); -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxSocketAddrBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultVoidBoxIronRdpError.cs similarity index 73% rename from ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxSocketAddrBoxIronRdpError.cs rename to ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultVoidBoxIronRdpError.cs index 2b5fe7c1c..686bbfb02 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultBoxSocketAddrBoxIronRdpError.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawUtilsFfiResultVoidBoxIronRdpError.cs @@ -12,13 +12,11 @@ namespace Devolutions.IronRdp.Raw; #nullable enable [StructLayout(LayoutKind.Sequential)] -public partial struct UtilsFfiResultBoxSocketAddrBoxIronRdpError +public partial struct UtilsFfiResultVoidBoxIronRdpError { [StructLayout(LayoutKind.Explicit)] private unsafe struct InnerUnion { - [FieldOffset(0)] - internal SocketAddr* ok; [FieldOffset(0)] internal IronRdpError* err; } @@ -28,14 +26,6 @@ private unsafe struct InnerUnion [MarshalAs(UnmanagedType.U1)] public bool isOk; - public unsafe SocketAddr* Ok - { - get - { - return _inner.ok; - } - } - public unsafe IronRdpError* Err { get diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs index 3a3ab325c..50b656777 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawVecU8.cs @@ -23,7 +23,7 @@ public partial struct VecU8 public static unsafe extern nuint GetSize(VecU8* self); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "VecU8_fill", ExactSpelling = true)] - public static unsafe extern void Fill(VecU8* self, byte* buffer, nuint bufferSz); + public static unsafe extern UtilsFfiResultVoidBoxIronRdpError Fill(VecU8* self, byte* buffer, nuint bufferSz); [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "VecU8_new_empty", ExactSpelling = true)] public static unsafe extern VecU8* NewEmpty(); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs deleted file mode 100644 index 8cca7360c..000000000 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/SocketAddr.cs +++ /dev/null @@ -1,109 +0,0 @@ -// by Diplomat - -#pragma warning disable 0105 -using System; -using System.Runtime.InteropServices; - -using Devolutions.IronRdp.Diplomat; -#pragma warning restore 0105 - -namespace Devolutions.IronRdp; - -#nullable enable - -public partial class SocketAddr: IDisposable -{ - private unsafe Raw.SocketAddr* _inner; - - /// - /// Creates a managed SocketAddr from a raw handle. - /// - /// - /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). - ///
- /// This constructor assumes the raw struct is allocated on Rust side. - /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. - ///
- public unsafe SocketAddr(Raw.SocketAddr* handle) - { - _inner = handle; - } - - /// - /// - /// A SocketAddr allocated on Rust side. - /// - public static SocketAddr LookUp(string host, ushort port) - { - unsafe - { - byte[] hostBuf = DiplomatUtils.StringToUtf8(host); - nuint hostBufLength = (nuint)hostBuf.Length; - fixed (byte* hostBufPtr = hostBuf) - { - Raw.UtilsFfiResultBoxSocketAddrBoxIronRdpError result = Raw.SocketAddr.LookUp(hostBufPtr, hostBufLength, port); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - Raw.SocketAddr* retVal = result.Ok; - return new SocketAddr(retVal); - } - } - } - - /// - /// - /// A SocketAddr allocated on Rust side. - /// - public static SocketAddr FromFfiStr(string addr) - { - unsafe - { - byte[] addrBuf = DiplomatUtils.StringToUtf8(addr); - nuint addrBufLength = (nuint)addrBuf.Length; - fixed (byte* addrBufPtr = addrBuf) - { - Raw.UtilsFfiResultBoxSocketAddrBoxIronRdpError result = Raw.SocketAddr.FromFfiStr(addrBufPtr, addrBufLength); - if (!result.isOk) - { - throw new IronRdpException(new IronRdpError(result.Err)); - } - Raw.SocketAddr* retVal = result.Ok; - return new SocketAddr(retVal); - } - } - } - - /// - /// Returns the underlying raw handle. - /// - public unsafe Raw.SocketAddr* AsFFI() - { - return _inner; - } - - /// - /// Destroys the underlying object immediately. - /// - public void Dispose() - { - unsafe - { - if (_inner == null) - { - return; - } - - Raw.SocketAddr.Destroy(_inner); - _inner = null; - - GC.SuppressFinalize(this); - } - } - - ~SocketAddr() - { - Dispose(); - } -} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs index 7dea00410..488d1e586 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/VecU8.cs @@ -66,6 +66,7 @@ public nuint GetSize() } } + /// public void Fill(byte[] buffer) { unsafe @@ -77,7 +78,11 @@ public void Fill(byte[] buffer) nuint bufferLength = (nuint)buffer.Length; fixed (byte* bufferPtr = buffer) { - Raw.VecU8.Fill(_inner, bufferPtr, bufferLength); + Raw.UtilsFfiResultVoidBoxIronRdpError result = Raw.VecU8.Fill(_inner, bufferPtr, bufferLength); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } } } } diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index df30c71b8..f4d4da627 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -15,7 +15,6 @@ pub mod ffi { ValueConsumedError, }, pdu::ffi::WriteBuf, - utils::ffi::SocketAddr, }; use super::{config::ffi::Config, result::ffi::Written}; @@ -32,11 +31,11 @@ pub mod ffi { } /// Must use - pub fn with_server_addr(&mut self, server_addr: &SocketAddr) -> Result<(), Box> { + pub fn with_server_addr(&mut self, server_addr: &str) -> Result<(), Box> { let Some(connector) = self.0.take() else { return Err(IronRdpErrorKind::Consumed.into()); }; - let server_addr = server_addr.0; + let server_addr = server_addr.parse().map_err(|_| IronRdpErrorKind::Generic)?; self.0 = Some(connector.with_server_addr(server_addr)); Ok(()) diff --git a/ffi/src/utils/mod.rs b/ffi/src/utils/mod.rs index b324dacfb..6ede3dae6 100644 --- a/ffi/src/utils/mod.rs +++ b/ffi/src/utils/mod.rs @@ -3,26 +3,6 @@ pub mod ffi { use crate::error::ffi::IronRdpError; - #[diplomat::opaque] - pub struct SocketAddr(pub std::net::SocketAddr); - - impl SocketAddr { - pub fn look_up(host: &str, port: u16) -> Result, Box> { - use std::net::ToSocketAddrs as _; - let addr = (host, port) - .to_socket_addrs()? - .next() - .ok_or("Failed to resolve address")?; - Ok(Box::new(SocketAddr(addr))) - } - - // named from_ffi_str to avoid conflict with std::net::SocketAddr::from_str - pub fn from_ffi_str(addr: &str) -> Result, Box> { - let addr = addr.parse().map_err(|_| "Failed to parse address")?; - Ok(Box::new(SocketAddr(addr))) - } - } - #[diplomat::opaque] pub struct VecU8(pub Vec); @@ -35,12 +15,12 @@ pub mod ffi { self.0.len() } - pub fn fill(&self, buffer: &mut [u8]) { + pub fn fill(&self, buffer: &mut [u8]) -> Result<(), Box> { if buffer.len() < self.0.len() { - //TODO: FIX: Should not panic, for prototype only - panic!("Buffer is too small") + return Err("Buffer is too small".into()); } - buffer.copy_from_slice(&self.0) + buffer.copy_from_slice(&self.0); + Ok(()) } pub fn new_empty() -> Box { From c55935db3fe146c33e4bb8cc0f19413571c7d12a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Cortier?= Date: Fri, 5 Apr 2024 10:19:21 -0400 Subject: [PATCH 33/44] Update ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs --- ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index 19bb4d86a..d09ee3625 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -1,6 +1,7 @@ using System.Net; using System.Net.Security; using System.Net.Sockets; + namespace Devolutions.IronRdp.ConnectExample { class Program From 59a0df795e3b70a4820352da4711904465c558c1 Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 5 Apr 2024 10:19:49 -0400 Subject: [PATCH 34/44] Review fix:checkout Cargo.lock from master --- Cargo.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 332229bb2..69b32ad17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "arbitrary" @@ -722,9 +722,9 @@ dependencies = [ [[package]] name = "crypto-mac" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ "generic-array", "subtle", @@ -776,9 +776,9 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "der_derive", @@ -1702,7 +1702,7 @@ dependencies = [ "pico-args", "rand", "rustls", - "rustls-pemfile 2.1.0", + "rustls-pemfile 2.1.1", "tokio", "tokio-rustls", "tracing", @@ -3403,9 +3403,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b" +checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" dependencies = [ "base64", "rustls-pki-types", @@ -3862,9 +3862,9 @@ checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "subtle" -version = "2.5.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" From e08d96bd27a3f28eeffcbf54bc693522fbc02c4f Mon Sep 17 00:00:00 2001 From: "irvingouj @ Devolutions" <139169536+irvingoujAtDevolution@users.noreply.github.com> Date: Fri, 5 Apr 2024 10:34:28 -0400 Subject: [PATCH 35/44] Update ffi/src/connector/config.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benoît Cortier --- ffi/src/connector/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/src/connector/config.rs b/ffi/src/connector/config.rs index d164604d3..e70f30066 100644 --- a/ffi/src/connector/config.rs +++ b/ffi/src/connector/config.rs @@ -73,7 +73,7 @@ pub mod ffi { Box::::default() } - pub fn with_username_and_passwrord(&mut self, username: &str, password: &str) { + pub fn with_username_and_password(&mut self, username: &str, password: &str) { self.credentials = Some(Credentials::UsernamePassword { username: username.to_owned(), password: password.to_owned(), From 948bebecd8a21b94fef589b19591e3a79dd37e5c Mon Sep 17 00:00:00 2001 From: "irvingouj @ Devolutions" <139169536+irvingoujAtDevolution@users.noreply.github.com> Date: Fri, 5 Apr 2024 10:34:34 -0400 Subject: [PATCH 36/44] Update ffi/src/connector/mod.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benoît Cortier --- ffi/src/connector/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index f4d4da627..a9a308241 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -1,4 +1,5 @@ #![allow(clippy::unnecessary_box_returns)] // Diplomat requires returning Boxed types + pub mod config; pub mod result; pub mod state; From 8883a75d690a5cc292592a00b99b43b1de9e43ed Mon Sep 17 00:00:00 2001 From: "irvingouj @ Devolutions" <139169536+irvingoujAtDevolution@users.noreply.github.com> Date: Fri, 5 Apr 2024 10:37:00 -0400 Subject: [PATCH 37/44] Update ffi/src/lib.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benoît Cortier --- ffi/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 9a9858c8f..d8884366b 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,4 +1,5 @@ #![allow(clippy::unnecessary_box_returns)] // Diplomat requires returning Boxed types + pub mod connector; pub mod credssp; pub mod dvc; From 7e7ca5762661412dba65ead8d00a358ae43ed9d2 Mon Sep 17 00:00:00 2001 From: "irvingouj @ Devolutions" <139169536+irvingoujAtDevolution@users.noreply.github.com> Date: Fri, 5 Apr 2024 10:37:16 -0400 Subject: [PATCH 38/44] Update ffi/src/connector/mod.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benoît Cortier --- ffi/src/connector/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index a9a308241..4acee0366 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -1,4 +1,3 @@ -#![allow(clippy::unnecessary_box_returns)] // Diplomat requires returning Boxed types pub mod config; pub mod result; From e08d2db61100c46210456529c3a1800699f5f79c Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 5 Apr 2024 10:45:28 -0400 Subject: [PATCH 39/44] Review fix:for C# --- ffi/dotnet/Devolutions.IronRdp/src/Framed.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs index a020e0b7b..c5f76021b 100644 --- a/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs +++ b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using Devolutions.IronRdp; public class Framed where S : Stream @@ -16,12 +17,16 @@ public Framed(S stream) return (this.stream, this.buffer); } - public byte[] Peek() + public Span Peek() { - return this.buffer.ToArray(); + return CollectionsMarshal.AsSpan(this.buffer); } - // read from 0 to size bytes from the front of the buffer, and remove them from the buffer,keep the rest + /// + /// read from 0 to size bytes from the front of the buffer, and remove them from the buffer,keep the rest + /// + /// The number of bytes to read. + /// An array of bytes containing the read data. public async Task ReadExact(nuint size) { while (true) @@ -57,6 +62,11 @@ public async Task Write(byte[] data) } + /// + /// Reads data from the buffer based on the provided PduHint. + /// + /// The PduHint object used to determine the size of the data to read. + /// An asynchronous task that represents the operation. The task result contains the read data as a byte array. public async Task ReadByHint(PduHint pduHint) { while (true) From e8fdf92f5a0ce0862c6934b1f5280e401935e2a7 Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 5 Apr 2024 10:47:38 -0400 Subject: [PATCH 40/44] CI:fmt --- ffi/src/connector/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index 4acee0366..18b869487 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -1,4 +1,3 @@ - pub mod config; pub mod result; pub mod state; From 1cc7c8babc38bc870e1c9c635d0c84cef5601a4a Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 5 Apr 2024 10:52:58 -0400 Subject: [PATCH 41/44] Review fix --- ffi/dotnet/Devolutions.IronRdp/src/Framed.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs index c5f76021b..8a4b4aa0e 100644 --- a/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs +++ b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs @@ -17,6 +17,11 @@ public Framed(S stream) return (this.stream, this.buffer); } + /// + /// Returns a span that represents a portion of the underlying buffer without modifying it. + /// + /// Memory Safe:The Framed instance should not be modified (any read opeartions) while span is in use. + /// A span that represents a portion of the underlying buffer. public Span Peek() { return CollectionsMarshal.AsSpan(this.buffer); From 6c09040dc74cb7246bb5d4ef5bed478c20ff39d7 Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 5 Apr 2024 10:54:16 -0400 Subject: [PATCH 42/44] CI:typo fix --- ffi/dotnet/Devolutions.IronRdp/src/Framed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs index 8a4b4aa0e..be0c2aaa8 100644 --- a/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs +++ b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs @@ -20,7 +20,7 @@ public Framed(S stream) /// /// Returns a span that represents a portion of the underlying buffer without modifying it. /// - /// Memory Safe:The Framed instance should not be modified (any read opeartions) while span is in use. + /// Memory Safe:The Framed instance should not be modified (any read operations) while span is in use. /// A span that represents a portion of the underlying buffer. public Span Peek() { From 99fefd9511a2268e381949f5f03d7031546310e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Cortier?= Date: Fri, 5 Apr 2024 11:01:54 -0400 Subject: [PATCH 43/44] Update ffi/dotnet/Devolutions.IronRdp/src/Framed.cs --- ffi/dotnet/Devolutions.IronRdp/src/Framed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs index be0c2aaa8..573c74fc9 100644 --- a/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs +++ b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs @@ -28,7 +28,7 @@ public Span Peek() } /// - /// read from 0 to size bytes from the front of the buffer, and remove them from the buffer,keep the rest + /// Reads from 0 to size bytes from the front of the buffer, and remove them from the buffer keeping the rest. /// /// The number of bytes to read. /// An array of bytes containing the read data. From 50be259c0c9550e755c21fd1fca9b5a9dd199cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Cortier?= Date: Fri, 5 Apr 2024 11:02:31 -0400 Subject: [PATCH 44/44] Update ffi/dotnet/Devolutions.IronRdp/src/Framed.cs --- ffi/dotnet/Devolutions.IronRdp/src/Framed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs index 573c74fc9..715b05407 100644 --- a/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs +++ b/ffi/dotnet/Devolutions.IronRdp/src/Framed.cs @@ -20,7 +20,7 @@ public Framed(S stream) /// /// Returns a span that represents a portion of the underlying buffer without modifying it. /// - /// Memory Safe:The Framed instance should not be modified (any read operations) while span is in use. + /// Memory safety: the Framed instance should not be modified (any read operations) while span is in use. /// A span that represents a portion of the underlying buffer. public Span Peek() {