mirror of
https://github.com/Devolutions/IronRDP.git
synced 2025-07-07 17:45:01 +00:00
feat(session): graceful disconnection support (#336)
This commit is contained in:
parent
d24000a2ae
commit
056ec6a034
11 changed files with 236 additions and 57 deletions
|
@ -69,7 +69,10 @@ impl GuiContext {
|
|||
});
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
control_flow.set_exit();
|
||||
if input_event_sender.send(RdpInputEvent::Close).is_err() {
|
||||
error!("Failed to send graceful shutdown event, closing the window");
|
||||
control_flow.set_exit();
|
||||
}
|
||||
}
|
||||
WindowEvent::DroppedFile(_) => {
|
||||
// TODO(#110): File upload
|
||||
|
@ -222,8 +225,8 @@ impl GuiContext {
|
|||
}
|
||||
Event::UserEvent(RdpOutputEvent::Terminated(result)) => {
|
||||
let exit_code = match result {
|
||||
Ok(()) => {
|
||||
println!("Terminated gracefully");
|
||||
Ok(reason) => {
|
||||
println!("Terminated gracefully: {reason}");
|
||||
proc_exit::sysexits::OK
|
||||
}
|
||||
Err(error) => {
|
||||
|
|
|
@ -3,7 +3,7 @@ use ironrdp::connector::{ConnectionResult, ConnectorResult};
|
|||
use ironrdp::graphics::image_processing::PixelFormat;
|
||||
use ironrdp::pdu::input::fast_path::FastPathInputEvent;
|
||||
use ironrdp::session::image::DecodedImage;
|
||||
use ironrdp::session::{ActiveStage, ActiveStageOutput, SessionResult};
|
||||
use ironrdp::session::{ActiveStage, ActiveStageOutput, GracefulDisconnectReason, SessionResult};
|
||||
use ironrdp::{cliprdr, connector, rdpdr, rdpsnd, session};
|
||||
use rdpdr::NoopRdpdrBackend;
|
||||
use smallvec::SmallVec;
|
||||
|
@ -20,7 +20,7 @@ pub enum RdpOutputEvent {
|
|||
PointerDefault,
|
||||
PointerHidden,
|
||||
PointerPosition { x: u16, y: u16 },
|
||||
Terminated(SessionResult<()>),
|
||||
Terminated(SessionResult<GracefulDisconnectReason>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -67,8 +67,8 @@ impl RdpClient {
|
|||
self.config.connector.desktop_size.width = width;
|
||||
self.config.connector.desktop_size.height = height;
|
||||
}
|
||||
Ok(RdpControlFlow::TerminatedGracefully) => {
|
||||
let _ = self.event_loop_proxy.send_event(RdpOutputEvent::Terminated(Ok(())));
|
||||
Ok(RdpControlFlow::TerminatedGracefully(reason)) => {
|
||||
let _ = self.event_loop_proxy.send_event(RdpOutputEvent::Terminated(Ok(reason)));
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -82,7 +82,7 @@ impl RdpClient {
|
|||
|
||||
enum RdpControlFlow {
|
||||
ReconnectWithNewSize { width: u16, height: u16 },
|
||||
TerminatedGracefully,
|
||||
TerminatedGracefully(GracefulDisconnectReason),
|
||||
}
|
||||
|
||||
type UpgradedFramed = ironrdp_tokio::TokioFramed<ironrdp_tls::TlsStream<TcpStream>>;
|
||||
|
@ -162,7 +162,7 @@ async fn active_session(
|
|||
|
||||
let mut active_stage = ActiveStage::new(connection_result, None);
|
||||
|
||||
'outer: loop {
|
||||
let disconnect_reason = 'outer: loop {
|
||||
let outputs = tokio::select! {
|
||||
frame = framed.read_pdu() => {
|
||||
let (action, payload) = frame.map_err(|e| session::custom_err!("read frame", e))?;
|
||||
|
@ -198,8 +198,7 @@ async fn active_session(
|
|||
active_stage.process_fastpath_input(&mut image, &events)?
|
||||
}
|
||||
RdpInputEvent::Close => {
|
||||
// TODO(#115): https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/27915739-8f77-487e-9927-55008af7fd68
|
||||
break 'outer;
|
||||
active_stage.graceful_shutdown()?
|
||||
}
|
||||
RdpInputEvent::Clipboard(event) => {
|
||||
if let Some(cliprdr) = active_stage.get_svc_processor::<ironrdp::cliprdr::Cliprdr>() {
|
||||
|
@ -281,10 +280,10 @@ async fn active_session(
|
|||
ActiveStageOutput::PointerBitmap(_) => {
|
||||
// Not applicable, because we use the software cursor rendering.
|
||||
}
|
||||
ActiveStageOutput::Terminate => break 'outer,
|
||||
ActiveStageOutput::Terminate(reason) => break 'outer reason,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(RdpControlFlow::TerminatedGracefully)
|
||||
Ok(RdpControlFlow::TerminatedGracefully(disconnect_reason))
|
||||
}
|
||||
|
|
|
@ -736,6 +736,22 @@ impl DisconnectReason {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn description(self) -> &'static str {
|
||||
match self {
|
||||
Self::DomainDisconnected => "domain disconnected",
|
||||
Self::ProviderInitiated => "server-initiated disconnect",
|
||||
Self::TokenPurged => "token purged",
|
||||
Self::UserRequested => "user-requested disconnect",
|
||||
Self::ChannelPurged => "channel purged",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for DisconnectReason {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_str(self.description())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
|
@ -749,6 +765,10 @@ impl DisconnectProviderUltimatum {
|
|||
pub const NAME: &'static str = "DisconnectProviderUltimatum";
|
||||
|
||||
pub const FIXED_PART_SIZE: usize = 2;
|
||||
|
||||
pub fn from_reason(reason: DisconnectReason) -> Self {
|
||||
Self { reason }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> McsPdu<'de> for DisconnectProviderUltimatum {
|
||||
|
|
|
@ -265,6 +265,7 @@ pub enum ShareDataPdu {
|
|||
ServerSetErrorInfo(ServerSetErrorInfoPdu),
|
||||
Input(InputEventPdu),
|
||||
ShutdownRequest,
|
||||
ShutdownDenied,
|
||||
SuppressOutput(SuppressOutputPdu),
|
||||
RefreshRectangle(RefreshRectanglePdu),
|
||||
}
|
||||
|
@ -281,7 +282,8 @@ impl ShareDataPdu {
|
|||
ShareDataPdu::FrameAcknowledge(_) => "Frame Acknowledge PDU",
|
||||
ShareDataPdu::ServerSetErrorInfo(_) => "Server Set Error Info PDU",
|
||||
ShareDataPdu::Input(_) => "Server Input PDU",
|
||||
ShareDataPdu::ShutdownRequest => "Shutdown Request",
|
||||
ShareDataPdu::ShutdownRequest => "Shutdown Request PDU",
|
||||
ShareDataPdu::ShutdownDenied => "Shutdown Denied PDU",
|
||||
ShareDataPdu::SuppressOutput(_) => "Suppress Output PDU",
|
||||
ShareDataPdu::RefreshRectangle(_) => "Refresh Rectangle PDU",
|
||||
}
|
||||
|
@ -309,6 +311,7 @@ impl ShareDataPdu {
|
|||
)),
|
||||
ShareDataPduType::Input => Ok(ShareDataPdu::Input(InputEventPdu::from_buffer(&mut stream)?)),
|
||||
ShareDataPduType::ShutdownRequest => Ok(ShareDataPdu::ShutdownRequest),
|
||||
ShareDataPduType::ShutdownDenied => Ok(ShareDataPdu::ShutdownDenied),
|
||||
ShareDataPduType::SuppressOutput => Ok(ShareDataPdu::SuppressOutput(SuppressOutputPdu::from_buffer(
|
||||
&mut stream,
|
||||
)?)),
|
||||
|
@ -318,7 +321,6 @@ impl ShareDataPdu {
|
|||
ShareDataPduType::Update
|
||||
| ShareDataPduType::Pointer
|
||||
| ShareDataPduType::PlaySound
|
||||
| ShareDataPduType::ShutdownDenied
|
||||
| ShareDataPduType::SetKeyboardIndicators
|
||||
| ShareDataPduType::BitmapCachePersistentList
|
||||
| ShareDataPduType::BitmapCacheErrorPdu
|
||||
|
@ -343,7 +345,7 @@ impl ShareDataPdu {
|
|||
ShareDataPdu::FrameAcknowledge(pdu) => pdu.to_buffer(&mut stream).map_err(RdpError::from),
|
||||
ShareDataPdu::ServerSetErrorInfo(pdu) => pdu.to_buffer(&mut stream).map_err(RdpError::from),
|
||||
ShareDataPdu::Input(pdu) => pdu.to_buffer(&mut stream).map_err(RdpError::from),
|
||||
ShareDataPdu::ShutdownRequest => Ok(()),
|
||||
ShareDataPdu::ShutdownRequest | ShareDataPdu::ShutdownDenied => Ok(()),
|
||||
ShareDataPdu::SuppressOutput(pdu) => pdu.to_buffer(&mut stream).map_err(RdpError::from),
|
||||
ShareDataPdu::RefreshRectangle(pdu) => pdu.to_buffer(&mut stream).map_err(RdpError::from),
|
||||
}
|
||||
|
@ -359,7 +361,7 @@ impl ShareDataPdu {
|
|||
ShareDataPdu::FrameAcknowledge(pdu) => pdu.buffer_length(),
|
||||
ShareDataPdu::ServerSetErrorInfo(pdu) => pdu.buffer_length(),
|
||||
ShareDataPdu::Input(pdu) => pdu.buffer_length(),
|
||||
ShareDataPdu::ShutdownRequest => 0,
|
||||
ShareDataPdu::ShutdownRequest | ShareDataPdu::ShutdownDenied => 0,
|
||||
ShareDataPdu::SuppressOutput(pdu) => pdu.buffer_length(),
|
||||
ShareDataPdu::RefreshRectangle(pdu) => pdu.buffer_length(),
|
||||
}
|
||||
|
@ -376,6 +378,7 @@ impl ShareDataPdu {
|
|||
ShareDataPdu::ServerSetErrorInfo(_) => ShareDataPduType::SetErrorInfoPdu,
|
||||
ShareDataPdu::Input(_) => ShareDataPduType::Input,
|
||||
ShareDataPdu::ShutdownRequest => ShareDataPduType::ShutdownRequest,
|
||||
ShareDataPdu::ShutdownDenied => ShareDataPduType::ShutdownDenied,
|
||||
ShareDataPdu::SuppressOutput(_) => ShareDataPduType::SuppressOutput,
|
||||
ShareDataPdu::RefreshRectangle(_) => ShareDataPduType::RefreshRectangle,
|
||||
}
|
||||
|
|
|
@ -4,14 +4,15 @@ use ironrdp_connector::ConnectionResult;
|
|||
use ironrdp_graphics::pointer::DecodedPointer;
|
||||
use ironrdp_pdu::geometry::InclusiveRectangle;
|
||||
use ironrdp_pdu::input::fast_path::{FastPathInput, FastPathInputEvent};
|
||||
use ironrdp_pdu::rdp::headers::ShareDataPdu;
|
||||
use ironrdp_pdu::write_buf::WriteBuf;
|
||||
use ironrdp_pdu::{Action, PduParsing};
|
||||
use ironrdp_pdu::{mcs, Action, PduParsing};
|
||||
use ironrdp_svc::{StaticVirtualChannelProcessor, SvcProcessorMessages};
|
||||
|
||||
use crate::fast_path::UpdateKind;
|
||||
use crate::image::DecodedImage;
|
||||
use crate::x224::GfxHandler;
|
||||
use crate::{fast_path, x224, SessionResult};
|
||||
use crate::{fast_path, x224, SessionError, SessionResult};
|
||||
|
||||
pub struct ActiveStage {
|
||||
x224_processor: x224::Processor,
|
||||
|
@ -105,21 +106,26 @@ impl ActiveStage {
|
|||
action: Action,
|
||||
frame: &[u8],
|
||||
) -> SessionResult<Vec<ActiveStageOutput>> {
|
||||
let (output, processor_updates) = match action {
|
||||
let (mut stage_outputs, processor_updates) = match action {
|
||||
Action::FastPath => {
|
||||
let mut output = WriteBuf::new();
|
||||
let processor_updates = self.fast_path_processor.process(image, frame, &mut output)?;
|
||||
(output.into_inner(), processor_updates)
|
||||
(
|
||||
vec![ActiveStageOutput::ResponseFrame(output.into_inner())],
|
||||
processor_updates,
|
||||
)
|
||||
}
|
||||
Action::X224 => {
|
||||
let outputs = self
|
||||
.x224_processor
|
||||
.process(frame)?
|
||||
.into_iter()
|
||||
.map(TryFrom::try_from)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
(outputs, Vec::new())
|
||||
}
|
||||
Action::X224 => (self.x224_processor.process(frame)?, Vec::new()),
|
||||
};
|
||||
|
||||
let mut stage_outputs = Vec::new();
|
||||
|
||||
if !output.is_empty() {
|
||||
stage_outputs.push(ActiveStageOutput::ResponseFrame(output));
|
||||
}
|
||||
|
||||
for update in processor_updates {
|
||||
match update {
|
||||
UpdateKind::None => {}
|
||||
|
@ -144,17 +150,27 @@ impl ActiveStage {
|
|||
Ok(stage_outputs)
|
||||
}
|
||||
|
||||
/// Encodes client-side graceful shutdown request. Note that upon sending this request,
|
||||
/// client should wait for server's ShutdownDenied PDU before closing the connection.
|
||||
///
|
||||
/// Client-side graceful shutdown is defined in [MS-RDPBCGR]
|
||||
///
|
||||
/// [MS-RDPBCGR]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/27915739-8f77-487e-9927-55008af7fd68
|
||||
pub fn graceful_shutdown(&self) -> SessionResult<Vec<ActiveStageOutput>> {
|
||||
let mut frame = WriteBuf::new();
|
||||
self.x224_processor
|
||||
.encode_static(&mut frame, ShareDataPdu::ShutdownRequest)?;
|
||||
|
||||
Ok(vec![ActiveStageOutput::ResponseFrame(frame.into_inner())])
|
||||
}
|
||||
|
||||
/// Sends a PDU on the dynamic channel.
|
||||
pub fn encode_dynamic(&self, output: &mut WriteBuf, channel_name: &str, dvc_data: &[u8]) -> SessionResult<()> {
|
||||
self.x224_processor.encode_dynamic(output, channel_name, dvc_data)
|
||||
}
|
||||
|
||||
/// Send a pdu on the static global channel. Typically used to send input events
|
||||
pub fn encode_static(
|
||||
&self,
|
||||
output: &mut WriteBuf,
|
||||
pdu: ironrdp_pdu::rdp::headers::ShareDataPdu,
|
||||
) -> SessionResult<usize> {
|
||||
pub fn encode_static(&self, output: &mut WriteBuf, pdu: ShareDataPdu) -> SessionResult<usize> {
|
||||
self.x224_processor.encode_static(output, pdu)
|
||||
}
|
||||
|
||||
|
@ -184,5 +200,49 @@ pub enum ActiveStageOutput {
|
|||
PointerHidden,
|
||||
PointerPosition { x: u16, y: u16 },
|
||||
PointerBitmap(Rc<DecodedPointer>),
|
||||
Terminate,
|
||||
Terminate(GracefulDisconnectReason),
|
||||
}
|
||||
|
||||
impl TryFrom<x224::ProcessorOutput> for ActiveStageOutput {
|
||||
type Error = SessionError;
|
||||
|
||||
fn try_from(value: x224::ProcessorOutput) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
x224::ProcessorOutput::ResponseFrame(frame) => Ok(Self::ResponseFrame(frame)),
|
||||
x224::ProcessorOutput::Disconnect(reason) => {
|
||||
let reason = match reason {
|
||||
mcs::DisconnectReason::UserRequested => GracefulDisconnectReason::UserInitiated,
|
||||
mcs::DisconnectReason::ProviderInitiated => GracefulDisconnectReason::ServerInitiated,
|
||||
other => GracefulDisconnectReason::Other(other.description()),
|
||||
};
|
||||
|
||||
Ok(Self::Terminate(reason))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reasons for graceful disconnect. This type provides GUI-friendly descriptions for
|
||||
/// disconnect reasons.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum GracefulDisconnectReason {
|
||||
UserInitiated,
|
||||
ServerInitiated,
|
||||
Other(&'static str),
|
||||
}
|
||||
|
||||
impl GracefulDisconnectReason {
|
||||
pub fn description(&self) -> &'static str {
|
||||
match self {
|
||||
GracefulDisconnectReason::UserInitiated => "user initiated disconnect",
|
||||
GracefulDisconnectReason::ServerInitiated => "server initiated disconnect",
|
||||
GracefulDisconnectReason::Other(description) => description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for GracefulDisconnectReason {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_str(self.description())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ mod active_stage;
|
|||
|
||||
use core::fmt;
|
||||
|
||||
pub use active_stage::{ActiveStage, ActiveStageOutput};
|
||||
pub use active_stage::{ActiveStage, ActiveStageOutput, GracefulDisconnectReason};
|
||||
|
||||
pub type SessionResult<T> = Result<T, SessionError>;
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use std::collections::HashMap;
|
|||
use ironrdp_connector::legacy::SendDataIndicationCtx;
|
||||
use ironrdp_connector::GraphicsConfig;
|
||||
use ironrdp_pdu::dvc::FieldType;
|
||||
use ironrdp_pdu::mcs::{DisconnectProviderUltimatum, DisconnectReason, McsMessage};
|
||||
use ironrdp_pdu::rdp::headers::ShareDataPdu;
|
||||
use ironrdp_pdu::rdp::server_error_info::{ErrorInfo, ProtocolIndependentCode, ServerSetErrorInfoPdu};
|
||||
use ironrdp_pdu::rdp::vc::dvc;
|
||||
|
@ -17,7 +18,7 @@ use ironrdp_svc::{
|
|||
StaticChannelSet, StaticVirtualChannel, StaticVirtualChannelProcessor, SvcMessage, SvcProcessorMessages,
|
||||
};
|
||||
|
||||
use crate::{SessionErrorExt as _, SessionResult};
|
||||
use crate::{SessionError, SessionErrorExt as _, SessionResult};
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub use self::gfx::GfxHandler;
|
||||
|
@ -25,6 +26,15 @@ pub use self::gfx::GfxHandler;
|
|||
pub const RDP8_GRAPHICS_PIPELINE_NAME: &str = "Microsoft::Windows::RDS::Graphics";
|
||||
pub const RDP8_DISPLAY_PIPELINE_NAME: &str = "Microsoft::Windows::RDS::DisplayControl";
|
||||
|
||||
/// X224 Processor output
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ProcessorOutput {
|
||||
/// A buffer with encoded data to send to the server.
|
||||
ResponseFrame(Vec<u8>),
|
||||
/// A graceful disconnect notification. Client should close the connection upon receiving this.
|
||||
Disconnect(DisconnectReason),
|
||||
}
|
||||
|
||||
pub struct Processor {
|
||||
channel_map: HashMap<String, u32>,
|
||||
static_channels: StaticChannelSet,
|
||||
|
@ -90,26 +100,28 @@ impl Processor {
|
|||
process_svc_messages(messages.into(), channel_id, self.user_channel_id)
|
||||
}
|
||||
|
||||
/// Processes a received PDU. Returns a buffer with encoded data to send to the server, if any.
|
||||
pub fn process(&mut self, frame: &[u8]) -> SessionResult<Vec<u8>> {
|
||||
/// Processes a received PDU. Returns a vector of [`ProcessorOutput`] that must be processed
|
||||
/// in the returned order.
|
||||
pub fn process(&mut self, frame: &[u8]) -> SessionResult<Vec<ProcessorOutput>> {
|
||||
let data_ctx: SendDataIndicationCtx<'_> =
|
||||
ironrdp_connector::legacy::decode_send_data_indication(frame).map_err(crate::legacy::map_error)?;
|
||||
let channel_id = data_ctx.channel_id;
|
||||
|
||||
if channel_id == self.io_channel_id {
|
||||
self.process_io_channel(data_ctx)?;
|
||||
Ok(Vec::new())
|
||||
self.process_io_channel(data_ctx)
|
||||
} else if self.drdynvc_channel_id == Some(channel_id) {
|
||||
self.process_dyvc(data_ctx)
|
||||
.map(|data| vec![ProcessorOutput::ResponseFrame(data)])
|
||||
} else if let Some(svc) = self.static_channels.get_by_channel_id_mut(channel_id) {
|
||||
let response_pdus = svc.process(data_ctx.user_data).map_err(crate::SessionError::pdu)?;
|
||||
process_svc_messages(response_pdus, channel_id, data_ctx.initiator_id)
|
||||
.map(|data| vec![ProcessorOutput::ResponseFrame(data)])
|
||||
} else {
|
||||
Err(reason_err!("X224", "unexpected channel received: ID {channel_id}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn process_io_channel(&self, data_ctx: SendDataIndicationCtx<'_>) -> SessionResult<()> {
|
||||
fn process_io_channel(&self, data_ctx: SendDataIndicationCtx<'_>) -> SessionResult<Vec<ProcessorOutput>> {
|
||||
debug_assert_eq!(data_ctx.channel_id, self.io_channel_id);
|
||||
|
||||
let ctx = ironrdp_connector::legacy::decode_share_data(data_ctx).map_err(crate::legacy::map_error)?;
|
||||
|
@ -117,16 +129,47 @@ impl Processor {
|
|||
match ctx.pdu {
|
||||
ShareDataPdu::SaveSessionInfo(session_info) => {
|
||||
debug!("Got Session Save Info PDU: {session_info:?}");
|
||||
Ok(())
|
||||
Ok(Vec::new())
|
||||
}
|
||||
ShareDataPdu::ServerSetErrorInfo(ServerSetErrorInfoPdu(ErrorInfo::ProtocolIndependentCode(
|
||||
ProtocolIndependentCode::None,
|
||||
))) => {
|
||||
debug!("Received None server error");
|
||||
Ok(())
|
||||
Ok(Vec::new())
|
||||
}
|
||||
ShareDataPdu::ServerSetErrorInfo(ServerSetErrorInfoPdu(e)) => {
|
||||
Err(reason_err!("ServerSetErrorInfo", "{}", e.description()))
|
||||
// This is a part of server-side graceful disconnect procedure defined
|
||||
// in [MS-RDPBCGR].
|
||||
//
|
||||
// [MS-RDPBCGR]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/149070b0-ecec-4c20-af03-934bbc48adb8
|
||||
let graceful_disconnect = error_info_to_graceful_disconnect_reason(&e);
|
||||
|
||||
if let Some(reason) = graceful_disconnect {
|
||||
debug!("Received server-side graceful disconnect request: {reason}");
|
||||
|
||||
Ok(vec![ProcessorOutput::Disconnect(reason)])
|
||||
} else {
|
||||
Err(reason_err!("ServerSetErrorInfo", "{}", e.description()))
|
||||
}
|
||||
}
|
||||
ShareDataPdu::ShutdownDenied => {
|
||||
debug!("ShutdownDenied received, session will be closed");
|
||||
|
||||
// As defined in [MS-RDPBCGR], when `ShareDataPdu::ShutdownDenied` is received, we
|
||||
// need to send a disconnect ultimatum to the server if we want to proceed with the
|
||||
// session shutdown.
|
||||
//
|
||||
// [MS-RDPBCGR]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/27915739-8f77-487e-9927-55008af7fd68
|
||||
let ultimatum = McsMessage::DisconnectProviderUltimatum(DisconnectProviderUltimatum::from_reason(
|
||||
DisconnectReason::UserRequested,
|
||||
));
|
||||
|
||||
let encoded_pdu = ironrdp_pdu::encode_vec(&ultimatum).map_err(SessionError::pdu);
|
||||
|
||||
Ok(vec![
|
||||
ProcessorOutput::ResponseFrame(encoded_pdu?),
|
||||
ProcessorOutput::Disconnect(DisconnectReason::UserRequested),
|
||||
])
|
||||
}
|
||||
_ => Err(reason_err!(
|
||||
"IO channel",
|
||||
|
@ -507,3 +550,24 @@ impl CompleteData {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an [`ErrorInfo`] into a [`DisconnectReason`].
|
||||
///
|
||||
/// Returns `None` if the error code is not a graceful disconnect code.
|
||||
pub fn error_info_to_graceful_disconnect_reason(error_info: &ErrorInfo) -> Option<DisconnectReason> {
|
||||
let code = if let ErrorInfo::ProtocolIndependentCode(code) = error_info {
|
||||
code
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
match code {
|
||||
ProtocolIndependentCode::RpcInitiatedDisconnect
|
||||
| ProtocolIndependentCode::RpcInitiatedLogoff
|
||||
| ProtocolIndependentCode::DisconnectedByOtherconnection => Some(DisconnectReason::ProviderInitiated),
|
||||
ProtocolIndependentCode::RpcInitiatedDisconnectByuser | ProtocolIndependentCode::LogoffByUser => {
|
||||
Some(DisconnectReason::UserRequested)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use ironrdp::graphics::image_processing::PixelFormat;
|
|||
use ironrdp::pdu::input::fast_path::FastPathInputEvent;
|
||||
use ironrdp::pdu::write_buf::WriteBuf;
|
||||
use ironrdp::session::image::DecodedImage;
|
||||
use ironrdp::session::{ActiveStage, ActiveStageOutput};
|
||||
use ironrdp::session::{ActiveStage, ActiveStageOutput, GracefulDisconnectReason};
|
||||
use rgb::AsPixels as _;
|
||||
use tap::prelude::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
@ -342,6 +342,7 @@ pub(crate) enum RdpInputEvent {
|
|||
Cliprdr(ClipboardMessage),
|
||||
ClipboardBackend(WasmClipboardBackendMessage),
|
||||
FastPath(FastPathInputEvents),
|
||||
TerminateSession,
|
||||
}
|
||||
|
||||
enum CursorStyle {
|
||||
|
@ -354,6 +355,18 @@ enum CursorStyle {
|
|||
},
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct SessionTerminationInfo {
|
||||
reason: GracefulDisconnectReason,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl SessionTerminationInfo {
|
||||
pub fn reason(&self) -> String {
|
||||
self.reason.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Session {
|
||||
desktop_size: connector::DesktopSize,
|
||||
|
@ -374,7 +387,7 @@ pub struct Session {
|
|||
|
||||
#[wasm_bindgen]
|
||||
impl Session {
|
||||
pub async fn run(&self) -> Result<(), IronRdpError> {
|
||||
pub async fn run(&self) -> Result<SessionTerminationInfo, IronRdpError> {
|
||||
let rdp_reader = self
|
||||
.rdp_reader
|
||||
.borrow_mut()
|
||||
|
@ -418,7 +431,7 @@ impl Session {
|
|||
|
||||
let mut active_stage = ActiveStage::new(connection_result, None);
|
||||
|
||||
'outer: loop {
|
||||
let disconnect_reason = 'outer: loop {
|
||||
let outputs = select! {
|
||||
frame = framed.read_pdu().fuse() => {
|
||||
let (action, payload) = frame.context("read frame")?;
|
||||
|
@ -471,7 +484,11 @@ impl Session {
|
|||
}
|
||||
RdpInputEvent::FastPath(events) => {
|
||||
active_stage.process_fastpath_input(&mut image, &events)
|
||||
.context("Fast path input events processing")?
|
||||
.context("fast path input events processing")?
|
||||
}
|
||||
RdpInputEvent::TerminateSession => {
|
||||
active_stage.graceful_shutdown()
|
||||
.context("graceful shutdown")?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -589,14 +606,16 @@ impl Session {
|
|||
hotspot_y,
|
||||
})?;
|
||||
}
|
||||
ActiveStageOutput::Terminate => break 'outer,
|
||||
ActiveStageOutput::Terminate(reason) => break 'outer reason,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
info!("RPD session terminated");
|
||||
info!(%disconnect_reason, "RPD session terminated");
|
||||
|
||||
Ok(())
|
||||
Ok(SessionTerminationInfo {
|
||||
reason: disconnect_reason,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn desktop_size(&self) -> DesktopSize {
|
||||
|
@ -651,9 +670,11 @@ impl Session {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)] // FIXME: not yet implemented
|
||||
pub fn shutdown(&self) -> Result<(), IronRdpError> {
|
||||
// TODO(#115): https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/27915739-8f77-487e-9927-55008af7fd68
|
||||
self.input_events_tx
|
||||
.unbounded_send(RdpInputEvent::TerminateSession)
|
||||
.context("failed to send terminate session event to writer task")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -291,7 +291,7 @@ fn active_stage(
|
|||
for out in outputs {
|
||||
match out {
|
||||
ActiveStageOutput::ResponseFrame(frame) => framed.write_all(&frame).context("write response")?,
|
||||
ActiveStageOutput::Terminate => break 'outer,
|
||||
ActiveStageOutput::Terminate(_) => break 'outer,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import init, {
|
|||
Session,
|
||||
SessionBuilder,
|
||||
ClipboardTransaction,
|
||||
SessionTerminationInfo,
|
||||
} from '../../../../crates/ironrdp-web/pkg/ironrdp_web';
|
||||
import { loggingService } from './logging.service';
|
||||
import { catchError, filter, map } from 'rxjs/operators';
|
||||
|
@ -188,6 +189,13 @@ export class WasmBridgeService {
|
|||
});
|
||||
return of(err);
|
||||
}),
|
||||
map((termination_info: SessionTerminationInfo) => {
|
||||
this.setVisibility(false);
|
||||
this.raiseSessionEvent({
|
||||
type: SessionEventType.TERMINATED,
|
||||
data: 'Session was terminated: ' + termination_info.reason() + '.',
|
||||
});
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
return session;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{local_bin, prelude::*};
|
||||
use crate::local_bin;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn check(sh: &Shell) -> anyhow::Result<()> {
|
||||
let _s = Section::new("WASM-CHECK");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue