1use crate::{limits::dimensions, Rect, VncError};
3use tokio::io::{AsyncRead, AsyncReadExt};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct ScreenLayout {
7 pub id: u32,
8 pub x: u16,
9 pub y: u16,
10 pub width: u16,
11 pub height: u16,
12 pub flags: u32,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct DesktopLayout {
17 pub width: u16,
18 pub height: u16,
19 pub screens: Vec<ScreenLayout>,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum DesktopReason {
24 Server,
25 ThisClient,
26 OtherClient,
27 Unknown(u16),
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum DesktopStatus {
32 Success,
33 Prohibited,
34 OutOfResources,
35 InvalidLayout,
36 Forwarded,
37 Unknown(u16),
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct DesktopUpdate {
42 pub reason: DesktopReason,
43 pub status: DesktopStatus,
44 pub layout: Option<DesktopLayout>,
46}
47
48#[derive(Default)]
51pub(crate) struct UpdateBatch {
52 updates: Vec<DesktopUpdate>,
53 retained_bytes: usize,
54}
55
56impl UpdateBatch {
57 const MAX_RETAINED_BYTES: usize = 4 * 1024 * 1024;
58
59 pub(crate) fn push(&mut self, update: DesktopUpdate) -> Result<(), VncError> {
60 let screens = update
61 .layout
62 .as_ref()
63 .map_or(0, |layout| layout.screens.capacity());
64 let bytes = screens
65 .checked_mul(std::mem::size_of::<ScreenLayout>())
66 .and_then(|bytes| bytes.checked_add(std::mem::size_of::<DesktopUpdate>()))
67 .and_then(|bytes| bytes.checked_add(self.retained_bytes))
68 .filter(|bytes| *bytes <= Self::MAX_RETAINED_BYTES)
69 .ok_or(VncError::InvalidImageData)?;
70 self.updates.push(update);
71 self.retained_bytes = bytes;
72 Ok(())
73 }
74
75 pub(crate) fn is_empty(&self) -> bool {
76 self.updates.is_empty()
77 }
78
79 pub(crate) fn into_updates(self) -> Vec<DesktopUpdate> {
80 self.updates
81 }
82}
83
84impl DesktopLayout {
85 pub(crate) fn validate(&self) -> Result<(), VncError> {
86 dimensions(self.width, self.height)?;
87 if self.screens.len() > 255 {
88 return Err(VncError::InvalidImageData);
89 }
90 for (index, s) in self.screens.iter().enumerate() {
91 if s.width == 0
92 || s.height == 0
93 || u32::from(s.x) + u32::from(s.width) > u32::from(self.width)
94 || u32::from(s.y) + u32::from(s.height) > u32::from(self.height)
95 {
96 return Err(VncError::InvalidImageData);
97 }
98 if self.screens[..index].iter().any(|other| other.id == s.id) {
99 return Err(VncError::InvalidImageData);
100 }
101 }
102 Ok(())
103 }
104
105 #[cfg(not(target_arch = "wasm32"))]
106 pub(crate) fn resized(&self, width: u16, height: u16) -> Result<Self, ResizeError> {
107 dimensions(width, height).map_err(|_| ResizeError::InvalidDimensions)?;
108 let [screen] = self.screens.as_slice() else {
109 return Err(ResizeError::UnsupportedLayout);
110 };
111 if screen.x != 0
112 || screen.y != 0
113 || screen.width != self.width
114 || screen.height != self.height
115 {
116 return Err(ResizeError::UnsupportedLayout);
117 }
118 Ok(Self {
119 width,
120 height,
121 screens: vec![ScreenLayout {
122 width,
123 height,
124 ..*screen
125 }],
126 })
127 }
128}
129
130impl DesktopUpdate {
131 pub(crate) async fn read<S: AsyncRead + Unpin>(
132 reader: &mut S,
133 rect: Rect,
134 ) -> Result<Self, VncError> {
135 let count = reader.read_u8().await?;
136 let mut padding = [0; 3];
137 reader.read_exact(&mut padding).await?;
138 let mut screens = Vec::with_capacity(usize::from(count));
139 for _ in 0..count {
140 screens.push(ScreenLayout {
141 id: reader.read_u32().await?,
142 x: reader.read_u16().await?,
143 y: reader.read_u16().await?,
144 width: reader.read_u16().await?,
145 height: reader.read_u16().await?,
146 flags: reader.read_u32().await?,
147 });
148 }
149 let reason = match rect.x {
150 0 => DesktopReason::Server,
151 1 => DesktopReason::ThisClient,
152 2 => DesktopReason::OtherClient,
153 n => DesktopReason::Unknown(n),
154 };
155 let status = if reason != DesktopReason::ThisClient {
156 DesktopStatus::Success
157 } else {
158 match rect.y {
159 0 => DesktopStatus::Success,
160 1 => DesktopStatus::Prohibited,
161 2 => DesktopStatus::OutOfResources,
162 3 => DesktopStatus::InvalidLayout,
163 4 => DesktopStatus::Forwarded,
164 n => DesktopStatus::Unknown(n),
165 }
166 };
167 let layout = if status == DesktopStatus::Success {
168 let layout = DesktopLayout {
169 width: rect.width,
170 height: rect.height,
171 screens,
172 };
173 layout.validate()?;
174 Some(layout)
175 } else {
176 None
177 };
178 Ok(Self {
179 reason,
180 status,
181 layout,
182 })
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
187pub enum ResizeError {
188 #[error("server has not advertised desktop resizing")]
189 Unsupported,
190 #[error("only a single screen covering the desktop can be resized")]
191 UnsupportedLayout,
192 #[error("desktop dimensions exceed decoder limits")]
193 InvalidDimensions,
194 #[error("another resize is pending")]
195 Busy,
196 #[error("resize denied: {0:?}")]
197 Denied(DesktopStatus),
198 #[error("resize could not be dispatched before the deadline")]
199 DispatchTimeout,
200 #[error("resize was not confirmed before the deadline; do not retry blindly")]
201 Timeout,
202 #[error("resize outcome is uncertain; reconnect and observe before retrying")]
203 Uncertain,
204 #[error("connection closed before resize could be dispatched")]
205 Disconnected,
206}