now_proto_pdu/session/
msg_box_req.rs1use 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#[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 const TITLE = 0x0001;
44
45 const STYLE = 0x0002;
49
50 const TIMEOUT = 0x0004;
54
55 const RESPONSE = 0x0008;
59 }
60}
61
62#[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 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 #[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 #[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}