Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

TDP Shared Directory Announce and Acknowledge #12405

Merged
merged 86 commits into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
24c1a90
Creates a general vchan Client that can read chunked messages. rdpdr …
Mar 31, 2022
41ac367
Tidying up
Mar 31, 2022
58cac52
Merge branch 'master' into isaiah/reuse-vchan-read
Mar 31, 2022
fa3531f
small fixes
Mar 31, 2022
ab4d66d
Merge branch 'master' into isaiah/reuse-vchan-read
Mar 31, 2022
aaa8369
fixes linting errors
Apr 1, 2022
54b7e3d
Merge branch 'master' into isaiah/reuse-vchan-read
Apr 1, 2022
071620b
Merge branch 'master' into isaiah/reuse-vchan-read
Apr 1, 2022
52a3d5d
Merge branch 'master' into isaiah/reuse-vchan-read
Apr 1, 2022
ceddf84
Adds drive redirection capability set to the Client Core Capability R…
Apr 1, 2022
6baccf6
Adds the ClientDeviceListAnnounce message, plus some prototype code t…
Apr 4, 2022
9036618
Adds to_unicode util
Apr 4, 2022
770f6e7
refactor of vchan and rdpdr to use it
Apr 4, 2022
17a35e8
refactors cliprdr to use generalized vchan header and chunking
Apr 4, 2022
c946b52
nits
Apr 4, 2022
787b67e
Merge branch 'master' into isaiah/reuse-vchan-write
Apr 4, 2022
6cd9ac2
small fixes from CR
Apr 4, 2022
997b7d6
Merge branch 'master' into isaiah/reuse-vchan-write
Apr 4, 2022
eb8ed1a
Merge branch 'isaiah/reuse-vchan-write' into isaiah/mock-drive-redire…
Apr 4, 2022
54b53d0
A right click sends an ClientDeviceListAnnounce message and the rdp c…
Apr 6, 2022
8f1ead3
adds prototype logic for handling IRP_MJ_QUERY_INFORMATION and IRP_MJ…
Apr 8, 2022
9a88738
removes some un needed comments. the link to the documentation is eno…
Apr 11, 2022
fb60ebb
Successfully mocks a drive for redirection
Apr 15, 2022
51f83e5
adding important todo
Apr 19, 2022
b4192a6
removes unneeded pub decl
Apr 25, 2022
6cf694a
refactors rdpdr into its own self-contained folder module
Apr 25, 2022
fa0d555
moving scard into rdpdr
Apr 26, 2022
4b9c330
allowing dead code
Apr 26, 2022
66eb895
stubs out request_file_info
Apr 26, 2022
687b84c
fixes mismatched declaration
Apr 27, 2022
058530b
completes IRP_MJ_DEVICE_CONTROL
Apr 27, 2022
1543585
adds a check for allow_directory_sharing so that we guarantee its fun…
Apr 27, 2022
549eb02
adds SharedDirectoryAnnounce and removes the right click hack from cl…
Apr 27, 2022
8c5e2b1
Adds SharedDirectoryAcknowledge tdp message, refactors rust-go interf…
Apr 28, 2022
de13b64
updates naming convention
Apr 28, 2022
8017ef2
Making sense of handle_client_id_confirm
Apr 28, 2022
1ce587f
finishes out shared directory ack
Apr 28, 2022
66b5b00
updates to new sd_acknowledge
Apr 29, 2022
de8e052
uses a struct for ack
Apr 30, 2022
0feae71
roadblock at 'static lifetime error
May 2, 2022
20d9aeb
refactors codebase to only contain shared directory announce and shar…
May 3, 2022
ec900b8
Merge branch 'master' into isaiah/tdp-sd-announce-ack
May 3, 2022
e43694b
Merge branch 'master' into isaiah/tdp-sd-announce-ack
May 3, 2022
95af26b
fixes function name typo
May 4, 2022
eec39f5
rather than passing errors via C-strings, we pass them as integers wh…
May 6, 2022
c43d688
Merge branch 'master' into isaiah/cgo-err-code
May 6, 2022
a56236f
giving CGOErrCode explicit values
May 7, 2022
20efe3e
removing useless format
May 7, 2022
dac6c41
adding feature flag
May 9, 2022
6251502
adding #[allow(dead_code)]
May 9, 2022
2cb663d
Merge branch 'master' into isaiah/cgo-err-code
May 9, 2022
3615580
Merge branch 'isaiah/cgo-err-code' into isaiah/tdp-sd-announce-ack
May 9, 2022
7fe5b95
Debugf
May 9, 2022
9274c31
Merge branch 'master' into isaiah/cgo-err-code
May 9, 2022
7975209
Merge branch 'isaiah/cgo-err-code' into isaiah/tdp-sd-announce-ack
May 9, 2022
ce1c6c5
removing mentions of code side
May 9, 2022
1732e90
Merge branch 'isaiah/cgo-err-code' into isaiah/tdp-sd-announce-ack
May 9, 2022
9ec0cba
making a mod.rs
May 9, 2022
ee34ddd
moving scard in rdpdr
May 9, 2022
dbedfc8
moves constants into consts.rs
May 9, 2022
df26b8f
Adds go build flags for directory sharing and some basic scaffolding …
May 9, 2022
e0bbfcf
Merge branch 'isaiah/shared-directory-build-flag' into isaiah/tdp-sd-…
May 9, 2022
eccab5a
Merge branch 'master' into isaiah/tdp-sd-announce-ack
May 9, 2022
51c956c
reverting e
May 9, 2022
8d532c8
Id to ID
May 10, 2022
750fe60
typo
May 10, 2022
c4371da
Merge branch 'master' into isaiah/tdp-sd-announce-ack
May 18, 2022
750e7ff
fixing build bug and go lint errors
May 19, 2022
b449124
fixing rust linting problems
May 19, 2022
332af85
Merge branch 'master' into isaiah/tdp-sd-announce-ack
May 25, 2022
416b8a0
Update lib/srv/desktop/rdp/rdpclient/librdprs.h
May 27, 2022
aff394c
Update lib/srv/desktop/rdp/rdpclient/src/lib.rs
May 27, 2022
d773592
Code review
May 27, 2022
ded34fc
refactoring handle_device_reply to clarify which part is for director…
May 31, 2022
7df9b4f
changing try_from to as
May 31, 2022
901aa3d
adds doc comment for new_drive
May 31, 2022
eade41e
adding doc for write_client_device_list_announce
May 31, 2022
9077167
Removes CapabilityType::CAP_DRIVE_TYPE from the capability set if dir…
May 31, 2022
88847b6
adds docstring to sharedDirectoryAcknowledge
May 31, 2022
ae2e515
Merge branch 'master' into isaiah/tdp-sd-announce-ack
Jun 1, 2022
9298408
Merge branch 'master' into isaiah/tdp-sd-announce-ack
Jun 1, 2022
97a5c3d
Merge branch 'master' into isaiah/tdp-sd-announce-ack
Jun 1, 2022
66dc1a0
Merge branch 'master' into isaiah/tdp-sd-announce-ack
Jun 13, 2022
3d716ca
Merge branch 'master' into isaiah/tdp-sd-announce-ack
Jun 13, 2022
0c7b1da
Merge branch 'master' into isaiah/tdp-sd-announce-ack
Jun 13, 2022
2dc6e32
Merge branch 'master' into isaiah/tdp-sd-announce-ack
Jun 14, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions lib/srv/desktop/rdp/rdpclient/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ rand_chacha = "0.3.1"
rsa = "0.6.1"
rdp-rs = { git = "https://github.com/gravitational/rdp-rs", rev = "17ec446ecb73c58b77ac47c6fc8598153f673076" }
uuid = { version = "1.1.2", features = ["v4"] }
utf16string = "0.2.0"

