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}