use crate::schema::{
AssetKind, ColorScheme, DesignSystem, Key, MAX_MODIFIER_WORDS, MaterialRole, MessageDuration,
Modifier, MotionRole, NotificationImportance, NotificationPermission, NotificationPresentation,
Paint, PropertyKind, Selection, ShapeRole, SpaceRole, TYPE_ROLE_COUNT, Theme, WidgetKind,
modifier_extra_words,
};
use core::fmt;
const TAG_ENVELOPE: u16 = 0;
const TAG_CREATE: u16 = 1;
const TAG_SET_PROP: u16 = 2;
const TAG_SET_MODIFIER: u16 = 3;
const TAG_INSERT: u16 = 4;
const TAG_MOVE: u16 = 5;
const TAG_REMOVE: u16 = 6;
const TAG_SET_TEXT: u16 = 7;
const TAG_APPEND_TEXT: u16 = 8;
const TAG_SET_THEME: u16 = 9;
const TAG_REGISTER_ASSET: u16 = 10;
const TAG_RELEASE_ASSET: u16 = 11;
const TAG_SHOW_MESSAGE: u16 = 12;
const TAG_SET_WINDOW: u16 = 13;
const TAG_POST_NOTIFICATION: u16 = 14;
const TAG_WITHDRAW_NOTIFICATION: u16 = 15;
const TAG_REQUEST_NOTIFICATION_PERMISSION: u16 = 16;
const TAG_EDIT_CODE: u16 = 17;
const TAG_START_ANIMATION: u16 = 18;
const TAG_CONTROL_ANIMATION: u16 = 19;
const TAG_SET_SELECTION: u16 = 20;
const TAG_REVEAL_RANGE: u16 = 21;
const TAG_SET_FOLDED: u16 = 22;
const ANIMATION_HEADER_LEN: usize = 52;
const KEYFRAME_HEAD_LEN: usize = 24;
const TRANSFORM_FUNCTION_LEN: usize = 28;
const KEYFRAME_FROM_PRESENTED: u16 = 1;
const ENVELOPE_LEN: usize = 12;
pub const THEME_PAYLOAD_LEN: usize = 8 + 4 * TYPE_ROLE_COUNT + 8;
const MIN_RECORD_LEN: usize = 4;
const VALUE_NONE: u16 = 0;
const VALUE_STRING: u16 = 1;
const VALUE_BOOL: u16 = 2;
const VALUE_I64: u16 = 3;
const VALUE_F32: u16 = 4;
const VALUE_BYTES: u16 = 5;
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyValue<'a> {
None,
String(&'a str),
Bool(bool),
Integer(i64),
Float(f32),
Bytes(&'a [u8]),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Mutation<'a> {
Create {
node_id: u32,
widget: WidgetKind,
},
SetProp {
node_id: u32,
property: PropertyKind,
value: PropertyValue<'a>,
},
SetModifier {
node_id: u32,
index: u16,
modifier: Modifier,
},
Insert {
parent_id: u32,
node_id: u32,
index: u32,
},
Move {
parent_id: u32,
node_id: u32,
index: u32,
},
Remove {
node_id: u32,
},
SetText {
node_id: u32,
text: &'a str,
selection: Option<Selection>,
},
AppendText {
node_id: u32,
text: &'a str,
},
SetTheme(Theme),
SetWindow(crate::schema::Window),
RegisterAsset {
asset_id: u32,
kind: AssetKind,
bytes: &'a [u8],
},
ReleaseAsset {
asset_id: u32,
},
ShowMessage {
handler_id: u64,
text: &'a str,
action: &'a str,
duration: MessageDuration,
},
PostNotification {
key: &'a str,
title: &'a str,
body: &'a str,
channel: &'a str,
action_1: &'a str,
action_2: &'a str,
importance: NotificationImportance,
presentation: NotificationPresentation,
},
WithdrawNotification {
key: &'a str,
},
RequestNotificationPermission,
EditCode {
node_id: u32,
request_id: u32,
base_version: u32,
start_line: u32,
start_column: u32,
end_line: u32,
end_column: u32,
text: &'a str,
},
StartAnimation(crate::schema::Animation<'a>),
ControlAnimation {
node_id: u32,
animation_id: u32,
property: crate::schema::AnimatedProperty,
slot: u8,
op: crate::schema::AnimationControl,
at_time_nanos: u64,
},
SetSelection {
node_id: u32,
base_version: u32,
anchor_line: u32,
anchor_column: u32,
active_line: u32,
active_column: u32,
},
RevealRange {
node_id: u32,
base_version: u32,
start_line: u32,
start_column: u32,
end_line: u32,
end_column: u32,
align: crate::schema::RevealAlign,
},
SetFolded {
node_id: u32,
start_line: u32,
collapsed: bool,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ProtocolError {
Truncated,
InvalidEnvelope,
InvalidRecordLength,
InvalidTag(u16),
InvalidWidget(u16),
InvalidProperty(u16),
InvalidValueKind(u16),
InvalidModifier(u16),
InvalidTheme(u16),
InvalidAssetKind(u16),
InvalidMessageDuration(u16),
InvalidNotification(u16),
InvalidStringRange,
InvalidUtf8,
LengthOverflow,
MeasureRefused(i32),
InvalidAnimation,
}
#[derive(Clone, Debug, PartialEq)]
pub struct HostEvent<'a> {
pub node_id: u32,
pub handler_id: u64,
pub payload: crate::schema::EventPayload<'a>,
}
const EVENT_CLICK: u16 = 1;
const EVENT_TEXT_CHANGED: u16 = 2;
const EVENT_TEXT_SUBMITTED: u16 = 3;
const EVENT_FOCUS_LOST: u16 = 4;
const EVENT_PROTOCOL_ERROR: u16 = 5;
const EVENT_KEY_DOWN: u16 = 6;
const EVENT_RANGE_REQUESTED: u16 = 7;
const EVENT_POINTER: u16 = 8;
const POINTER_RECORD_LEN: usize = 72;
const EVENT_VALUE_CHANGED: u16 = 16;
const EVENT_WINDOW_SIZE_CHANGED: u16 = 17;
const EVENT_RESYNC: u16 = 18;
const EVENT_LIFECYCLE_START: u16 = 19;
const EVENT_LIFECYCLE_STOP: u16 = 20;
const EVENT_DESIGN_SYSTEM_RESOLVED: u16 = 21;
const EVENT_FILES_ENTERED: u16 = 22;
const EVENT_FILES_DROPPED: u16 = 23;
const EVENT_NOTIFICATION_ACTIVATED: u16 = 24;
const EVENT_NOTIFICATION_PERMISSION_CHANGED: u16 = 25;
const EVENT_CODE_CHANGED: u16 = 26;
const EVENT_CODE_EDIT_REJECTED: u16 = 27;
const EVENT_CODE_HOVERED: u16 = 28;
const EVENT_CODE_SAVE_REQUESTED: u16 = 29;
const EVENT_DECORATION_ACTIVATED: u16 = 30;
const EVENT_ANIMATION: u16 = 31;
const EVENT_REDUCED_MOTION_CHANGED: u16 = 32;
const EVENT_ZOOM_CHANGED: u16 = 33;
const EVENT_SELECTION_CHANGED: u16 = 34;
const EVENT_VISIBLE_RANGE_CHANGED: u16 = 35;
const EVENT_EDITOR_FOCUS_CHANGED: u16 = 36;
const EVENT_FOLD_CHANGED: u16 = 37;
const EVENT_COMPOSITION_CHANGED: u16 = 38;
const MODIFIER_SHIFT: u8 = 1 << 0;
const MODIFIER_CTRL: u8 = 1 << 1;
const MODIFIER_ALT: u8 = 1 << 2;
const MODIFIER_META: u8 = 1 << 3;
fn pointer_tag<T: TryFrom<u16, Error = ()>>(
bytes: &[u8],
offset: usize,
) -> Result<T, ProtocolError> {
let raw = read_u32(bytes, offset)?;
u16::try_from(raw)
.ok()
.and_then(|tag| T::try_from(tag).ok())
.ok_or(ProtocolError::InvalidValueKind(
raw.min(u32::from(u16::MAX)) as u16,
))
}
fn decode_pointer(bytes: &[u8]) -> Result<crate::schema::EventPayload<'_>, ProtocolError> {
Ok(crate::schema::EventPayload::Pointer {
kind: pointer_tag(bytes, 16)?,
pointer_id: read_u32(bytes, 20)?,
pointer_type: pointer_tag(bytes, 24)?,
x: f32::from_bits(read_u32(bytes, 28)?),
y: f32::from_bits(read_u32(bytes, 32)?),
buttons: read_u32(bytes, 36)?,
button: read_u32(bytes, 40)?,
modifiers: read_u32(bytes, 44)?,
click_count: read_u32(bytes, 48)?,
delta_x: f32::from_bits(read_u32(bytes, 52)?),
delta_y: f32::from_bits(read_u32(bytes, 56)?),
delta_mode: pointer_tag(bytes, 60)?,
timestamp_nanos: read_u64(bytes, 64)?,
})
}
pub fn decode_event(bytes: &[u8]) -> Result<HostEvent<'_>, ProtocolError> {
if bytes.len() < 16 {
return Err(ProtocolError::Truncated);
}
let tag = read_u16(bytes, 0)?;
let record_len = usize::from(read_u16(bytes, 2)?);
if record_len < 16 || record_len % 4 != 0 || record_len > bytes.len() {
return Err(ProtocolError::InvalidRecordLength);
}
let node_id = read_u32(bytes, 4)?;
let handler_id = read_u64(bytes, 8)?;
let payload = match tag {
EVENT_CLICK if record_len == 16 => crate::schema::EventPayload::Clicked,
EVENT_TEXT_CHANGED if record_len == 24 => {
crate::schema::EventPayload::TextChanged(read_string(bytes, 16, record_len)?)
}
EVENT_TEXT_SUBMITTED if record_len == 24 => {
crate::schema::EventPayload::TextSubmitted(read_string(bytes, 16, record_len)?)
}
EVENT_FOCUS_LOST if record_len == 16 => crate::schema::EventPayload::FocusLost,
EVENT_PROTOCOL_ERROR if record_len == 28 => crate::schema::EventPayload::ProtocolError {
code: read_u32(bytes, 16)?,
message: read_string(bytes, 20, record_len)?,
},
EVENT_KEY_DOWN if record_len == 20 => {
let raw_key = read_u16(bytes, 16)?;
let modifiers = *bytes.get(18).ok_or(ProtocolError::Truncated)?;
crate::schema::EventPayload::KeyDown {
key: Key::try_from(raw_key)
.map_err(|()| ProtocolError::InvalidValueKind(raw_key))?,
shift_key: modifiers & MODIFIER_SHIFT != 0,
ctrl_key: modifiers & MODIFIER_CTRL != 0,
alt_key: modifiers & MODIFIER_ALT != 0,
meta_key: modifiers & MODIFIER_META != 0,
}
}
EVENT_RANGE_REQUESTED if record_len == 24 => crate::schema::EventPayload::RangeRequested {
start: read_u32(bytes, 16)?,
count: read_u32(bytes, 20)?,
},
EVENT_VALUE_CHANGED if record_len == 24 => {
crate::schema::EventPayload::ValueChanged(f64::from_bits(read_u64(bytes, 16)?))
}
EVENT_WINDOW_SIZE_CHANGED if record_len == 32 => {
let raw_class = read_u32(bytes, 24)?;
let class = u16::try_from(raw_class)
.ok()
.and_then(|value| crate::schema::WindowSizeClass::try_from(value).ok())
.ok_or(ProtocolError::InvalidValueKind(raw_class as u16))?;
let raw_height_class = read_u32(bytes, 28)?;
let height_class = u16::try_from(raw_height_class)
.ok()
.and_then(|value| crate::schema::WindowHeightClass::try_from(value).ok())
.ok_or(ProtocolError::InvalidValueKind(raw_height_class as u16))?;
crate::schema::EventPayload::WindowSizeChanged {
width_dp: f32::from_bits(read_u32(bytes, 16)?),
height_dp: f32::from_bits(read_u32(bytes, 20)?),
class,
height_class,
}
}
EVENT_DESIGN_SYSTEM_RESOLVED if record_len == 20 => {
let raw = read_u32(bytes, 16)?;
let system = u16::try_from(raw)
.ok()
.and_then(|tag| crate::schema::DesignSystem::try_from(tag).ok())
.ok_or(ProtocolError::InvalidValueKind(raw as u16))?;
crate::schema::EventPayload::DesignSystemResolved(system)
}
EVENT_FILES_ENTERED if record_len == 16 => crate::schema::EventPayload::FilesEntered,
EVENT_FILES_DROPPED if record_len == 24 => {
crate::schema::EventPayload::FilesDropped(read_string(bytes, 16, record_len)?)
}
EVENT_NOTIFICATION_ACTIVATED if record_len == 28 => {
let action = read_u32(bytes, 16)?;
if action > 2 {
return Err(ProtocolError::InvalidValueKind(
action.min(u32::from(u16::MAX)) as u16,
));
}
crate::schema::EventPayload::NotificationActivated {
action,
key: read_string(bytes, 20, record_len)?,
}
}
EVENT_NOTIFICATION_PERMISSION_CHANGED if record_len == 20 => {
let raw = read_u32(bytes, 16)?;
let state = u16::try_from(raw)
.ok()
.and_then(|tag| NotificationPermission::try_from(tag).ok())
.ok_or(ProtocolError::InvalidValueKind(
raw.min(u32::from(u16::MAX)) as u16,
))?;
crate::schema::EventPayload::NotificationPermissionChanged(state)
}
EVENT_ANIMATION if record_len == 40 => {
let raw_kind = u16::from(read_u8(bytes, 20)?);
let raw_property = u16::from(read_u8(bytes, 21)?);
crate::schema::EventPayload::AnimationEvent {
animation_id: read_u32(bytes, 16)?,
kind: crate::schema::AnimationEventKind::try_from(raw_kind)
.map_err(|()| ProtocolError::InvalidValueKind(raw_kind))?,
property: crate::schema::AnimatedProperty::try_from(raw_property)
.map_err(|()| ProtocolError::InvalidValueKind(raw_property))?,
slot: read_u8(bytes, 22)?,
iteration: read_u32(bytes, 24)?,
elapsed_ms: f32::from_bits(read_u32(bytes, 28)?),
time_nanos: read_u64(bytes, 32)?,
}
}
EVENT_POINTER if record_len == POINTER_RECORD_LEN => decode_pointer(bytes)?,
EVENT_REDUCED_MOTION_CHANGED if record_len == 20 => {
let raw = read_u32(bytes, 16)?;
let state = u16::try_from(raw)
.ok()
.and_then(|tag| crate::schema::ReducedMotion::try_from(tag).ok())
.ok_or(ProtocolError::InvalidValueKind(
raw.min(u32::from(u16::MAX)) as u16,
))?;
crate::schema::EventPayload::ReducedMotionChanged(state)
}
EVENT_CODE_CHANGED if record_len == 44 => crate::schema::EventPayload::CodeChanged {
version: read_u32(bytes, 16)?,
start_line: read_u32(bytes, 20)?,
start_column: read_u32(bytes, 24)?,
end_line: read_u32(bytes, 28)?,
end_column: read_u32(bytes, 32)?,
text: read_string(bytes, 36, record_len)?,
},
EVENT_CODE_EDIT_REJECTED if record_len == 44 => {
crate::schema::EventPayload::CodeEditRejected {
request_id: read_u32(bytes, 16)?,
base_version: read_u32(bytes, 20)?,
current_version: read_u32(bytes, 24)?,
start_line: read_u32(bytes, 28)?,
start_column: read_u32(bytes, 32)?,
end_line: read_u32(bytes, 36)?,
end_column: read_u32(bytes, 40)?,
}
}
EVENT_CODE_HOVERED if record_len == 36 => {
let raw_phase = read_u32(bytes, 32)?;
let phase = u16::try_from(raw_phase)
.ok()
.and_then(|tag| crate::schema::HoverPhase::try_from(tag).ok())
.ok_or(ProtocolError::InvalidValueKind(raw_phase as u16))?;
crate::schema::EventPayload::CodeHovered {
decoration: read_u64(bytes, 16)?,
line: read_u32(bytes, 24)?,
column: read_u32(bytes, 28)?,
phase,
}
}
EVENT_CODE_SAVE_REQUESTED if record_len == 24 => {
crate::schema::EventPayload::CodeSaveRequested {
version: read_u32(bytes, 16)?,
}
}
EVENT_DECORATION_ACTIVATED if record_len == 24 => {
crate::schema::EventPayload::DecorationActivated {
decoration: read_u64(bytes, 16)?,
}
}
EVENT_CODE_CHANGED..=EVENT_DECORATION_ACTIVATED => {
return Err(ProtocolError::InvalidRecordLength);
}
EVENT_SELECTION_CHANGED if record_len == 36 => {
crate::schema::EventPayload::SelectionChanged {
version: read_u32(bytes, 16)?,
anchor_line: read_u32(bytes, 20)?,
anchor_column: read_u32(bytes, 24)?,
active_line: read_u32(bytes, 28)?,
active_column: read_u32(bytes, 32)?,
}
}
EVENT_VISIBLE_RANGE_CHANGED if record_len == 28 => {
crate::schema::EventPayload::VisibleRangeChanged {
version: read_u32(bytes, 16)?,
first_line: read_u32(bytes, 20)?,
last_line: read_u32(bytes, 24)?,
}
}
EVENT_EDITOR_FOCUS_CHANGED if record_len == 24 => {
crate::schema::EventPayload::EditorFocusChanged {
focused: read_flag(bytes, 16)?,
}
}
EVENT_FOLD_CHANGED if record_len == 24 => crate::schema::EventPayload::FoldChanged {
start_line: read_u32(bytes, 16)?,
collapsed: read_flag(bytes, 20)?,
},
EVENT_COMPOSITION_CHANGED if record_len == 24 => {
crate::schema::EventPayload::CompositionChanged {
composing: read_flag(bytes, 16)?,
}
}
EVENT_SELECTION_CHANGED..=EVENT_COMPOSITION_CHANGED => {
return Err(ProtocolError::InvalidRecordLength);
}
EVENT_ZOOM_CHANGED if record_len == 28 => {
let level = read_u32(bytes, 24)? as i32;
if !(i32::from(crate::zoom::MIN_ZOOM_LEVEL)..=i32::from(crate::zoom::MAX_ZOOM_LEVEL))
.contains(&level)
{
return Err(ProtocolError::InvalidValueKind(level as u16));
}
crate::schema::EventPayload::ZoomChanged {
k: f32::from_bits(read_u32(bytes, 16)?),
os: f32::from_bits(read_u32(bytes, 20)?),
level,
}
}
EVENT_RESYNC if record_len == 16 => crate::schema::EventPayload::Resync,
EVENT_LIFECYCLE_START if record_len == 16 => crate::schema::EventPayload::LifecycleStart,
EVENT_LIFECYCLE_STOP if record_len == 16 => crate::schema::EventPayload::LifecycleStop,
EVENT_CLICK..=EVENT_RANGE_REQUESTED
| EVENT_VALUE_CHANGED..=EVENT_NOTIFICATION_PERMISSION_CHANGED
| EVENT_ANIMATION
| EVENT_POINTER
| EVENT_REDUCED_MOTION_CHANGED
| EVENT_ZOOM_CHANGED => {
return Err(ProtocolError::InvalidRecordLength);
}
other => return Err(ProtocolError::InvalidTag(other)),
};
Ok(HostEvent {
node_id,
handler_id,
payload,
})
}
pub fn encode_event(event: &HostEvent<'_>, output: &mut Vec<u8>) -> Result<(), ProtocolError> {
output.clear();
if let crate::schema::EventPayload::KeyDown {
key,
shift_key,
ctrl_key,
alt_key,
meta_key,
} = event.payload
{
output.extend_from_slice(&EVENT_KEY_DOWN.to_le_bytes());
output.extend_from_slice(&20_u16.to_le_bytes());
output.extend_from_slice(&event.node_id.to_le_bytes());
output.extend_from_slice(&event.handler_id.to_le_bytes());
output.extend_from_slice(&(key as u16).to_le_bytes());
let modifiers = (u8::from(shift_key) * MODIFIER_SHIFT)
| (u8::from(ctrl_key) * MODIFIER_CTRL)
| (u8::from(alt_key) * MODIFIER_ALT)
| (u8::from(meta_key) * MODIFIER_META);
output.push(modifiers);
output.push(0);
return Ok(());
}
if let crate::schema::EventPayload::ValueChanged(value) = event.payload {
output.extend_from_slice(&EVENT_VALUE_CHANGED.to_le_bytes());
output.extend_from_slice(&24_u16.to_le_bytes());
output.extend_from_slice(&event.node_id.to_le_bytes());
output.extend_from_slice(&event.handler_id.to_le_bytes());
output.extend_from_slice(&value.to_bits().to_le_bytes());
return Ok(());
}
if let crate::schema::EventPayload::WindowSizeChanged {
width_dp,
height_dp,
class,
height_class,
} = event.payload
{
output.extend_from_slice(&EVENT_WINDOW_SIZE_CHANGED.to_le_bytes());
output.extend_from_slice(&32_u16.to_le_bytes());
output.extend_from_slice(&event.node_id.to_le_bytes());
output.extend_from_slice(&event.handler_id.to_le_bytes());
output.extend_from_slice(&width_dp.to_bits().to_le_bytes());
output.extend_from_slice(&height_dp.to_bits().to_le_bytes());
output.extend_from_slice(&u32::from(u16::from(class)).to_le_bytes());
output.extend_from_slice(&u32::from(u16::from(height_class)).to_le_bytes());
return Ok(());
}
if let crate::schema::EventPayload::DesignSystemResolved(system) = event.payload {
output.extend_from_slice(&EVENT_DESIGN_SYSTEM_RESOLVED.to_le_bytes());
output.extend_from_slice(&20_u16.to_le_bytes());
output.extend_from_slice(&event.node_id.to_le_bytes());
output.extend_from_slice(&event.handler_id.to_le_bytes());
output.extend_from_slice(&u32::from(u16::from(system)).to_le_bytes());
return Ok(());
}
if let crate::schema::EventPayload::NotificationPermissionChanged(state) = event.payload {
output.extend_from_slice(&EVENT_NOTIFICATION_PERMISSION_CHANGED.to_le_bytes());
output.extend_from_slice(&20_u16.to_le_bytes());
output.extend_from_slice(&event.node_id.to_le_bytes());
output.extend_from_slice(&event.handler_id.to_le_bytes());
output.extend_from_slice(&u32::from(u16::from(state)).to_le_bytes());
return Ok(());
}
if let crate::schema::EventPayload::AnimationEvent {
animation_id,
kind,
property,
slot,
iteration,
elapsed_ms,
time_nanos,
} = event.payload
{
output.extend_from_slice(&EVENT_ANIMATION.to_le_bytes());
output.extend_from_slice(&40_u16.to_le_bytes());
output.extend_from_slice(&event.node_id.to_le_bytes());
output.extend_from_slice(&event.handler_id.to_le_bytes());
output.extend_from_slice(&animation_id.to_le_bytes());
output.push(kind as u8);
output.push(property as u8);
output.push(slot);
output.push(0);
output.extend_from_slice(&iteration.to_le_bytes());
output.extend_from_slice(&elapsed_ms.to_bits().to_le_bytes());
output.extend_from_slice(&time_nanos.to_le_bytes());
return Ok(());
}
if let crate::schema::EventPayload::Pointer {
kind,
pointer_id,
pointer_type,
x,
y,
buttons,
button,
modifiers,
click_count,
delta_x,
delta_y,
delta_mode,
timestamp_nanos,
} = event.payload
{
output.extend_from_slice(&EVENT_POINTER.to_le_bytes());
output.extend_from_slice(&(POINTER_RECORD_LEN as u16).to_le_bytes());
output.extend_from_slice(&event.node_id.to_le_bytes());
output.extend_from_slice(&event.handler_id.to_le_bytes());
output.extend_from_slice(&u32::from(u16::from(kind)).to_le_bytes());
output.extend_from_slice(&pointer_id.to_le_bytes());
output.extend_from_slice(&u32::from(u16::from(pointer_type)).to_le_bytes());
output.extend_from_slice(&x.to_bits().to_le_bytes());
output.extend_from_slice(&y.to_bits().to_le_bytes());
output.extend_from_slice(&buttons.to_le_bytes());
output.extend_from_slice(&button.to_le_bytes());
output.extend_from_slice(&modifiers.to_le_bytes());
output.extend_from_slice(&click_count.to_le_bytes());
output.extend_from_slice(&delta_x.to_bits().to_le_bytes());
output.extend_from_slice(&delta_y.to_bits().to_le_bytes());
output.extend_from_slice(&u32::from(u16::from(delta_mode)).to_le_bytes());
output.extend_from_slice(×tamp_nanos.to_le_bytes());
return Ok(());
}
if let crate::schema::EventPayload::ReducedMotionChanged(state) = event.payload {
output.extend_from_slice(&EVENT_REDUCED_MOTION_CHANGED.to_le_bytes());
output.extend_from_slice(&20_u16.to_le_bytes());
output.extend_from_slice(&event.node_id.to_le_bytes());
output.extend_from_slice(&event.handler_id.to_le_bytes());
output.extend_from_slice(&u32::from(u16::from(state)).to_le_bytes());
return Ok(());
}
match event.payload {
crate::schema::EventPayload::CodeChanged {
version,
start_line,
start_column,
end_line,
end_column,
text,
} => {
put_event_header(output, EVENT_CODE_CHANGED, 44, event);
for value in [version, start_line, start_column, end_line, end_column] {
output.extend_from_slice(&value.to_le_bytes());
}
let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
output.extend_from_slice(&44_u32.to_le_bytes());
output.extend_from_slice(&len.to_le_bytes());
output.extend_from_slice(text.as_bytes());
return Ok(());
}
crate::schema::EventPayload::CodeEditRejected {
request_id,
base_version,
current_version,
start_line,
start_column,
end_line,
end_column,
} => {
put_event_header(output, EVENT_CODE_EDIT_REJECTED, 44, event);
for value in [
request_id,
base_version,
current_version,
start_line,
start_column,
end_line,
end_column,
] {
output.extend_from_slice(&value.to_le_bytes());
}
return Ok(());
}
crate::schema::EventPayload::CodeHovered {
decoration,
line,
column,
phase,
} => {
put_event_header(output, EVENT_CODE_HOVERED, 36, event);
output.extend_from_slice(&decoration.to_le_bytes());
output.extend_from_slice(&line.to_le_bytes());
output.extend_from_slice(&column.to_le_bytes());
output.extend_from_slice(&u32::from(u16::from(phase)).to_le_bytes());
return Ok(());
}
crate::schema::EventPayload::CodeSaveRequested { version } => {
put_event_header(output, EVENT_CODE_SAVE_REQUESTED, 24, event);
output.extend_from_slice(&version.to_le_bytes());
output.extend_from_slice(&0_u32.to_le_bytes());
return Ok(());
}
crate::schema::EventPayload::DecorationActivated { decoration } => {
put_event_header(output, EVENT_DECORATION_ACTIVATED, 24, event);
output.extend_from_slice(&decoration.to_le_bytes());
return Ok(());
}
crate::schema::EventPayload::SelectionChanged {
version,
anchor_line,
anchor_column,
active_line,
active_column,
} => {
put_event_header(output, EVENT_SELECTION_CHANGED, 36, event);
for value in [
version,
anchor_line,
anchor_column,
active_line,
active_column,
] {
output.extend_from_slice(&value.to_le_bytes());
}
return Ok(());
}
crate::schema::EventPayload::VisibleRangeChanged {
version,
first_line,
last_line,
} => {
put_event_header(output, EVENT_VISIBLE_RANGE_CHANGED, 28, event);
for value in [version, first_line, last_line] {
output.extend_from_slice(&value.to_le_bytes());
}
return Ok(());
}
crate::schema::EventPayload::EditorFocusChanged { focused } => {
put_event_header(output, EVENT_EDITOR_FOCUS_CHANGED, 24, event);
output.extend_from_slice(&u32::from(focused).to_le_bytes());
output.extend_from_slice(&0_u32.to_le_bytes());
return Ok(());
}
crate::schema::EventPayload::FoldChanged {
start_line,
collapsed,
} => {
put_event_header(output, EVENT_FOLD_CHANGED, 24, event);
output.extend_from_slice(&start_line.to_le_bytes());
output.extend_from_slice(&u32::from(collapsed).to_le_bytes());
return Ok(());
}
crate::schema::EventPayload::CompositionChanged { composing } => {
put_event_header(output, EVENT_COMPOSITION_CHANGED, 24, event);
output.extend_from_slice(&u32::from(composing).to_le_bytes());
output.extend_from_slice(&0_u32.to_le_bytes());
return Ok(());
}
_ => {}
}
if let crate::schema::EventPayload::ZoomChanged { k, os, level } = event.payload {
output.extend_from_slice(&EVENT_ZOOM_CHANGED.to_le_bytes());
output.extend_from_slice(&28_u16.to_le_bytes());
output.extend_from_slice(&event.node_id.to_le_bytes());
output.extend_from_slice(&event.handler_id.to_le_bytes());
output.extend_from_slice(&k.to_bits().to_le_bytes());
output.extend_from_slice(&os.to_bits().to_le_bytes());
output.extend_from_slice(&level.to_le_bytes());
return Ok(());
}
if let crate::schema::EventPayload::RangeRequested { start, count } = event.payload {
output.extend_from_slice(&EVENT_RANGE_REQUESTED.to_le_bytes());
output.extend_from_slice(&24_u16.to_le_bytes());
output.extend_from_slice(&event.node_id.to_le_bytes());
output.extend_from_slice(&event.handler_id.to_le_bytes());
output.extend_from_slice(&start.to_le_bytes());
output.extend_from_slice(&count.to_le_bytes());
return Ok(());
}
let (tag, record_len, text, error_code) = match &event.payload {
crate::schema::EventPayload::Clicked => (EVENT_CLICK, 16_u16, None, None),
crate::schema::EventPayload::TextChanged(value) => {
(EVENT_TEXT_CHANGED, 24, Some(*value), None)
}
crate::schema::EventPayload::TextSubmitted(value) => {
(EVENT_TEXT_SUBMITTED, 24, Some(*value), None)
}
crate::schema::EventPayload::FocusLost => (EVENT_FOCUS_LOST, 16, None, None),
crate::schema::EventPayload::FilesEntered => (EVENT_FILES_ENTERED, 16, None, None),
crate::schema::EventPayload::FilesDropped(paths) => {
(EVENT_FILES_DROPPED, 24, Some(*paths), None)
}
crate::schema::EventPayload::Resync => (EVENT_RESYNC, 16, None, None),
crate::schema::EventPayload::LifecycleStart => (EVENT_LIFECYCLE_START, 16, None, None),
crate::schema::EventPayload::LifecycleStop => (EVENT_LIFECYCLE_STOP, 16, None, None),
crate::schema::EventPayload::ProtocolError { code, message } => {
(EVENT_PROTOCOL_ERROR, 28, Some(*message), Some(*code))
}
crate::schema::EventPayload::NotificationActivated { action, key } => {
(EVENT_NOTIFICATION_ACTIVATED, 28, Some(*key), Some(*action))
}
crate::schema::EventPayload::KeyDown { .. }
| crate::schema::EventPayload::RangeRequested { .. }
| crate::schema::EventPayload::ValueChanged(_)
| crate::schema::EventPayload::WindowSizeChanged { .. }
| crate::schema::EventPayload::DesignSystemResolved(_)
| crate::schema::EventPayload::NotificationPermissionChanged(_)
| crate::schema::EventPayload::CodeChanged { .. }
| crate::schema::EventPayload::CodeEditRejected { .. }
| crate::schema::EventPayload::CodeHovered { .. }
| crate::schema::EventPayload::CodeSaveRequested { .. }
| crate::schema::EventPayload::DecorationActivated { .. }
| crate::schema::EventPayload::SelectionChanged { .. }
| crate::schema::EventPayload::VisibleRangeChanged { .. }
| crate::schema::EventPayload::EditorFocusChanged { .. }
| crate::schema::EventPayload::FoldChanged { .. }
| crate::schema::EventPayload::CompositionChanged { .. } => unreachable!(),
crate::schema::EventPayload::AnimationEvent { .. }
| crate::schema::EventPayload::Pointer { .. }
| crate::schema::EventPayload::ReducedMotionChanged(_)
| crate::schema::EventPayload::ZoomChanged { .. } => unreachable!(),
};
output.extend_from_slice(&tag.to_le_bytes());
output.extend_from_slice(&record_len.to_le_bytes());
output.extend_from_slice(&event.node_id.to_le_bytes());
output.extend_from_slice(&event.handler_id.to_le_bytes());
if let Some(code) = error_code {
output.extend_from_slice(&code.to_le_bytes());
}
if let Some(text) = text {
let offset = u32::from(record_len);
let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
output.extend_from_slice(&offset.to_le_bytes());
output.extend_from_slice(&len.to_le_bytes());
output.extend_from_slice(text.as_bytes());
}
Ok(())
}
fn put_event_header(output: &mut Vec<u8>, tag: u16, record_len: u16, event: &HostEvent<'_>) {
output.extend_from_slice(&tag.to_le_bytes());
output.extend_from_slice(&record_len.to_le_bytes());
output.extend_from_slice(&event.node_id.to_le_bytes());
output.extend_from_slice(&event.handler_id.to_le_bytes());
}
impl fmt::Display for ProtocolError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "protocol error: {self:?}")
}
}
impl std::error::Error for ProtocolError {}
pub struct BatchEncoder {
records: Vec<u8>,
strings: Vec<u8>,
string_fixups: Vec<usize>,
arena: Vec<u8>,
record_count: u32,
}
impl Default for BatchEncoder {
fn default() -> Self {
Self::with_capacity(4096, 1024, 64)
}
}
impl BatchEncoder {
pub fn with_capacity(record_bytes: usize, string_bytes: usize, string_count: usize) -> Self {
let mut encoder = Self {
records: Vec::with_capacity(record_bytes),
strings: Vec::with_capacity(string_bytes),
string_fixups: Vec::with_capacity(string_count),
arena: Vec::with_capacity(record_bytes + string_bytes),
record_count: 0,
};
encoder.clear();
encoder
}
pub fn clear(&mut self) {
self.records.clear();
self.strings.clear();
self.string_fixups.clear();
self.arena.clear();
self.record_count = 0;
self.begin_record(TAG_ENVELOPE, 8);
self.put_u32(0);
self.put_u32(0);
}
pub fn encode(&mut self, mutation: &Mutation<'_>) -> Result<(), ProtocolError> {
match mutation {
Mutation::Create { node_id, widget } => {
self.begin_record(TAG_CREATE, 8);
self.put_u32(*node_id);
self.put_u16(*widget as u16);
self.put_u16(0);
}
Mutation::SetProp {
node_id,
property,
value,
} => {
self.begin_record(TAG_SET_PROP, 20);
self.put_u32(*node_id);
self.put_u16(*property as u16);
match value {
PropertyValue::None => {
self.put_u16(VALUE_NONE);
self.put_u64(0);
self.put_u32(0);
}
PropertyValue::String(value) => {
self.put_u16(VALUE_STRING);
self.put_string_ref(value)?;
self.put_u32(0);
}
PropertyValue::Bool(value) => {
self.put_u16(VALUE_BOOL);
self.put_u64(u64::from(*value));
self.put_u32(0);
}
PropertyValue::Integer(value) => {
self.put_u16(VALUE_I64);
self.put_u64(*value as u64);
self.put_u32(0);
}
PropertyValue::Float(value) => {
self.put_u16(VALUE_F32);
self.put_u64(u64::from(value.to_bits()));
self.put_u32(0);
}
PropertyValue::Bytes(value) => {
self.put_u16(VALUE_BYTES);
self.put_bytes_ref(value)?;
self.put_u32(0);
}
}
}
Mutation::SetModifier {
node_id,
index,
modifier,
} => {
let (tag, words) = modifier_fields(modifier);
let extra = modifier_extra_words(tag).ok_or(ProtocolError::InvalidModifier(tag))?;
self.begin_record(TAG_SET_MODIFIER, 24 + 8 * u16::from(extra));
self.put_u32(*node_id);
self.put_u16(*index);
self.put_u16(tag);
for word in &words[..2 + usize::from(extra)] {
self.put_u64(*word);
}
}
Mutation::Insert {
parent_id,
node_id,
index,
} => {
self.begin_record(TAG_INSERT, 12);
self.put_u32(*parent_id);
self.put_u32(*node_id);
self.put_u32(*index);
}
Mutation::Move {
parent_id,
node_id,
index,
} => {
self.begin_record(TAG_MOVE, 12);
self.put_u32(*parent_id);
self.put_u32(*node_id);
self.put_u32(*index);
}
Mutation::Remove { node_id } => {
self.begin_record(TAG_REMOVE, 4);
self.put_u32(*node_id);
}
Mutation::SetText {
node_id,
text,
selection,
} => {
self.begin_record(TAG_SET_TEXT, 20);
self.put_u32(*node_id);
self.put_string_ref(text)?;
let (start, end) =
selection.map_or((u32::MAX, u32::MAX), |value| (value.start, value.end));
self.put_u32(start);
self.put_u32(end);
}
Mutation::AppendText { node_id, text } => {
self.begin_record(TAG_APPEND_TEXT, 12);
self.put_u32(*node_id);
self.put_string_ref(text)?;
}
Mutation::RegisterAsset {
asset_id,
kind,
bytes,
} => {
self.begin_record(TAG_REGISTER_ASSET, 16);
self.put_u32(*asset_id);
self.put_u16(*kind as u16);
self.put_u16(0);
self.put_bytes_ref(bytes)?;
}
Mutation::ReleaseAsset { asset_id } => {
self.begin_record(TAG_RELEASE_ASSET, 4);
self.put_u32(*asset_id);
}
Mutation::ShowMessage {
handler_id,
text,
action,
duration,
} => {
self.begin_record(TAG_SHOW_MESSAGE, 28);
self.put_u64(*handler_id);
self.put_string_ref(text)?;
self.put_string_ref(action)?;
self.put_u16(*duration as u16);
self.put_u16(0);
}
Mutation::EditCode {
node_id,
request_id,
base_version,
start_line,
start_column,
end_line,
end_column,
text,
} => {
self.begin_record(TAG_EDIT_CODE, 36);
self.put_u32(*node_id);
self.put_u32(*request_id);
self.put_u32(*base_version);
self.put_u32(*start_line);
self.put_u32(*start_column);
self.put_u32(*end_line);
self.put_u32(*end_column);
self.put_string_ref(text)?;
}
Mutation::SetSelection {
node_id,
base_version,
anchor_line,
anchor_column,
active_line,
active_column,
} => {
self.begin_record(TAG_SET_SELECTION, 24);
self.put_u32(*node_id);
self.put_u32(*base_version);
self.put_u32(*anchor_line);
self.put_u32(*anchor_column);
self.put_u32(*active_line);
self.put_u32(*active_column);
}
Mutation::RevealRange {
node_id,
base_version,
start_line,
start_column,
end_line,
end_column,
align,
} => {
self.begin_record(TAG_REVEAL_RANGE, 28);
self.put_u32(*node_id);
self.put_u32(*base_version);
self.put_u32(*start_line);
self.put_u32(*start_column);
self.put_u32(*end_line);
self.put_u32(*end_column);
self.put_u32(u32::from(*align as u16));
}
Mutation::SetFolded {
node_id,
start_line,
collapsed,
} => {
self.begin_record(TAG_SET_FOLDED, 12);
self.put_u32(*node_id);
self.put_u32(*start_line);
self.put_u32(u32::from(*collapsed));
}
Mutation::SetTheme(theme) => {
self.begin_record(TAG_SET_THEME, THEME_PAYLOAD_LEN as u16);
self.put_u16(theme.design_system as u16);
self.put_u16(theme.fallback as u16);
self.put_u16(theme.color_scheme as u16);
self.put_u16(u16::from(theme.adaptive));
for asset in theme.fonts {
self.put_u32(asset);
}
self.put_palette_ref(theme.palette)?;
}
Mutation::SetWindow(window) => {
self.begin_record(TAG_SET_WINDOW, 28);
self.put_u16(window.chrome as u16);
self.put_u16(window.title_bar as u16);
self.put_u16(window.width);
self.put_u16(window.height);
self.put_u16(window.min_width);
self.put_u16(window.min_height);
self.put_u16(u16::from(window.resizable));
self.put_u16(match window.zoom_level {
None => 0,
Some(level) => 1 | (u16::from(crate::zoom::clamp_level(level) as u8) << 8),
});
self.put_string_ref(window.title)?;
self.put_u32(window.icon);
}
Mutation::PostNotification {
key,
title,
body,
channel,
action_1,
action_2,
importance,
presentation,
} => {
self.begin_record(TAG_POST_NOTIFICATION, 52);
self.put_string_ref(key)?;
self.put_string_ref(title)?;
self.put_string_ref(body)?;
self.put_string_ref(channel)?;
self.put_string_ref(action_1)?;
self.put_string_ref(action_2)?;
self.put_u16(*importance as u16);
self.put_u16(*presentation as u16);
}
Mutation::WithdrawNotification { key } => {
self.begin_record(TAG_WITHDRAW_NOTIFICATION, 8);
self.put_string_ref(key)?;
}
Mutation::RequestNotificationPermission => {
self.begin_record(TAG_REQUEST_NOTIFICATION_PERMISSION, 0);
}
Mutation::StartAnimation(animation) => self.put_animation(animation)?,
Mutation::ControlAnimation {
node_id,
animation_id,
property,
slot,
op,
at_time_nanos,
} => {
self.begin_record(TAG_CONTROL_ANIMATION, 20);
self.put_u32(*node_id);
self.put_u32(*animation_id);
self.records.push(*property as u8);
self.records.push(*slot);
self.records.push(*op as u8);
self.records.push(0);
self.put_u64(*at_time_nanos);
}
}
self.record_count = self
.record_count
.checked_add(1)
.ok_or(ProtocolError::LengthOverflow)?;
Ok(())
}
pub fn finish(&mut self) -> Result<&[u8], ProtocolError> {
let records_len =
u32::try_from(self.records.len()).map_err(|_| ProtocolError::LengthOverflow)?;
self.records[4..8].copy_from_slice(&records_len.to_le_bytes());
self.records[8..12].copy_from_slice(&self.record_count.to_le_bytes());
let total = self
.records
.len()
.checked_add(self.strings.len())
.ok_or(ProtocolError::LengthOverflow)?;
self.arena.clear();
if self.arena.capacity() < total {
self.arena.reserve(total);
}
self.arena.extend_from_slice(&self.records);
self.arena.extend_from_slice(&self.strings);
for &position in &self.string_fixups {
let relative = read_u32(&self.arena, position)?;
let absolute = records_len
.checked_add(relative)
.ok_or(ProtocolError::LengthOverflow)?;
self.arena[position..position + 4].copy_from_slice(&absolute.to_le_bytes());
}
Ok(&self.arena)
}
pub fn arena(&self) -> (*const u8, usize) {
(self.arena.as_ptr(), self.arena.capacity())
}
pub fn capacity(&self) -> usize {
self.records.capacity() + self.strings.capacity() + self.arena.capacity()
}
fn put_animation(
&mut self,
animation: &crate::schema::Animation<'_>,
) -> Result<(), ProtocolError> {
use crate::schema::{KeyframeValue, Timing};
let keyframes_len: usize = animation
.keyframes
.iter()
.map(|keyframe| {
KEYFRAME_HEAD_LEN
+ match &keyframe.value {
KeyframeValue::Alpha(_) => 4,
KeyframeValue::Paint(_) => 8,
KeyframeValue::Transform(functions) => {
4 + TRANSFORM_FUNCTION_LEN * functions.len()
}
}
})
.sum();
let payload = u16::try_from(ANIMATION_HEADER_LEN - 4 + keyframes_len)
.ok()
.filter(|payload| *payload <= u16::MAX - 4)
.ok_or(ProtocolError::LengthOverflow)?;
let keyframe_count =
u16::try_from(animation.keyframes.len()).map_err(|_| ProtocolError::LengthOverflow)?;
self.begin_record(TAG_START_ANIMATION, payload);
self.put_u32(animation.node_id);
self.put_u32(animation.animation_id);
self.records.push(animation.property as u8);
self.records.push(animation.slot);
self.records.push(animation.direction as u8);
self.records.push(animation.fill as u8);
self.records.push(animation.play_state as u8);
self.records
.push(animation.interpolation.map_or(0, |value| value as u8));
self.put_u16(keyframe_count);
self.put_u64(animation.start_time_nanos);
self.put_u32(animation.delay_ms.to_bits());
self.put_u32(animation.duration_ms.to_bits());
self.put_u32(animation.iterations.to_bits());
self.put_u32(animation.origin_x.to_bits());
self.put_u32(animation.origin_y.to_bits());
self.records.push(animation.events.0);
self.records.extend_from_slice(&[0, 0, 0]);
for keyframe in animation.keyframes.iter() {
self.put_u32(keyframe.offset.to_bits());
let (kind, position, numbers) = match keyframe.timing {
Timing::Linear => (1_u8, 0_u8, [0_u32; 4]),
Timing::CubicBezier { x1, y1, x2, y2 } => (
2,
0,
[x1.to_bits(), y1.to_bits(), x2.to_bits(), y2.to_bits()],
),
Timing::Steps { count, position } => (3, position as u8, [count, 0, 0, 0]),
};
self.records.push(kind);
self.records.push(position);
self.put_u16(if keyframe.from_presented {
KEYFRAME_FROM_PRESENTED
} else {
0
});
for number in numbers {
self.put_u32(number);
}
match &keyframe.value {
KeyframeValue::Alpha(value) => self.put_u32(value.to_bits()),
KeyframeValue::Paint(paint) => self.put_u64(paint.to_bits()),
KeyframeValue::Transform(functions) => {
let count = u16::try_from(functions.len())
.map_err(|_| ProtocolError::LengthOverflow)?;
self.put_u16(count);
self.put_u16(0);
for function in functions.iter() {
self.put_u32(u32::from(u16::from(function.kind)));
for value in function.values {
self.put_u32(value.to_bits());
}
}
}
}
}
Ok(())
}
fn begin_record(&mut self, tag: u16, payload_len: u16) {
self.put_u16(tag);
self.put_u16(payload_len + 4);
}
fn put_palette_ref(
&mut self,
palette: Option<&crate::palette::Palette>,
) -> Result<(), ProtocolError> {
let start = self.strings.len();
let offset = u32::try_from(start).map_err(|_| ProtocolError::LengthOverflow)?;
if let Some(palette) = palette {
palette.write_into(&mut self.strings);
}
let len =
u32::try_from(self.strings.len() - start).map_err(|_| ProtocolError::LengthOverflow)?;
self.string_fixups.push(self.records.len());
self.put_u32(offset);
self.put_u32(len);
Ok(())
}
fn put_string_ref(&mut self, value: &str) -> Result<(), ProtocolError> {
self.put_bytes_ref(value.as_bytes())
}
fn put_bytes_ref(&mut self, value: &[u8]) -> Result<(), ProtocolError> {
let offset =
u32::try_from(self.strings.len()).map_err(|_| ProtocolError::LengthOverflow)?;
let len = u32::try_from(value.len()).map_err(|_| ProtocolError::LengthOverflow)?;
self.string_fixups.push(self.records.len());
self.put_u32(offset);
self.put_u32(len);
self.strings.extend_from_slice(value);
Ok(())
}
fn put_u16(&mut self, value: u16) {
self.records.extend_from_slice(&value.to_le_bytes());
}
fn put_u32(&mut self, value: u32) {
self.records.extend_from_slice(&value.to_le_bytes());
}
fn put_u64(&mut self, value: u64) {
self.records.extend_from_slice(&value.to_le_bytes());
}
}
pub fn decode_batch(bytes: &[u8]) -> Result<Vec<Mutation<'_>>, ProtocolError> {
if bytes.len() < ENVELOPE_LEN
|| read_u16(bytes, 0)? != TAG_ENVELOPE
|| read_u16(bytes, 2)? != 12
{
return Err(ProtocolError::InvalidEnvelope);
}
let records_len =
usize::try_from(read_u32(bytes, 4)?).map_err(|_| ProtocolError::LengthOverflow)?;
let count = usize::try_from(read_u32(bytes, 8)?).map_err(|_| ProtocolError::LengthOverflow)?;
if records_len < ENVELOPE_LEN || records_len > bytes.len() || records_len % 4 != 0 {
return Err(ProtocolError::InvalidEnvelope);
}
let capacity = count.min((records_len - ENVELOPE_LEN) / MIN_RECORD_LEN);
let mut output = Vec::with_capacity(capacity);
let mut position = ENVELOPE_LEN;
while position < records_len {
let tag = read_u16(bytes, position)?;
let len = usize::from(read_u16(bytes, position + 2)?);
if len < 4 || len % 4 != 0 || position + len > records_len {
return Err(ProtocolError::InvalidRecordLength);
}
let payload = position + 4;
let mutation = match tag {
TAG_CREATE if len == 12 => Mutation::Create {
node_id: read_u32(bytes, payload)?,
widget: WidgetKind::try_from(read_u16(bytes, payload + 4)?).map_err(|()| {
ProtocolError::InvalidWidget(read_u16(bytes, payload + 4).unwrap_or(0))
})?,
},
TAG_SET_PROP if len == 24 => {
let property_raw = read_u16(bytes, payload + 4)?;
let value_kind = read_u16(bytes, payload + 6)?;
let value = match value_kind {
VALUE_NONE => PropertyValue::None,
VALUE_STRING => {
PropertyValue::String(read_string(bytes, payload + 8, records_len)?)
}
VALUE_BOOL => PropertyValue::Bool(read_u64(bytes, payload + 8)? != 0),
VALUE_I64 => PropertyValue::Integer(read_u64(bytes, payload + 8)? as i64),
VALUE_F32 => {
PropertyValue::Float(f32::from_bits(read_u64(bytes, payload + 8)? as u32))
}
VALUE_BYTES => {
PropertyValue::Bytes(read_bytes(bytes, payload + 8, records_len)?)
}
other => return Err(ProtocolError::InvalidValueKind(other)),
};
Mutation::SetProp {
node_id: read_u32(bytes, payload)?,
property: PropertyKind::try_from(property_raw)
.map_err(|()| ProtocolError::InvalidProperty(property_raw))?,
value,
}
}
TAG_SET_MODIFIER if len >= 28 => {
let modifier_tag = read_u16(bytes, payload + 6)?;
let extra = usize::from(
modifier_extra_words(modifier_tag)
.ok_or(ProtocolError::InvalidModifier(modifier_tag))?,
);
if len != 28 + 8 * extra {
return Err(ProtocolError::InvalidRecordLength);
}
let mut words = [0_u64; MAX_MODIFIER_WORDS];
for (word_index, word) in words.iter_mut().take(2 + extra).enumerate() {
*word = read_u64(bytes, payload + 8 + 8 * word_index)?;
}
Mutation::SetModifier {
node_id: read_u32(bytes, payload)?,
index: read_u16(bytes, payload + 4)?,
modifier: decode_modifier(modifier_tag, &words)?,
}
}
TAG_INSERT if len == 16 => Mutation::Insert {
parent_id: read_u32(bytes, payload)?,
node_id: read_u32(bytes, payload + 4)?,
index: read_u32(bytes, payload + 8)?,
},
TAG_MOVE if len == 16 => Mutation::Move {
parent_id: read_u32(bytes, payload)?,
node_id: read_u32(bytes, payload + 4)?,
index: read_u32(bytes, payload + 8)?,
},
TAG_REMOVE if len == 8 => Mutation::Remove {
node_id: read_u32(bytes, payload)?,
},
TAG_SET_TEXT if len == 24 => {
let start = read_u32(bytes, payload + 12)?;
let end = read_u32(bytes, payload + 16)?;
Mutation::SetText {
node_id: read_u32(bytes, payload)?,
text: read_string(bytes, payload + 4, records_len)?,
selection: (start != u32::MAX || end != u32::MAX)
.then_some(Selection { start, end }),
}
}
TAG_APPEND_TEXT if len == 16 => Mutation::AppendText {
node_id: read_u32(bytes, payload)?,
text: read_string(bytes, payload + 4, records_len)?,
},
TAG_SET_THEME if len == 4 + THEME_PAYLOAD_LEN => {
let design_system = read_u16(bytes, payload)?;
let fallback = read_u16(bytes, payload + 2)?;
let color_scheme = read_u16(bytes, payload + 4)?;
let adaptive = read_u16(bytes, payload + 6)?;
if adaptive > 1 {
return Err(ProtocolError::InvalidTheme(adaptive));
}
let mut fonts = [0u32; TYPE_ROLE_COUNT];
for (index, slot) in fonts.iter_mut().enumerate() {
*slot = read_u32(bytes, payload + 8 + 4 * index)?;
}
let palette_bytes =
read_bytes(bytes, payload + 8 + 4 * TYPE_ROLE_COUNT, records_len)?;
let palette = if palette_bytes.is_empty() {
None
} else {
let (palette, _problems) = crate::palette::decode_palette(palette_bytes);
Some(&*Box::leak(Box::new(palette)))
};
Mutation::SetTheme(Theme {
design_system: DesignSystem::try_from(design_system)
.map_err(|()| ProtocolError::InvalidTheme(design_system))?,
fallback: DesignSystem::try_from(fallback)
.map_err(|()| ProtocolError::InvalidTheme(fallback))?,
color_scheme: ColorScheme::try_from(color_scheme)
.map_err(|()| ProtocolError::InvalidTheme(color_scheme))?,
adaptive: adaptive == 1,
fonts,
palette,
})
}
TAG_SET_WINDOW if len == 32 => {
let chrome = read_u16(bytes, payload)?;
let title_bar = read_u16(bytes, payload + 2)?;
let resizable = read_u16(bytes, payload + 12)?;
if resizable > 1 {
return Err(ProtocolError::InvalidValueKind(resizable));
}
let zoom = read_u16(bytes, payload + 14)?;
let zoom_level = match zoom & 0xFF {
0 => None,
1 => {
let level = (zoom >> 8) as u8 as i8;
if !(crate::zoom::MIN_ZOOM_LEVEL..=crate::zoom::MAX_ZOOM_LEVEL)
.contains(&level)
{
return Err(ProtocolError::InvalidValueKind(zoom));
}
Some(level)
}
_ => return Err(ProtocolError::InvalidValueKind(zoom)),
};
Mutation::SetWindow(crate::schema::Window {
chrome: crate::schema::Chrome::try_from(chrome)
.map_err(|()| ProtocolError::InvalidValueKind(chrome))?,
title_bar: crate::schema::TitleBar::try_from(title_bar)
.map_err(|()| ProtocolError::InvalidValueKind(title_bar))?,
width: read_u16(bytes, payload + 4)?,
height: read_u16(bytes, payload + 6)?,
min_width: read_u16(bytes, payload + 8)?,
min_height: read_u16(bytes, payload + 10)?,
resizable: resizable == 1,
zoom_level,
icon: read_u32(bytes, payload + 24)?,
title: Box::leak(
read_string(bytes, payload + 16, records_len)?
.to_owned()
.into_boxed_str(),
),
})
}
TAG_REGISTER_ASSET if len == 20 => {
let raw_kind = read_u16(bytes, payload + 4)?;
Mutation::RegisterAsset {
asset_id: read_u32(bytes, payload)?,
kind: AssetKind::try_from(raw_kind)
.map_err(|()| ProtocolError::InvalidAssetKind(raw_kind))?,
bytes: read_bytes(bytes, payload + 8, records_len)?,
}
}
TAG_RELEASE_ASSET if len == 8 => Mutation::ReleaseAsset {
asset_id: read_u32(bytes, payload)?,
},
TAG_SHOW_MESSAGE if len == 32 => {
let raw_duration = read_u16(bytes, payload + 24)?;
Mutation::ShowMessage {
handler_id: read_u64(bytes, payload)?,
text: read_string(bytes, payload + 8, records_len)?,
action: read_string(bytes, payload + 16, records_len)?,
duration: MessageDuration::try_from(raw_duration)
.map_err(|()| ProtocolError::InvalidMessageDuration(raw_duration))?,
}
}
TAG_EDIT_CODE if len == 40 => Mutation::EditCode {
node_id: read_u32(bytes, payload)?,
request_id: read_u32(bytes, payload + 4)?,
base_version: read_u32(bytes, payload + 8)?,
start_line: read_u32(bytes, payload + 12)?,
start_column: read_u32(bytes, payload + 16)?,
end_line: read_u32(bytes, payload + 20)?,
end_column: read_u32(bytes, payload + 24)?,
text: read_string(bytes, payload + 28, records_len)?,
},
TAG_POST_NOTIFICATION if len == 56 => {
let raw_importance = read_u16(bytes, payload + 48)?;
let raw_presentation = read_u16(bytes, payload + 50)?;
Mutation::PostNotification {
key: read_string(bytes, payload, records_len)?,
title: read_string(bytes, payload + 8, records_len)?,
body: read_string(bytes, payload + 16, records_len)?,
channel: read_string(bytes, payload + 24, records_len)?,
action_1: read_string(bytes, payload + 32, records_len)?,
action_2: read_string(bytes, payload + 40, records_len)?,
importance: NotificationImportance::try_from(raw_importance)
.map_err(|()| ProtocolError::InvalidNotification(raw_importance))?,
presentation: NotificationPresentation::try_from(raw_presentation)
.map_err(|()| ProtocolError::InvalidNotification(raw_presentation))?,
}
}
TAG_WITHDRAW_NOTIFICATION if len == 12 => Mutation::WithdrawNotification {
key: read_string(bytes, payload, records_len)?,
},
TAG_REQUEST_NOTIFICATION_PERMISSION if len == 4 => {
Mutation::RequestNotificationPermission
}
TAG_START_ANIMATION if len >= ANIMATION_HEADER_LEN => {
Mutation::StartAnimation(
decode_animation(&bytes[position..position + len]).map_err(|error| {
if error == ProtocolError::Truncated {
ProtocolError::InvalidAnimation
} else {
error
}
})?,
)
}
TAG_CONTROL_ANIMATION if len == 24 => {
let raw_property = u16::from(read_u8(bytes, payload + 8)?);
let raw_op = u16::from(read_u8(bytes, payload + 10)?);
Mutation::ControlAnimation {
node_id: read_u32(bytes, payload)?,
animation_id: read_u32(bytes, payload + 4)?,
property: crate::schema::AnimatedProperty::try_from(raw_property)
.map_err(|()| ProtocolError::InvalidAnimation)?,
slot: read_u8(bytes, payload + 9)?,
op: crate::schema::AnimationControl::try_from(raw_op)
.map_err(|()| ProtocolError::InvalidAnimation)?,
at_time_nanos: read_u64(bytes, payload + 12)?,
}
}
TAG_SET_SELECTION if len == 28 => Mutation::SetSelection {
node_id: read_u32(bytes, payload)?,
base_version: read_u32(bytes, payload + 4)?,
anchor_line: read_u32(bytes, payload + 8)?,
anchor_column: read_u32(bytes, payload + 12)?,
active_line: read_u32(bytes, payload + 16)?,
active_column: read_u32(bytes, payload + 20)?,
},
TAG_REVEAL_RANGE if len == 32 => {
let raw_align = read_u32(bytes, payload + 24)?;
Mutation::RevealRange {
node_id: read_u32(bytes, payload)?,
base_version: read_u32(bytes, payload + 4)?,
start_line: read_u32(bytes, payload + 8)?,
start_column: read_u32(bytes, payload + 12)?,
end_line: read_u32(bytes, payload + 16)?,
end_column: read_u32(bytes, payload + 20)?,
align: u16::try_from(raw_align)
.ok()
.and_then(|tag| crate::schema::RevealAlign::try_from(tag).ok())
.ok_or(ProtocolError::InvalidValueKind(
raw_align.min(u32::from(u16::MAX)) as u16,
))?,
}
}
TAG_SET_FOLDED if len == 16 => Mutation::SetFolded {
node_id: read_u32(bytes, payload)?,
start_line: read_u32(bytes, payload + 4)?,
collapsed: read_flag(bytes, payload + 8)?,
},
TAG_CREATE..=TAG_EDIT_CODE
| TAG_START_ANIMATION
| TAG_CONTROL_ANIMATION
| TAG_SET_SELECTION..=TAG_SET_FOLDED => {
return Err(ProtocolError::InvalidRecordLength);
}
other => return Err(ProtocolError::InvalidTag(other)),
};
output.push(mutation);
position += len;
}
if output.len() != count {
return Err(ProtocolError::InvalidEnvelope);
}
Ok(output)
}
fn decode_animation(record: &[u8]) -> Result<crate::schema::Animation<'static>, ProtocolError> {
use crate::schema::{
AnimatedProperty, Animation, AnimationEvents, ColorInterpolation, FillMode, Keyframe,
KeyframeValue, PlayState, PlaybackDirection, StepPosition, Timing, TransformFunction,
TransformFunctionKind,
};
let bad = ProtocolError::InvalidAnimation;
fn byte_enum<T: TryFrom<u16, Error = ()>>(
record: &[u8],
at: usize,
) -> Result<T, ProtocolError> {
T::try_from(u16::from(read_u8(record, at)?)).map_err(|()| ProtocolError::InvalidAnimation)
}
let float = |at: usize| read_u32(record, at).map(f32::from_bits);
let property: AnimatedProperty = byte_enum(record, 12)?;
let direction: PlaybackDirection = byte_enum(record, 14)?;
let fill: FillMode = byte_enum(record, 15)?;
let play_state: PlayState = byte_enum(record, 16)?;
let colour = matches!(
property,
AnimatedProperty::Color | AnimatedProperty::Background | AnimatedProperty::BorderColor
);
let interpolation = match (colour, read_u8(record, 17)?) {
(false, 0) => None,
(true, raw) => Some(
ColorInterpolation::try_from(u16::from(raw))
.map_err(|()| ProtocolError::InvalidAnimation)?,
),
(false, _) => return Err(bad),
};
let keyframe_count = usize::from(read_u16(record, 18)?);
let delay_ms = float(28)?;
let duration_ms = float(32)?;
let iterations = float(36)?;
let origin_x = float(40)?;
let origin_y = float(44)?;
let events = read_u8(record, 48)?;
if keyframe_count < 2
|| !delay_ms.is_finite()
|| !(duration_ms.is_finite() && duration_ms >= 0.0)
|| iterations.is_nan()
|| iterations < 0.0
|| !(origin_x.is_finite() && origin_y.is_finite())
|| events & !AnimationEvents::ALL.0 != 0
{
return Err(bad);
}
let mut keyframes = Vec::with_capacity(keyframe_count);
let mut at = ANIMATION_HEADER_LEN;
for index in 0..keyframe_count {
let offset = float(at)?;
let raw_timing = read_u8(record, at + 4)?;
let raw_position = read_u8(record, at + 5)?;
let flags = read_u16(record, at + 6)?;
let timing = match raw_timing {
1 => Timing::Linear,
2 => {
let (x1, y1, x2, y2) = (
float(at + 8)?,
float(at + 12)?,
float(at + 16)?,
float(at + 20)?,
);
if ![x1, y1, x2, y2].iter().all(|value| value.is_finite())
|| !(0.0..=1.0).contains(&x1)
|| !(0.0..=1.0).contains(&x2)
{
return Err(bad);
}
Timing::CubicBezier { x1, y1, x2, y2 }
}
3 => {
let count = read_u32(record, at + 8)?;
let position =
StepPosition::try_from(u16::from(raw_position)).map_err(|()| bad.clone())?;
let least = if position == StepPosition::JumpNone {
2
} else {
1
};
if count < least {
return Err(bad);
}
Timing::Steps { count, position }
}
_ => return Err(bad),
};
if flags & !KEYFRAME_FROM_PRESENTED != 0
|| (index > 0 && flags & KEYFRAME_FROM_PRESENTED != 0)
|| !(0.0..=1.0).contains(&offset)
{
return Err(bad);
}
let value_at = at + KEYFRAME_HEAD_LEN;
let (value, value_len) = match property {
AnimatedProperty::Alpha => {
let value = float(value_at)?;
if !value.is_finite() {
return Err(bad);
}
(KeyframeValue::Alpha(value), 4)
}
AnimatedProperty::Color
| AnimatedProperty::Background
| AnimatedProperty::BorderColor => {
let paint = Paint::from_bits(read_u64(record, value_at)?)
.filter(|paint| !matches!(paint, Paint::Asset(_)))
.ok_or(bad.clone())?;
(KeyframeValue::Paint(paint), 8)
}
AnimatedProperty::Transform => {
let count = usize::from(read_u16(record, value_at)?);
let mut functions = Vec::with_capacity(count);
for function in 0..count {
let function_at = value_at + 4 + TRANSFORM_FUNCTION_LEN * function;
let raw_kind = read_u32(record, function_at)?;
let kind = u16::try_from(raw_kind)
.ok()
.and_then(|kind| TransformFunctionKind::try_from(kind).ok())
.ok_or(bad.clone())?;
let mut values = [0.0_f32; 6];
for (slot, value) in values.iter_mut().enumerate() {
*value = float(function_at + 4 + 4 * slot)?;
}
if !values.iter().all(|value| value.is_finite()) {
return Err(bad);
}
functions.push(TransformFunction { kind, values });
}
(
KeyframeValue::Transform(std::borrow::Cow::Owned(functions)),
4 + TRANSFORM_FUNCTION_LEN * count,
)
}
};
keyframes.push(Keyframe {
offset,
timing,
from_presented: flags & KEYFRAME_FROM_PRESENTED != 0,
value,
});
at = value_at + value_len;
}
if at != record.len() {
return Err(bad);
}
let first = keyframes.first().map(|keyframe| keyframe.offset);
let last = keyframes.last().map(|keyframe| keyframe.offset);
if first != Some(0.0)
|| last != Some(1.0)
|| keyframes
.windows(2)
.any(|pair| pair[1].offset < pair[0].offset)
{
return Err(bad);
}
if let Some(KeyframeValue::Transform(shape)) = keyframes.first().map(|keyframe| &keyframe.value)
{
let kinds: Vec<_> = shape.iter().map(|function| function.kind).collect();
let lined_up = keyframes.iter().all(|keyframe| match &keyframe.value {
KeyframeValue::Transform(functions) => functions
.iter()
.map(|function| function.kind)
.eq(kinds.iter().copied()),
_ => false,
});
if !lined_up {
return Err(bad);
}
}
Ok(Animation {
node_id: read_u32(record, 4)?,
animation_id: read_u32(record, 8)?,
property,
slot: read_u8(record, 13)?,
direction,
fill,
play_state,
interpolation,
start_time_nanos: read_u64(record, 20)?,
delay_ms,
duration_ms,
iterations,
origin_x,
origin_y,
events: AnimationEvents(events),
keyframes: std::borrow::Cow::Owned(keyframes),
})
}
const fn pack_floats(low: f32, high: f32) -> u64 {
(low.to_bits() as u64) | ((high.to_bits() as u64) << 32)
}
const fn unpack_low(word: u64) -> f32 {
f32::from_bits(word as u32)
}
const fn unpack_high(word: u64) -> f32 {
f32::from_bits((word >> 32) as u32)
}
const fn two_words(first: u64, second: u64) -> [u64; MAX_MODIFIER_WORDS] {
[first, second, 0, 0, 0, 0]
}
fn float_word(value: f32) -> u64 {
u64::from(value.to_bits())
}
fn modifier_fields(modifier: &Modifier) -> (u16, [u64; MAX_MODIFIER_WORDS]) {
match modifier {
Modifier::Empty => (0, two_words(0, 0)),
Modifier::Padding(value) => (1, two_words(float_word(*value), 0)),
Modifier::FillMaxWidth => (2, two_words(0, 0)),
Modifier::FillMaxHeight => (3, two_words(0, 0)),
Modifier::Width(value) => (4, two_words(float_word(*value), 0)),
Modifier::Height(value) => (5, two_words(float_word(*value), 0)),
Modifier::Size { width, height } => (6, two_words(float_word(*width), float_word(*height))),
Modifier::Background(paint) => (7, two_words(paint.to_bits(), 0)),
Modifier::Clickable { handler_id } => (8, two_words(*handler_id, 0)),
Modifier::PaddingRole(role) => (9, two_words(*role as u64, 0)),
Modifier::PaddingEach {
start,
top,
end,
bottom,
} => (
10,
two_words(pack_floats(*start, *top), pack_floats(*end, *bottom)),
),
Modifier::Weight(value) => (11, two_words(float_word(*value), 0)),
Modifier::Shape {
top_start,
top_end,
bottom_end,
bottom_start,
} => (
12,
two_words(
pack_floats(*top_start, *top_end),
pack_floats(*bottom_end, *bottom_start),
),
),
Modifier::ShapeRole(role) => (13, two_words(*role as u64, 0)),
Modifier::Border { width, paint } => (14, two_words(float_word(*width), paint.to_bits())),
Modifier::Elevation(dp) => (15, two_words(float_word(*dp), 0)),
Modifier::ObserveSize { token } => (16, two_words(u64::from(*token), 0)),
Modifier::Motion(role) => (17, two_words(*role as u64, 0)),
Modifier::Material(role) => (18, two_words(*role as u64, 0)),
Modifier::Offset { x, y } => (19, two_words(float_word(*x), float_word(*y))),
Modifier::RequiredSize { width, height } => {
(20, two_words(float_word(*width), float_word(*height)))
}
Modifier::BorderEach {
top,
right,
bottom,
left,
top_paint,
right_paint,
bottom_paint,
left_paint,
} => (
21,
[
pack_floats(*top, *right),
pack_floats(*bottom, *left),
top_paint.to_bits(),
right_paint.to_bits(),
bottom_paint.to_bits(),
left_paint.to_bits(),
],
),
Modifier::CornerEach {
top_left,
top_right,
bottom_right,
bottom_left,
} => (
22,
two_words(
pack_floats(*top_left, *top_right),
pack_floats(*bottom_right, *bottom_left),
),
),
Modifier::Shadow {
x,
y,
blur,
spread,
paint,
} => (
23,
[
pack_floats(*x, *y),
pack_floats(*blur, *spread),
paint.to_bits(),
0,
0,
0,
],
),
Modifier::Clip(enabled) => (24, two_words(u64::from(*enabled), 0)),
Modifier::Alpha(value) => (25, two_words(float_word(*value), 0)),
Modifier::Transform {
a,
b,
c,
d,
e,
f,
origin_x,
origin_y,
} => (
26,
[
pack_floats(*a, *b),
pack_floats(*c, *d),
pack_floats(*e, *f),
pack_floats(*origin_x, *origin_y),
0,
0,
],
),
}
}
fn decode_role<T: TryFrom<u16, Error = ()>>(first: u64) -> Result<T, ProtocolError> {
let tag = u16::try_from(first).map_err(|_| ProtocolError::InvalidModifier(0))?;
T::try_from(tag).map_err(|()| ProtocolError::InvalidModifier(tag))
}
fn decode_paint(bits: u64) -> Result<Paint, ProtocolError> {
Paint::from_bits(bits).ok_or(ProtocolError::InvalidModifier(0))
}
fn decode_modifier(tag: u16, words: &[u64; MAX_MODIFIER_WORDS]) -> Result<Modifier, ProtocolError> {
let [first, second, third, fourth, fifth, sixth] = *words;
match tag {
0 => Ok(Modifier::Empty),
1 => Ok(Modifier::Padding(f32::from_bits(first as u32))),
2 => Ok(Modifier::FillMaxWidth),
3 => Ok(Modifier::FillMaxHeight),
4 => Ok(Modifier::Width(f32::from_bits(first as u32))),
5 => Ok(Modifier::Height(f32::from_bits(first as u32))),
6 => Ok(Modifier::Size {
width: f32::from_bits(first as u32),
height: f32::from_bits(second as u32),
}),
7 => Ok(Modifier::Background(decode_paint(first)?)),
8 => Ok(Modifier::Clickable { handler_id: first }),
9 => Ok(Modifier::PaddingRole(decode_role::<SpaceRole>(first)?)),
10 => Ok(Modifier::PaddingEach {
start: unpack_low(first),
top: unpack_high(first),
end: unpack_low(second),
bottom: unpack_high(second),
}),
11 => Ok(Modifier::Weight(f32::from_bits(first as u32))),
12 => Ok(Modifier::Shape {
top_start: unpack_low(first),
top_end: unpack_high(first),
bottom_end: unpack_low(second),
bottom_start: unpack_high(second),
}),
13 => Ok(Modifier::ShapeRole(decode_role::<ShapeRole>(first)?)),
14 => Ok(Modifier::Border {
width: f32::from_bits(first as u32),
paint: decode_paint(second)?,
}),
15 => Ok(Modifier::Elevation(f32::from_bits(first as u32))),
16 => Ok(Modifier::ObserveSize {
token: first as u32,
}),
17 => Ok(Modifier::Motion(decode_role::<MotionRole>(first)?)),
18 => Ok(Modifier::Material(decode_role::<MaterialRole>(first)?)),
19 => Ok(Modifier::Offset {
x: f32::from_bits(first as u32),
y: f32::from_bits(second as u32),
}),
20 => Ok(Modifier::RequiredSize {
width: f32::from_bits(first as u32),
height: f32::from_bits(second as u32),
}),
21 => Ok(Modifier::BorderEach {
top: unpack_low(first),
right: unpack_high(first),
bottom: unpack_low(second),
left: unpack_high(second),
top_paint: decode_paint(third)?,
right_paint: decode_paint(fourth)?,
bottom_paint: decode_paint(fifth)?,
left_paint: decode_paint(sixth)?,
}),
22 => Ok(Modifier::CornerEach {
top_left: unpack_low(first),
top_right: unpack_high(first),
bottom_right: unpack_low(second),
bottom_left: unpack_high(second),
}),
23 => Ok(Modifier::Shadow {
x: unpack_low(first),
y: unpack_high(first),
blur: unpack_low(second),
spread: unpack_high(second),
paint: decode_paint(third)?,
}),
24 => match first {
0 => Ok(Modifier::Clip(false)),
1 => Ok(Modifier::Clip(true)),
_ => Err(ProtocolError::InvalidModifier(24)),
},
25 => Ok(Modifier::Alpha(f32::from_bits(first as u32))),
26 => {
let values = [
unpack_low(first),
unpack_high(first),
unpack_low(second),
unpack_high(second),
unpack_low(third),
unpack_high(third),
unpack_low(fourth),
unpack_high(fourth),
];
if values.iter().any(|value| !value.is_finite()) {
return Err(ProtocolError::InvalidModifier(26));
}
let [a, b, c, d, e, f, origin_x, origin_y] = values;
Ok(Modifier::Transform {
a,
b,
c,
d,
e,
f,
origin_x,
origin_y,
})
}
other => Err(ProtocolError::InvalidModifier(other)),
}
}
fn read_string(bytes: &[u8], position: usize, arena_start: usize) -> Result<&str, ProtocolError> {
let value = read_bytes(bytes, position, arena_start)?;
std::str::from_utf8(value).map_err(|_| ProtocolError::InvalidUtf8)
}
fn read_bytes(bytes: &[u8], position: usize, arena_start: usize) -> Result<&[u8], ProtocolError> {
let offset =
usize::try_from(read_u32(bytes, position)?).map_err(|_| ProtocolError::LengthOverflow)?;
let len = usize::try_from(read_u32(bytes, position + 4)?)
.map_err(|_| ProtocolError::LengthOverflow)?;
let end = offset
.checked_add(len)
.ok_or(ProtocolError::InvalidStringRange)?;
if offset < arena_start {
return Err(ProtocolError::InvalidStringRange);
}
bytes
.get(offset..end)
.ok_or(ProtocolError::InvalidStringRange)
}
fn read_u8(bytes: &[u8], position: usize) -> Result<u8, ProtocolError> {
bytes.get(position).copied().ok_or(ProtocolError::Truncated)
}
fn read_u16(bytes: &[u8], position: usize) -> Result<u16, ProtocolError> {
let value = bytes
.get(position..position + 2)
.ok_or(ProtocolError::Truncated)?;
Ok(u16::from_le_bytes([value[0], value[1]]))
}
fn read_u32(bytes: &[u8], position: usize) -> Result<u32, ProtocolError> {
let value = bytes
.get(position..position + 4)
.ok_or(ProtocolError::Truncated)?;
Ok(u32::from_le_bytes([value[0], value[1], value[2], value[3]]))
}
fn read_flag(bytes: &[u8], position: usize) -> Result<bool, ProtocolError> {
match read_u32(bytes, position)? {
0 => Ok(false),
1 => Ok(true),
other => Err(ProtocolError::InvalidValueKind(
other.min(u32::from(u16::MAX)) as u16,
)),
}
}
fn read_u64(bytes: &[u8], position: usize) -> Result<u64, ProtocolError> {
let value = bytes
.get(position..position + 8)
.ok_or(ProtocolError::Truncated)?;
Ok(u64::from_le_bytes(
value.try_into().map_err(|_| ProtocolError::Truncated)?,
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::schema::TypeRole;
#[test]
fn round_trips_all_m0_mutations() {
let mutations = [
Mutation::Create {
node_id: 1,
widget: WidgetKind::Column,
},
Mutation::SetProp {
node_id: 2,
property: PropertyKind::Text,
value: PropertyValue::String("안녕"),
},
Mutation::SetProp {
node_id: 3,
property: PropertyKind::Enabled,
value: PropertyValue::Bool(true),
},
Mutation::SetModifier {
node_id: 1,
index: 0,
modifier: Modifier::Padding(16.0),
},
Mutation::Insert {
parent_id: 1,
node_id: 2,
index: 0,
},
Mutation::Move {
parent_id: 1,
node_id: 2,
index: 1,
},
Mutation::Remove { node_id: 3 },
Mutation::SetText {
node_id: 4,
text: "compose",
selection: Some(Selection { start: 1, end: 4 }),
},
Mutation::AppendText {
node_id: 4,
text: " token",
},
];
let mut encoder = BatchEncoder::default();
for mutation in &mutations {
encoder.encode(mutation).unwrap();
}
let decoded = decode_batch(encoder.finish().unwrap()).unwrap();
assert_eq!(decoded, mutations);
}
#[test]
fn fr13_paint_role_and_literal_share_one_record_length() {
let encode_one = |modifier| {
let mut encoder = BatchEncoder::default();
encoder
.encode(&Mutation::SetModifier {
node_id: 1,
index: 0,
modifier,
})
.unwrap();
encoder.finish().unwrap().to_vec()
};
let role = encode_one(Modifier::Background(Paint::Role(
crate::schema::ColorRole::Surface,
)));
let literal = encode_one(Modifier::Background(Paint::Literal(
crate::schema::Color::rgb(0x1b1b1f),
)));
assert_eq!(role.len(), literal.len());
assert_eq!(
decode_batch(&role).unwrap(),
[Mutation::SetModifier {
node_id: 1,
index: 0,
modifier: Modifier::Background(Paint::Role(crate::schema::ColorRole::Surface)),
}]
);
assert_eq!(
decode_batch(&literal).unwrap(),
[Mutation::SetModifier {
node_id: 1,
index: 0,
modifier: Modifier::Background(Paint::Literal(crate::schema::Color::rgb(0x1b1b1f))),
}]
);
}
#[test]
fn fr13_design_modifiers_round_trip_in_two_words() {
let modifiers = [
Modifier::PaddingRole(SpaceRole::Md),
Modifier::PaddingEach {
start: 1.0,
top: 2.0,
end: 3.0,
bottom: 4.0,
},
Modifier::Weight(0.25),
Modifier::Shape {
top_start: 4.0,
top_end: 8.0,
bottom_end: 12.0,
bottom_start: 16.0,
},
Modifier::ShapeRole(ShapeRole::Full),
Modifier::Border {
width: 2.0,
paint: Paint::Role(crate::schema::ColorRole::Outline),
},
Modifier::Elevation(6.0),
];
let mut encoder = BatchEncoder::default();
let expected: Vec<_> = modifiers
.iter()
.enumerate()
.map(|(index, modifier)| Mutation::SetModifier {
node_id: 1,
index: index as u16,
modifier: modifier.clone(),
})
.collect();
for mutation in &expected {
encoder.encode(mutation).unwrap();
}
let bytes = encoder.finish().unwrap();
assert_eq!(bytes.len(), ENVELOPE_LEN + expected.len() * 28);
assert_eq!(decode_batch(bytes).unwrap(), expected);
}
#[test]
fn fr23_set_theme_round_trips_with_a_slot_for_every_type_role() {
let theme = Theme::adaptive(DesignSystem::Cupertino)
.with_color_scheme(ColorScheme::Dark)
.with_font(TypeRole::Display, 7);
let length = 20 + 4 * TYPE_ROLE_COUNT as u16;
let mut encoder = BatchEncoder::default();
encoder.encode(&Mutation::SetTheme(theme)).unwrap();
let bytes = encoder.finish().unwrap();
assert_eq!(bytes.len(), ENVELOPE_LEN + length as usize);
assert_eq!(read_u16(bytes, ENVELOPE_LEN).unwrap(), TAG_SET_THEME);
assert_eq!(read_u16(bytes, ENVELOPE_LEN + 2).unwrap(), length);
assert_eq!(decode_batch(bytes).unwrap(), [Mutation::SetTheme(theme)]);
}
#[test]
fn fr23_a_font_is_named_for_one_role_and_no_others() {
let theme = Theme::unified(DesignSystem::Material3).with_font(TypeRole::Display, 3);
assert_eq!(theme.font(TypeRole::Display), Some(3));
for role in [
TypeRole::Body,
TypeRole::Title,
TypeRole::Mono,
TypeRole::Caption,
] {
assert_eq!(theme.font(role), None);
}
}
#[test]
fn fr14_rejects_unknown_theme_tags() {
let mut encoder = BatchEncoder::default();
encoder
.encode(&Mutation::SetTheme(Theme::default()))
.unwrap();
let mut bytes = encoder.finish().unwrap().to_vec();
bytes[ENVELOPE_LEN + 4..ENVELOPE_LEN + 6].copy_from_slice(&99_u16.to_le_bytes());
assert_eq!(decode_batch(&bytes), Err(ProtocolError::InvalidTheme(99)));
}
#[test]
fn rejects_unknown_tags_without_panicking() {
let mut bytes = vec![0, 0, 12, 0, 20, 0, 0, 0, 1, 0, 0, 0];
bytes.extend_from_slice(&999_u16.to_le_bytes());
bytes.extend_from_slice(&8_u16.to_le_bytes());
bytes.extend_from_slice(&0_u32.to_le_bytes());
assert_eq!(decode_batch(&bytes), Err(ProtocolError::InvalidTag(999)));
}
#[test]
fn pr4_renderer_events_round_trip_fixed_layout() {
let events = [
HostEvent {
node_id: 7,
handler_id: 11,
payload: crate::EventPayload::Clicked,
},
HostEvent {
node_id: 8,
handler_id: 12,
payload: crate::EventPayload::TextChanged("한글"),
},
HostEvent {
node_id: 8,
handler_id: 13,
payload: crate::EventPayload::TextSubmitted("done"),
},
HostEvent {
node_id: 8,
handler_id: 14,
payload: crate::EventPayload::FocusLost,
},
HostEvent {
node_id: 9,
handler_id: 16,
payload: crate::EventPayload::RangeRequested {
start: 100,
count: 20,
},
},
HostEvent {
node_id: 8,
handler_id: 15,
payload: crate::EventPayload::KeyDown {
key: Key::Enter,
shift_key: true,
ctrl_key: false,
alt_key: true,
meta_key: false,
},
},
];
let mut bytes = Vec::new();
for event in events {
encode_event(&event, &mut bytes).unwrap();
assert_eq!(decode_event(&bytes).unwrap(), event);
}
}
#[test]
fn fr20_window_size_changed_round_trips_in_thirty_two_bytes() {
let event = HostEvent {
node_id: 0,
handler_id: 0,
payload: crate::EventPayload::WindowSizeChanged {
width_dp: 841.5,
height_dp: 600.25,
class: crate::WindowSizeClass::Expanded,
height_class: crate::WindowHeightClass::Medium,
},
};
let mut bytes = Vec::new();
encode_event(&event, &mut bytes).unwrap();
assert_eq!(bytes.len(), 32);
assert_eq!(decode_event(&bytes).unwrap(), event);
}
#[test]
fn fr20_unknown_window_size_class_is_a_protocol_error() {
let event = HostEvent {
node_id: 0,
handler_id: 0,
payload: crate::EventPayload::WindowSizeChanged {
width_dp: 320.0,
height_dp: 640.0,
class: crate::WindowSizeClass::Compact,
height_class: crate::WindowHeightClass::Medium,
},
};
let mut bytes = Vec::new();
encode_event(&event, &mut bytes).unwrap();
bytes[24..28].copy_from_slice(&99_u32.to_le_bytes());
assert_eq!(
decode_event(&bytes),
Err(ProtocolError::InvalidValueKind(99))
);
}
#[test]
fn fr20_unknown_window_height_class_is_a_protocol_error() {
let event = HostEvent {
node_id: 0,
handler_id: 0,
payload: crate::EventPayload::WindowSizeChanged {
width_dp: 320.0,
height_dp: 640.0,
class: crate::WindowSizeClass::Compact,
height_class: crate::WindowHeightClass::Medium,
},
};
let mut bytes = Vec::new();
encode_event(&event, &mut bytes).unwrap();
bytes[28..32].copy_from_slice(&99_u32.to_le_bytes());
assert_eq!(
decode_event(&bytes),
Err(ProtocolError::InvalidValueKind(99))
);
}
#[test]
fn fr20_window_size_classes_follow_the_material_boundaries() {
use crate::WindowSizeClass;
assert_eq!(
WindowSizeClass::from_width_dp(599.9),
WindowSizeClass::Compact
);
assert_eq!(
WindowSizeClass::from_width_dp(600.0),
WindowSizeClass::Medium
);
assert_eq!(
WindowSizeClass::from_width_dp(839.9),
WindowSizeClass::Medium
);
assert_eq!(
WindowSizeClass::from_width_dp(840.0),
WindowSizeClass::Expanded
);
}
#[test]
fn fr20_window_height_classes_follow_the_material_boundaries() {
use crate::WindowHeightClass;
assert_eq!(
WindowHeightClass::from_height_dp(479.9),
WindowHeightClass::Compact
);
assert_eq!(
WindowHeightClass::from_height_dp(480.0),
WindowHeightClass::Medium
);
assert_eq!(
WindowHeightClass::from_height_dp(899.9),
WindowHeightClass::Medium
);
assert_eq!(
WindowHeightClass::from_height_dp(900.0),
WindowHeightClass::Expanded
);
}
fn post_notification() -> Mutation<'static> {
Mutation::PostNotification {
key: "session/7",
title: "세션이 끝났습니다",
body: "refactor-parser: 테스트 214개 통과",
channel: "세션",
action_1: "열기",
action_2: "",
importance: NotificationImportance::Urgent,
presentation: NotificationPresentation::WhenInactive,
}
}
#[test]
fn fr36_notification_commands_round_trip_at_their_fixed_lengths() {
let mutations = [
post_notification(),
Mutation::WithdrawNotification { key: "session/7" },
Mutation::RequestNotificationPermission,
];
let mut lengths = Vec::new();
let mut encoder = BatchEncoder::default();
for mutation in &mutations {
encoder.encode(mutation).unwrap();
}
let bytes = encoder.finish().unwrap();
let mut position = ENVELOPE_LEN;
for _ in &mutations {
let length = read_u16(bytes, position + 2).unwrap();
lengths.push((read_u16(bytes, position).unwrap(), length));
position += usize::from(length);
}
assert_eq!(
lengths,
[
(TAG_POST_NOTIFICATION, 56),
(TAG_WITHDRAW_NOTIFICATION, 12),
(TAG_REQUEST_NOTIFICATION_PERMISSION, 4),
]
);
assert_eq!(decode_batch(bytes).unwrap(), mutations);
}
#[test]
fn fr36_unknown_importance_and_presentation_are_protocol_errors() {
for (offset, expected) in [(52, 9_u16), (54, 7_u16)] {
let mut encoder = BatchEncoder::default();
encoder.encode(&post_notification()).unwrap();
let mut bytes = encoder.finish().unwrap().to_vec();
bytes[ENVELOPE_LEN + offset..ENVELOPE_LEN + offset + 2]
.copy_from_slice(&expected.to_le_bytes());
assert_eq!(
decode_batch(&bytes),
Err(ProtocolError::InvalidNotification(expected))
);
}
}
#[test]
fn fr36_a_notification_record_of_the_wrong_length_is_rejected() {
let mut bytes = vec![0, 0, 12, 0, 20, 0, 0, 0, 1, 0, 0, 0];
bytes.extend_from_slice(&TAG_REQUEST_NOTIFICATION_PERMISSION.to_le_bytes());
bytes.extend_from_slice(&8_u16.to_le_bytes());
bytes.extend_from_slice(&0_u32.to_le_bytes());
assert_eq!(
decode_batch(&bytes),
Err(ProtocolError::InvalidRecordLength)
);
}
#[test]
fn fr36_notification_events_round_trip_fixed_layout() {
let activated = HostEvent {
node_id: 0,
handler_id: 0,
payload: crate::EventPayload::NotificationActivated {
action: 2,
key: "session/7",
},
};
let mut bytes = Vec::new();
encode_event(&activated, &mut bytes).unwrap();
assert_eq!(read_u16(&bytes, 0).unwrap(), EVENT_NOTIFICATION_ACTIVATED);
assert_eq!(read_u16(&bytes, 2).unwrap(), 28);
assert_eq!(bytes.len(), 28 + "session/7".len());
assert_eq!(decode_event(&bytes).unwrap(), activated);
for state in [
NotificationPermission::NotDetermined,
NotificationPermission::Granted,
NotificationPermission::Denied,
NotificationPermission::Unsupported,
] {
let changed = HostEvent {
node_id: 0,
handler_id: 0,
payload: crate::EventPayload::NotificationPermissionChanged(state),
};
encode_event(&changed, &mut bytes).unwrap();
assert_eq!(bytes.len(), 20);
assert_eq!(decode_event(&bytes).unwrap(), changed);
}
}
#[test]
fn fr36_unknown_permission_state_is_a_protocol_error() {
let mut bytes = Vec::new();
encode_event(
&HostEvent {
node_id: 0,
handler_id: 0,
payload: crate::EventPayload::NotificationPermissionChanged(
NotificationPermission::Granted,
),
},
&mut bytes,
)
.unwrap();
bytes[16..20].copy_from_slice(&5_u32.to_le_bytes());
assert_eq!(
decode_event(&bytes),
Err(ProtocolError::InvalidValueKind(5))
);
}
#[test]
fn fr36_an_activation_naming_a_third_button_is_a_protocol_error() {
let mut bytes = Vec::new();
encode_event(
&HostEvent {
node_id: 0,
handler_id: 0,
payload: crate::EventPayload::NotificationActivated {
action: 1,
key: "k",
},
},
&mut bytes,
)
.unwrap();
bytes[16..20].copy_from_slice(&3_u32.to_le_bytes());
assert_eq!(
decode_event(&bytes),
Err(ProtocolError::InvalidValueKind(3))
);
}
#[test]
fn fr43_zoom_changed_round_trips_fixed_layout() {
let event = HostEvent {
node_id: 0,
handler_id: 0,
payload: crate::EventPayload::ZoomChanged {
k: 1.25 * 1.44,
os: 1.25,
level: 2,
},
};
let mut bytes = Vec::new();
encode_event(&event, &mut bytes).unwrap();
assert_eq!(read_u16(&bytes, 0).unwrap(), 33);
assert_eq!(read_u16(&bytes, 2).unwrap(), 28);
assert_eq!(bytes.len(), 28);
assert_eq!(decode_event(&bytes).unwrap(), event);
let smaller = HostEvent {
node_id: 0,
handler_id: 0,
payload: crate::EventPayload::ZoomChanged {
k: 0.5,
os: 1.0,
level: -4,
},
};
encode_event(&smaller, &mut bytes).unwrap();
assert_eq!(decode_event(&bytes).unwrap(), smaller);
}
#[test]
fn fr43_a_zoom_level_out_of_range_is_rejected() {
let event = HostEvent {
node_id: 0,
handler_id: 0,
payload: crate::EventPayload::ZoomChanged {
k: 1.0,
os: 1.0,
level: 9,
},
};
let mut bytes = Vec::new();
encode_event(&event, &mut bytes).unwrap();
assert_eq!(
decode_event(&bytes),
Err(ProtocolError::InvalidValueKind(9))
);
bytes[2..4].copy_from_slice(&24_u16.to_le_bytes());
assert_eq!(
decode_event(&bytes),
Err(ProtocolError::InvalidRecordLength)
);
}
#[test]
fn fr43_the_window_record_carries_a_zoom_level() {
for level in [None, Some(-8), Some(0), Some(2), Some(8)] {
let mut window = crate::schema::Window::new().with_title("zoom");
window.zoom_level = level;
let mut encoder = BatchEncoder::default();
encoder.encode(&Mutation::SetWindow(window)).unwrap();
let bytes = encoder.finish().unwrap().to_vec();
if level.is_none() {
assert_eq!(read_u16(&bytes, ENVELOPE_LEN + 18).unwrap(), 0);
}
let decoded = decode_batch(&bytes).unwrap();
assert_eq!(decoded, vec![Mutation::SetWindow(window)]);
}
assert_eq!(
crate::schema::Window::new().with_zoom_level(20).zoom_level,
Some(8)
);
}
}