31 changes: 31 additions & 0 deletions lib/srv/desktop/rdp/rdpclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,18 @@ func (c *Client) start() {
} else {
c.cfg.Log.Warning("Recieved an empty clipboard message")
}
case tdp.SharedDirectoryAnnounce:
if c.cfg.AllowDirectorySharing {
driveName := C.CString(m.Name)
defer C.free(unsafe.Pointer(driveName))
if err := C.handle_tdp_sd_announce(c.rustClient, C.CGOSharedDirectoryAnnounce{
zmb3 marked this conversation as resolved.
Show resolved Hide resolved
directory_id: C.uint32_t(m.DirectoryID),
name: driveName,
}); err != C.ErrCodeSuccess {
c.cfg.Log.Errorf("Device announce failed: %v", err)
return
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume a return here breaks the connection and the session is terminated. Is that desirable? Is there a graceful way to handle the error instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is in keeping with how we handle the other TDP --> RDP translation failures.

This could be improved by sending back a SharedDirectoryAcknowledge with a failure code in cases where handle_tdp_sd_announce fails, and only returning a CGOErrCode::ErrCodeFailure when an irrecoverable failure occurs like

let client = match Client::from_ptr(client_ptr) {
Ok(client) => client,
Err(cgo_error) => {
return cgo_error;
}
};

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I think if we can't translate a bitmap or something then terminating the connection is probably reasonable.

Question is, how likely is it that we see an error here? If not likely, then we can stick with the existing pattern.

I think this is all out of scope for this PR to be honest, but is makes me wonder whether we should have support for a TDP error message that doesn't kill the connection. We could show a little toast item or something in the UI to indicate a non-critical error but keep the session running.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question is, how likely is it that we see an error here? If not likely, then we can stick with the existing pattern.

Based on my experience working with this thus far, I think it's unlikely enough that we are fine to leave this as is for now.

I agree with all of that. I had the same general thought while developing this feature, noticing that we didn't have a consistent system for when we kill the connection (typically by returning an Err(...) for an RdpResult, vs when we send back an RDP response with NTSTATUS::STATUS_UNSUCCESSFUL, vs when we send a TDP error between those options.

I created an issue to remind us to go back and do that: #13041

}
}
default:
c.cfg.Log.Warningf("Skipping unimplemented TDP message type %T", msg)
}
Expand Down Expand Up @@ -434,6 +446,25 @@ func (c *Client) handleRemoteCopy(data []byte) C.CGOErrCode {
return C.ErrCodeSuccess
}

