1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-27 02:25:28 +03:00
wezterm/codec/src/lib.rs

1010 lines
32 KiB
Rust

//! encode and decode the frames for the mux protocol.
//! The frames include the length of a PDU as well as an identifier
//! that informs us how to decode it. The length, ident and serial
//! number are encoded using a variable length integer encoding.
//! Rather than rely solely on serde to serialize and deserialize an
//! enum, we encode the enum variants with a version/identifier tag
//! for ourselves. This will make it a little easier to manage
//! client and server instances that are built from different versions
//! of this code; in this way the client and server can more gracefully
//! manage unknown enum variants.
#![allow(dead_code)]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::range_plus_one))]
use anyhow::{bail, Context as _, Error};
use leb128;
use mux::domain::DomainId;
use mux::pane::PaneId;
use mux::renderable::{RenderableDimensions, StableCursorPosition};
use mux::tab::{PaneNode, SerdeUrl, SplitDirection, TabId};
use mux::window::WindowId;
use portable_pty::{CommandBuilder, PtySize};
use rangeset::*;
use serde::{Deserialize, Serialize};
use smol::io::AsyncWriteExt;
use smol::prelude::*;
use std::convert::TryInto;
use std::io::Cursor;
use std::ops::Range;
use std::sync::Arc;
use termwiz::hyperlink::Hyperlink;
use termwiz::surface::Line;
use varbincode;
use wezterm_term::color::ColorPalette;
use wezterm_term::{Alert, ClipboardSelection, StableRowIndex};
/// Returns the encoded length of the leb128 representation of value
fn encoded_length(value: u64) -> usize {
struct NullWrite {}
impl std::io::Write for NullWrite {
fn write(&mut self, buf: &[u8]) -> std::result::Result<usize, std::io::Error> {
Ok(buf.len())
}
fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
Ok(())
}
}
leb128::write::unsigned(&mut NullWrite {}, value).unwrap()
}
const COMPRESSED_MASK: u64 = 1 << 63;
fn encode_raw_as_vec(
ident: u64,
serial: u64,
data: &[u8],
is_compressed: bool,
) -> anyhow::Result<Vec<u8>> {
let len = data.len() + encoded_length(ident) + encoded_length(serial);
let masked_len = if is_compressed {
(len as u64) | COMPRESSED_MASK
} else {
len as u64
};
// Double-buffer the data; since we run with nodelay enabled, it is
// desirable for the write to be a single packet (or at least, for
// the header portion to go out in a single packet)
let mut buffer = Vec::with_capacity(len + encoded_length(masked_len));
leb128::write::unsigned(&mut buffer, masked_len).context("writing pdu len")?;
leb128::write::unsigned(&mut buffer, serial).context("writing pdu serial")?;
leb128::write::unsigned(&mut buffer, ident).context("writing pdu ident")?;
buffer.extend_from_slice(data);
if is_compressed {
metrics::histogram!("pdu.encode.compressed.size", buffer.len() as f64);
} else {
metrics::histogram!("pdu.encode.size", buffer.len() as f64);
}
Ok(buffer)
}
/// Encode a frame. If the data is compressed, the high bit of the length
/// is set to indicate that. The data written out has the format:
/// tagged_len: leb128 (u64 msb is set if data is compressed)
/// serial: leb128
/// ident: leb128
/// data bytes
fn encode_raw<W: std::io::Write>(
ident: u64,
serial: u64,
data: &[u8],
is_compressed: bool,
mut w: W,
) -> anyhow::Result<usize> {
let buffer = encode_raw_as_vec(ident, serial, data, is_compressed)?;
w.write_all(&buffer).context("writing pdu data buffer")?;
Ok(buffer.len())
}
async fn encode_raw_async<W: Unpin + AsyncWriteExt>(
ident: u64,
serial: u64,
data: &[u8],
is_compressed: bool,
w: &mut W,
) -> anyhow::Result<usize> {
let buffer = encode_raw_as_vec(ident, serial, data, is_compressed)?;
w.write_all(&buffer)
.await
.context("writing pdu data buffer")?;
Ok(buffer.len())
}
/// Read a single leb128 encoded value from the stream
async fn read_u64_async<R>(r: &mut R) -> anyhow::Result<u64>
where
R: Unpin + AsyncRead + std::fmt::Debug,
{
let mut buf = vec![];
loop {
let mut byte = [0u8];
let nread = r.read(&mut byte).await?;
if nread == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"EOF while reading leb128 encoded value",
)
.into());
}
buf.push(byte[0]);
match leb128::read::unsigned(&mut buf.as_slice()) {
Ok(n) => {
return Ok(n);
}
Err(leb128::read::Error::IoError(_)) => continue,
Err(leb128::read::Error::Overflow) => anyhow::bail!("leb128 is too large"),
}
}
}
/// Read a single leb128 encoded value from the stream
fn read_u64<R: std::io::Read>(mut r: R) -> anyhow::Result<u64> {
leb128::read::unsigned(&mut r)
.map_err(|err| match err {
leb128::read::Error::IoError(ioerr) => anyhow::Error::new(ioerr),
err => anyhow::Error::new(err),
})
.context("reading leb128")
}
#[derive(Debug)]
struct Decoded {
ident: u64,
serial: u64,
data: Vec<u8>,
is_compressed: bool,
}
/// Decode a frame.
/// See encode_raw() for the frame format.
async fn decode_raw_async<R: Unpin + AsyncRead + std::fmt::Debug>(
r: &mut R,
) -> anyhow::Result<Decoded> {
let len = read_u64_async(r).await.context("reading PDU length")?;
let (len, is_compressed) = if (len & COMPRESSED_MASK) != 0 {
(len & !COMPRESSED_MASK, true)
} else {
(len, false)
};
let serial = read_u64_async(r).await.context("reading PDU serial")?;
let ident = read_u64_async(r).await.context("reading PDU ident")?;
let data_len =
match (len as usize).overflowing_sub(encoded_length(ident) + encoded_length(serial)) {
(_, true) => {
anyhow::bail!(
"sizes don't make sense: len:{} serial:{} (enc={}) ident:{} (enc={})",
len,
serial,
encoded_length(serial),
ident,
encoded_length(ident)
);
}
(data_len, false) => data_len,
};
if is_compressed {
metrics::histogram!("pdu.decode.compressed.size", data_len as f64);
} else {
metrics::histogram!("pdu.decode.size", data_len as f64);
}
let mut data = vec![0u8; data_len];
r.read_exact(&mut data).await.with_context(|| {
format!(
"reading {} bytes of data for PDU of length {} with serial={} ident={}",
data_len, len, serial, ident
)
})?;
Ok(Decoded {
ident,
serial,
data,
is_compressed,
})
}
/// Decode a frame.
/// See encode_raw() for the frame format.
fn decode_raw<R: std::io::Read>(mut r: R) -> anyhow::Result<Decoded> {
let len = read_u64(r.by_ref()).context("reading PDU length")?;
let (len, is_compressed) = if (len & COMPRESSED_MASK) != 0 {
(len & !COMPRESSED_MASK, true)
} else {
(len, false)
};
let serial = read_u64(r.by_ref()).context("reading PDU serial")?;
let ident = read_u64(r.by_ref()).context("reading PDU ident")?;
let data_len =
match (len as usize).overflowing_sub(encoded_length(ident) + encoded_length(serial)) {
(_, true) => {
anyhow::bail!(
"sizes don't make sense: len:{} serial:{} (enc={}) ident:{} (enc={})",
len,
serial,
encoded_length(serial),
ident,
encoded_length(ident)
);
}
(data_len, false) => data_len,
};
if is_compressed {
metrics::histogram!("pdu.decode.compressed.size", data_len as f64);
} else {
metrics::histogram!("pdu.decode.size", data_len as f64);
}
let mut data = vec![0u8; data_len];
r.read_exact(&mut data).with_context(|| {
format!(
"reading {} bytes of data for PDU of length {} with serial={} ident={}",
data_len, len, serial, ident
)
})?;
Ok(Decoded {
ident,
serial,
data,
is_compressed,
})
}
#[derive(Debug, PartialEq)]
pub struct DecodedPdu {
pub serial: u64,
pub pdu: Pdu,
}
/// If the serialized size is larger than this, then we'll consider compressing it
const COMPRESS_THRESH: usize = 32;
fn serialize<T: serde::Serialize>(t: &T) -> Result<(Vec<u8>, bool), Error> {
let mut uncompressed = Vec::new();
let mut encode = varbincode::Serializer::new(&mut uncompressed);
t.serialize(&mut encode)?;
if uncompressed.len() <= COMPRESS_THRESH {
return Ok((uncompressed, false));
}
// It's a little heavy; let's try compressing it
let mut compressed = Vec::new();
let mut compress = zstd::Encoder::new(&mut compressed, zstd::DEFAULT_COMPRESSION_LEVEL)?;
let mut encode = varbincode::Serializer::new(&mut compress);
t.serialize(&mut encode)?;
drop(encode);
compress.finish()?;
log::debug!(
"serialized+compress len {} vs {}",
compressed.len(),
uncompressed.len()
);
if compressed.len() < uncompressed.len() {
Ok((compressed, true))
} else {
Ok((uncompressed, false))
}
}
fn deserialize<T: serde::de::DeserializeOwned, R: std::io::Read>(
mut r: R,
is_compressed: bool,
) -> Result<T, Error> {
if is_compressed {
let mut decompress = zstd::Decoder::new(r)?;
let mut decode = varbincode::Deserializer::new(&mut decompress);
serde::Deserialize::deserialize(&mut decode).map_err(Into::into)
} else {
let mut decode = varbincode::Deserializer::new(&mut r);
serde::Deserialize::deserialize(&mut decode).map_err(Into::into)
}
}
macro_rules! pdu {
($( $name:ident:$vers:expr),* $(,)?) => {
#[derive(PartialEq, Debug)]
pub enum Pdu {
Invalid{ident: u64},
$(
$name($name)
,)*
}
impl Pdu {
pub fn encode<W: std::io::Write>(&self, w: W, serial: u64) -> Result<(), Error> {
match self {
Pdu::Invalid{..} => bail!("attempted to serialize Pdu::Invalid"),
$(
Pdu::$name(s) => {
let (data, is_compressed) = serialize(s)?;
let encoded_size = encode_raw($vers, serial, &data, is_compressed, w)?;
metrics::histogram!("pdu.size", encoded_size as f64, "pdu" => stringify!($name));
Ok(())
}
,)*
}
}
pub async fn encode_async<W: Unpin + AsyncWriteExt>(&self, w: &mut W, serial: u64) -> Result<(), Error> {
match self {
Pdu::Invalid{..} => bail!("attempted to serialize Pdu::Invalid"),
$(
Pdu::$name(s) => {
let (data, is_compressed) = serialize(s)?;
let encoded_size = encode_raw_async($vers, serial, &data, is_compressed, w).await?;
metrics::histogram!("pdu.size", encoded_size as f64, "pdu" => stringify!($name));
Ok(())
}
,)*
}
}
pub fn decode<R: std::io::Read>(r: R) -> Result<DecodedPdu, Error> {
let decoded = decode_raw(r).context("decoding a PDU")?;
match decoded.ident {
$(
$vers => {
metrics::histogram!("pdu.size", decoded.data.len() as f64, "pdu" => stringify!($name));
Ok(DecodedPdu {
serial: decoded.serial,
pdu: Pdu::$name(deserialize(decoded.data.as_slice(), decoded.is_compressed)?)
})
}
,)*
_ => {
metrics::histogram!("pdu.size", decoded.data.len() as f64, "pdu" => "??");
Ok(DecodedPdu {
serial: decoded.serial,
pdu: Pdu::Invalid{ident:decoded.ident}
})
}
}
}
pub async fn decode_async<R>(r: &mut R) -> Result<DecodedPdu, Error>
where R: std::marker::Unpin,
R: AsyncRead,
R: std::fmt::Debug
{
let decoded = decode_raw_async(r).await.context("decoding a PDU")?;
match decoded.ident {
$(
$vers => {
metrics::histogram!("pdu.size", decoded.data.len() as f64, "pdu" => stringify!($name));
Ok(DecodedPdu {
serial: decoded.serial,
pdu: Pdu::$name(deserialize(decoded.data.as_slice(), decoded.is_compressed)?)
})
}
,)*
_ => {
metrics::histogram!("pdu.size", decoded.data.len() as f64, "pdu" => "??");
Ok(DecodedPdu {
serial: decoded.serial,
pdu: Pdu::Invalid{ident:decoded.ident}
})
}
}
}
}
}
}
/// The overall version of the codec.
/// This must be bumped when backwards incompatible changes
/// are made to the types and protocol.
pub const CODEC_VERSION: usize = 8;
// Defines the Pdu enum.
// Each struct has an explicit identifying number.
// This allows removal of obsolete structs,
// and defining newer structs as the protocol evolves.
pdu! {
ErrorResponse: 0,
Ping: 1,
Pong: 2,
ListPanes: 3,
ListPanesResponse: 4,
Spawn: 7,
SpawnResponse: 8,
WriteToPane: 9,
UnitResponse: 10,
SendKeyDown: 11,
SendMouseEvent: 12,
SendPaste: 13,
Resize: 14,
SetClipboard: 20,
GetLines: 22,
GetLinesResponse: 23,
GetPaneRenderChanges: 24,
GetPaneRenderChangesResponse: 25,
GetCodecVersion: 26,
GetCodecVersionResponse: 27,
GetTlsCreds: 28,
GetTlsCredsResponse: 29,
LivenessResponse: 30,
SearchScrollbackRequest: 31,
SearchScrollbackResponse: 32,
SetPaneZoomed: 33,
SplitPane: 34,
KillPane: 35,
SpawnV2: 36,
PaneRemoved: 37,
SetPalette: 38,
NotifyAlert: 39,
}
impl Pdu {
pub fn stream_decode(buffer: &mut Vec<u8>) -> anyhow::Result<Option<DecodedPdu>> {
let mut cursor = Cursor::new(buffer.as_slice());
match Self::decode(&mut cursor) {
Ok(decoded) => {
let consumed = cursor.position() as usize;
let remain = buffer.len() - consumed;
// Remove `consumed` bytes from the start of the vec.
// This is safe because the vec is just bytes and we are
// constrained the offsets accordingly.
unsafe {
std::ptr::copy_nonoverlapping(
buffer.as_ptr().add(consumed),
buffer.as_mut_ptr(),
remain,
);
}
buffer.truncate(remain);
Ok(Some(decoded))
}
Err(err) => {
if let Some(ioerr) = err.root_cause().downcast_ref::<std::io::Error>() {
match ioerr.kind() {
std::io::ErrorKind::UnexpectedEof | std::io::ErrorKind::WouldBlock => {
return Ok(None);
}
_ => {}
}
} else {
log::error!("not an ioerror in stream_decode: {:?}", err);
}
Err(err)
}
}
}
pub fn try_read_and_decode<R: std::io::Read>(
r: &mut R,
buffer: &mut Vec<u8>,
) -> anyhow::Result<Option<DecodedPdu>> {
loop {
if let Some(decoded) =
Self::stream_decode(buffer).context("stream_decode of buffer for PDU")?
{
return Ok(Some(decoded));
}
let mut buf = [0u8; 4096];
let size = match r.read(&mut buf) {
Ok(size) => size,
Err(err) => {
if err.kind() == std::io::ErrorKind::WouldBlock {
return Ok(None);
}
return Err(err.into());
}
};
if size == 0 {
return Err(
std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "End Of File").into(),
);
}
buffer.extend_from_slice(&buf[0..size]);
}
}
pub fn pane_id(&self) -> Option<PaneId> {
match self {
Pdu::GetPaneRenderChangesResponse(GetPaneRenderChangesResponse { pane_id, .. })
| Pdu::SetPalette(SetPalette { pane_id, .. })
| Pdu::NotifyAlert(NotifyAlert { pane_id, .. })
| Pdu::SetClipboard(SetClipboard { pane_id, .. })
| Pdu::PaneRemoved(PaneRemoved { pane_id }) => Some(*pane_id),
_ => None,
}
}
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct UnitResponse {}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct ErrorResponse {
pub reason: String,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetCodecVersion {}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetCodecVersionResponse {
pub codec_vers: usize,
pub version_string: String,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct Ping {}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct Pong {}
/// Requests a client certificate to authenticate against
/// the TLS based server
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetTlsCreds {}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetTlsCredsResponse {
/// The signing certificate
pub ca_cert_pem: String,
/// A client authentication certificate and private
/// key, PEM encoded
pub client_cert_pem: String,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct ListPanes {}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct ListPanesResponse {
pub tabs: Vec<PaneNode>,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct Spawn {
pub domain_id: DomainId,
/// If None, create a new window for this new tab
pub window_id: Option<WindowId>,
pub command: Option<CommandBuilder>,
pub command_dir: Option<String>,
pub size: PtySize,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SplitPane {
pub pane_id: PaneId,
pub direction: SplitDirection,
pub command: Option<CommandBuilder>,
pub command_dir: Option<String>,
pub domain: config::keyassignment::SpawnTabDomain,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SpawnV2 {
pub domain: config::keyassignment::SpawnTabDomain,
/// If None, create a new window for this new tab
pub window_id: Option<WindowId>,
pub command: Option<CommandBuilder>,
pub command_dir: Option<String>,
pub size: PtySize,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct PaneRemoved {
pub pane_id: PaneId,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct KillPane {
pub pane_id: PaneId,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SpawnResponse {
pub tab_id: TabId,
pub pane_id: PaneId,
pub window_id: WindowId,
pub size: PtySize,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct WriteToPane {
pub pane_id: PaneId,
pub data: Vec<u8>,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SendPaste {
pub pane_id: PaneId,
pub data: String,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SendKeyDown {
pub pane_id: TabId,
pub event: termwiz::input::KeyEvent,
pub input_serial: InputSerial,
}
/// InputSerial is used to sequence input requests with output events.
/// It started life as a monotonic sequence number but evolved into
/// the number of milliseconds since the unix epoch.
#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]
pub struct InputSerial(u64);
impl InputSerial {
pub const fn empty() -> Self {
Self(0)
}
pub fn now() -> Self {
std::time::SystemTime::now().into()
}
pub fn elapsed_millis(&self) -> u64 {
let now = InputSerial::now();
now.0 - self.0
}
}
impl Into<InputSerial> for std::time::SystemTime {
fn into(self) -> InputSerial {
let duration = self
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime before unix epoch?");
let millis: u64 = duration
.as_millis()
.try_into()
.expect("millisecond count to fit in u64");
InputSerial(millis)
}
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SendMouseEvent {
pub pane_id: PaneId,
pub event: wezterm_term::input::MouseEvent,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SetClipboard {
pub pane_id: PaneId,
pub clipboard: Option<String>,
pub selection: ClipboardSelection,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SetPalette {
pub pane_id: PaneId,
pub palette: ColorPalette,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct NotifyAlert {
pub pane_id: PaneId,
pub alert: Alert,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct Resize {
pub containing_tab_id: TabId,
pub pane_id: PaneId,
pub size: PtySize,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SetPaneZoomed {
pub containing_tab_id: TabId,
pub pane_id: PaneId,
pub zoomed: bool,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetPaneRenderChanges {
pub pane_id: PaneId,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct LivenessResponse {
pub pane_id: PaneId,
pub is_alive: bool,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetPaneRenderChangesResponse {
pub pane_id: PaneId,
pub mouse_grabbed: bool,
pub cursor_position: StableCursorPosition,
pub dimensions: RenderableDimensions,
pub dirty_lines: Vec<Range<StableRowIndex>>,
pub title: String,
pub working_dir: Option<SerdeUrl>,
/// Lines that the server thought we'd almost certainly
/// want to fetch as soon as we received this response
pub bonus_lines: SerializedLines,
pub input_serial: Option<InputSerial>,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetLines {
pub pane_id: PaneId,
pub lines: Vec<Range<StableRowIndex>>,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
struct CellCoordinates {
line_idx: usize,
cols: Range<usize>,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
struct LineHyperlink {
link: Hyperlink,
coords: Vec<CellCoordinates>,
}
/// What's all this?
/// Cells hold references to Arc<Hyperlink> and it is important to us to
/// maintain identity of the hyperlinks in the individual cells, while also
/// only sending a single copy of the associated URL.
/// This section of code extracts the hyperlinks from the cells and builds
/// up a mapping that can be used to restore the identity when the `lines()`
/// method is called.
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SerializedLines {
lines: Vec<(StableRowIndex, Line)>,
hyperlinks: Vec<LineHyperlink>,
// TODO: image references
}
impl SerializedLines {
pub fn lines(self) -> Vec<(StableRowIndex, Line)> {
self.into()
}
}
impl From<Vec<(StableRowIndex, Line)>> for SerializedLines {
fn from(mut lines: Vec<(StableRowIndex, Line)>) -> Self {
let mut hyperlinks = vec![];
for (line_idx, (_, line)) in lines.iter_mut().enumerate() {
let mut current_link: Option<Arc<Hyperlink>> = None;
let mut current_range = 0..0;
for (x, cell) in line
.cells_mut_for_attr_changes_only()
.iter_mut()
.enumerate()
{
// Unset the hyperlink on the cell, if any, and record that
// in the hyperlinks data for later restoration.
if let Some(link) = cell.attrs_mut().hyperlink().map(Arc::clone) {
cell.attrs_mut().set_hyperlink(None);
match current_link.as_ref() {
Some(current) if Arc::ptr_eq(&current, &link) => {
// Continue the current streak
current_range = range_union(current_range, x..x + 1);
}
Some(prior) => {
// It's a different URL, push the current data and start a new one
hyperlinks.push(LineHyperlink {
link: (**prior).clone(),
coords: vec![CellCoordinates {
line_idx,
cols: current_range,
}],
});
current_range = x..x + 1;
current_link = Some(link);
}
None => {
// Starting a new streak
current_range = x..x + 1;
current_link = Some(link);
}
}
} else if let Some(link) = current_link.take() {
// Wrap up a prior streak
hyperlinks.push(LineHyperlink {
link: (*link).clone(),
coords: vec![CellCoordinates {
line_idx,
cols: current_range,
}],
});
current_range = 0..0;
}
// TODO: something smart for image cells
}
if let Some(link) = current_link.take() {
// Wrap up final streak
hyperlinks.push(LineHyperlink {
link: (*link).clone(),
coords: vec![CellCoordinates {
line_idx,
cols: current_range,
}],
});
}
}
Self { lines, hyperlinks }
}
}
/// Reconsitute hyperlinks or other attributes that were decomposed for
/// serialization, and return the line data.
impl Into<Vec<(StableRowIndex, Line)>> for SerializedLines {
fn into(self) -> Vec<(StableRowIndex, Line)> {
if self.hyperlinks.is_empty() {
self.lines
} else {
let mut lines = self.lines;
for link in self.hyperlinks {
let url = Arc::new(link.link);
for coord in link.coords {
if let Some((_, line)) = lines.get_mut(coord.line_idx) {
if let Some(cells) =
line.cells_mut_for_attr_changes_only().get_mut(coord.cols)
{
for cell in cells {
cell.attrs_mut().set_hyperlink(Some(Arc::clone(&url)));
}
}
}
}
}
lines
}
}
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetLinesResponse {
pub pane_id: PaneId,
pub lines: SerializedLines,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SearchScrollbackRequest {
pub pane_id: PaneId,
pub pattern: mux::pane::Pattern,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SearchScrollbackResponse {
pub results: Vec<mux::pane::SearchResult>,
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_frame() {
let mut encoded = Vec::new();
encode_raw(0x81, 0x42, b"hello", false, &mut encoded).unwrap();
assert_eq!(&encoded, b"\x08\x42\x81\x01hello");
let decoded = decode_raw(encoded.as_slice()).unwrap();
assert_eq!(decoded.ident, 0x81);
assert_eq!(decoded.serial, 0x42);
assert_eq!(decoded.data, b"hello");
}
#[test]
fn test_frame_lengths() {
let mut serial = 1;
for target_len in &[128, 247, 256, 65536, 16777216] {
let mut payload = Vec::with_capacity(*target_len);
payload.resize(*target_len, b'a');
let mut encoded = Vec::new();
encode_raw(0x42, serial, payload.as_slice(), false, &mut encoded).unwrap();
let decoded = decode_raw(encoded.as_slice()).unwrap();
assert_eq!(decoded.ident, 0x42);
assert_eq!(decoded.serial, serial);
assert_eq!(decoded.data, payload);
serial += 1;
}
}
#[test]
fn test_pdu_ping() {
let mut encoded = Vec::new();
Pdu::Ping(Ping {}).encode(&mut encoded, 0x40).unwrap();
assert_eq!(&encoded, &[2, 0x40, 1]);
assert_eq!(
DecodedPdu {
serial: 0x40,
pdu: Pdu::Ping(Ping {})
},
Pdu::decode(encoded.as_slice()).unwrap()
);
}
#[test]
fn stream_decode() {
let mut encoded = Vec::new();
Pdu::Ping(Ping {}).encode(&mut encoded, 0x1).unwrap();
Pdu::Pong(Pong {}).encode(&mut encoded, 0x2).unwrap();
assert_eq!(encoded.len(), 6);
let mut cursor = Cursor::new(encoded.as_slice());
let mut read_buffer = Vec::new();
assert_eq!(
Pdu::try_read_and_decode(&mut cursor, &mut read_buffer).unwrap(),
Some(DecodedPdu {
serial: 1,
pdu: Pdu::Ping(Ping {})
})
);
assert_eq!(
Pdu::try_read_and_decode(&mut cursor, &mut read_buffer).unwrap(),
Some(DecodedPdu {
serial: 2,
pdu: Pdu::Pong(Pong {})
})
);
let err = Pdu::try_read_and_decode(&mut cursor, &mut read_buffer).unwrap_err();
assert_eq!(
err.downcast_ref::<std::io::Error>().unwrap().kind(),
std::io::ErrorKind::UnexpectedEof
);
}
#[test]
fn test_pdu_ping_base91() {
let mut encoded = Vec::new();
{
let mut encoder = base91::Base91Encoder::new(&mut encoded);
Pdu::Ping(Ping {}).encode(&mut encoder, 0x41).unwrap();
}
assert_eq!(&encoded, &[60, 67, 75, 65]);
let decoded = base91::decode(&encoded);
assert_eq!(
DecodedPdu {
serial: 0x41,
pdu: Pdu::Ping(Ping {})
},
Pdu::decode(decoded.as_slice()).unwrap()
);
}
#[test]
fn test_pdu_pong() {
let mut encoded = Vec::new();
Pdu::Pong(Pong {}).encode(&mut encoded, 0x42).unwrap();
assert_eq!(&encoded, &[2, 0x42, 2]);
assert_eq!(
DecodedPdu {
serial: 0x42,
pdu: Pdu::Pong(Pong {})
},
Pdu::decode(encoded.as_slice()).unwrap()
);
}
#[test]
fn test_bogus_pdu() {
let mut encoded = Vec::new();
encode_raw(0xdeadbeef, 0x42, b"hello", false, &mut encoded).unwrap();
assert_eq!(
DecodedPdu {
serial: 0x42,
pdu: Pdu::Invalid { ident: 0xdeadbeef }
},
Pdu::decode(encoded.as_slice()).unwrap()
);
}
}