Skip to main content

vnc/client/
connection.rs

1use futures::TryStreamExt;
2use tokio_stream::wrappers::ReceiverStream;
3
4use std::{
5    future::Future,
6    sync::{
7        atomic::{AtomicU32, Ordering},
8        Arc,
9    },
10};
11use tokio::{
12    io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
13    sync::{
14        mpsc::{channel, error::TryRecvError, Receiver, Sender},
15        oneshot, Mutex,
16    },
17};
18use tokio_util::compat::*;
19use tracing::*;
20
21use crate::{codec, PixelFormat, Rect, VncEncoding, VncError, VncEvent, X11Event};
22const NETWORK_CHANNEL_SIZE: usize = 4096;
23const INPUT_CHANNEL_SIZE: usize = 4096;
24const OUTPUT_CHANNEL_SIZE: usize = 2;
25
26mod output;
27
28#[cfg(not(target_arch = "wasm32"))]
29use tokio::spawn;
30#[cfg(target_arch = "wasm32")]
31use wasm_bindgen_futures::spawn_local as spawn;
32
33use super::messages::{ClientMsg, ServerMsg};
34use super::resize::DesktopState;
35
36struct ImageRect {
37    rect: Rect,
38    encoding: VncEncoding,
39}
40
41impl TryFrom<[u8; 12]> for ImageRect {
42    type Error = VncError;
43    fn try_from(buf: [u8; 12]) -> Result<Self, VncError> {
44        Ok(Self {
45            rect: Rect {
46                x: ((buf[0] as u16) << 8) | buf[1] as u16,
47                y: ((buf[2] as u16) << 8) | buf[3] as u16,
48                width: ((buf[4] as u16) << 8) | buf[5] as u16,
49                height: ((buf[6] as u16) << 8) | buf[7] as u16,
50            },
51            encoding: VncEncoding::from_wire(
52                ((buf[8] as u32) << 24)
53                    | ((buf[9] as u32) << 16)
54                    | ((buf[10] as u32) << 8)
55                    | (buf[11] as u32),
56            )?,
57        })
58    }
59}
60
61impl ImageRect {
62    async fn read<S>(reader: &mut S) -> Result<Self, VncError>
63    where
64        S: AsyncRead + Unpin,
65    {
66        let mut rect_buf = [0_u8; 12];
67        reader.read_exact(&mut rect_buf).await?;
68        rect_buf.try_into()
69    }
70}
71
72/// The framebuffer size is shared between the decoder, which learns of resizes,
73/// and the input path, which sizes refresh requests; pack it into one atomic.
74fn pack_screen((width, height): (u16, u16)) -> u32 {
75    (u32::from(width) << 16) | u32::from(height)
76}
77
78fn unpack_screen(packed: u32) -> (u16, u16) {
79    ((packed >> 16) as u16, packed as u16)
80}
81
82struct VncInner {
83    name: String,
84    screen: Arc<AtomicU32>,
85    desktop: Arc<DesktopState>,
86    input_ch: Sender<ClientMsg>,
87    output_ch: Receiver<VncEvent>,
88    decoding_stop: Option<oneshot::Sender<()>>,
89    net_conn_stop: Option<oneshot::Sender<()>>,
90    closed: bool,
91}
92
93/// The instance of a connected vnc client
94///
95impl VncInner {
96    async fn new<S>(
97        mut stream: S,
98        shared: bool,
99        mut pixel_format: Option<PixelFormat>,
100        encodings: Vec<VncEncoding>,
101    ) -> Result<Self, VncError>
102    where
103        S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
104    {
105        let (conn_ch_tx, conn_ch_rx) = channel(NETWORK_CHANNEL_SIZE);
106        let (input_ch_tx, input_ch_rx) = channel(INPUT_CHANNEL_SIZE);
107        let (output_ch_tx, output_ch_rx) = channel(OUTPUT_CHANNEL_SIZE);
108        let (decoding_stop_tx, mut decoding_stop_rx) = oneshot::channel();
109        let (net_conn_stop_tx, net_conn_stop_rx) = oneshot::channel();
110
111        trace!("client init msg");
112        send_client_init(&mut stream, shared).await?;
113
114        trace!("server init msg");
115        let (name, (width, height)) =
116            read_server_init(&mut stream, &mut pixel_format, &|e| async {
117                output_ch_tx.send(e).await?;
118                Ok(())
119            })
120            .await?;
121
122        let screen = Arc::new(AtomicU32::new(pack_screen((width, height))));
123        let decoder_screen = Arc::clone(&screen);
124        let desktop = Arc::new(DesktopState::default());
125        let decoder_desktop = Arc::clone(&desktop);
126        let network_desktop = Arc::clone(&desktop);
127        trace!("client encodings: {:?}", encodings);
128        send_client_encoding(&mut stream, encodings.clone()).await?;
129
130        trace!("Require the first frame");
131        input_ch_tx
132            .send(ClientMsg::FramebufferUpdateRequest(
133                Rect {
134                    x: 0,
135                    y: 0,
136                    width,
137                    height,
138                },
139                0,
140            ))
141            .await?;
142
143        // start the decoding thread
144        spawn(async move {
145            trace!("Decoding thread starts");
146            let mut conn_ch_rx = {
147                let conn_ch_rx = ReceiverStream::new(conn_ch_rx).into_async_read();
148                FuturesAsyncReadCompatExt::compat(conn_ch_rx)
149            };
150
151            let output_func = |e| async {
152                output_ch_tx.send(e).await?;
153                Ok(())
154            };
155
156            let pf = pixel_format.as_ref().unwrap();
157            let result = asycn_vnc_read_loop(
158                &mut conn_ch_rx,
159                pf,
160                &output_func,
161                &mut decoding_stop_rx,
162                &encodings,
163                &decoder_screen,
164                &decoder_desktop,
165            )
166            .await;
167            // Release the network worker and fail any pending resize before waiting
168            // for output capacity.
169            drop(conn_ch_rx);
170            decoder_desktop.close();
171            if let Err(error) = result {
172                output::report_error(error, &output_ch_tx, &mut decoding_stop_rx).await;
173            }
174            trace!("Decoding thread stops");
175        });
176
177        // start the traffic process thread
178        spawn(async move {
179            trace!("Net Connection thread starts");
180            let _ =
181                async_connection_process_loop(stream, input_ch_rx, conn_ch_tx, net_conn_stop_rx)
182                    .await;
183            network_desktop.close();
184            trace!("Net Connection thread stops");
185        });
186
187        info!("VNC Client {name} starts");
188        Ok(Self {
189            name,
190            screen,
191            desktop,
192            input_ch: input_ch_tx,
193            output_ch: output_ch_rx,
194            decoding_stop: Some(decoding_stop_tx),
195            net_conn_stop: Some(net_conn_stop_tx),
196            closed: false,
197        })
198    }
199
200    fn input_message(&self, event: X11Event) -> Result<ClientMsg, VncError> {
201        if self.closed {
202            Err(VncError::ClientNotRunning)
203        } else {
204            let (width, height) = unpack_screen(self.screen.load(Ordering::Acquire));
205            let msg = match event {
206                X11Event::Refresh => ClientMsg::FramebufferUpdateRequest(
207                    Rect {
208                        x: 0,
209                        y: 0,
210                        width,
211                        height,
212                    },
213                    1,
214                ),
215                X11Event::FullRefresh => ClientMsg::FramebufferUpdateRequest(
216                    Rect {
217                        x: 0,
218                        y: 0,
219                        width,
220                        height,
221                    },
222                    0, // non-incremental: server sends entire framebuffer
223                ),
224                X11Event::KeyEvent(key) => ClientMsg::KeyEvent(key.keycode, key.down),
225                X11Event::PointerEvent(mouse) => {
226                    ClientMsg::PointerEvent(mouse.position_x, mouse.position_y, mouse.bottons)
227                }
228                X11Event::CopyText(text) => {
229                    if text.len() > crate::limits::MAX_TEXT {
230                        return Err(VncError::InvalidImageData);
231                    }
232                    ClientMsg::ClientCutText(text)
233                }
234            };
235            Ok(msg)
236        }
237    }
238
239    async fn recv_event(&mut self) -> Result<VncEvent, VncError> {
240        if self.closed {
241            Err(VncError::ClientNotRunning)
242        } else {
243            match self.output_ch.recv().await {
244                Some(e) => Ok(e),
245                None => {
246                    self.closed = true;
247                    Err(VncError::ClientNotRunning)
248                }
249            }
250        }
251    }
252
253    async fn poll_event(&mut self) -> Result<Option<VncEvent>, VncError> {
254        if self.closed {
255            Err(VncError::ClientNotRunning)
256        } else {
257            match self.output_ch.try_recv() {
258                Err(TryRecvError::Disconnected) => {
259                    self.closed = true;
260                    Err(VncError::ClientNotRunning)
261                }
262                Err(TryRecvError::Empty) => Ok(None),
263                Ok(e) => Ok(Some(e)),
264            }
265            // Ok(self.output_ch.recv().await)
266        }
267    }
268
269    /// Stop the VNC engine and release resources
270    ///
271    fn close(&mut self) -> Result<(), VncError> {
272        self.desktop.close();
273        if self.net_conn_stop.is_some() {
274            let net_conn_stop: oneshot::Sender<()> = self.net_conn_stop.take().unwrap();
275            let _ = net_conn_stop.send(());
276        }
277        if self.decoding_stop.is_some() {
278            let decoding_stop = self.decoding_stop.take().unwrap();
279            let _ = decoding_stop.send(());
280        }
281        self.closed = true;
282        Ok(())
283    }
284}
285
286impl Drop for VncInner {
287    fn drop(&mut self) {
288        info!("VNC Client {} stops", self.name);
289        let _ = self.close();
290    }
291}
292
293pub struct VncClient {
294    inner: Arc<Mutex<VncInner>>,
295    desktop: Arc<DesktopState>,
296    input_ch: Sender<ClientMsg>,
297}
298
299impl VncClient {
300    pub(super) async fn new<S>(
301        stream: S,
302        shared: bool,
303        pixel_format: Option<PixelFormat>,
304        encodings: Vec<VncEncoding>,
305    ) -> Result<Self, VncError>
306    where
307        S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
308    {
309        let inner = VncInner::new(stream, shared, pixel_format, encodings).await?;
310        Ok(Self {
311            desktop: Arc::clone(&inner.desktop),
312            input_ch: inner.input_ch.clone(),
313            inner: Arc::new(Mutex::new(inner)),
314        })
315    }
316
317    /// Last validated ExtendedDesktopSize layout; None means support is unconfirmed.
318    pub fn desktop_layout(&self) -> Option<crate::DesktopLayout> {
319        self.desktop.layout()
320    }
321
322    /// Request a negotiated single-screen resize and wait for the server result.
323    /// Available on native targets with a Tokio time driver; wasm supports observation only.
324    /// Continue draining events concurrently. Each queue/confirmation wait is bounded
325    /// to five seconds. Cancellation after dispatch makes later requests uncertain;
326    /// reconnect and observe before retrying. Dispatch includes one incremental one-pixel
327    /// update request so servers can deliver the confirmation.
328    #[cfg(not(target_arch = "wasm32"))]
329    pub async fn resize_desktop(
330        &self,
331        width: u16,
332        height: u16,
333    ) -> Result<crate::DesktopLayout, crate::ResizeError> {
334        self.desktop.request(&self.input_ch, width, height).await
335    }
336
337    /// Input a `X11Event` from the frontend
338    ///
339    pub async fn input(&self, event: X11Event) -> Result<(), VncError> {
340        let sender = {
341            let inner = self.inner.lock().await;
342            if inner.closed {
343                return Err(VncError::ClientNotRunning);
344            }
345            inner.input_ch.clone()
346        };
347        // Do not hold the client mutex while backpressure waits: close needs it.
348        let permit = sender.reserve().await?;
349        let inner = self.inner.lock().await;
350        permit.send(inner.input_message(event)?);
351        Ok(())
352    }
353
354    /// Receive a `VncEvent` from the engine
355    /// This function will block until a `VncEvent` is received
356    ///
357    pub async fn recv_event(&self) -> Result<VncEvent, VncError> {
358        self.inner.lock().await.recv_event().await
359    }
360
361    /// polling `VncEvent` from the engine and give it to the client
362    ///
363    pub async fn poll_event(&self) -> Result<Option<VncEvent>, VncError> {
364        self.inner.lock().await.poll_event().await
365    }
366
367    /// Stop the VNC engine and release resources
368    ///
369    pub async fn close(&self) -> Result<(), VncError> {
370        self.inner.lock().await.close()
371    }
372}
373
374impl Clone for VncClient {
375    fn clone(&self) -> Self {
376        Self {
377            inner: self.inner.clone(),
378            desktop: Arc::clone(&self.desktop),
379            input_ch: self.input_ch.clone(),
380        }
381    }
382}
383
384async fn send_client_init<S>(stream: &mut S, shared: bool) -> Result<(), VncError>
385where
386    S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
387{
388    trace!("Send shared flag: {}", shared);
389    stream.write_u8(shared as u8).await?;
390    Ok(())
391}
392
393async fn read_server_init<S, F, Fut>(
394    stream: &mut S,
395    pf: &mut Option<PixelFormat>,
396    output_func: &F,
397) -> Result<(String, (u16, u16)), VncError>
398where
399    S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
400    F: Fn(VncEvent) -> Fut,
401    Fut: Future<Output = Result<(), VncError>>,
402{
403    // +--------------+--------------+------------------------------+
404    // | No. of bytes | Type [Value] | Description                  |
405    // +--------------+--------------+------------------------------+
406    // | 2            | U16          | framebuffer-width in pixels  |
407    // | 2            | U16          | framebuffer-height in pixels |
408    // | 16           | PIXEL_FORMAT | server-pixel-format          |
409    // | 4            | U32          | name-length                  |
410    // | name-length  | U8 array     | name-string                  |
411    // +--------------+--------------+------------------------------+
412
413    let screen_width = stream.read_u16().await?;
414    let screen_height = stream.read_u16().await?;
415    crate::limits::dimensions(screen_width, screen_height)?;
416    let mut send_our_pf = false;
417
418    output_func(VncEvent::SetResolution(
419        (screen_width, screen_height).into(),
420    ))
421    .await?;
422
423    let pixel_format = PixelFormat::read(stream).await?;
424    if pf.is_none() {
425        output_func(VncEvent::SetPixelFormat(pixel_format)).await?;
426        let _ = pf.insert(pixel_format);
427    } else {
428        send_our_pf = true;
429    }
430
431    let name = crate::limits::string(stream, crate::limits::MAX_NAME).await?;
432
433    if send_our_pf {
434        trace!("Send customized pixel format {:#?}", pf);
435        ClientMsg::SetPixelFormat(*pf.as_ref().unwrap())
436            .write(stream)
437            .await?;
438    }
439    Ok((name, (screen_width, screen_height)))
440}
441
442async fn send_client_encoding<S>(
443    stream: &mut S,
444    encodings: Vec<VncEncoding>,
445) -> Result<(), VncError>
446where
447    S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
448{
449    ClientMsg::SetEncodings(encodings).write(stream).await?;
450    Ok(())
451}
452
453async fn asycn_vnc_read_loop<S, F, Fut>(
454    stream: &mut S,
455    pf: &PixelFormat,
456    output_func: &F,
457    stop_ch: &mut oneshot::Receiver<()>,
458    encodings: &[VncEncoding],
459    screen: &AtomicU32,
460    desktop: &DesktopState,
461) -> Result<(), VncError>
462where
463    S: AsyncRead + Unpin,
464    F: Fn(VncEvent) -> Fut,
465    Fut: Future<Output = Result<(), VncError>>,
466{
467    tokio::select! {
468        biased;
469        _ = stop_ch => Ok(()),
470        result = read_vnc_messages(stream, pf, output_func, encodings, screen, desktop) => result,
471    }
472}
473
474async fn read_vnc_messages<S, F, Fut>(
475    stream: &mut S,
476    pf: &PixelFormat,
477    output_func: &F,
478    encodings: &[VncEncoding],
479    shared_screen: &AtomicU32,
480    desktop: &DesktopState,
481) -> Result<(), VncError>
482where
483    S: AsyncRead + Unpin,
484    F: Fn(VncEvent) -> Fut,
485    Fut: Future<Output = Result<(), VncError>>,
486{
487    let mut raw_decoder = codec::RawDecoder::new();
488    let mut zrle_decoder = codec::ZrleDecoder::new();
489    let mut tight_decoder = codec::TightDecoder::new();
490    let mut trle_decoder = codec::TrleDecoder::new();
491    let mut cursor = codec::CursorDecoder::new();
492    let mut screen = unpack_screen(shared_screen.load(Ordering::Acquire));
493
494    // main decoding loop
495    loop {
496        let server_msg = ServerMsg::read(stream).await?;
497        trace!("Server message got: {:?}", server_msg);
498        match server_msg {
499            ServerMsg::FramebufferUpdate(rect_num) => {
500                let mut updates = crate::desktop::UpdateBatch::default();
501                let mut framebuffer_changed = false;
502                for _ in 0..rect_num {
503                    let rect = ImageRect::read(stream).await?;
504                    if rect.encoding != VncEncoding::Raw && !encodings.contains(&rect.encoding) {
505                        return Err(VncError::InvalidImageData);
506                    }
507                    if !matches!(
508                        rect.encoding,
509                        VncEncoding::DesktopSizePseudo
510                            | VncEncoding::ExtendedDesktopSizePseudo
511                            | VncEncoding::LastRectPseudo
512                            | VncEncoding::CursorPseudo
513                    ) {
514                        crate::limits::rectangle(&rect.rect, screen)?;
515                    }
516                    if !matches!(
517                        rect.encoding,
518                        VncEncoding::CursorPseudo
519                            | VncEncoding::ExtendedDesktopSizePseudo
520                            | VncEncoding::LastRectPseudo
521                    ) {
522                        if !updates.is_empty() {
523                            return Err(VncError::InvalidImageData);
524                        }
525                        framebuffer_changed = true;
526                    }
527
528                    match rect.encoding {
529                        VncEncoding::Raw => {
530                            raw_decoder
531                                .decode(pf, &rect.rect, stream, output_func)
532                                .await?;
533                        }
534                        VncEncoding::CopyRect => {
535                            let source_x = stream.read_u16().await?;
536                            let source_y = stream.read_u16().await?;
537                            let mut src_rect = rect.rect;
538                            src_rect.x = source_x;
539                            src_rect.y = source_y;
540                            crate::limits::rectangle(&src_rect, screen)?;
541                            output_func(VncEvent::Copy(rect.rect, src_rect)).await?;
542                        }
543                        VncEncoding::Tight => {
544                            tight_decoder
545                                .decode(pf, &rect.rect, stream, output_func)
546                                .await?;
547                        }
548                        VncEncoding::Trle => {
549                            trle_decoder
550                                .decode(pf, &rect.rect, stream, output_func)
551                                .await?;
552                        }
553                        VncEncoding::Zrle => {
554                            zrle_decoder
555                                .decode(pf, &rect.rect, stream, output_func)
556                                .await?;
557                        }
558                        VncEncoding::CursorPseudo => {
559                            cursor.decode(pf, &rect.rect, stream, output_func).await?;
560                        }
561                        VncEncoding::ExtendedDesktopSizePseudo => {
562                            // Confirm only after the entire message excludes framebuffer changes.
563                            // Cursor metadata may accompany desktop layout updates.
564                            if framebuffer_changed {
565                                return Err(VncError::InvalidImageData);
566                            }
567                            updates.push(crate::DesktopUpdate::read(stream, rect.rect).await?)?;
568                        }
569                        VncEncoding::DesktopSizePseudo => {
570                            crate::limits::dimensions(rect.rect.width, rect.rect.height)?;
571                            if rect.rect.x != 0 || rect.rect.y != 0 {
572                                return Err(VncError::InvalidImageData);
573                            }
574                            screen = (rect.rect.width, rect.rect.height);
575                            shared_screen.store(pack_screen(screen), Ordering::Release);
576                            desktop.legacy_resize();
577                            output_func(VncEvent::SetResolution(
578                                (rect.rect.width, rect.rect.height).into(),
579                            ))
580                            .await?;
581                        }
582                        VncEncoding::LastRectPseudo => {
583                            break;
584                        }
585                    }
586                }
587                for update in updates.into_updates() {
588                    if let Some(layout) = &update.layout {
589                        screen = (layout.width, layout.height);
590                        shared_screen.store(pack_screen(screen), Ordering::Release);
591                    }
592                    desktop.observe(&update);
593                    output_func(VncEvent::DesktopUpdate(update)).await?;
594                }
595            }
596            // SetColorMapEntries,
597            ServerMsg::Bell => {
598                output_func(VncEvent::Bell).await?;
599            }
600            ServerMsg::ServerCutText(text) => {
601                output_func(VncEvent::Text(text)).await?;
602            }
603        }
604    }
605}
606
607async fn async_connection_process_loop<S>(
608    mut stream: S,
609    mut input_ch: Receiver<ClientMsg>,
610    conn_ch: Sender<std::io::Result<Vec<u8>>>,
611    mut stop_ch: oneshot::Receiver<()>,
612) -> Result<(), VncError>
613where
614    S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
615{
616    let mut buffer = [0; 65535];
617    let mut pending = 0;
618
619    loop {
620        tokio::select! {
621            _ = &mut stop_ch => break,
622            _ = conn_ch.closed() => break,
623            permit = conn_ch.reserve(), if pending > 0 => {
624                match permit {
625                    Ok(permit) => {
626                        permit.send(Ok(buffer[..pending].to_vec()));
627                        pending = 0;
628                    }
629                    Err(_) => break,
630                }
631            }
632            result = stream.read(&mut buffer), if pending == 0 => {
633                match result {
634                    Ok(0) | Err(_) => break,
635                    Ok(length) => pending = length,
636                }
637            }
638            message = input_ch.recv() => {
639                let Some(message) = message else { break; };
640                tokio::select! {
641                    biased;
642                    _ = &mut stop_ch => break,
643                    result = message.write(&mut stream) => result?,
644                }
645            }
646        }
647    }
648    // Dropping the bridge signals EOF without blocking shutdown on a full queue.
649    Ok(())
650}
651
652#[cfg(test)]
653mod tests;
654
655#[cfg(test)]
656mod queue_tests;
657
658#[cfg(test)]
659mod resize_tests;