diff --git a/crates/now-proto-pdu/src/channel/capset.rs b/crates/now-proto-pdu/src/channel/capset.rs index ad93301..f5ba9f7 100644 --- a/crates/now-proto-pdu/src/channel/capset.rs +++ b/crates/now-proto-pdu/src/channel/capset.rs @@ -1,7 +1,6 @@ use core::time; use bitflags::bitflags; - use ironrdp_core::{ ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor, }; @@ -227,7 +226,7 @@ impl NowChannelCapsetMsg { let system_capset = NowSystemCapsetFlags::from_bits_retain(src.read_u16()); let session_capset = NowSessionCapsetFlags::from_bits_retain(src.read_u16()); let exec_capset = NowExecCapsetFlags::from_bits_retain(src.read_u16()); - // Read heartbeat interval unconditionaly even if `SET_HEARTBEAT` flags is not set. + // Read heartbeat interval unconditionally even if `SET_HEARTBEAT` flags is not set. let heartbeat_interval_value = src.read_u32(); let heartbeat_interval = flags diff --git a/crates/now-proto-pdu/src/channel/mod.rs b/crates/now-proto-pdu/src/channel/mod.rs index ebc1639..41cb5f6 100644 --- a/crates/now-proto-pdu/src/channel/mod.rs +++ b/crates/now-proto-pdu/src/channel/mod.rs @@ -2,14 +2,16 @@ mod capset; mod heartbeat; mod terminate; +pub use capset::{ + NowChannelCapsetFlags, NowChannelCapsetMsg, NowExecCapsetFlags, NowProtoVersion, NowSessionCapsetFlags, + NowSystemCapsetFlags, +}; +pub use heartbeat::NowChannelHeartbeatMsg; use ironrdp_core::{DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, WriteCursor}; +pub use terminate::{NowChannelTerminateMsg, OwnedNowChannelTerminateMsg}; use crate::NowHeader; -pub use capset::NowChannelCapsetMsg; -pub use heartbeat::NowChannelHeartbeatMsg; -pub use terminate::{NowChannelTerminateMsg, OwnedNowChannelTerminateMsg}; - // Wrapper for the `NOW_CHANNEL_MSG_CLASS_ID` message class. #[derive(Debug, Clone, PartialEq, Eq)] pub enum NowChannelMessage<'a> { diff --git a/crates/now-proto-pdu/src/channel/terminate.rs b/crates/now-proto-pdu/src/channel/terminate.rs index 4ea0f2c..bb08f89 100644 --- a/crates/now-proto-pdu/src/channel/terminate.rs +++ b/crates/now-proto-pdu/src/channel/terminate.rs @@ -1,6 +1,6 @@ use ironrdp_core::{invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, WriteCursor}; -use crate::{NowChannelMessage, NowChannelMsgKind, NowHeader, NowMessage, NowMessageClass, NowStatus}; +use crate::{NowChannelMessage, NowChannelMsgKind, NowHeader, NowMessage, NowMessageClass, NowStatus, NowStatusError}; /// Channel termination notice, could be sent by either parties at any moment of communication to /// gracefully close DVC channel. @@ -23,29 +23,36 @@ impl IntoOwned for NowChannelTerminateMsg<'_> { } } +impl Default for NowChannelTerminateMsg<'_> { + fn default() -> Self { + let status = NowStatus::new_success(); + + Self { status } + } +} + impl<'a> NowChannelTerminateMsg<'a> { const NAME: &'static str = "NOW_CHANNEL_TERMINATE_MSG"; - pub fn from_status(status: NowStatus<'a>) -> EncodeResult { + pub fn from_error(error: impl Into) -> EncodeResult { + let status = NowStatus::new_error(error); + let msg = Self { status }; - msg.ensure_message_size().expect("validated in constructor"); + ensure_now_message_size!(msg.status.size()); Ok(msg) } + pub fn to_result(&self) -> Result<(), NowStatusError> { + self.status.to_result() + } + pub(super) fn decode_from_body(_header: NowHeader, src: &mut ReadCursor<'a>) -> DecodeResult { let status = NowStatus::decode(src)?; Ok(Self { status }) } - - fn ensure_message_size(&self) -> EncodeResult<()> { - let _message_size = - u32::try_from(self.status.size()).map_err(|_| invalid_field_err!("size", "message size overflow"))?; - - Ok(()) - } } impl Encode for NowChannelTerminateMsg<'_> { diff --git a/crates/now-proto-pdu/src/core/buffer.rs b/crates/now-proto-pdu/src/core/buffer.rs index db038f4..8ae376d 100644 --- a/crates/now-proto-pdu/src/core/buffer.rs +++ b/crates/now-proto-pdu/src/core/buffer.rs @@ -14,12 +14,10 @@ use crate::VarU32; /// /// NOW-PROTO: NOW_VARBUF #[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct NowVarBuf<'a>(Cow<'a, [u8]>); - -impl_pdu_borrowing!(NowVarBuf<'_>, OwnedNowVarBuf); +pub(crate) struct NowVarBuf<'a>(Cow<'a, [u8]>); impl IntoOwned for NowVarBuf<'_> { - type Owned = OwnedNowVarBuf; + type Owned = NowVarBuf<'static>; fn into_owned(self) -> Self::Owned { NowVarBuf(Cow::Owned(self.0.into_owned())) @@ -30,7 +28,7 @@ impl<'a> NowVarBuf<'a> { const NAME: &'static str = "NOW_VARBUF"; /// Create a new `NowVarBuf` instance. Returns an error if the provided value is too large. - pub fn new(value: impl Into>) -> EncodeResult { + pub(crate) fn new(value: impl Into>) -> EncodeResult { let value = value.into(); let _: u32 = value @@ -42,11 +40,6 @@ impl<'a> NowVarBuf<'a> { Ok(NowVarBuf(value)) } - - /// Get the buffer value. - pub fn value(&self) -> &[u8] { - &self.0 - } } impl Encode for NowVarBuf<'_> { diff --git a/crates/now-proto-pdu/src/core/mod.rs b/crates/now-proto-pdu/src/core/mod.rs index 48a3678..9ac7ebd 100644 --- a/crates/now-proto-pdu/src/core/mod.rs +++ b/crates/now-proto-pdu/src/core/mod.rs @@ -1,4 +1,7 @@ //! This module contains `NOW-PROTO` core types definitions. +//! +//! Note that these types are not intended to be used directly by the user, and not exported in the +//! public API. mod buffer; mod header; @@ -6,8 +9,11 @@ mod number; mod status; mod string; -pub use buffer::{NowVarBuf, OwnedNowVarBuf}; -pub use header::{NowHeader, NowMessageClass}; -pub use number::VarU32; -pub use status::{NowProtoError, NowStatus, NowStatusError, NowStatusErrorKind, OwnedNowStatus}; -pub use string::{NowVarStr, OwnedNowVarStr}; +pub(crate) use buffer::NowVarBuf; +pub(crate) use header::{NowHeader, NowMessageClass}; +pub(crate) use number::VarU32; +pub(crate) use status::NowStatus; +// Only public-exported type is the status error, which should be available to the user for error +// handling. +pub use status::{NowProtoError, NowStatusError, NowStatusErrorKind}; +pub(crate) use string::NowVarStr; diff --git a/crates/now-proto-pdu/src/core/status.rs b/crates/now-proto-pdu/src/core/status.rs index 87aeb2b..dfa29f8 100644 --- a/crates/now-proto-pdu/src/core/status.rs +++ b/crates/now-proto-pdu/src/core/status.rs @@ -1,7 +1,8 @@ -use alloc::{borrow::Cow, fmt}; +use alloc::borrow::Cow; +use alloc::fmt; +use core::ops::Deref; use bitflags::bitflags; - use ironrdp_core::{ ensure_fixed_part_size, Decode, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, WriteCursor, }; @@ -216,13 +217,41 @@ impl NowStatusErrorKind { } /// Wrapper type around NOW_STATUS errors. Provides rust-friendly interface for error handling. -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct NowStatusError { kind: NowStatusErrorKind, message: NowVarStr<'static>, } impl NowStatusError { + pub fn new_generic(code: u32) -> Self { + Self { + kind: NowStatusErrorKind::Generic(code), + message: Default::default(), + } + } + + pub fn new_proto(error: NowProtoError) -> Self { + Self { + kind: NowStatusErrorKind::Now(error), + message: Default::default(), + } + } + + pub fn new_winapi(code: u32) -> Self { + Self { + kind: NowStatusErrorKind::WinApi(code), + message: Default::default(), + } + } + + pub fn new_unix(code: u32) -> Self { + Self { + kind: NowStatusErrorKind::Unix(code), + message: Default::default(), + } + } + pub fn kind(&self) -> NowStatusErrorKind { self.kind } @@ -230,9 +259,7 @@ impl NowStatusError { pub fn message(&self) -> &str { &self.message } -} -impl NowStatusError { /// Attach optional message to NOW_STATUS error. pub fn with_message(self, message: impl Into>) -> EncodeResult { Ok(Self { @@ -248,7 +275,7 @@ impl core::fmt::Display for NowStatusError { // Write optional message if provided. if !self.message.is_empty() { - write!(f, " ({})", self.message.value())?; + write!(f, " ({})", self.message.deref())?; } Ok(()) @@ -276,20 +303,18 @@ impl core::error::Error for NowStatusError {} /// /// NOW-PROTO: NOW_STATUS #[derive(Debug, Clone, PartialEq, Eq)] -pub struct NowStatus<'a> { +pub(crate) struct NowStatus<'a> { flags: NowStatusFlags, kind: RawNowStatusKind, code: u32, message: NowVarStr<'a>, } -impl_pdu_borrowing!(NowStatus<'_>, OwnedNowStatus); - impl IntoOwned for NowStatus<'_> { - type Owned = OwnedNowStatus; + type Owned = NowStatus<'static>; fn into_owned(self) -> Self::Owned { - OwnedNowStatus { + Self::Owned { flags: self.flags, kind: self.kind, code: self.code, @@ -303,7 +328,7 @@ impl NowStatus<'_> { const FIXED_PART_SIZE: usize = 8; /// Create a new success status. - pub fn new_success() -> Self { + pub(crate) fn new_success() -> Self { Self { flags: NowStatusFlags::empty(), kind: RawNowStatusKind::GENERIC, @@ -313,7 +338,7 @@ impl NowStatus<'_> { } /// Create a new status with error. - pub fn new_error(error: impl Into) -> Self { + pub(crate) fn new_error(error: impl Into) -> Self { let error: NowStatusError = error.into(); let flags = if error.message.is_empty() { @@ -331,7 +356,7 @@ impl NowStatus<'_> { } /// Convert status to result with 'static error. - pub fn to_result(&self) -> Result<(), NowStatusError> { + pub(crate) fn to_result(&self) -> Result<(), NowStatusError> { if !self.flags.contains(NowStatusFlags::ERROR) { return Ok(()); } diff --git a/crates/now-proto-pdu/src/core/string.rs b/crates/now-proto-pdu/src/core/string.rs index 0ed713c..51f965a 100644 --- a/crates/now-proto-pdu/src/core/string.rs +++ b/crates/now-proto-pdu/src/core/string.rs @@ -1,7 +1,8 @@ //! String types use alloc::borrow::Cow; -use core::{ops::Deref, str}; +use core::ops::Deref; +use core::str; use ironrdp_core::{ cast_length, ensure_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, @@ -14,12 +15,10 @@ use crate::VarU32; /// /// NOW-PROTO: NOW_VARSTR #[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct NowVarStr<'a>(Cow<'a, str>); - -impl_pdu_borrowing!(NowVarStr<'_>, OwnedNowVarStr); +pub(crate) struct NowVarStr<'a>(Cow<'a, str>); impl IntoOwned for NowVarStr<'_> { - type Owned = OwnedNowVarStr; + type Owned = NowVarStr<'static>; fn into_owned(self) -> Self::Owned { NowVarStr(Cow::Owned(self.0.into_owned())) @@ -27,12 +26,10 @@ impl IntoOwned for NowVarStr<'_> { } impl<'a> NowVarStr<'a> { - pub const MAX_SIZE: usize = VarU32::MAX as usize; - const NAME: &'static str = "NOW_VARSTR"; /// Creates `NowVarStr` from std string. Returns error if string is too big for the protocol. - pub fn new(value: impl Into>) -> EncodeResult { + pub(crate) fn new(value: impl Into>) -> EncodeResult { let value = value.into(); // IMPORTANT: we need to check for encoded UTF-8 size, not the string length. @@ -46,10 +43,6 @@ impl<'a> NowVarStr<'a> { Ok(NowVarStr(value)) } - - pub fn value(&self) -> &str { - &self.0 - } } impl Encode for NowVarStr<'_> { diff --git a/crates/now-proto-pdu/src/exec/batch.rs b/crates/now-proto-pdu/src/exec/batch.rs index ed5eef0..fa488ed 100644 --- a/crates/now-proto-pdu/src/exec/batch.rs +++ b/crates/now-proto-pdu/src/exec/batch.rs @@ -1,6 +1,6 @@ use alloc::borrow::Cow; -use bitflags::bitflags; +use bitflags::bitflags; use ironrdp_core::{ cast_length, ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, WriteCursor, @@ -95,18 +95,15 @@ impl<'a> NowExecBatchMsg<'a> { } fn ensure_message_size(&self) -> EncodeResult<()> { - let _message_size = Self::FIXED_PART_SIZE - .checked_add(self.command.size()) - .and_then(|size| size.checked_add(self.directory.size())) - .ok_or_else(|| invalid_field_err!("size", "message size overflow"))?; + ensure_now_message_size!(Self::FIXED_PART_SIZE, self.command.size(), self.directory.size()); Ok(()) } - pub(super) fn decode_from_body(_header: NowHeader, src: &mut ReadCursor<'a>) -> DecodeResult { + pub(super) fn decode_from_body(header: NowHeader, src: &mut ReadCursor<'a>) -> DecodeResult { ensure_fixed_part_size!(in: src); - let flags = NowExecBatchFlags::from_bits_retain(src.read_u16()); + let flags = NowExecBatchFlags::from_bits_retain(header.flags); let session_id = src.read_u32(); let command = NowVarStr::decode(src)?; let directory = NowVarStr::decode(src)?; diff --git a/crates/now-proto-pdu/src/exec/cancel_rsp.rs b/crates/now-proto-pdu/src/exec/cancel_rsp.rs index 78a89b2..5b557db 100644 --- a/crates/now-proto-pdu/src/exec/cancel_rsp.rs +++ b/crates/now-proto-pdu/src/exec/cancel_rsp.rs @@ -37,9 +37,6 @@ impl<'a> NowExecCancelRspMsg<'a> { status: NowStatus::new_success(), }; - msg.ensure_message_size() - .expect("success message size always fits into payload"); - msg } @@ -49,7 +46,7 @@ impl<'a> NowExecCancelRspMsg<'a> { status: NowStatus::new_error(error), }; - msg.ensure_message_size()?; + ensure_now_message_size!(Self::FIXED_PART_SIZE, msg.status.size()); Ok(msg) } @@ -68,14 +65,6 @@ impl<'a> NowExecCancelRspMsg<'a> { Self::FIXED_PART_SIZE + self.status.size() } - fn ensure_message_size(&self) -> EncodeResult<()> { - let _message_size = Self::FIXED_PART_SIZE - .checked_add(self.status.size()) - .ok_or_else(|| invalid_field_err!("size", "message size overflow"))?; - - Ok(()) - } - pub(super) fn decode_from_body(_header: NowHeader, src: &mut ReadCursor<'a>) -> DecodeResult { ensure_fixed_part_size!(in: src); let session_id = src.read_u32(); diff --git a/crates/now-proto-pdu/src/exec/data.rs b/crates/now-proto-pdu/src/exec/data.rs index 9903088..32a3085 100644 --- a/crates/now-proto-pdu/src/exec/data.rs +++ b/crates/now-proto-pdu/src/exec/data.rs @@ -1,7 +1,6 @@ use alloc::borrow::Cow; use bitflags::bitflags; - use ironrdp_core::{ cast_length, ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, WriteCursor, @@ -32,6 +31,7 @@ bitflags! { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NowExecDataStreamKind { Stdin, Stdout, @@ -39,7 +39,7 @@ pub enum NowExecDataStreamKind { } impl NowExecDataStreamKind { - fn to_flags(&self) -> NowExecDataFlags { + fn to_flags(self) -> NowExecDataFlags { match self { NowExecDataStreamKind::Stdin => NowExecDataFlags::STDIN, NowExecDataStreamKind::Stdout => NowExecDataFlags::STDOUT, @@ -109,7 +109,7 @@ impl<'a> NowExecDataMsg<'a> { data: NowVarBuf::new(data)?, }; - msg.ensure_message_size()?; + ensure_now_message_size!(Self::FIXED_PART_SIZE, msg.data.size()); Ok(msg) } @@ -137,14 +137,6 @@ impl<'a> NowExecDataMsg<'a> { Self::FIXED_PART_SIZE + self.data.size() } - fn ensure_message_size(&self) -> EncodeResult<()> { - let _message_size = Self::FIXED_PART_SIZE - .checked_add(self.data.size()) - .ok_or_else(|| invalid_field_err!("size", "message size overflow"))?; - - Ok(()) - } - pub(super) fn decode_from_body(header: NowHeader, src: &mut ReadCursor<'a>) -> DecodeResult { ensure_fixed_part_size!(in: src); diff --git a/crates/now-proto-pdu/src/exec/mod.rs b/crates/now-proto-pdu/src/exec/mod.rs index 02066f5..c97b957 100644 --- a/crates/now-proto-pdu/src/exec/mod.rs +++ b/crates/now-proto-pdu/src/exec/mod.rs @@ -11,21 +11,19 @@ mod shell; mod started; mod win_ps; -use ironrdp_core::{invalid_field_err, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, WriteCursor}; - -pub(crate) use win_ps::NowExecWinPsFlags; - pub use abort::NowExecAbortMsg; pub use batch::{NowExecBatchMsg, OwnedNowExecBatchMsg}; pub use cancel_req::NowExecCancelReqMsg; pub use cancel_rsp::{NowExecCancelRspMsg, OwnedNowExecCancelRspMsg}; -pub use data::{NowExecDataMsg, OwnedNowExecDataMsg}; +pub use data::{NowExecDataMsg, NowExecDataStreamKind, OwnedNowExecDataMsg}; +use ironrdp_core::{invalid_field_err, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, WriteCursor}; pub use process::{NowExecProcessMsg, OwnedNowExecProcessMsg}; pub use pwsh::{NowExecPwshMsg, OwnedNowExecPwshMsg}; pub use result::{NowExecResultMsg, OwnedNowExecResultMsg}; pub use run::{NowExecRunMsg, OwnedNowExecRunMsg}; pub use shell::{NowExecShellMsg, OwnedNowExecShellMsg}; pub use started::NowExecStartedMsg; +pub(crate) use win_ps::NowExecWinPsFlags; pub use win_ps::{ApartmentStateKind, NowExecWinPsMsg, OwnedNowExecWinPsMsg}; use crate::NowHeader; @@ -136,17 +134,17 @@ pub struct NowExecMsgKind(pub u8); impl NowExecMsgKind { /// NOW-PROTO: NOW_EXEC_ABORT_MSG_ID - pub const ABORT: Self = Self(0x00); + pub const ABORT: Self = Self(0x01); /// NOW-PROTO: NOW_EXEC_CANCEL_REQ_MSG_ID - pub const CANCEL_REQ: Self = Self(0x01); + pub const CANCEL_REQ: Self = Self(0x02); /// NOW-PROTO: NOW_EXEC_CANCEL_RSP_MSG_ID - pub const CANCEL_RSP: Self = Self(0x02); + pub const CANCEL_RSP: Self = Self(0x03); /// NOW-PROTO: NOW_EXEC_RESULT_MSG_ID - pub const RESULT: Self = Self(0x03); + pub const RESULT: Self = Self(0x04); /// NOW-PROTO: NOW_EXEC_DATA_MSG_ID - pub const DATA: Self = Self(0x04); + pub const DATA: Self = Self(0x05); /// NOW-PROTO: NOW_EXEC_STARTED_MSG_ID - pub const STARTED: Self = Self(0x05); + pub const STARTED: Self = Self(0x06); /// NOW-PROTO: NOW_EXEC_RUN_MSG_ID pub const RUN: Self = Self(0x10); /// NOW-PROTO: NOW_EXEC_PROCESS_MSG_ID diff --git a/crates/now-proto-pdu/src/exec/process.rs b/crates/now-proto-pdu/src/exec/process.rs index 9e8592d..5f19033 100644 --- a/crates/now-proto-pdu/src/exec/process.rs +++ b/crates/now-proto-pdu/src/exec/process.rs @@ -1,7 +1,6 @@ use alloc::borrow::Cow; use bitflags::bitflags; - use ironrdp_core::{ cast_length, ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, WriteCursor, @@ -90,11 +89,12 @@ impl<'a> NowExecProcessMsg<'a> { } fn ensure_message_size(&self) -> EncodeResult<()> { - let _message_size = Self::FIXED_PART_SIZE - .checked_add(self.filename.size()) - .and_then(|size| size.checked_add(self.parameters.size())) - .and_then(|size| size.checked_add(self.directory.size())) - .ok_or_else(|| invalid_field_err!("size", "message size overflow"))?; + ensure_now_message_size!( + Self::FIXED_PART_SIZE, + self.filename.size(), + self.parameters.size(), + self.directory.size() + ); Ok(()) } diff --git a/crates/now-proto-pdu/src/exec/result.rs b/crates/now-proto-pdu/src/exec/result.rs index 51414f1..42b9fec 100644 --- a/crates/now-proto-pdu/src/exec/result.rs +++ b/crates/now-proto-pdu/src/exec/result.rs @@ -74,9 +74,7 @@ impl<'a> NowExecResultMsg<'a> { } fn ensure_message_size(&self) -> EncodeResult<()> { - let _message_size = Self::FIXED_PART_SIZE - .checked_add(self.status.size()) - .ok_or_else(|| invalid_field_err!("size", "message size overflow"))?; + ensure_now_message_size!(Self::FIXED_PART_SIZE, self.status.size()); Ok(()) } diff --git a/crates/now-proto-pdu/src/exec/run.rs b/crates/now-proto-pdu/src/exec/run.rs index be366f3..1905f1d 100644 --- a/crates/now-proto-pdu/src/exec/run.rs +++ b/crates/now-proto-pdu/src/exec/run.rs @@ -42,19 +42,11 @@ impl<'a> NowExecRunMsg<'a> { command: NowVarStr::new(command)?, }; - msg.ensure_message_size()?; + ensure_now_message_size!(Self::FIXED_PART_SIZE, msg.command.size()); Ok(msg) } - fn ensure_message_size(&self) -> EncodeResult<()> { - let _message_size = Self::FIXED_PART_SIZE - .checked_add(self.command.size()) - .ok_or_else(|| invalid_field_err!("size", "message size overflow"))?; - - Ok(()) - } - pub fn session_id(&self) -> u32 { self.session_id } diff --git a/crates/now-proto-pdu/src/exec/shell.rs b/crates/now-proto-pdu/src/exec/shell.rs index 952c1c6..3de0cc7 100644 --- a/crates/now-proto-pdu/src/exec/shell.rs +++ b/crates/now-proto-pdu/src/exec/shell.rs @@ -1,7 +1,6 @@ use alloc::borrow::Cow; use bitflags::bitflags; - use ironrdp_core::{ cast_length, ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, WriteCursor, @@ -90,11 +89,12 @@ impl<'a> NowExecShellMsg<'a> { } fn ensure_message_size(&self) -> EncodeResult<()> { - let _message_size = Self::FIXED_PART_SIZE - .checked_add(self.command.size()) - .and_then(|size| size.checked_add(self.shell.size())) - .and_then(|size| size.checked_add(self.directory.size())) - .ok_or_else(|| invalid_field_err!("size", "message size overflow"))?; + ensure_now_message_size!( + Self::FIXED_PART_SIZE, + self.command.size(), + self.shell.size(), + self.directory.size() + ); Ok(()) } diff --git a/crates/now-proto-pdu/src/exec/win_ps.rs b/crates/now-proto-pdu/src/exec/win_ps.rs index 02f0d9b..52a77ec 100644 --- a/crates/now-proto-pdu/src/exec/win_ps.rs +++ b/crates/now-proto-pdu/src/exec/win_ps.rs @@ -1,7 +1,6 @@ use alloc::borrow::Cow; use bitflags::bitflags; - use ironrdp_core::{ cast_length, ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, WriteCursor, @@ -55,13 +54,14 @@ bitflags! { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ApartmentStateKind { Sta, Mta, } impl ApartmentStateKind { - pub(crate) fn to_flags(&self) -> NowExecWinPsFlags { + pub(crate) fn to_flags(self) -> NowExecWinPsFlags { match self { ApartmentStateKind::Sta => NowExecWinPsFlags::STA, ApartmentStateKind::Mta => NowExecWinPsFlags::MTA, @@ -188,12 +188,13 @@ impl<'a> NowExecWinPsMsg<'a> { } fn ensure_message_size(&self) -> EncodeResult<()> { - let _message_size = Self::FIXED_PART_SIZE - .checked_add(self.command.size()) - .and_then(|size| size.checked_add(self.directory.size())) - .and_then(|size| size.checked_add(self.execution_policy.size())) - .and_then(|size| size.checked_add(self.configuration_name.size())) - .ok_or_else(|| invalid_field_err!("size", "message size overflow"))?; + ensure_now_message_size!( + Self::FIXED_PART_SIZE, + self.command.size(), + self.directory.size(), + self.execution_policy.size(), + self.configuration_name.size() + ); Ok(()) } diff --git a/crates/now-proto-pdu/src/lib.rs b/crates/now-proto-pdu/src/lib.rs index 3314cb2..6c4837d 100644 --- a/crates/now-proto-pdu/src/lib.rs +++ b/crates/now-proto-pdu/src/lib.rs @@ -7,35 +7,6 @@ extern crate alloc; extern crate ironrdp_core; -/// Asserts that constant expressions evaluate to `true`. -/// -/// From -#[macro_export] -macro_rules! const_assert { - ($x:expr $(,)?) => { - #[allow(unknown_lints, clippy::eq_op)] - const _: [(); 0 - !{ - const ASSERT: bool = $x; - ASSERT - } as usize] = []; - }; -} - -/// Implements additional traits for a borrowing PDU and defines a static-bounded owned version. -#[macro_export] -macro_rules! impl_pdu_borrowing { - ($pdu_ty:ident $(<$($lt:lifetime),+>)?, $owned_ty:ident) => { - pub type $owned_ty = $pdu_ty<'static>; - - impl $crate::ironrdp_core::DecodeOwned for $owned_ty { - fn decode_owned(src: &mut ReadCursor<'_>) -> DecodeResult { - let pdu = <$pdu_ty $(<$($lt),+>)? as $crate::ironrdp_core::Decode>::decode(src)?; - Ok($crate::ironrdp_core::IntoOwned::into_owned(pdu)) - } - } - }; -} - // Ensure that we do not compile on platforms with less than 4 bytes per u32. It is pretty safe // to assume that NOW-PROTO will not ever be used on 8/16-bit MCUs or CPUs. // @@ -52,8 +23,9 @@ mod message; mod session; mod system; -pub use channel::*; pub use core::*; + +pub use channel::*; pub use exec::*; pub use message::*; pub use session::*; diff --git a/crates/now-proto-pdu/src/macros.rs b/crates/now-proto-pdu/src/macros.rs index e1fb25b..e9214d3 100644 --- a/crates/now-proto-pdu/src/macros.rs +++ b/crates/now-proto-pdu/src/macros.rs @@ -12,3 +12,49 @@ macro_rules! unsupported_message_err { unsupported_message_err!(Self::NAME, class: $class, kind: $kind) }}; } + +#[macro_export] +macro_rules! ensure_now_message_size { + ($e:expr) => { + u32::try_from($e).map_err(|_| ironrdp_core::invalid_field_err!("size", "message size overflow"))?; + }; + ($e1:expr, $e2:expr) => { + $e1.checked_add($e2) + .ok_or_else(|| ironrdp_core::invalid_field_err!("size", "message size overflow"))?; + }; + + ($e1:expr, $e2:expr, $($er:expr),+) => { + $e1.checked_add($e2) + $(.and_then(|size| size.checked_add($er)))* + .ok_or_else(|| ironrdp_core::invalid_field_err!("size", "message size overflow"))?; + }; +} + +/// Asserts that constant expressions evaluate to `true`. +/// +/// From +#[macro_export] +macro_rules! const_assert { + ($x:expr $(,)?) => { + #[allow(unknown_lints, clippy::eq_op)] + const _: [(); 0 - !{ + const ASSERT: bool = $x; + ASSERT + } as usize] = []; + }; +} + +/// Implements additional traits for a borrowing PDU and defines a static-bounded owned version. +#[macro_export] +macro_rules! impl_pdu_borrowing { + ($pdu_ty:ident $(<$($lt:lifetime),+>)?, $owned_ty:ident) => { + pub type $owned_ty = $pdu_ty<'static>; + + impl $crate::ironrdp_core::DecodeOwned for $owned_ty { + fn decode_owned(src: &mut ReadCursor<'_>) -> DecodeResult { + let pdu = <$pdu_ty $(<$($lt),+>)? as $crate::ironrdp_core::Decode>::decode(src)?; + Ok($crate::ironrdp_core::IntoOwned::into_owned(pdu)) + } + } + }; +} diff --git a/crates/now-proto-pdu/src/session/msg_box_req.rs b/crates/now-proto-pdu/src/session/msg_box_req.rs index 48da243..5627f6e 100644 --- a/crates/now-proto-pdu/src/session/msg_box_req.rs +++ b/crates/now-proto-pdu/src/session/msg_box_req.rs @@ -1,7 +1,7 @@ use alloc::borrow::Cow; +use core::time; use bitflags::bitflags; - use ironrdp_core::{ cast_length, ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, WriteCursor, @@ -105,17 +105,11 @@ impl<'a> NowSessionMsgBoxReqMsg<'a> { message: NowVarStr::new(message)?, }; - msg.ensure_message_size()?; - Ok(msg) } fn ensure_message_size(&self) -> EncodeResult<()> { - let _message_size = Self::FIXED_PART_SIZE - .checked_add(self.title.size()) - .and_then(|size| size.checked_add(self.message.size())) - .ok_or_else(|| invalid_field_err!("size", "message size overflow"))?; - + ensure_now_message_size!(Self::FIXED_PART_SIZE, self.title.size(), self.message.size()); Ok(()) } @@ -135,11 +129,19 @@ impl<'a> NowSessionMsgBoxReqMsg<'a> { self } - #[must_use] - pub fn with_timeout(mut self, timeout: u32) -> Self { + pub fn with_timeout(mut self, timeout: time::Duration) -> EncodeResult { + // Sanity check: Limit message box timeout to ~1 week. + const MAX_MSGBOX_TINEOUT: time::Duration = time::Duration::from_secs(60 * 60 * 24 * 7); + + if timeout > MAX_MSGBOX_TINEOUT { + return Err(invalid_field_err!("timeout", "too big message box timeout")); + } + + let timeout = u32::try_from(timeout.as_secs()).expect("timeout is within u32 range"); + self.flags |= NowSessionMessageBoxFlags::TIMEOUT; self.timeout = timeout; - self + Ok(self) } #[must_use] @@ -160,9 +162,9 @@ impl<'a> NowSessionMsgBoxReqMsg<'a> { } } - pub fn timeout(&self) -> Option { + pub fn timeout(&self) -> Option { if self.flags.contains(NowSessionMessageBoxFlags::TIMEOUT) && self.timeout > 0 { - Some(self.timeout) + Some(time::Duration::from_secs(self.timeout.into())) } else { None } diff --git a/crates/now-proto-pdu/src/session/msg_box_rsp.rs b/crates/now-proto-pdu/src/session/msg_box_rsp.rs index 183b0e3..695b3eb 100644 --- a/crates/now-proto-pdu/src/session/msg_box_rsp.rs +++ b/crates/now-proto-pdu/src/session/msg_box_rsp.rs @@ -100,12 +100,16 @@ impl<'a> NowSessionMsgBoxRspMsg<'a> { } } - pub fn new_error(request_id: u32, error: impl Into) -> Self { - Self { + pub fn new_error(request_id: u32, error: impl Into) -> EncodeResult { + let msg = Self { request_id, response: NowMsgBoxResponse(0), status: NowStatus::new_error(error), - } + }; + + ensure_now_message_size!(Self::FIXED_PART_SIZE, msg.status.size()); + + Ok(msg) } pub fn request_id(&self) -> u32 { @@ -148,6 +152,8 @@ impl Encode for NowSessionMsgBoxRspMsg<'_> { dst.write_u32(self.request_id); dst.write_u32(self.response.value()); + self.status.encode(dst)?; + Ok(()) } @@ -156,7 +162,7 @@ impl Encode for NowSessionMsgBoxRspMsg<'_> { } fn size(&self) -> usize { - NowHeader::FIXED_PART_SIZE + Self::FIXED_PART_SIZE + NowHeader::FIXED_PART_SIZE + Self::FIXED_PART_SIZE + self.status.size() } } diff --git a/crates/now-proto-pdu/src/system/shutdown.rs b/crates/now-proto-pdu/src/system/shutdown.rs index 176f73f..bb760af 100644 --- a/crates/now-proto-pdu/src/system/shutdown.rs +++ b/crates/now-proto-pdu/src/system/shutdown.rs @@ -1,3 +1,6 @@ +use alloc::borrow::Cow; +use core::time; + use bitflags::bitflags; use ironrdp_core::{ cast_length, ensure_fixed_part_size, invalid_field_err, Decode as _, DecodeResult, Encode, EncodeResult, IntoOwned, @@ -6,7 +9,6 @@ use ironrdp_core::{ use crate::system::NowSystemMessageKind; use crate::{NowHeader, NowMessage, NowMessageClass, NowSystemMessage, NowVarStr}; -use alloc::borrow::Cow; bitflags! { /// NOW_PROTO: NOW_SYSTEM_SHUTDOWN_FLAG_* constants. @@ -53,14 +55,23 @@ impl<'a> NowSystemShutdownMsg<'a> { const NAME: &'static str = "NOW_SYSTEM_SHUTDOWN_MSG"; const FIXED_PART_SIZE: usize = 4 /* u32 timeout */; - pub fn new(timeout: u32, message: impl Into>) -> EncodeResult { + pub fn new(timeout: time::Duration, message: impl Into>) -> EncodeResult { + // Sanity check: Limit shutdown timeout to ~1 year. + const MAX_SHUTDOWN_TINEOUT: time::Duration = time::Duration::from_secs(60 * 60 * 24 * 365); + + if timeout > MAX_SHUTDOWN_TINEOUT { + return Err(invalid_field_err!("timeout", "too big shutdown timeout")); + } + + let timeout = u32::try_from(timeout.as_secs()).expect("timeout is within u32 range"); + let msg = Self { flags: NowSystemShutdownFlags::empty(), timeout, message: NowVarStr::new(message)?, }; - msg.ensure_message_size()?; + ensure_now_message_size!(Self::FIXED_PART_SIZE, msg.message.size()); Ok(msg) } @@ -85,12 +96,12 @@ impl<'a> NowSystemShutdownMsg<'a> { self.flags.contains(NowSystemShutdownFlags::REBOOT) } - fn ensure_message_size(&self) -> EncodeResult<()> { - let _message_size = Self::FIXED_PART_SIZE - .checked_add(self.message.size()) - .ok_or_else(|| invalid_field_err!("size", "message size overflow"))?; + pub fn timeout(&self) -> time::Duration { + time::Duration::from_secs(self.timeout.into()) + } - Ok(()) + pub fn message(&self) -> &str { + &self.message } // LINTS: Overall message size is validated in the constructor/decode method diff --git a/crates/now-proto-testsuite/src/lib.rs b/crates/now-proto-testsuite/src/lib.rs index ce27fed..24d3639 100644 --- a/crates/now-proto-testsuite/src/lib.rs +++ b/crates/now-proto-testsuite/src/lib.rs @@ -6,4 +6,4 @@ #![allow(clippy::cast_sign_loss)] #![allow(unused_crate_dependencies)] -pub mod proto; \ No newline at end of file +pub mod proto; diff --git a/crates/now-proto-testsuite/src/proto/mod.rs b/crates/now-proto-testsuite/src/proto/mod.rs index 979f4d5..307afdc 100644 --- a/crates/now-proto-testsuite/src/proto/mod.rs +++ b/crates/now-proto-testsuite/src/proto/mod.rs @@ -1,18 +1,18 @@ -use now_proto_pdu::NowMessage; - -use ironrdp_core::{Decode as _, ReadCursor}; - use expect_test::Expect; +use ironrdp_core::{Decode, IntoOwned, ReadCursor}; +use now_proto_pdu::NowMessage; -pub fn now_msg_roundtrip(msg: impl Into, expected_bytes: Expect) { +pub fn now_msg_roundtrip(msg: impl Into>, expected_bytes: Expect) -> NowMessage<'static> { let msg = msg.into(); - let buf = ironrdp_core::encode_vec(&msg).unwrap(); + let buf = ironrdp_core::encode_vec(&msg).expect("failed to encode message"); expected_bytes.assert_eq(&format!("{:02X?}", buf)); let mut cursor = ReadCursor::new(&buf); - let decoded = NowMessage::decode(&mut cursor).unwrap(); + let decoded = NowMessage::decode(&mut cursor).expect("failed to decode message"); assert_eq!(msg, decoded); + + decoded.into_owned() } diff --git a/crates/now-proto-testsuite/tests/main.rs b/crates/now-proto-testsuite/tests/main.rs index 21305db..a834799 100644 --- a/crates/now-proto-testsuite/tests/main.rs +++ b/crates/now-proto-testsuite/tests/main.rs @@ -1,4 +1,5 @@ #![allow(unused_crate_dependencies)] // false positives because there is both a library and a binary +#![allow(clippy::unwrap_used)] // allow for tests //! Integration Tests (IT) //! @@ -11,4 +12,4 @@ //! Cargo will run all tests from a single binary in parallel, but //! binaries themselves are run sequentally. -mod proto; \ No newline at end of file +mod proto; diff --git a/crates/now-proto-testsuite/tests/proto/buffer.rs b/crates/now-proto-testsuite/tests/proto/buffer.rs deleted file mode 100644 index 84edae1..0000000 --- a/crates/now-proto-testsuite/tests/proto/buffer.rs +++ /dev/null @@ -1,16 +0,0 @@ -use now_proto_pdu::*; -use rstest::rstest; - -#[rstest] -#[case(b"hello", &[0x05, b'h', b'e', b'l', b'l', b'o'])] -#[case(&[], &[0x00])] -fn now_varbuf_roundtrip(#[case] value: &[u8], #[case] expected_encoded: &[u8]) { - let mut encoded_value = [0u8; 32]; - let encoded_size = ironrdp_core::encode(&NowVarBuf::new(value).unwrap(), &mut encoded_value).unwrap(); - - assert_eq!(encoded_size, expected_encoded.len()); - assert_eq!(&encoded_value[..encoded_size], expected_encoded); - - let decoded_value = ironrdp_core::decode::(&encoded_value).unwrap(); - assert_eq!(decoded_value.value(), value); -} diff --git a/crates/now-proto-testsuite/tests/proto/channel.rs b/crates/now-proto-testsuite/tests/proto/channel.rs new file mode 100644 index 0000000..c3c4660 --- /dev/null +++ b/crates/now-proto-testsuite/tests/proto/channel.rs @@ -0,0 +1,98 @@ +use core::time; + +use expect_test::expect; +use now_proto_pdu::*; +use now_proto_testsuite::proto::now_msg_roundtrip; + +#[test] +fn roundtrip_channel_capset_default() { + let msg = NowChannelCapsetMsg::default(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0E, 00, 00, 00, 10, 01, 00, 00, 01, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00]"], + ); + + let actual = match decoded { + NowMessage::Channel(NowChannelMessage::Capset(msg)) => msg, + _ => panic!("Expected NowChannelCapsetMsg"), + }; + + assert!(actual.system_capset().is_empty()); + assert!(actual.session_capset().is_empty()); + assert!(actual.exec_capset().is_empty()); + assert!(actual.heartbeat_interval().is_none()); +} + +#[test] +fn roundtrip_channel_capset_arbitrary() { + let msg = NowChannelCapsetMsg::default() + .with_exec_capset(NowExecCapsetFlags::STYLE_RUN | NowExecCapsetFlags::STYLE_SHELL) + .with_system_capset(NowSystemCapsetFlags::SHUTDOWN) + .with_session_capset(NowSessionCapsetFlags::MSGBOX) + .with_heartbeat_interval(time::Duration::from_secs(300)) + .unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0E, 00, 00, 00, 10, 01, 01, 00, 01, 00, 00, 00, 01, 00, 04, 00, 05, 00, 2C, 01, 00, 00]"], + ); + + let actual = match decoded { + NowMessage::Channel(NowChannelMessage::Capset(msg)) => msg, + _ => panic!("Expected NowChannelCapsetMsg"), + }; + + assert_eq!(actual.system_capset(), NowSystemCapsetFlags::SHUTDOWN); + assert_eq!(actual.session_capset(), NowSessionCapsetFlags::MSGBOX); + assert_eq!( + actual.exec_capset(), + NowExecCapsetFlags::STYLE_RUN | NowExecCapsetFlags::STYLE_SHELL + ); + assert_eq!(actual.heartbeat_interval(), Some(time::Duration::from_secs(300))); +} + +#[test] +fn roundtrip_channel_heartbeat() { + now_msg_roundtrip( + NowChannelHeartbeatMsg::default(), + expect!["[00, 00, 00, 00, 10, 02, 00, 00]"], + ); +} + +#[test] +fn roundtrip_channel_terminate_normal() { + let msg = NowChannelTerminateMsg::default(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0A, 00, 00, 00, 10, 03, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00]"], + ); + + let actual = match decoded { + NowMessage::Channel(NowChannelMessage::Terminate(msg)) => msg, + _ => panic!("Expected NowChannelTerminateMsg"), + }; + + assert!(actual.to_result().is_ok()); +} + +#[test] +fn roundtrip_channel_terminate_error() { + let msg = NowChannelTerminateMsg::from_error(NowStatusError::from(NowStatusErrorKind::Generic(0))).unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0A, 00, 00, 00, 10, 03, 00, 00, 01, 00, 00, 00, 00, 00, 00, 00, 00, 00]"], + ); + + let actual = match decoded { + NowMessage::Channel(NowChannelMessage::Terminate(msg)) => msg, + _ => panic!("Expected NowChannelTerminateMsg"), + }; + + assert_eq!( + actual.to_result(), + Err(NowStatusError::from(NowStatusErrorKind::Generic(0))) + ); +} diff --git a/crates/now-proto-testsuite/tests/proto/exec.rs b/crates/now-proto-testsuite/tests/proto/exec.rs index a2430b4..67cdad0 100644 --- a/crates/now-proto-testsuite/tests/proto/exec.rs +++ b/crates/now-proto-testsuite/tests/proto/exec.rs @@ -1,126 +1,417 @@ use expect_test::expect; -use now_proto_testsuite::proto::now_msg_roundtrip; use now_proto_pdu::*; +use now_proto_testsuite::proto::now_msg_roundtrip; #[test] -fn roundtrip_exec_capset() { - now_msg_roundtrip( - NowExecCapsetMsg::new(NowExecCapsetFlags::all()), - expect!["[00, 00, 00, 00, 13, 00, FF, 00]"], +fn roundtrip_exec_abort() { + let msg = NowExecAbortMsg::new(0x12345678, 1); + + let decoded = now_msg_roundtrip( + msg, + expect!["[08, 00, 00, 00, 13, 01, 00, 00, 78, 56, 34, 12, 01, 00, 00, 00]"], ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Abort(msg)) => msg, + _ => panic!("Expected NowExecAbortMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.exit_code(), 1); } #[test] -fn roundtrip_exec_abort() { - now_msg_roundtrip( - NowExecAbortMsg::new(0x12345678, NowStatus::new(NowSeverity::Fatal, NowStatusCode::FAILURE)), - expect!["[08, 00, 00, 00, 13, 01, 00, 00, 78, 56, 34, 12, C0, 00, FF, FF]"], +fn roundtrip_exec_cancel_req() { + let msg = NowExecCancelReqMsg::new(0x12345678); + + let decoded = now_msg_roundtrip(msg, expect!["[04, 00, 00, 00, 13, 02, 00, 00, 78, 56, 34, 12]"]); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::CancelReq(msg)) => msg, + _ => panic!("Expected NowExecCancelReqMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); +} + +#[test] +fn roundtrip_exec_cancel_rsp() { + let msg = NowExecCancelRspMsg::new_success(0x12345678); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0E, 00, 00, 00, 13, 03, 00, 00, 78, 56, 34, 12, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00]"], ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::CancelRsp(msg)) => msg, + _ => panic!("Expected NowExecCancelRspMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert!(actual.to_result().is_ok()); } #[test] -fn roundtrip_exec_cancel_req() { - now_msg_roundtrip( - NowExecCancelReqMsg::new(0x12345678), - expect!["[04, 00, 00, 00, 13, 02, 00, 00, 78, 56, 34, 12]"], +fn roundtrip_exec_cancel_rsp_error() { + let msg = NowExecCancelRspMsg::new_error(0x12345678, NowStatusError::new_generic(0xDEADBEEF)).unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0E, 00, 00, 00, 13, 03, 00, 00, 78, 56, 34, 12, 01, 00, 00, 00, EF, BE, AD, DE, 00, 00]"], ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::CancelRsp(msg)) => msg, + _ => panic!("Expected NowExecCancelRspMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.to_result().unwrap_err(), NowStatusError::new_generic(0xDEADBEEF)); } #[test] -fn roundtrip_exec_cancel_rsp() { - now_msg_roundtrip( - NowExecCancelRspMsg::new(0x12345678, NowStatus::new(NowSeverity::Error, NowStatusCode::FAILURE)), - expect!["[08, 00, 00, 00, 13, 03, 00, 00, 78, 56, 34, 12, 80, 00, FF, FF]"], +fn roundtrip_exec_result_success() { + let msg = NowExecResultMsg::new_success(0x12345678, 42); + + let decoded = now_msg_roundtrip( + msg, + expect![ + "[12, 00, 00, 00, 13, 04, 00, 00, 78, 56, 34, 12, 2A, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00]" + ], ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Result(msg)) => msg, + _ => panic!("Expected NowExecResultMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.to_result().unwrap(), 42); } #[test] -fn roundtrip_exec_result() { - now_msg_roundtrip( - NowExecResultMsg::new(0x12345678, NowStatus::new(NowSeverity::Error, NowStatusCode::FAILURE)), - expect!["[08, 00, 00, 00, 13, 04, 00, 00, 78, 56, 34, 12, 80, 00, FF, FF]"], +fn roundtrip_exec_result_error() { + let msg = NowExecResultMsg::new_error( + 0x12345678, + NowStatusError::new_generic(0xDEADBEEF).with_message("ABC").unwrap(), + ) + .unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[15, 00, 00, 00, 13, 04, 00, 00, 78, 56, 34, 12, 00, 00, 00, 00, 03, 00, 00, 00, EF, BE, AD, DE, 03, 41, 42, 43, 00]"], + ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Result(msg)) => msg, + _ => panic!("Expected NowExecResultMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!( + actual.to_result().unwrap_err(), + NowStatusError::new_generic(0xDEADBEEF).with_message("ABC").unwrap() ); } #[test] fn roundtrip_exec_data() { - now_msg_roundtrip( - NowExecDataMsg::new( - NowExecDataFlags::LAST, - 0x12345678, - NowVarBuf::new(vec![0x01, 0x02, 0x03]).unwrap(), - ), - expect!["[08, 00, 00, 00, 13, 05, 02, 00, 78, 56, 34, 12, 03, 01, 02, 03]"], + let msg = NowExecDataMsg::new(0x12345678, NowExecDataStreamKind::Stdout, true, &[0x01, 0x02, 0x03]).unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[08, 00, 00, 00, 13, 05, 05, 00, 78, 56, 34, 12, 03, 01, 02, 03]"], ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Data(msg)) => msg, + _ => panic!("Expected NowExecDataMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.stream_kind().unwrap(), NowExecDataStreamKind::Stdout); + assert!(actual.is_last()); +} + +#[test] +fn roundtrip_exec_data_empty() { + let msg = NowExecDataMsg::new(0x12345678, NowExecDataStreamKind::Stdin, false, &[]).unwrap(); + + let decoded = now_msg_roundtrip(msg, expect!["[05, 00, 00, 00, 13, 05, 02, 00, 78, 56, 34, 12, 00]"]); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Data(msg)) => msg, + _ => panic!("Expected NowExecDataMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.stream_kind().unwrap(), NowExecDataStreamKind::Stdin); + assert!(!actual.is_last()); + assert_eq!(actual.data(), &[]); } #[test] fn roundtrip_exec_run() { - now_msg_roundtrip( - NowExecRunMsg::new(0x1234567, NowVarStr::new("hello".to_owned()).unwrap()).unwrap(), + let msg = NowExecRunMsg::new(0x1234567, "hello").unwrap(); + + let decoded = now_msg_roundtrip( + msg, expect!["[0B, 00, 00, 00, 13, 10, 00, 00, 67, 45, 23, 01, 05, 68, 65, 6C, 6C, 6F, 00]"], ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Run(msg)) => msg, + _ => panic!("Expected NowExecRunMsg"), + }; + + assert_eq!(actual.session_id(), 0x1234567); + assert_eq!(actual.command(), "hello"); } #[test] fn roundtrip_exec_process() { - now_msg_roundtrip( - NowExecProcessMsg::new( - 0x12345678, - NowVarStr::new("a".to_owned()).unwrap(), - NowVarStr::new("b".to_owned()).unwrap(), - NowVarStr::new("c".to_owned()).unwrap(), - ) - .unwrap(), - expect!["[0D, 00, 00, 00, 13, 12, 00, 00, 78, 56, 34, 12, 01, 61, 00, 01, 62, 00, 01, 63, 00]"], + let msg = NowExecProcessMsg::new(0x12345678, "a") + .unwrap() + .with_parameters("b") + .unwrap() + .with_directory("c") + .unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0D, 00, 00, 00, 13, 11, 03, 00, 78, 56, 34, 12, 01, 61, 00, 01, 62, 00, 01, 63, 00]"], + ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Process(msg)) => msg, + _ => panic!("Expected NowExecProcessMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.filename(), "a"); + assert_eq!(actual.parameters().unwrap(), "b"); + assert_eq!(actual.directory().unwrap(), "c"); +} + +#[test] +fn roundtrip_exec_process_simple() { + let msg = NowExecProcessMsg::new(0x12345678, "a").unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0B, 00, 00, 00, 13, 11, 00, 00, 78, 56, 34, 12, 01, 61, 00, 00, 00, 00, 00]"], ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Process(msg)) => msg, + _ => panic!("Expected NowExecProcessMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.filename(), "a"); + assert!(actual.parameters().is_none()); + assert!(actual.directory().is_none()); } #[test] fn roundtrip_exec_shell() { - now_msg_roundtrip( - NowExecShellMsg::new( - 0x12345678, - NowVarStr::new("a".to_owned()).unwrap(), - NowVarStr::new("b".to_owned()).unwrap(), - ) - .unwrap(), - expect!["[0A, 00, 00, 00, 13, 13, 00, 00, 78, 56, 34, 12, 01, 61, 00, 01, 62, 00]"], + let msg = NowExecShellMsg::new(0x12345678, "a") + .unwrap() + .with_shell("b") + .unwrap() + .with_directory("c") + .unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0D, 00, 00, 00, 13, 12, 03, 00, 78, 56, 34, 12, 01, 61, 00, 01, 62, 00, 01, 63, 00]"], ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Shell(msg)) => msg, + _ => panic!("Expected NowExecShellMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.command(), "a"); + assert_eq!(actual.shell().unwrap(), "b"); + assert_eq!(actual.directory().unwrap(), "c"); +} + +#[test] +fn roundtrip_exec_shell_simple() { + let msg = NowExecShellMsg::new(0x12345678, "a").unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0B, 00, 00, 00, 13, 12, 00, 00, 78, 56, 34, 12, 01, 61, 00, 00, 00, 00, 00]"], + ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Shell(msg)) => msg, + _ => panic!("Expected NowExecShellMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.command(), "a"); + assert!(actual.shell().is_none()); + assert!(actual.directory().is_none()); } #[test] fn roundtrip_exec_batch() { - now_msg_roundtrip( - NowExecBatchMsg::new(0x12345678, NowVarStr::new("a".to_owned()).unwrap()), - expect!["[07, 00, 00, 00, 13, 14, 00, 00, 78, 56, 34, 12, 01, 61, 00]"], + let msg = NowExecBatchMsg::new(0x12345678, "a") + .unwrap() + .with_directory("b") + .unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0A, 00, 00, 00, 13, 13, 01, 00, 78, 56, 34, 12, 01, 61, 00, 01, 62, 00]"], + ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Batch(msg)) => msg, + _ => panic!("Expected NowExecBatchMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.command(), "a"); + assert_eq!(actual.directory().unwrap(), "b"); +} + +#[test] +fn roundtrip_exec_batch_simple() { + let msg = NowExecBatchMsg::new(0x12345678, "a").unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[09, 00, 00, 00, 13, 13, 00, 00, 78, 56, 34, 12, 01, 61, 00, 00, 00]"], ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Batch(msg)) => msg, + _ => panic!("Expected NowExecBatchMsg"), + }; + + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.command(), "a"); + assert!(actual.directory().is_none()); } #[test] fn roundtrip_exec_ps() { - now_msg_roundtrip( - NowExecWinPsMsg::new(0x12345678, NowVarStr::new("a".to_owned()).unwrap()) - .unwrap() - .with_flags(NowExecWinPsFlags::NO_PROFILE) - .with_execution_policy(NowVarStr::new("b".to_owned()).unwrap()) - .unwrap() - .with_configuration_name(NowVarStr::new("c".to_owned()).unwrap()) - .unwrap(), - expect!["[0D, 00, 00, 00, 13, 15, D0, 00, 78, 56, 34, 12, 01, 61, 00, 01, 62, 00, 01, 63, 00]"], + let msg = NowExecWinPsMsg::new(0x12345678, "a") + .unwrap() + .with_apartment_state(ApartmentStateKind::Mta) + .set_no_profile() + .set_no_logo() + .with_directory("d") + .unwrap() + .with_execution_policy("b") + .unwrap() + .with_configuration_name("c") + .unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[10, 00, 00, 00, 13, 14, D9, 01, 78, 56, 34, 12, 01, 61, 00, 01, 64, 00, 01, 62, 00, 01, 63, 00]"], ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::WinPs(msg)) => msg, + _ => panic!("Expected NowExecPwshMsg::WinPs"), + }; + + assert!(actual.is_no_profile()); + assert!(actual.is_no_logo()); + assert_eq!(actual.apartment_state().unwrap(), Some(ApartmentStateKind::Mta)); + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.command(), "a"); + assert_eq!(actual.directory().unwrap(), "d"); + assert_eq!(actual.execution_policy().unwrap(), "b"); + assert_eq!(actual.configuration_name().unwrap(), "c"); +} + +#[test] +fn roundtrip_exec_ps_simple() { + let msg = NowExecWinPsMsg::new(0x12345678, "a").unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0D, 00, 00, 00, 13, 14, 00, 00, 78, 56, 34, 12, 01, 61, 00, 00, 00, 00, 00, 00, 00]"], + ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::WinPs(msg)) => msg, + _ => panic!("Expected NowExecPwshMsg::WinPs"), + }; + + assert!(!actual.is_no_profile()); + assert!(!actual.is_no_logo()); + assert!(actual.apartment_state().unwrap().is_none()); + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.command(), "a"); + assert!(actual.directory().is_none()); + assert!(actual.execution_policy().is_none()); + assert!(actual.configuration_name().is_none()); } #[test] fn roundtrip_exec_pwsh() { - now_msg_roundtrip( - NowExecPwshMsg::new(0x12345678, NowVarStr::new("a".to_owned()).unwrap()) - .unwrap() - .with_flags(NowExecWinPsFlags::NO_PROFILE) - .with_execution_policy(NowVarStr::new("b".to_owned()).unwrap()) - .unwrap() - .with_configuration_name(NowVarStr::new("c".to_owned()).unwrap()) - .unwrap(), - expect!["[0D, 00, 00, 00, 13, 16, D0, 00, 78, 56, 34, 12, 01, 61, 00, 01, 62, 00, 01, 63, 00]"], + let msg = NowExecPwshMsg::new(0x12345678, "a") + .unwrap() + .with_apartment_state(ApartmentStateKind::Mta) + .set_no_profile() + .set_no_logo() + .with_directory("d") + .unwrap() + .with_execution_policy("b") + .unwrap() + .with_configuration_name("c") + .unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[10, 00, 00, 00, 13, 15, D9, 01, 78, 56, 34, 12, 01, 61, 00, 01, 64, 00, 01, 62, 00, 01, 63, 00]"], ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Pwsh(msg)) => msg, + _ => panic!("Expected NowExecPwshMsg::Pwsh"), + }; + + assert!(actual.is_no_profile()); + assert!(actual.is_no_logo()); + assert_eq!(actual.apartment_state().unwrap(), Some(ApartmentStateKind::Mta)); + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.command(), "a"); + assert_eq!(actual.directory().unwrap(), "d"); + assert_eq!(actual.execution_policy().unwrap(), "b"); + assert_eq!(actual.configuration_name().unwrap(), "c"); +} + +#[test] +fn roundtrip_exec_pwsh_simple() { + let msg = NowExecPwshMsg::new(0x12345678, "a").unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0D, 00, 00, 00, 13, 15, 00, 00, 78, 56, 34, 12, 01, 61, 00, 00, 00, 00, 00, 00, 00]"], + ); + + let actual = match decoded { + NowMessage::Exec(NowExecMessage::Pwsh(msg)) => msg, + _ => panic!("Expected NowExecPwshMsg::Pwsh"), + }; + + assert!(!actual.is_no_profile()); + assert!(!actual.is_no_logo()); + assert!(actual.apartment_state().unwrap().is_none()); + assert_eq!(actual.session_id(), 0x12345678); + assert_eq!(actual.command(), "a"); + assert!(actual.directory().is_none()); + assert!(actual.execution_policy().is_none()); + assert!(actual.configuration_name().is_none()); } diff --git a/crates/now-proto-testsuite/tests/proto/mod.rs b/crates/now-proto-testsuite/tests/proto/mod.rs index 062e177..48ad78c 100644 --- a/crates/now-proto-testsuite/tests/proto/mod.rs +++ b/crates/now-proto-testsuite/tests/proto/mod.rs @@ -1,7 +1,4 @@ -mod buffer; +mod channel; mod exec; -mod number; mod session; -mod status; -mod string; mod system; diff --git a/crates/now-proto-testsuite/tests/proto/number.rs b/crates/now-proto-testsuite/tests/proto/number.rs deleted file mode 100644 index 20ca91c..0000000 --- a/crates/now-proto-testsuite/tests/proto/number.rs +++ /dev/null @@ -1,24 +0,0 @@ -use now_proto_pdu::*; -use rstest::rstest; - -#[rstest] -#[case(0x00, &[0x00])] -#[case(0x3F, &[0x3F])] -#[case(0x40, &[0x40, 0x40])] -#[case(0x14000, &[0x81, 0x40, 0x00])] -#[case(0x3FFFFFFF, &[0xFF, 0xFF, 0xFF, 0xFF])] -fn var_u32_roundtrip(#[case] value: u32, #[case] expected_encoded: &'static [u8]) { - let mut encoded_value = [0u8; 4]; - let encoded_size = ironrdp_core::encode(&VarU32::new(value).unwrap(), &mut encoded_value).unwrap(); - - assert_eq!(encoded_size, expected_encoded.len()); - assert_eq!(&encoded_value[..encoded_size], expected_encoded); - - let decoded_value = ironrdp_core::decode::(&encoded_value).unwrap(); - assert_eq!(decoded_value.value(), value); -} - -#[test] -fn constructed_var_int_too_large() { - VarU32::new(0x40000000).unwrap_err(); -} diff --git a/crates/now-proto-testsuite/tests/proto/session.rs b/crates/now-proto-testsuite/tests/proto/session.rs index 80a1032..4232ed0 100644 --- a/crates/now-proto-testsuite/tests/proto/session.rs +++ b/crates/now-proto-testsuite/tests/proto/session.rs @@ -1,6 +1,6 @@ use expect_test::expect; -use now_proto_testsuite::proto::now_msg_roundtrip; use now_proto_pdu::*; +use now_proto_testsuite::proto::now_msg_roundtrip; #[test] fn roundtrip_session_lock() { @@ -19,23 +19,101 @@ fn roundtrip_session_logoff() { } #[test] -fn roundtip_session_msgbox_req() { - now_msg_roundtrip( - NowSessionMsgBoxReqMsg::new( - 0x76543210, - NowVarStr::new("hello".to_owned()).unwrap(), - ).unwrap().with_response().with_style(NowMessageBoxStyle::ABORT_RETRY_IGNORE) - .with_title(NowVarStr::new("world".to_owned()).unwrap()) +fn roundtrip_session_msgbox_req() { + let msg = NowSessionMsgBoxReqMsg::new(0x76543210, "hello") + .unwrap() + .with_response() + .with_style(NowMessageBoxStyle::ABORT_RETRY_IGNORE) + .with_title("world") .unwrap() - .with_timeout(3), + .with_timeout(core::time::Duration::from_secs(3)) + .unwrap(); + + let decoded = now_msg_roundtrip( + msg, expect!["[1A, 00, 00, 00, 12, 03, 0F, 00, 10, 32, 54, 76, 02, 00, 00, 00, 03, 00, 00, 00, 05, 77, 6F, 72, 6C, 64, 00, 05, 68, 65, 6C, 6C, 6F, 00]"] ); + + let actual = match decoded { + NowMessage::Session(NowSessionMessage::MsgBoxReq(msg)) => msg, + _ => panic!("Expected NowSessionMsgBoxReqMsg"), + }; + + assert_eq!(actual.request_id(), 0x76543210); + assert_eq!(actual.message(), "hello"); + assert!(actual.is_response_expected()); + assert_eq!(actual.style(), NowMessageBoxStyle::ABORT_RETRY_IGNORE); + assert_eq!(actual.title().unwrap(), "world"); + assert_eq!(actual.timeout().unwrap(), core::time::Duration::from_secs(3)); +} + +#[test] +fn roundtrip_session_msgbox_req_simple() { + let msg = NowSessionMsgBoxReqMsg::new(0x76543210, "hello").unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[15, 00, 00, 00, 12, 03, 00, 00, 10, 32, 54, 76, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 05, 68, 65, 6C, 6C, 6F, 00]"] + ); + + let actual = match decoded { + NowMessage::Session(NowSessionMessage::MsgBoxReq(msg)) => msg, + _ => panic!("Expected NowSessionMsgBoxReqMsg"), + }; + + assert_eq!(actual.request_id(), 0x76543210); + assert_eq!(actual.message(), "hello"); + assert!(!actual.is_response_expected()); + assert_eq!(actual.style(), NowMessageBoxStyle::OK); + assert!(actual.title().is_none()); + assert!(actual.timeout().is_none()); } #[test] fn roundtrip_session_msgbox_rsp() { - now_msg_roundtrip( - NowSessionMsgBoxRspMsg::new(0x01234567, NowMsgBoxResponse::RETRY), - expect!["[08, 00, 00, 00, 12, 04, 00, 00, 67, 45, 23, 01, 04, 00, 00, 00]"], + let msg = NowSessionMsgBoxRspMsg::new_success(0x01234567, NowMsgBoxResponse::RETRY); + + let decoded = now_msg_roundtrip( + msg, + expect![ + "[08, 00, 00, 00, 12, 04, 00, 00, 67, 45, 23, 01, 04, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00]" + ], + ); + + let actual = match decoded { + NowMessage::Session(NowSessionMessage::MsgBoxRsp(msg)) => msg, + _ => panic!("Expected NowSessionMsgBoxRspMsg"), + }; + + assert_eq!(actual.request_id(), 0x01234567); + assert_eq!(actual.to_result().unwrap(), NowMsgBoxResponse::RETRY); +} + +#[test] +fn roundtrip_session_msgbox_rsp_error() { + let msg = NowSessionMsgBoxRspMsg::new_error( + 0x01234567, + NowStatusError::from(NowStatusErrorKind::Now(NowProtoError::NotImplemented)) + .with_message("err") + .unwrap(), + ) + .unwrap(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[08, 00, 00, 00, 12, 04, 00, 00, 67, 45, 23, 01, 00, 00, 00, 00, 03, 00, 01, 00, 07, 00, 00, 00, 03, 65, 72, 72, 00]"], + ); + + let actual = match decoded { + NowMessage::Session(NowSessionMessage::MsgBoxRsp(msg)) => msg, + _ => panic!("Expected NowSessionMsgBoxRspMsg"), + }; + + assert_eq!(actual.request_id(), 0x01234567); + assert_eq!( + actual.to_result().unwrap_err(), + NowStatusError::from(NowStatusErrorKind::Now(NowProtoError::NotImplemented)) + .with_message("err") + .unwrap() ); } diff --git a/crates/now-proto-testsuite/tests/proto/status.rs b/crates/now-proto-testsuite/tests/proto/status.rs deleted file mode 100644 index 4e5de74..0000000 --- a/crates/now-proto-testsuite/tests/proto/status.rs +++ /dev/null @@ -1,20 +0,0 @@ -use ironrdp_core::{Decode as _, Encode as _, ReadCursor, WriteCursor}; -use now_proto_pdu::{NowSeverity, NowStatus, NowStatusCode}; - -#[test] -fn now_status_roundtrip() { - let status = NowStatus::new(NowSeverity::Error, NowStatusCode::FILE_NOT_FOUND) - .with_kind(0x07) - .unwrap(); - - let mut buf = [0; 4]; - let mut cursor = WriteCursor::new(&mut buf); - status.encode(&mut cursor).unwrap(); - - assert_eq!(&buf, &[0x80, 0x07, 0x02, 0x00]); - - let mut cursor = ReadCursor::new(&buf); - let decoded = NowStatus::decode(&mut cursor).unwrap(); - - assert_eq!(status, decoded); -} diff --git a/crates/now-proto-testsuite/tests/proto/string.rs b/crates/now-proto-testsuite/tests/proto/string.rs deleted file mode 100644 index 3848c47..0000000 --- a/crates/now-proto-testsuite/tests/proto/string.rs +++ /dev/null @@ -1,22 +0,0 @@ -use now_proto_pdu::*; -use rstest::rstest; - -#[rstest] -#[case("hello", &[0x05, b'h', b'e', b'l', b'l', b'o', 0x00])] -#[case("", &[0x00, 0x00])] -fn now_varstr_roundtrip(#[case] value: &str, #[case] expected_encoded: &'static [u8]) { - let mut encoded_value = [0u8; 32]; - let encoded_size = ironrdp_core::encode(&NowVarStr::new(value).unwrap(), &mut encoded_value).unwrap(); - - assert_eq!(encoded_size, expected_encoded.len()); - assert_eq!(&encoded_value[..encoded_size], expected_encoded); - - let decoded_value = ironrdp_core::decode::(&encoded_value).unwrap(); - assert_eq!(decoded_value.value(), value); -} - -#[test] -fn decoded_now_varstr_invalid_utf8() { - let encoded = [0x01, 0xFF, 0x00]; - ironrdp_core::decode::(&encoded).unwrap_err(); -} diff --git a/crates/now-proto-testsuite/tests/proto/system.rs b/crates/now-proto-testsuite/tests/proto/system.rs index 0344a8b..1900ddb 100644 --- a/crates/now-proto-testsuite/tests/proto/system.rs +++ b/crates/now-proto-testsuite/tests/proto/system.rs @@ -1,16 +1,22 @@ use expect_test::expect; -use now_proto_testsuite::proto::now_msg_roundtrip; use now_proto_pdu::*; +use now_proto_testsuite::proto::now_msg_roundtrip; #[test] -fn roundtip_system_shutdown() { - now_msg_roundtrip( - NowSystemShutdownMsg::new( - NowSystemShutdownFlags::FORCE, - 0x12345678, - NowVarStr::new("hello".to_owned()).unwrap(), - ) - .unwrap(), - expect!["[0B, 00, 00, 00, 11, 03, 01, 00, 78, 56, 34, 12, 05, 68, 65, 6C, 6C, 6F, 00]"], +fn roundtrip_system_shutdown() { + let msg = NowSystemShutdownMsg::new(core::time::Duration::from_secs(123), "hello") + .unwrap() + .with_force_shutdown(); + + let decoded = now_msg_roundtrip( + msg, + expect!["[0B, 00, 00, 00, 11, 03, 01, 00, 7B, 00, 00, 00, 05, 68, 65, 6C, 6C, 6F, 00]"], ); + + let actual = match decoded { + NowMessage::System(NowSystemMessage::Shutdown(msg)) => msg, + _ => panic!("Expected NowSystemShutdownMsg"), + }; + + assert_eq!(actual.timeout(), core::time::Duration::from_secs(123)); } diff --git a/doc/NOW-spec.md b/doc/NOW-spec.md index 0cec19c..a4abb95 100644 --- a/doc/NOW-spec.md +++ b/doc/NOW-spec.md @@ -784,12 +784,12 @@ The NOW_EXEC_MSG message is used to execute remote commands or scripts. | Value | Meaning | |-------|---------| -| NOW_EXEC_ABORT_MSG_ID
0x00 | NOW_EXEC_ABORT_MSG | -| NOW_EXEC_CANCEL_REQ_MSG_ID
0x01 | NOW_EXEC_CANCEL_REQ_MSG | -| NOW_EXEC_CANCEL_RSP_MSG_ID
0x02 | NOW_EXEC_CANCEL_RSP_MSG | -| NOW_EXEC_RESULT_MSG_ID
0x03 | NOW_EXEC_RESULT_MSG | -| NOW_EXEC_DATA_MSG_ID
0x04 | NOW_EXEC_DATA_MSG | -| NOW_EXEC_STARTED_MSG_ID
0x05 | NOW_EXEC_STARTED_MSG | +| NOW_EXEC_ABORT_MSG_ID
0x01 | NOW_EXEC_ABORT_MSG | +| NOW_EXEC_CANCEL_REQ_MSG_ID
0x02 | NOW_EXEC_CANCEL_REQ_MSG | +| NOW_EXEC_CANCEL_RSP_MSG_ID
0x03 | NOW_EXEC_CANCEL_RSP_MSG | +| NOW_EXEC_RESULT_MSG_ID
0x04 | NOW_EXEC_RESULT_MSG | +| NOW_EXEC_DATA_MSG_ID
0x05 | NOW_EXEC_DATA_MSG | +| NOW_EXEC_STARTED_MSG_ID
0x06 | NOW_EXEC_STARTED_MSG | | NOW_EXEC_RUN_MSG_ID
0x10 | NOW_EXEC_RUN_MSG | | NOW_EXEC_PROCESS_MSG_ID
0x11 | NOW_EXEC_PROCESS_MSG | | NOW_EXEC_SHELL_MSG_ID
0x12 | NOW_EXEC_SHELL_MSG |