//export tdp_sd_acknowledge
func tdp_sd_acknowledge(handle C.uintptr_t, ack *C.CGOSharedDirectoryAcknowledge) C.CGOErrCode {
return cgo.Handle(handle).Value().(*Client).sharedDirectoryAcknowledge(tdp.SharedDirectoryAcknowledge{
Err: uint32(ack.err),
DirectoryID: uint32(ack.directory_id),
})
}

// sharedDirectoryAcknowledge acknowledges that a `Shared Directory Announce` TDP message was processed.
func (c *Client) sharedDirectoryAcknowledge(ack tdp.SharedDirectoryAcknowledge) C.CGOErrCode {
ibeckermayer marked this conversation as resolved.
Show resolved Hide resolved
if c.cfg.AllowDirectorySharing {
if err := c.cfg.Conn.OutputMessage(ack); err != nil {
c.cfg.Log.Errorf("failed to send SharedDirectoryAcknowledge: %v", err)
return C.ErrCodeFailure
}
}
return C.ErrCodeSuccess
}

// Wait blocks until the client disconnects and runs the cleanup.
func (c *Client) Wait() error {
c.wg.Wait()
Expand Down
37 changes: 35 additions & 2 deletions lib/srv/desktop/rdp/rdpclient/librdprs.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@

#define SPECIAL_NO_RESPONSE 4294967295

#define SCARD_DEVICE_ID 1

#define VERSION_MAJOR 1

#define VERSION_MINOR 12

#define SMARTCARD_CAPABILITY_VERSION_01 1

#define DRIVE_CAPABILITY_VERSION_02 2

#define GENERAL_CAPABILITY_VERSION_01 1

#define GENERAL_CAPABILITY_VERSION_02 2

#define SCARD_DEVICE_ID 1

/**
* The default maximum chunk size for virtual channel data.
*
Expand Down Expand Up @@ -68,6 +70,11 @@ typedef struct ClientOrError {
enum CGOErrCode err;
} ClientOrError;

typedef struct CGOSharedDirectoryAnnounce {
uint32_t directory_id;
char *name;
} CGOSharedDirectoryAnnounce;

/**
* CGOMousePointerEvent is a CGO-compatible version of PointerEvent that we pass back to Go.
* PointerEvent is a mouse move or click update from the user.
Expand Down Expand Up @@ -107,6 +114,15 @@ typedef struct CGOBitmap {
uintptr_t data_cap;
} CGOBitmap;

/**
* CGOSharedDirectoryAcknowledge is a CGO-compatible version of
* the TDP Shared Directory Knowledge message that we pass back to Go.
*/
typedef struct CGOSharedDirectoryAcknowledge {
uint32_t err;
uint32_t directory_id;
} CGOSharedDirectoryAcknowledge;

void init(void);

/**
Expand Down Expand Up @@ -141,6 +157,17 @@ struct ClientOrError connect_rdp(uintptr_t go_ref,
*/
enum CGOErrCode update_clipboard(struct Client *client_ptr, uint8_t *data, uint32_t len);

/**
* handle_tdp_sd_announce announces a new drive that's ready to be
* redirected over RDP.
*
* # Safety
*
* The caller must ensure that sd_announce.name points to a valid buffer.
*/
enum CGOErrCode handle_tdp_sd_announce(struct Client *client_ptr,
struct CGOSharedDirectoryAnnounce sd_announce);

/**
* `read_rdp_output` reads incoming RDP bitmap frames from client at client_ref and forwards them to
* handle_bitmap.
Expand Down Expand Up @@ -192,3 +219,9 @@ void free_rust_string(char *s);
extern enum CGOErrCode handle_bitmap(uintptr_t client_ref, struct CGOBitmap *b);

extern enum CGOErrCode handle_remote_copy(uintptr_t client_ref, uint8_t *data, uint32_t len);

/**
* Shared Directory Acknowledge
*/
extern enum CGOErrCode tdp_sd_acknowledge(uintptr_t client_ref,
struct CGOSharedDirectoryAcknowledge *ack);
6 changes: 3 additions & 3 deletions lib/srv/desktop/rdp/rdpclient/src/cliprdr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ fn encode_clipboard(mut data: String) -> (Vec<u8>, ClipboardFormat) {

(data.into_bytes(), ClipboardFormat::CF_TEXT)
} else {
let encoded = util::to_nul_terminated_utf16le(&data);
let encoded = util::to_unicode(&data, true);
(encoded, ClipboardFormat::CF_UNICODETEXT)
}
}
Expand Down Expand Up @@ -676,7 +676,7 @@ impl FormatName for LongFormatName {
// must be encoded as a single Unicode null character (two zero bytes)
None => w.write_u16::<LittleEndian>(0)?,
Some(name) => {
w.append(&mut util::to_nul_terminated_utf16le(name));
w.append(&mut util::to_unicode(name, true));
}
};

