aboutsummaryrefslogtreecommitdiff
path: root/src/protocol/surface_reuse.rs
blob: bba858246b16dd9f528e11e88ae7aa6ae50df410 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! Optional endpoint encoding that retains unchanged terminal cells across projections.

use super::{CellData, PaneSurfaceFrame, ServerMessage};
use serde::{Deserialize, Serialize};

pub(crate) const CAPABILITY: &str = "surface_reuse";
pub(crate) const MESSAGE_KIND: &str = "endpoint.surface-reuse.v1";

#[derive(Serialize, Deserialize)]
struct SurfaceReuse<S> {
    base_surface_revision: u64,
    surface: S,
}

pub(crate) fn message(
    base_surface_revision: u64,
    surface: &mut PaneSurfaceFrame,
) -> serde_json::Result<Option<ServerMessage>> {
    let cells = std::mem::take(&mut surface.frame.cells);
    let data = serde_json::to_string(&SurfaceReuse {
        base_surface_revision,
        surface: &*surface,
    });
    surface.frame.cells = cells;
    let message = ServerMessage::EndpointControl {
        kind: MESSAGE_KIND.into(),
        data: data?,
    };
    // JSON can expand non-cell data (for example escaped hyperlink URLs). A
    // failed compact encoding must fall back, not strand a newer snapshot.
    let size = bincode::serde::encode_into_std_write(
        &message,
        &mut std::io::sink(),
        bincode::config::standard(),
    );
    match size {
        Ok(size) => Ok((size <= super::MAX_FRAME_SIZE).then_some(message)),
        Err(error) => {
            tracing::warn!(%error, "failed to size surface reuse");
            Ok(None)
        }
    }
}

#[derive(Default)]
struct CellBaseline {
    boot_id: String,
    projection_revision: u64,
    surface_revision: u64,
    width: u16,
    height: u16,
    cells: Vec<CellData>,
}

/// Connection-local decoding happens before activation and presentation filtering, so
/// switching endpoints cannot discard a baseline needed by the next wire message.
#[derive(Default)]
pub(crate) struct Decoder {
    baseline: Option<CellBaseline>,
}

impl Decoder {
    pub(crate) fn decode(&mut self, message: ServerMessage) -> Result<ServerMessage, String> {
        let message = match message {
            ServerMessage::EndpointControl { kind, data } if kind == MESSAGE_KIND => {
                let reuse: SurfaceReuse<PaneSurfaceFrame> = serde_json::from_str(&data)
                    .map_err(|error| format!("invalid surface reuse: {error}"))?;
                let Some(base) = &mut self.baseline else {
                    return Err("surface reuse without a baseline".into());
                };
                let mut surface = reuse.surface;
                if base.boot_id != surface.boot_id
                    || base.surface_revision != reuse.base_surface_revision
                    || surface.surface_revision != base.surface_revision.saturating_add(1)
                    || base.width != surface.frame.width
                    || base.height != surface.frame.height
                    || !surface.frame.cells.is_empty()
                {
                    return Err("surface reuse does not match its baseline".into());
                }
                surface.frame.cells.clone_from(&base.cells);
                base.projection_revision = surface.projection_revision;
                base.surface_revision = surface.surface_revision;
                return Ok(ServerMessage::PaneSurface(surface));
            }
            message => message,
        };
        match &message {
            ServerMessage::PaneSurface(surface) => {
                let base = self.baseline.get_or_insert_with(CellBaseline::default);
                base.boot_id.clone_from(&surface.boot_id);
                base.projection_revision = surface.projection_revision;
                base.surface_revision = surface.surface_revision;
                base.width = surface.frame.width;
                base.height = surface.frame.height;
                base.cells.clone_from(&surface.frame.cells);
            }
            ServerMessage::PaneSurfacePatch(patch) => {
                if let Some(base) = &mut self.baseline {
                    if patch.boot_id != base.boot_id
                        || patch.projection_revision != base.projection_revision
                        || patch.base_surface_revision != base.surface_revision
                        || patch.surface_revision != base.surface_revision.saturating_add(1)
                    {
                        self.baseline = None;
                    } else {
                        for row in &patch.rows {
                            let start =
                                usize::from(row.y) * usize::from(base.width) + usize::from(row.x);
                            let end = start.saturating_add(row.cells.len());
                            if row.y >= base.height
                                || usize::from(row.x) + row.cells.len() > usize::from(base.width)
                                || end > base.cells.len()
                            {
                                return Err("surface patch exceeds the cell baseline".into());
                            }
                            base.cells[start..end].clone_from_slice(&row.cells);
                        }
                        base.surface_revision = patch.surface_revision;
                    }
                }
            }
            _ => {}
        }
        Ok(message)
    }
}