now_proto_pdu/session/
msg_box_req.rs

1use alloc::borrow::Cow;
2use core::time;
3
4use bitflags::bitflags;
5use ironrdp_core::{
6    cast_length, ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned,
7    ReadCursor, WriteCursor,
8};
9
10use crate::{NowHeader, NowMessage, NowMessageClass, NowSessionMessage, NowSessionMessageKind, NowVarStr};
11
12/// Message box style; Directly maps to the WinAPI MessageBox function message box style field.
13///
14/// NOW_PROTO: `style` field from NOW_SESSION_MESSAGE_BOX_REQ_MSG
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct NowMessageBoxStyle(u32);
17
18impl NowMessageBoxStyle {
19    pub const OK: Self = Self(0x00000000);
20    pub const OK_CANCEL: Self = Self(0x00000001);
21    pub const ABORT_RETRY_IGNORE: Self = Self(0x00000002);
22    pub const YES_NO_CANCEL: Self = Self(0x00000003);
23    pub const YES_NO: Self = Self(0x00000004);
24    pub const RETRY_CANCEL: Self = Self(0x00000005);
25    pub const CANCEL_TRY_CONTINUE: Self = Self(0x00000006);
26    pub const HELP: Self = Self(0x00004000);
27
28    pub fn new(style: u32) -> Self {
29        Self(style)
30    }
31
32    pub fn value(&self) -> u32 {
33        self.0
34    }
35}
36
37bitflags! {
38    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
39    pub struct NowSessionMessageBoxFlags: u16 {
40        /// The title field contains non-default value.
41        ///
42        /// NOW_PROTO: NOW_SESSION_MSGBOX_FLAG_TITLE
43        const TITLE = 0x0001;
44
45        /// The style field contains non-default value.
46        ///
47        /// NOW_PROTO: NOW_SESSION_MSGBOX_FLAG_STYLE
48        const STYLE = 0x0002;
49
50        /// The timeout field contains non-default value.
51        ///
52        /// NOW_PROTO: NOW_SESSION_MSGBOX_FLAG_TIMEOUT
53        const TIMEOUT = 0x0004;
54
55        /// A response message is expected (don't fire and forget)
56        ///
57        /// NOW_PROTO: NOW_SESSION_MSGBOX_FLAG_RESPONSE
58        const RESPONSE = 0x0008;
59    }
60}
61
62/// The NOW_SESSION_MSGBOX_REQ_MSG is used to show a message box in the user session, similar to
63/// what the [WTSSendMessage function](https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtssendmessagew)
64/// does.
65///
66/// NOW_PROTO: NOW_SESSION_MSGBOX_REQ_MSG
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct NowSessionMsgBoxReqMsg<'a> {
69    flags: NowSessionMessageBoxFlags,
70    request_id: u32,
71    style: NowMessageBoxStyle,
72    timeout: u32,
73    title: NowVarStr<'a>,
74    message: NowVarStr<'a>,
75}
76
77impl_pdu_borrowing!(NowSessionMsgBoxReqMsg<'_>, OwnedNowSessionMsgBoxReqMsg);
78
79impl IntoOwned for NowSessionMsgBoxReqMsg<'_> {
80    type Owned = OwnedNowSessionMsgBoxReqMsg;
81
82    fn into_owned(self) -> Self::Owned {
83        OwnedNowSessionMsgBoxReqMsg {
84            flags: self.flags,
85            request_id: self.request_id,
86            style: self.style,
87            timeout: self.timeout,
88            title: self.title.into_owned(),
89            message: self.message.into_owned(),
90        }
91    }
92}
93
94impl<'a> NowSessionMsgBoxReqMsg<'a> {
95    const NAME: &'static str = "NOW_SESSION_MSGBOX_REQ_MSG";
96    const FIXED_PART_SIZE: usize = 12;
97
98    pub fn new(request_id: u32, message: impl Into<Cow<'a, str>>) -> EncodeResult<Self> {
99        let msg = Self {
100            flags: NowSessionMessageBoxFlags::empty(),
101            request_id,
102            style: NowMessageBoxStyle::OK,
103            timeout: 0,
104            title: NowVarStr::default(),
105            message: NowVarStr::new(message)?,
106        };
107
108        Ok(msg)
109    }
110
111    fn ensure_message_size(&self) -> EncodeResult<()> {
112        ensure_now_message_size!(Self::FIXED_PART_SIZE, self.title.size(), self.message.size());
113        Ok(())
114    }
115
116    pub fn with_title(mut self, title: impl Into<Cow<'a, str>>) -> EncodeResult<Self> {
117        self.flags |= NowSessionMessageBoxFlags::TITLE;
118        self.title = NowVarStr::new(title)?;
119
120        self.ensure_message_size()?;
121
122        Ok(self)
123    }
124
125    #[must_use]
126    pub fn with_style(mut self, style: NowMessageBoxStyle) -> Self {
127        self.flags |= NowSessionMessageBoxFlags::STYLE;
128        self.style = style;
129        self
130    }
131
132    pub fn with_timeout(mut self, timeout: time::Duration) -> EncodeResult<Self> {
133        // Sanity check: Limit message box timeout to ~1 week.
134        const MAX_MSGBOX_TINEOUT: time::Duration = time::Duration::from_secs(60 * 60 * 24 * 7);
135
136        if timeout > MAX_MSGBOX_TINEOUT {
137            return Err(invalid_field_err!("timeout", "too big message box timeout"));
138        }
139
140        let timeout = u32::try_from(timeout.as_secs()).expect("timeout is within u32 range");
141
142        self.flags |= NowSessionMessageBoxFlags::TIMEOUT;
143        self.timeout = timeout;
144        Ok(self)
145    }
146
147    #[must_use]
148    pub fn with_response(mut self) -> Self {
149        self.flags |= NowSessionMessageBoxFlags::RESPONSE;
150        self
151    }
152
153    pub fn request_id(&self) -> u32 {
154        self.request_id
155    }
156
157    pub fn style(&self) -> NowMessageBoxStyle {
158        if self.flags.contains(NowSessionMessageBoxFlags::STYLE) {
159            self.style
160        } else {
161            NowMessageBoxStyle::OK
162        }
163    }
164
165    pub fn timeout(&self) -> Option<time::Duration> {
166        if self.flags.contains(NowSessionMessageBoxFlags::TIMEOUT) && self.timeout > 0 {
167            Some(time::Duration::from_secs(self.timeout.into()))
168        } else {
169            None
170        }
171    }
172
173    pub fn title(&self) -> Option<&str> {
174        if self.flags.contains(NowSessionMessageBoxFlags::TITLE) {
175            Some(&self.title)
176        } else {
177            None
178        }
179    }
180
181    pub fn message(&self) -> &str {
182        &self.message
183    }
184
185    pub fn is_response_expected(&self) -> bool {
186        self.flags.contains(NowSessionMessageBoxFlags::RESPONSE)
187    }
188
189    // LINTS: Overall message size is validated in the constructor/decode method
190    #[allow(clippy::arithmetic_side_effects)]
191    fn body_size(&self) -> usize {
192        Self::FIXED_PART_SIZE + self.title.size() + self.message.size()
193    }
194
195    pub(super) fn decode_from_body(header: NowHeader, src: &mut ReadCursor<'a>) -> DecodeResult<Self> {
196        ensure_fixed_part_size!(in: src);
197
198        let flags = NowSessionMessageBoxFlags::from_bits_retain(header.flags);
199        let request_id = src.read_u32();
200        let style = NowMessageBoxStyle(src.read_u32());
201        let timeout = src.read_u32();
202        let title = NowVarStr::decode(src)?;
203        let message = NowVarStr::decode(src)?;
204
205        let msg = Self {
206            flags,
207            request_id,
208            style,
209            timeout,
210            title,
211            message,
212        };
213
214        Ok(msg)
215    }
216}
217
218impl Encode for NowSessionMsgBoxReqMsg<'_> {
219    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
220        let header = NowHeader {
221            size: cast_length!("size", self.body_size())?,
222            class: NowMessageClass::SESSION,
223            kind: NowSessionMessageKind::MSGBOX_REQ.0,
224            flags: self.flags.bits(),
225        };
226
227        header.encode(dst)?;
228
229        ensure_fixed_part_size!(in: dst);
230        dst.write_u32(self.request_id);
231        dst.write_u32(self.style.value());
232        dst.write_u32(self.timeout);
233        self.title.encode(dst)?;
234        self.message.encode(dst)?;
235
236        Ok(())
237    }
238
239    fn name(&self) -> &'static str {
240        Self::NAME
241    }
242
243    // LINTS: See body_size()
244    #[allow(clippy::arithmetic_side_effects)]
245    fn size(&self) -> usize {
246        NowHeader::FIXED_PART_SIZE + self.body_size()
247    }
248}
249
250impl<'de> Decode<'de> for NowSessionMsgBoxReqMsg<'de> {
251    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
252        let header = NowHeader::decode(src)?;
253
254        match (header.class, NowSessionMessageKind(header.kind)) {
255            (NowMessageClass::SESSION, NowSessionMessageKind::MSGBOX_REQ) => Self::decode_from_body(header, src),
256            _ => Err(unsupported_message_err!(class: header.class.0, kind: header.kind)),
257        }
258    }
259}
260
261impl<'a> From<NowSessionMsgBoxReqMsg<'a>> for NowMessage<'a> {
262    fn from(val: NowSessionMsgBoxReqMsg<'a>) -> Self {
263        NowMessage::Session(NowSessionMessage::MsgBoxReq(val))
264    }
265}