Expand Down Expand Up @@ -1037,7 +1037,7 @@ mod tests {
#[test]
fn responds_to_format_data_request_hasdata() {
// a null-terminated utf-16 string, represented as a Vec<u8>
let test_data = util::to_nul_terminated_utf16le("test");
let test_data = util::to_unicode("test", true);

let mut c: Client = Default::default();
c.clipboard
Expand Down
8 changes: 8 additions & 0 deletions lib/srv/desktop/rdp/rdpclient/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@ pub fn invalid_data_error(msg: &str) -> Error {
Error::RdpError(RdpError::new(RdpErrorKind::InvalidData, msg))
}

pub fn not_implemented_error(msg: &str) -> Error {
Error::RdpError(RdpError::new(RdpErrorKind::NotImplemented, msg))
}

pub fn try_error(msg: &str) -> Error {
Error::TryError(msg.to_string())
}

pub fn rejected_by_server_error(msg: &str) -> Error {
Error::RdpError(RdpError::new(RdpErrorKind::RejectedByServer, msg))
}

// NTSTATUS_OK is a Windows NTStatus value that means "success".
pub const NTSTATUS_OK: u32 = 0;
// SPECIAL_NO_RESPONSE is our custom (not defined by Windows) NTStatus value that means "don't send
Expand Down
105 changes: 97 additions & 8 deletions lib/srv/desktop/rdp/rdpclient/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

pub mod cliprdr;
pub mod errors;
pub mod piv;
pub mod rdpdr;
pub mod util;
pub mod vchan;
mod cliprdr;
mod errors;
mod piv;
mod rdpdr;
mod util;
mod vchan;

#[macro_use]
extern crate log;
Expand Down Expand Up @@ -251,12 +251,27 @@ fn connect_rdp_inner(
KeyboardLayout::US,
"rdp-rs",
);
// Client for the "rdpdr" channel - smartcard emulation.

let tdp_sd_acknowledge = Box::new(move |ack: SharedDirectoryAcknowledge| -> RdpResult<()> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thus far, the Rust code didn't know anything about TDP - it just invoked callbacks into Go with some data, and Go handled all TDP.

Have we moved away from this, or is it just a naming thing?

Copy link
Contributor Author

@ibeckermayer ibeckermayer May 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've decided that we're moving away from this.

It's possible to name this something else, and pass data as function parameters and/or differently named structs (and so on with other similar functions I add in the future), but ultimately that would just be a slight of hand. Functionally, this client would still be carrying out the same process of converting RDP into TDP and vice versa.

I can see an argument for treating this client as a "pure" RDP client, which blindly takes RDP messages it receives and passes them into Go, and which exposes an API that only allows users to call functions that directly send RDP messages to the Windows server. That would require some refactoring of our existing code, and a major overhaul of my stack of existing PR's.

Such a design would offer the benefit of pushing much (all?) of the Teleport specific functionality out of this client (besides the fact that we use Go). It would allow us to do things like, for example, swapping out TDP for another protocol (such as say, Guacamole, or just using RDP directly) without changing this client.

My thought is that we don't have a good reason to refactor this client in that way right now, because we don't have any plans for moving away from TDP. Granted I might be being biased due to my personal investment in building this system out without that design goal in mind, or I may be missing some other important line of reasoning.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, the crate is literally called "rdpclient" so I'm of the mind it should only do RDP.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting this here for documentation: we discussed on a call that our rdpclient primarily does RDP at the low level protocol. It will also have functionality to create data structures and call functions with those data structures that are named for the TDP messages they stand for, but it remains blind to the low level details of TDP. After this implementation is working, we should decide whether our naming conventions should change in order to make this separation more explicit.

unsafe {
ibeckermayer marked this conversation as resolved.
Show resolved Hide resolved
if tdp_sd_acknowledge(go_ref, &mut CGOSharedDirectoryAcknowledge::from(ack))
!= CGOErrCode::ErrCodeSuccess
{
return Err(RdpError::TryError(String::from(
"call to tdp_sd_acknowledge failed",
)));
}
}
Ok(())
});

// Client for the "rdpdr" channel - smartcard emulation and drive redirection.
let rdpdr = rdpdr::Client::new(
params.cert_der,
params.key_der,
pin,
params.allow_directory_sharing,
tdp_sd_acknowledge,
zmb3 marked this conversation as resolved.
Show resolved Hide resolved
);

// Client for the "cliprdr" channel - clipboard sharing.
Expand Down Expand Up @@ -336,6 +351,14 @@ impl<S: Read + Write> RdpClient<S> {
}
}

