diff options
Diffstat (limited to 'src/protocol/wire.rs')
| -rw-r--r-- | src/protocol/wire.rs | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/src/protocol/wire.rs b/src/protocol/wire.rs index aba166d6..e7fb2743 100644 --- a/src/protocol/wire.rs +++ b/src/protocol/wire.rs @@ -1190,9 +1190,55 @@ pub struct SurfaceGraphicsAssetKey { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SurfaceGraphicsAsset { pub key: SurfaceGraphicsAssetKey, + #[serde( + serialize_with = "serialize_graphics_bytes", + deserialize_with = "deserialize_graphics_bytes" + )] pub data: Vec<u8>, } +fn serialize_graphics_bytes<S: serde::Serializer>( + data: &[u8], + serializer: S, +) -> Result<S::Ok, S::Error> { + // Bincode's byte slice has the same length+bytes layout as Vec<u8>, + // but avoids per-byte serialization. Keep human-readable codecs unchanged. + if serializer.is_human_readable() { + data.serialize(serializer) + } else { + serializer.serialize_bytes(data) + } +} + +fn deserialize_graphics_bytes<'de, D: serde::Deserializer<'de>>( + deserializer: D, +) -> Result<Vec<u8>, D::Error> { + if deserializer.is_human_readable() { + return Vec::<u8>::deserialize(deserializer); + } + + struct BytesVisitor; + impl<'de> serde::de::Visitor<'de> for BytesVisitor { + type Value = Vec<u8>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("image bytes") + } + + fn visit_bytes<E: serde::de::Error>(self, bytes: &[u8]) -> Result<Self::Value, E> { + Ok(bytes.to_vec()) + } + + fn visit_byte_buf<E: serde::de::Error>(self, bytes: Vec<u8>) -> Result<Self::Value, E> { + Ok(bytes) + } + } + + // The framed slice decoder checks the length against available input before + // handing bytes to the visitor, so a forged length cannot cause an allocation. + deserializer.deserialize_bytes(BytesVisitor) +} + /// One already-clipped desired placement relative to its target surface. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bincode::Decode)] pub struct SurfaceGraphicsPlacement { @@ -1733,6 +1779,85 @@ pub fn check_client_version(client_version: u32) -> VersionCheck { #[cfg(test)] mod tests { use super::*; + + #[test] + fn graphics_bulk_codec_preserves_legacy_wire_and_json() { + #[derive(Serialize, Deserialize)] + struct LegacyAsset { + key: SurfaceGraphicsAssetKey, + data: Vec<u8>, + } + for len in [0, 1, 250, 251, 65535, 65536, 800 * 480 * 4] { + let asset = SurfaceGraphicsAsset { + key: SurfaceGraphicsAssetKey { + source: SurfaceGraphicsSource::Terminal { + target: SurfaceGraphicsTarget::Pane { + pane_id: "pane-1".into(), + }, + image_id: 7, + }, + image_width: 800, + image_height: 480, + format: SurfaceGraphicsFormat::Rgba, + data_len: len as u64, + data_fingerprint: 42, + }, + data: (0..len).map(|i| (i % 256) as u8).collect(), + }; + let legacy = LegacyAsset { + key: asset.key.clone(), + data: asset.data.clone(), + }; + let config = bincode::config::standard(); + let before = bincode::serde::encode_to_vec(&legacy, config).unwrap(); + let after = bincode::serde::encode_to_vec(&asset, config).unwrap(); + assert_eq!(after, before); + let (decoded, used): (SurfaceGraphicsAsset, _) = + bincode::serde::decode_from_slice(&before, config).unwrap(); + assert_eq!(decoded, asset); + assert_eq!(used, before.len()); + let (decoded, used): (LegacyAsset, _) = + bincode::serde::decode_from_slice(&after, config).unwrap(); + assert_eq!(decoded.data, asset.data); + assert_eq!(decoded.key, asset.key); + assert_eq!(used, after.len()); + let json = serde_json::to_value(&legacy).unwrap(); + assert_eq!(serde_json::to_value(&asset).unwrap(), json); + assert_eq!( + serde_json::from_value::<SurfaceGraphicsAsset>(json).unwrap(), + asset + ); + assert!( + bincode::serde::decode_from_slice::<SurfaceGraphicsAsset, _>( + &before[..before.len() - 1], + config + ) + .is_err() + ); + } + } + #[test] + fn graphics_bulk_decode_rejects_truncated_and_forged_lengths() { + #[derive(Debug, Deserialize)] + struct Bytes(#[serde(deserialize_with = "deserialize_graphics_bytes")] Vec<u8>); + let config = bincode::config::standard(); + let original: Vec<u8> = (0..=255).collect(); + let encoded = bincode::serde::encode_to_vec(&original, config).unwrap(); + for end in 0..encoded.len() { + assert!( + bincode::serde::decode_from_slice::<Bytes, _>(&encoded[..end], config).is_err() + ); + } + let (decoded, consumed): (Bytes, _) = + bincode::serde::decode_from_slice(&encoded, config).unwrap(); + assert_eq!(decoded.0, original); + assert_eq!(consumed, encoded.len()); + for length in [MAX_GRAPHICS_FRAME_SIZE as u64 + 1, u64::MAX] { + let forged = bincode::serde::encode_to_vec(length, config).unwrap(); + assert!(bincode::serde::decode_from_slice::<Bytes, _>(&forged, config).is_err()); + } + } + use ratatui::style::{Color, Modifier}; use sha2::{Digest, Sha256}; |
