vnc/client/connector.rs
1use super::{
2 auth::{AuthHelper, AuthResult, SecurityType},
3 connection::VncClient,
4};
5use std::future::Future;
6use std::pin::Pin;
7use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite};
8use tracing::{info, trace};
9
10use crate::{PixelFormat, VncEncoding, VncError, VncVersion};
11
12pub enum VncState<S, F>
13where
14 S: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static,
15 F: Future<Output = Result<String, VncError>> + Send + Sync + 'static,
16{
17 Handshake(VncConnector<S, F>),
18 Authenticate(VncConnector<S, F>),
19 Connected(VncClient),
20}
21
22impl<S, F> VncState<S, F>
23where
24 S: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static,
25 F: Future<Output = Result<String, VncError>> + Send + Sync + 'static,
26{
27 pub fn try_start(
28 self,
29 ) -> Pin<Box<dyn Future<Output = Result<Self, VncError>> + Send + Sync + 'static>> {
30 Box::pin(async move {
31 match self {
32 VncState::Handshake(mut connector) => {
33 // Read the rfbversion informed by the server
34 let rfbversion = VncVersion::read(&mut connector.stream).await?;
35 trace!(
36 "Our version {:?}, server version {:?}",
37 connector.rfb_version,
38 rfbversion
39 );
40 let rfbversion = if connector.rfb_version < rfbversion {
41 connector.rfb_version
42 } else {
43 rfbversion
44 };
45
46 // Record the negotiated rfbversion
47 connector.rfb_version = rfbversion;
48 trace!("Negotiated rfb version: {:?}", rfbversion);
49 rfbversion.write(&mut connector.stream).await?;
50 Ok(VncState::Authenticate(connector).try_start().await?)
51 }
52 VncState::Authenticate(mut connector) => {
53 let security_types =
54 SecurityType::read(&mut connector.stream, &connector.rfb_version).await?;
55
56 assert!(!security_types.is_empty());
57
58 if security_types.contains(&SecurityType::None) {
59 match connector.rfb_version {
60 VncVersion::RFB33 => {
61 // If the security-type is 1, for no authentication, the server does not
62 // send the SecurityResult message but proceeds directly to the
63 // initialization messages (Section 7.3).
64 info!("No auth needed in vnc3.3");
65 }
66 VncVersion::RFB37 => {
67 // After the security handshake, if the security-type is 1, for no
68 // authentication, the server does not send the SecurityResult message
69 // but proceeds directly to the initialization messages (Section 7.3).
70 info!("No auth needed in vnc3.7");
71 SecurityType::write(&SecurityType::None, &mut connector.stream)
72 .await?;
73 }
74 VncVersion::RFB38 => {
75 info!("No auth needed in vnc3.8");
76 SecurityType::write(&SecurityType::None, &mut connector.stream)
77 .await?;
78 let mut ok = [0; 4];
79 connector.stream.read_exact(&mut ok).await?;
80 }
81 }
82 } else {
83 // choose a auth method
84 if security_types.contains(&SecurityType::VncAuth) {
85 if connector.rfb_version != VncVersion::RFB33 {
86 // In the security handshake (Section 7.1.2), rather than a two-way
87 // negotiation, the server decides the security type and sends a single
88 // word:
89
90 // +--------------+--------------+---------------+
91 // | No. of bytes | Type [Value] | Description |
92 // +--------------+--------------+---------------+
93 // | 4 | U32 | security-type |
94 // +--------------+--------------+---------------+
95
96 // The security-type may only take the value 0, 1, or 2. A value of 0
97 // means that the connection has failed and is followed by a string
98 // giving the reason, as described in Section 7.1.2.
99 SecurityType::write(&SecurityType::VncAuth, &mut connector.stream)
100 .await?;
101 }
102 } else {
103 let msg = "Security type apart from Vnc Auth has not been implemented";
104 return Err(VncError::General(msg.to_owned()));
105 }
106
107 // get password
108 if connector.auth_methond.is_none() {
109 return Err(VncError::NoPassword);
110 }
111
112 let credential = (connector.auth_methond.take().unwrap()).await?;
113
114 // auth
115 let auth = AuthHelper::read(&mut connector.stream, &credential).await?;
116 auth.write(&mut connector.stream).await?;
117 let result = auth.finish(&mut connector.stream).await?;
118 if let AuthResult::Failed = result {
119 if let VncVersion::RFB37 = connector.rfb_version {
120 // In VNC Authentication (Section 7.2.2), if the authentication fails,
121 // the server sends the SecurityResult message, but does not send an
122 // error message before closing the connection.
123 return Err(VncError::WrongPassword);
124 } else {
125 let _ = connector.stream.read_u32().await?;
126 let mut err_msg = String::new();
127 connector.stream.read_to_string(&mut err_msg).await?;
128 return Err(VncError::General(err_msg));
129 }
130 }
131 }
132 info!("auth done, client connected");
133
134 Ok(VncState::Connected(
135 VncClient::new(
136 connector.stream,
137 connector.allow_shared,
138 connector.pixel_format,
139 connector.encodings,
140 )
141 .await?,
142 ))
143 }
144 _ => unreachable!(),
145 }
146 })
147 }
148
149 pub fn finish(self) -> Result<VncClient, VncError> {
150 if let VncState::Connected(client) = self {
151 Ok(client)
152 } else {
153 Err(VncError::ConnectError)
154 }
155 }
156}
157
158/// Connection Builder to setup a vnc client
159pub struct VncConnector<S, F>
160where
161 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
162 F: Future<Output = Result<String, VncError>> + Send + Sync + 'static,
163{
164 stream: S,
165 auth_methond: Option<F>,
166 rfb_version: VncVersion,
167 allow_shared: bool,
168 pixel_format: Option<PixelFormat>,
169 encodings: Vec<VncEncoding>,
170}
171
172impl<S, F> VncConnector<S, F>
173where
174 S: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static,
175 F: Future<Output = Result<String, VncError>> + Send + Sync + 'static,
176{
177 /// To new a vnc client configuration with stream `S`
178 ///
179 /// `S` should implement async I/O methods
180 ///
181 /// ```no_run
182 /// use vnc::{PixelFormat, VncConnector, VncError};
183 /// use tokio::{self, net::TcpStream};
184 ///
185 /// #[tokio::main]
186 /// async fn main() -> Result<(), VncError> {
187 /// let tcp = TcpStream::connect("127.0.0.1:5900").await?;
188 /// let vnc = VncConnector::new(tcp)
189 /// .set_auth_method(async move { Ok("password".to_string()) })
190 /// .add_encoding(vnc::VncEncoding::Tight)
191 /// .add_encoding(vnc::VncEncoding::Zrle)
192 /// .add_encoding(vnc::VncEncoding::CopyRect)
193 /// .add_encoding(vnc::VncEncoding::Raw)
194 /// .allow_shared(true)
195 /// .set_pixel_format(PixelFormat::bgra())
196 /// .build()?
197 /// .try_start()
198 /// .await?
199 /// .finish()?;
200 /// Ok(())
201 /// }
202 /// ```
203 ///
204 pub fn new(stream: S) -> Self {
205 Self {
206 stream,
207 auth_methond: None,
208 allow_shared: true,
209 rfb_version: VncVersion::RFB38,
210 pixel_format: None,
211 encodings: Vec::new(),
212 }
213 }
214
215 /// An async callback which is used to query credentials if the vnc server has set
216 ///
217 /// ```no_compile
218 /// connector = connector.set_auth_method(async move { Ok("password".to_string()) })
219 /// ```
220 ///
221 /// if you're building a wasm app,
222 /// the async callback also allows you to combine it to a promise
223 ///
224 /// ```no_compile
225 /// #[wasm_bindgen]
226 /// extern "C" {
227 /// fn get_password() -> js_sys::Promise;
228 /// }
229 ///
230 /// connector = connector
231 /// .set_auth_method(async move {
232 /// let auth = JsFuture::from(get_password()).await.unwrap();
233 /// Ok(auth.as_string().unwrap())
234 /// });
235 /// ```
236 ///
237 /// While in the js code
238 ///
239 ///
240 /// ```javascript
241 /// var password = '';
242 /// function get_password() {
243 /// return new Promise((reslove, reject) => {
244 /// document.getElementById("submit_password").addEventListener("click", () => {
245 /// password = window.document.getElementById("input_password").value
246 /// reslove(password)
247 /// })
248 /// });
249 /// }
250 /// ```
251 ///
252 /// The future won't be polled if the sever doesn't apply any password protections to the session
253 ///
254 pub fn set_auth_method(mut self, auth_callback: F) -> Self {
255 self.auth_methond = Some(auth_callback);
256 self
257 }
258
259 /// The max vnc version that we supported
260 ///
261 /// Version should be one of the [VncVersion]
262 ///
263 pub fn set_version(mut self, version: VncVersion) -> Self {
264 self.rfb_version = version;
265 self
266 }
267
268 /// Set the rgb order which you will use to resolve the image data
269 ///
270 /// In most of the case, use `PixelFormat::bgra()` on little endian PCs
271 ///
272 /// And use `PixelFormat::rgba()` on wasm apps (with canvas)
273 ///
274 /// Also, customized format is allowed
275 ///
276 /// Will use the default format informed by the vnc server if not set
277 ///
278 /// In this condition, the client will get a [crate::VncEvent::SetPixelFormat] event notified
279 ///
280 pub fn set_pixel_format(mut self, pf: PixelFormat) -> Self {
281 self.pixel_format = Some(pf);
282 self
283 }
284
285 /// Shared-flag is non-zero (true) if the server should try to share the
286 ///
287 /// desktop by leaving other clients connected, and zero (false) if it
288 ///
289 /// should give exclusive access to this client by disconnecting all
290 ///
291 /// other clients.
292 ///
293 pub fn allow_shared(mut self, allow_shared: bool) -> Self {
294 self.allow_shared = allow_shared;
295 self
296 }
297
298 /// Client encodings that we want to use
299 ///
300 /// One of [VncEncoding]
301 ///
302 /// [VncEncoding::Raw] must be sent as the RFC required
303 ///
304 /// The order to add encodings is the order to inform the server
305 ///
306 pub fn add_encoding(mut self, encoding: VncEncoding) -> Self {
307 self.encodings.push(encoding);
308 self
309 }
310
311 /// Complete the client configuration
312 ///
313 pub fn build(self) -> Result<VncState<S, F>, VncError> {
314 if self.encodings.is_empty() {
315 return Err(VncError::NoEncoding);
316 }
317 Ok(VncState::Handshake(self))
318 }
319}