pub fn write_client_device_list_announce(
&mut self,
req: rdpdr::ClientDeviceListAnnounce,
) -> RdpResult<()> {
self.rdpdr
.write_client_device_list_announce(req, &mut self.mcs)
}

pub fn shutdown(&mut self) -> RdpResult<()> {
self.mcs.shutdown()
}
Expand Down Expand Up @@ -458,6 +481,38 @@ pub unsafe extern "C" fn update_clipboard(
}
}

/// handle_tdp_sd_announce announces a new drive that's ready to be
/// redirected over RDP.
///
/// # Safety
///
/// The caller must ensure that sd_announce.name points to a valid buffer.
#[no_mangle]
pub unsafe extern "C" fn handle_tdp_sd_announce(
client_ptr: *mut Client,
sd_announce: CGOSharedDirectoryAnnounce,
) -> CGOErrCode {
let client = match Client::from_ptr(client_ptr) {
Ok(client) => client,
Err(cgo_error) => {
return cgo_error;
}
};

let drive_name = from_go_string(sd_announce.name);
let new_drive =
rdpdr::ClientDeviceListAnnounce::new_drive(sd_announce.directory_id, drive_name);

let mut rdp_client = client.rdp_client.lock().unwrap();
match rdp_client.write_client_device_list_announce(new_drive) {
Ok(()) => CGOErrCode::ErrCodeSuccess,
Err(e) => {
error!("failed to announce new drive: {:?}", e);
CGOErrCode::ErrCodeFailure
}
}
}

/// `read_rdp_output` reads incoming RDP bitmap frames from client at client_ref and forwards them to
/// handle_bitmap.
///
Expand Down Expand Up @@ -521,7 +576,7 @@ fn read_rdp_output_inner(client: &Client) -> Option<String> {
match res {
Err(RdpError::Io(io_err)) if io_err.kind() == ErrorKind::UnexpectedEof => return None,
Err(e) => {
return Some(format!("failed forwarding RDP bitmap frame: {:?}", e));
return Some(format!("RDP read failed: {:?}", e));
}
_ => {}
}
Expand Down Expand Up @@ -700,6 +755,8 @@ pub unsafe extern "C" fn free_rust_string(s: *mut c_char) {
/// # Safety
///
/// s must be a C-style null terminated string.
/// s is copied here, and the caller is responsible for ensuring
/// that the original memory is freed
unsafe fn from_go_string(s: *mut c_char) -> String {
CStr::from_ptr(s).to_string_lossy().into_owned()
}
Expand All @@ -718,11 +775,43 @@ pub enum CGOErrCode {
ErrCodeFailure = 1,
}

#[repr(C)]
pub struct CGOSharedDirectoryAnnounce {
pub directory_id: u32,
pub name: *mut c_char,
}

pub struct SharedDirectoryAcknowledge {
pub err: u32,
pub directory_id: u32,
}

/// CGOSharedDirectoryAcknowledge is a CGO-compatible version of
/// the TDP Shared Directory Knowledge message that we pass back to Go.
#[repr(C)]
pub struct CGOSharedDirectoryAcknowledge {
pub err: u32,
pub directory_id: u32,
}

impl From<SharedDirectoryAcknowledge> for CGOSharedDirectoryAcknowledge {
fn from(ack: SharedDirectoryAcknowledge) -> CGOSharedDirectoryAcknowledge {
CGOSharedDirectoryAcknowledge {
err: ack.err,
directory_id: ack.directory_id,
}
}
}

// These functions are defined on the Go side. Look for functions with '//export funcname'
// comments.
extern "C" {
fn handle_bitmap(client_ref: usize, b: *mut CGOBitmap) -> CGOErrCode;
fn handle_remote_copy(client_ref: usize, data: *mut u8, len: u32) -> CGOErrCode;

/// Shared Directory Acknowledge
fn tdp_sd_acknowledge(client_ref: usize, ack: *mut CGOSharedDirectoryAcknowledge)
-> CGOErrCode;
}

/// Payload is a generic type used to represent raw incoming RDP messages for parsing.
Expand Down
Loading