Skip to main content

compose_rust/
protocol.rs

1//! Fixed-layout little-endian boundary protocol.
2
3use crate::schema::{
4    AssetKind, ColorScheme, DesignSystem, Key, MAX_MODIFIER_WORDS, MaterialRole, MessageDuration,
5    Modifier, MotionRole, NotificationImportance, NotificationPermission, NotificationPresentation,
6    Paint, PropertyKind, Selection, ShapeRole, SpaceRole, TYPE_ROLE_COUNT, Theme, WidgetKind,
7    modifier_extra_words,
8};
9use core::fmt;
10
11const TAG_ENVELOPE: u16 = 0;
12const TAG_CREATE: u16 = 1;
13const TAG_SET_PROP: u16 = 2;
14const TAG_SET_MODIFIER: u16 = 3;
15const TAG_INSERT: u16 = 4;
16const TAG_MOVE: u16 = 5;
17const TAG_REMOVE: u16 = 6;
18const TAG_SET_TEXT: u16 = 7;
19const TAG_APPEND_TEXT: u16 = 8;
20const TAG_SET_THEME: u16 = 9;
21const TAG_REGISTER_ASSET: u16 = 10;
22const TAG_RELEASE_ASSET: u16 = 11;
23const TAG_SHOW_MESSAGE: u16 = 12;
24const TAG_SET_WINDOW: u16 = 13;
25const TAG_POST_NOTIFICATION: u16 = 14;
26const TAG_WITHDRAW_NOTIFICATION: u16 = 15;
27const TAG_REQUEST_NOTIFICATION_PERMISSION: u16 = 16;
28const TAG_EDIT_CODE: u16 = 17;
29const TAG_START_ANIMATION: u16 = 18;
30const TAG_CONTROL_ANIMATION: u16 = 19;
31// 20 to 22 are the code editor's: the selection, a range to bring into view, a fold.
32const TAG_SET_SELECTION: u16 = 20;
33const TAG_REVEAL_RANGE: u16 = 21;
34const TAG_SET_FOLDED: u16 = 22;
35// 23 and 24 are the windows': a theme for one window, and bringing one forward.
36const TAG_SET_WINDOW_THEME: u16 = 23;
37const TAG_FOCUS_WINDOW: u16 = 24;
38/// The head of a `StartAnimation` record, before its keyframes.
39const ANIMATION_HEADER_LEN: usize = 52;
40/// A keyframe before its value: offset, timing, step position, flags and four numbers.
41const KEYFRAME_HEAD_LEN: usize = 24;
42/// One transform function: its kind and six numbers.
43const TRANSFORM_FUNCTION_LEN: usize = 28;
44const KEYFRAME_FROM_PRESENTED: u16 = 1;
45const ENVELOPE_LEN: usize = 12;
46/// `SetTheme` after its header: four `u16` fields, one font slot per type role, and the
47/// palette's `(offset, len)` reference. 52 bytes, so the record is 56.
48pub const THEME_PAYLOAD_LEN: usize = 8 + 4 * TYPE_ROLE_COUNT + 8;
49/// The shortest mutation record on the wire (`RequestNotificationPermission`: the 4-byte
50/// header and nothing else).
51const MIN_RECORD_LEN: usize = 4;
52
53const VALUE_NONE: u16 = 0;
54const VALUE_STRING: u16 = 1;
55const VALUE_BOOL: u16 = 2;
56const VALUE_I64: u16 = 3;
57const VALUE_F32: u16 = 4;
58/// An opaque byte run in the arena. Same `(offset, length)` layout as a string, with no
59/// UTF-8 check, because a Canvas command list is not text.
60const VALUE_BYTES: u16 = 5;
61
62#[derive(Clone, Debug, PartialEq)]
63pub enum PropertyValue<'a> {
64    None,
65    String(&'a str),
66    Bool(bool),
67    Integer(i64),
68    Float(f32),
69    Bytes(&'a [u8]),
70}
71
72#[derive(Clone, Debug, PartialEq)]
73pub enum Mutation<'a> {
74    Create {
75        node_id: u32,
76        widget: WidgetKind,
77    },
78    SetProp {
79        node_id: u32,
80        property: PropertyKind,
81        value: PropertyValue<'a>,
82    },
83    SetModifier {
84        node_id: u32,
85        index: u16,
86        modifier: Modifier,
87    },
88    Insert {
89        parent_id: u32,
90        node_id: u32,
91        index: u32,
92    },
93    Move {
94        parent_id: u32,
95        node_id: u32,
96        index: u32,
97    },
98    Remove {
99        node_id: u32,
100    },
101    SetText {
102        node_id: u32,
103        text: &'a str,
104        selection: Option<Selection>,
105    },
106    /// Appends the streamed tail to a Text node instead of resending the whole string.
107    AppendText {
108        node_id: u32,
109        text: &'a str,
110    },
111    /// The root theme. Sent once as the first record of the initial batch.
112    SetTheme(Theme),
113    /// What the application asked of its own window.
114    ///
115    /// Written once per rebuild beside the theme, and read by the Renderer before it
116    /// stands the window up: `compose_rust_host_init` answers with the first batch, so
117    /// the setting is in hand before there is a window to apply it to. Platforms where
118    /// the window is not ours ignore it, which is not an error but an absence of anywhere
119    /// to put it.
120    SetWindow(crate::schema::Window),
121    /// Hands the Renderer the bytes of one asset. The Renderer copies them into its own
122    /// cache inside this call, because the batch buffer is only valid for the call that
123    /// carries it and an image has to outlive the frame that draws it.
124    RegisterAsset {
125        asset_id: u32,
126        kind: AssetKind,
127        bytes: &'a [u8],
128    },
129    /// Drops an asset from the Renderer's cache. The Host owns the lifetime.
130    ReleaseAsset {
131        asset_id: u32,
132    },
133    /// Says a sentence to the user once. It is not a node, because the Host would then
134    /// have to hold "showing until four seconds from now" and run a render to take it
135    /// away again. How long it stays, where it sits and what happens when a second one
136    /// arrives while the first is up are all the Renderer's.
137    ///
138    /// `handler_id` is what the action label reports when it is pressed, or 0 when the
139    /// message has no action, in which case `action` is empty too.
140    ShowMessage {
141        handler_id: u64,
142        text: &'a str,
143        action: &'a str,
144        duration: MessageDuration,
145    },
146    /// Asks the platform to show one notification outside the window.
147    ///
148    /// Not a node, for the same reason a message is not: it outlives the component that
149    /// posted it, and when it goes away depends on what the user does in the notification
150    /// centre, which the Host never hears about. Posting again under the same `key`
151    /// replaces it. An empty `key` asks the Renderer to name it, and such a notification
152    /// can be neither replaced nor withdrawn. An empty action label means no button.
153    PostNotification {
154        key: &'a str,
155        title: &'a str,
156        body: &'a str,
157        channel: &'a str,
158        action_1: &'a str,
159        action_2: &'a str,
160        importance: NotificationImportance,
161        presentation: NotificationPresentation,
162    },
163    /// Takes back the notification posted under `key`, if it is still showing.
164    WithdrawNotification {
165        key: &'a str,
166    },
167    /// Asks the platform for permission to show notifications. It carries nothing: the
168    /// answer comes back as a permission event.
169    RequestNotificationPermission,
170    /// Replaces a range of a code editor's document, as it stood at `base_version`, with
171    /// `text`.
172    ///
173    /// The Renderer owns the document, so this is a request and not an assignment: where
174    /// the reader has typed since that version the range is moved along with what they
175    /// typed, and where they typed in the same place the edit is refused and
176    /// `CodeEditRejected` names `request_id`. An edit that is applied comes back as an
177    /// ordinary `CodeChanged`, so the version stays one sequence whoever made the edit.
178    EditCode {
179        node_id: u32,
180        request_id: u32,
181        base_version: u32,
182        start_line: u32,
183        start_column: u32,
184        end_line: u32,
185        end_column: u32,
186        text: &'a str,
187    },
188    /// Hands the Renderer one animation to play on its own frame clock until it ends.
189    /// Nothing crosses the boundary while it plays unless it asked for events.
190    StartAnimation(crate::schema::Animation<'a>),
191    /// Pauses, resumes or cancels one animation the Renderer is playing. A key the Renderer
192    /// does not hold, or an id that is not the one it holds, is left alone: an animation
193    /// that ended on its own is not an error to stop.
194    ControlAnimation {
195        node_id: u32,
196        animation_id: u32,
197        property: crate::schema::AnimatedProperty,
198        slot: u8,
199        op: crate::schema::AnimationControl,
200        /// On the Renderer's frame clock. Zero means the first frame that draws it.
201        at_time_nanos: u64,
202    },
203    /// Puts a code editor's selection between two places in the document as it stood at
204    /// `base_version`. Both ends are moved along with whatever was typed since, and an end
205    /// that is outside the document, or between the two halves of a pair, drops the
206    /// command and comes back as a protocol error.
207    SetSelection {
208        node_id: u32,
209        base_version: u32,
210        anchor_line: u32,
211        anchor_column: u32,
212        active_line: u32,
213        active_column: u32,
214    },
215    /// Scrolls a code editor until a range of the document, as it stood at `base_version`,
216    /// is on screen. A range inside a folded stretch unfolds it first.
217    RevealRange {
218        node_id: u32,
219        base_version: u32,
220        start_line: u32,
221        start_column: u32,
222        end_line: u32,
223        end_column: u32,
224        align: crate::schema::RevealAlign,
225    },
226    /// Folds or unfolds the stretch of a code editor that starts on `start_line`.
227    SetFolded {
228        node_id: u32,
229        start_line: u32,
230        collapsed: bool,
231    },
232    /// A theme for one `Window` node, over the application's. The payload is
233    /// `SetTheme`'s, after the node id. A window without one follows the application's.
234    SetWindowTheme {
235        node_id: u32,
236        theme: Theme,
237    },
238    /// Brings a window forward and makes it the key window. A command and not a
239    /// property: it happens once.
240    FocusWindow {
241        node_id: u32,
242    },
243}
244
245#[derive(Clone, Debug, Eq, PartialEq)]
246pub enum ProtocolError {
247    Truncated,
248    InvalidEnvelope,
249    InvalidRecordLength,
250    InvalidTag(u16),
251    InvalidWidget(u16),
252    InvalidProperty(u16),
253    InvalidValueKind(u16),
254    InvalidModifier(u16),
255    InvalidTheme(u16),
256    InvalidAssetKind(u16),
257    InvalidMessageDuration(u16),
258    /// A notification importance or presentation this decoder does not know.
259    InvalidNotification(u16),
260    InvalidStringRange,
261    InvalidUtf8,
262    LengthOverflow,
263    /// The Renderer refused a whole measure call with this status: its buffer could not be
264    /// read, it came from the wrong thread, or there was nothing to measure with yet.
265    MeasureRefused(i32),
266    /// An animation record that is well formed bytes but not an animation: an unknown
267    /// value, fewer than two keyframes, offsets that do not run from 0 to 1, a negative or
268    /// non-numeric duration or iteration count, keyframes whose values or transform lists
269    /// do not match, or a length that does not hold its keyframes.
270    InvalidAnimation,
271    /// A `Window` node that is not a child of the root, or a node moved from one window
272    /// into another. Compose keeps one composition per window and has no
273    /// way to hand a node from one to the next.
274    MisplacedWindow,
275}
276
277#[derive(Clone, Debug, PartialEq)]
278pub struct HostEvent<'a> {
279    pub node_id: u32,
280    pub handler_id: u64,
281    pub payload: crate::schema::EventPayload<'a>,
282}
283
284const EVENT_CLICK: u16 = 1;
285const EVENT_TEXT_CHANGED: u16 = 2;
286const EVENT_TEXT_SUBMITTED: u16 = 3;
287const EVENT_FOCUS_LOST: u16 = 4;
288const EVENT_PROTOCOL_ERROR: u16 = 5;
289const EVENT_KEY_DOWN: u16 = 6;
290const EVENT_RANGE_REQUESTED: u16 = 7;
291// Tag 8 is the pointer event and 9 to 15 are reserved for the gesture events, so this one
292// starts at 16 and the window size report continues from 17.
293const EVENT_POINTER: u16 = 8;
294const POINTER_RECORD_LEN: usize = 72;
295const EVENT_VALUE_CHANGED: u16 = 16;
296const EVENT_WINDOW_SIZE_CHANGED: u16 = 17;
297const EVENT_RESYNC: u16 = 18;
298const EVENT_LIFECYCLE_START: u16 = 19;
299const EVENT_LIFECYCLE_STOP: u16 = 20;
300const EVENT_DESIGN_SYSTEM_RESOLVED: u16 = 21;
301const EVENT_FILES_ENTERED: u16 = 22;
302const EVENT_FILES_DROPPED: u16 = 23;
303const EVENT_NOTIFICATION_ACTIVATED: u16 = 24;
304const EVENT_NOTIFICATION_PERMISSION_CHANGED: u16 = 25;
305const EVENT_CODE_CHANGED: u16 = 26;
306const EVENT_CODE_EDIT_REJECTED: u16 = 27;
307const EVENT_CODE_HOVERED: u16 = 28;
308const EVENT_CODE_SAVE_REQUESTED: u16 = 29;
309const EVENT_DECORATION_ACTIVATED: u16 = 30;
310const EVENT_ANIMATION: u16 = 31;
311const EVENT_REDUCED_MOTION_CHANGED: u16 = 32;
312// Tags 26 to 32 belong to events other requirements have numbered.
313const EVENT_ZOOM_CHANGED: u16 = 33;
314// 33 is the zoom event's. 34 to 38 are the code editor's.
315const EVENT_SELECTION_CHANGED: u16 = 34;
316const EVENT_VISIBLE_RANGE_CHANGED: u16 = 35;
317const EVENT_EDITOR_FOCUS_CHANGED: u16 = 36;
318const EVENT_FOLD_CHANGED: u16 = 37;
319const EVENT_COMPOSITION_CHANGED: u16 = 38;
320// 39 to 47 are the windows'.
321const EVENT_WINDOW_CLOSE_REQUESTED: u16 = 39;
322const EVENT_WINDOW_FOCUS_CHANGED: u16 = 40;
323const EVENT_WINDOW_MOVED: u16 = 41;
324const EVENT_WINDOW_DRAG: u16 = 42;
325const EVENT_WINDOW_DRAG_OVER: u16 = 43;
326const EVENT_WINDOW_UNAVAILABLE: u16 = 44;
327const EVENT_WINDOW_CLOSED: u16 = 45;
328const EVENT_WINDOW_REQUESTED: u16 = 46;
329const EVENT_APPLICATION_REOPEN: u16 = 47;
330
331/// Where `SetWindow` keeps the mode and the last window policy: in the bits of the
332/// `resizable` word above the flag.
333const WINDOW_MODE_SHIFT: u32 = 8;
334const WINDOW_POLICY_SHIFT: u32 = 12;
335
336/// The phases of a `WindowDrag`.
337pub const WINDOW_DRAG_MOVE: u32 = 1;
338pub const WINDOW_DRAG_END: u32 = 2;
339pub const WINDOW_DRAG_CANCEL: u32 = 3;
340/// The kinds of a `WindowDragOver`.
341pub const WINDOW_DRAG_OVER_ENTER: u32 = 1;
342pub const WINDOW_DRAG_OVER_MOVE: u32 = 2;
343pub const WINDOW_DRAG_OVER_LEAVE: u32 = 3;
344pub const WINDOW_DRAG_OVER_DROP: u32 = 4;
345
346const MODIFIER_SHIFT: u8 = 1 << 0;
347const MODIFIER_CTRL: u8 = 1 << 1;
348const MODIFIER_ALT: u8 = 1 << 2;
349const MODIFIER_META: u8 = 1 << 3;
350
351/// Reads a pointer enum's tag out of a `u32` word. A word that does not fit a tag, or names
352/// none, is a value the sides disagree on.
353fn pointer_tag<T: TryFrom<u16, Error = ()>>(
354    bytes: &[u8],
355    offset: usize,
356) -> Result<T, ProtocolError> {
357    let raw = read_u32(bytes, offset)?;
358    u16::try_from(raw)
359        .ok()
360        .and_then(|tag| T::try_from(tag).ok())
361        .ok_or(ProtocolError::InvalidValueKind(
362            raw.min(u32::from(u16::MAX)) as u16,
363        ))
364}
365
366/// The 72-byte pointer record: see the field table of the pointer event.
367fn decode_pointer(bytes: &[u8]) -> Result<crate::schema::EventPayload<'_>, ProtocolError> {
368    Ok(crate::schema::EventPayload::Pointer {
369        kind: pointer_tag(bytes, 16)?,
370        pointer_id: read_u32(bytes, 20)?,
371        pointer_type: pointer_tag(bytes, 24)?,
372        x: f32::from_bits(read_u32(bytes, 28)?),
373        y: f32::from_bits(read_u32(bytes, 32)?),
374        buttons: read_u32(bytes, 36)?,
375        button: read_u32(bytes, 40)?,
376        modifiers: read_u32(bytes, 44)?,
377        click_count: read_u32(bytes, 48)?,
378        delta_x: f32::from_bits(read_u32(bytes, 52)?),
379        delta_y: f32::from_bits(read_u32(bytes, 56)?),
380        delta_mode: pointer_tag(bytes, 60)?,
381        timestamp_nanos: read_u64(bytes, 64)?,
382    })
383}
384
385/// Decode one Renderer-to-Host event record.
386pub fn decode_event(bytes: &[u8]) -> Result<HostEvent<'_>, ProtocolError> {
387    if bytes.len() < 16 {
388        return Err(ProtocolError::Truncated);
389    }
390    let tag = read_u16(bytes, 0)?;
391    let record_len = usize::from(read_u16(bytes, 2)?);
392    if record_len < 16 || record_len % 4 != 0 || record_len > bytes.len() {
393        return Err(ProtocolError::InvalidRecordLength);
394    }
395    let node_id = read_u32(bytes, 4)?;
396    let handler_id = read_u64(bytes, 8)?;
397    let payload = match tag {
398        EVENT_CLICK if record_len == 16 => crate::schema::EventPayload::Clicked,
399        EVENT_TEXT_CHANGED if record_len == 24 => {
400            crate::schema::EventPayload::TextChanged(read_string(bytes, 16, record_len)?)
401        }
402        EVENT_TEXT_SUBMITTED if record_len == 24 => {
403            crate::schema::EventPayload::TextSubmitted(read_string(bytes, 16, record_len)?)
404        }
405        EVENT_FOCUS_LOST if record_len == 16 => crate::schema::EventPayload::FocusLost,
406        EVENT_PROTOCOL_ERROR if record_len == 28 => crate::schema::EventPayload::ProtocolError {
407            code: read_u32(bytes, 16)?,
408            message: read_string(bytes, 20, record_len)?,
409        },
410        EVENT_KEY_DOWN if record_len == 20 => {
411            let raw_key = read_u16(bytes, 16)?;
412            let modifiers = *bytes.get(18).ok_or(ProtocolError::Truncated)?;
413            crate::schema::EventPayload::KeyDown {
414                key: Key::try_from(raw_key)
415                    .map_err(|()| ProtocolError::InvalidValueKind(raw_key))?,
416                shift_key: modifiers & MODIFIER_SHIFT != 0,
417                ctrl_key: modifiers & MODIFIER_CTRL != 0,
418                alt_key: modifiers & MODIFIER_ALT != 0,
419                meta_key: modifiers & MODIFIER_META != 0,
420            }
421        }
422        EVENT_RANGE_REQUESTED if record_len == 24 => crate::schema::EventPayload::RangeRequested {
423            start: read_u32(bytes, 16)?,
424            count: read_u32(bytes, 20)?,
425        },
426        EVENT_VALUE_CHANGED if record_len == 24 => {
427            crate::schema::EventPayload::ValueChanged(f64::from_bits(read_u64(bytes, 16)?))
428        }
429        EVENT_WINDOW_SIZE_CHANGED if record_len == 32 => {
430            let raw_class = read_u32(bytes, 24)?;
431            let class = u16::try_from(raw_class)
432                .ok()
433                .and_then(|value| crate::schema::WindowSizeClass::try_from(value).ok())
434                .ok_or(ProtocolError::InvalidValueKind(raw_class as u16))?;
435            let raw_height_class = read_u32(bytes, 28)?;
436            let height_class = u16::try_from(raw_height_class)
437                .ok()
438                .and_then(|value| crate::schema::WindowHeightClass::try_from(value).ok())
439                .ok_or(ProtocolError::InvalidValueKind(raw_height_class as u16))?;
440            crate::schema::EventPayload::WindowSizeChanged {
441                width_dp: f32::from_bits(read_u32(bytes, 16)?),
442                height_dp: f32::from_bits(read_u32(bytes, 20)?),
443                class,
444                height_class,
445            }
446        }
447        EVENT_DESIGN_SYSTEM_RESOLVED if record_len == 20 => {
448            let raw = read_u32(bytes, 16)?;
449            let system = u16::try_from(raw)
450                .ok()
451                .and_then(|tag| crate::schema::DesignSystem::try_from(tag).ok())
452                .ok_or(ProtocolError::InvalidValueKind(raw as u16))?;
453            crate::schema::EventPayload::DesignSystemResolved(system)
454        }
455        EVENT_FILES_ENTERED if record_len == 16 => crate::schema::EventPayload::FilesEntered,
456        EVENT_FILES_DROPPED if record_len == 24 => {
457            crate::schema::EventPayload::FilesDropped(read_string(bytes, 16, record_len)?)
458        }
459        EVENT_NOTIFICATION_ACTIVATED if record_len == 28 => {
460            let action = read_u32(bytes, 16)?;
461            // Zero is the body and one and two are the two buttons a notification can have.
462            // Anything else names a button that cannot exist.
463            if action > 2 {
464                return Err(ProtocolError::InvalidValueKind(
465                    action.min(u32::from(u16::MAX)) as u16,
466                ));
467            }
468            crate::schema::EventPayload::NotificationActivated {
469                action,
470                key: read_string(bytes, 20, record_len)?,
471            }
472        }
473        EVENT_NOTIFICATION_PERMISSION_CHANGED if record_len == 20 => {
474            let raw = read_u32(bytes, 16)?;
475            let state = u16::try_from(raw)
476                .ok()
477                .and_then(|tag| NotificationPermission::try_from(tag).ok())
478                .ok_or(ProtocolError::InvalidValueKind(
479                    raw.min(u32::from(u16::MAX)) as u16,
480                ))?;
481            crate::schema::EventPayload::NotificationPermissionChanged(state)
482        }
483        EVENT_ANIMATION if record_len == 40 => {
484            let raw_kind = u16::from(read_u8(bytes, 20)?);
485            let raw_property = u16::from(read_u8(bytes, 21)?);
486            crate::schema::EventPayload::AnimationEvent {
487                animation_id: read_u32(bytes, 16)?,
488                kind: crate::schema::AnimationEventKind::try_from(raw_kind)
489                    .map_err(|()| ProtocolError::InvalidValueKind(raw_kind))?,
490                property: crate::schema::AnimatedProperty::try_from(raw_property)
491                    .map_err(|()| ProtocolError::InvalidValueKind(raw_property))?,
492                slot: read_u8(bytes, 22)?,
493                iteration: read_u32(bytes, 24)?,
494                elapsed_ms: f32::from_bits(read_u32(bytes, 28)?),
495                time_nanos: read_u64(bytes, 32)?,
496            }
497        }
498        EVENT_POINTER if record_len == POINTER_RECORD_LEN => decode_pointer(bytes)?,
499        EVENT_REDUCED_MOTION_CHANGED if record_len == 20 => {
500            let raw = read_u32(bytes, 16)?;
501            let state = u16::try_from(raw)
502                .ok()
503                .and_then(|tag| crate::schema::ReducedMotion::try_from(tag).ok())
504                .ok_or(ProtocolError::InvalidValueKind(
505                    raw.min(u32::from(u16::MAX)) as u16,
506                ))?;
507            crate::schema::EventPayload::ReducedMotionChanged(state)
508        }
509        EVENT_CODE_CHANGED if record_len == 44 => crate::schema::EventPayload::CodeChanged {
510            version: read_u32(bytes, 16)?,
511            start_line: read_u32(bytes, 20)?,
512            start_column: read_u32(bytes, 24)?,
513            end_line: read_u32(bytes, 28)?,
514            end_column: read_u32(bytes, 32)?,
515            text: read_string(bytes, 36, record_len)?,
516        },
517        EVENT_CODE_EDIT_REJECTED if record_len == 44 => {
518            crate::schema::EventPayload::CodeEditRejected {
519                request_id: read_u32(bytes, 16)?,
520                base_version: read_u32(bytes, 20)?,
521                current_version: read_u32(bytes, 24)?,
522                start_line: read_u32(bytes, 28)?,
523                start_column: read_u32(bytes, 32)?,
524                end_line: read_u32(bytes, 36)?,
525                end_column: read_u32(bytes, 40)?,
526            }
527        }
528        EVENT_CODE_HOVERED if record_len == 36 => {
529            let raw_phase = read_u32(bytes, 32)?;
530            let phase = u16::try_from(raw_phase)
531                .ok()
532                .and_then(|tag| crate::schema::HoverPhase::try_from(tag).ok())
533                .ok_or(ProtocolError::InvalidValueKind(raw_phase as u16))?;
534            crate::schema::EventPayload::CodeHovered {
535                decoration: read_u64(bytes, 16)?,
536                line: read_u32(bytes, 24)?,
537                column: read_u32(bytes, 28)?,
538                phase,
539            }
540        }
541        EVENT_CODE_SAVE_REQUESTED if record_len == 24 => {
542            crate::schema::EventPayload::CodeSaveRequested {
543                version: read_u32(bytes, 16)?,
544            }
545        }
546        EVENT_DECORATION_ACTIVATED if record_len == 24 => {
547            crate::schema::EventPayload::DecorationActivated {
548                decoration: read_u64(bytes, 16)?,
549            }
550        }
551        EVENT_CODE_CHANGED..=EVENT_DECORATION_ACTIVATED => {
552            return Err(ProtocolError::InvalidRecordLength);
553        }
554        EVENT_SELECTION_CHANGED if record_len == 36 => {
555            crate::schema::EventPayload::SelectionChanged {
556                version: read_u32(bytes, 16)?,
557                anchor_line: read_u32(bytes, 20)?,
558                anchor_column: read_u32(bytes, 24)?,
559                active_line: read_u32(bytes, 28)?,
560                active_column: read_u32(bytes, 32)?,
561            }
562        }
563        EVENT_VISIBLE_RANGE_CHANGED if record_len == 28 => {
564            crate::schema::EventPayload::VisibleRangeChanged {
565                version: read_u32(bytes, 16)?,
566                first_line: read_u32(bytes, 20)?,
567                last_line: read_u32(bytes, 24)?,
568            }
569        }
570        EVENT_EDITOR_FOCUS_CHANGED if record_len == 24 => {
571            crate::schema::EventPayload::EditorFocusChanged {
572                focused: read_flag(bytes, 16)?,
573            }
574        }
575        EVENT_FOLD_CHANGED if record_len == 24 => crate::schema::EventPayload::FoldChanged {
576            start_line: read_u32(bytes, 16)?,
577            collapsed: read_flag(bytes, 20)?,
578        },
579        EVENT_COMPOSITION_CHANGED if record_len == 24 => {
580            crate::schema::EventPayload::CompositionChanged {
581                composing: read_flag(bytes, 16)?,
582            }
583        }
584        EVENT_SELECTION_CHANGED..=EVENT_COMPOSITION_CHANGED => {
585            return Err(ProtocolError::InvalidRecordLength);
586        }
587        EVENT_WINDOW_CLOSE_REQUESTED if record_len == 16 => {
588            crate::schema::EventPayload::WindowCloseRequested
589        }
590        EVENT_WINDOW_FOCUS_CHANGED if record_len == 20 => {
591            crate::schema::EventPayload::WindowFocusChanged {
592                focused: read_flag(bytes, 16)?,
593            }
594        }
595        EVENT_WINDOW_MOVED if record_len == 32 => crate::schema::EventPayload::WindowMoved {
596            x: f32::from_bits(read_u32(bytes, 16)?),
597            y: f32::from_bits(read_u32(bytes, 20)?),
598            width: f32::from_bits(read_u32(bytes, 24)?),
599            height: f32::from_bits(read_u32(bytes, 28)?),
600        },
601        EVENT_WINDOW_DRAG if record_len == 32 => {
602            let phase = read_u32(bytes, 16)?;
603            if !(WINDOW_DRAG_MOVE..=WINDOW_DRAG_CANCEL).contains(&phase) {
604                return Err(ProtocolError::InvalidValueKind(
605                    phase.min(u32::from(u16::MAX)) as u16,
606                ));
607            }
608            crate::schema::EventPayload::WindowDrag {
609                phase,
610                screen_x: f32::from_bits(read_u32(bytes, 20)?),
611                screen_y: f32::from_bits(read_u32(bytes, 24)?),
612                over_window: read_u32(bytes, 28)?,
613            }
614        }
615        EVENT_WINDOW_DRAG_OVER if record_len == 32 => {
616            let kind = read_u32(bytes, 16)?;
617            if !(WINDOW_DRAG_OVER_ENTER..=WINDOW_DRAG_OVER_DROP).contains(&kind) {
618                return Err(ProtocolError::InvalidValueKind(
619                    kind.min(u32::from(u16::MAX)) as u16,
620                ));
621            }
622            crate::schema::EventPayload::WindowDragOver {
623                kind,
624                window: read_u32(bytes, 20)?,
625                x: f32::from_bits(read_u32(bytes, 24)?),
626                y: f32::from_bits(read_u32(bytes, 28)?),
627            }
628        }
629        EVENT_WINDOW_UNAVAILABLE if record_len == 16 => {
630            crate::schema::EventPayload::WindowUnavailable
631        }
632        EVENT_WINDOW_CLOSED if record_len == 16 => crate::schema::EventPayload::WindowClosed,
633        EVENT_WINDOW_REQUESTED if record_len == 28 => {
634            crate::schema::EventPayload::WindowRequested {
635                request: read_u32(bytes, 16)?,
636                key: read_string(bytes, 20, record_len)?,
637            }
638        }
639        EVENT_APPLICATION_REOPEN if record_len == 16 => {
640            crate::schema::EventPayload::ApplicationReopen
641        }
642        EVENT_WINDOW_CLOSE_REQUESTED..=EVENT_APPLICATION_REOPEN => {
643            return Err(ProtocolError::InvalidRecordLength);
644        }
645        EVENT_ZOOM_CHANGED if record_len == 28 => {
646            let level = read_u32(bytes, 24)? as i32;
647            if !(i32::from(crate::zoom::MIN_ZOOM_LEVEL)..=i32::from(crate::zoom::MAX_ZOOM_LEVEL))
648                .contains(&level)
649            {
650                return Err(ProtocolError::InvalidValueKind(level as u16));
651            }
652            crate::schema::EventPayload::ZoomChanged {
653                k: f32::from_bits(read_u32(bytes, 16)?),
654                os: f32::from_bits(read_u32(bytes, 20)?),
655                level,
656            }
657        }
658        EVENT_RESYNC if record_len == 16 => crate::schema::EventPayload::Resync,
659        EVENT_LIFECYCLE_START if record_len == 16 => crate::schema::EventPayload::LifecycleStart,
660        EVENT_LIFECYCLE_STOP if record_len == 16 => crate::schema::EventPayload::LifecycleStop,
661        EVENT_CLICK..=EVENT_RANGE_REQUESTED
662        | EVENT_VALUE_CHANGED..=EVENT_NOTIFICATION_PERMISSION_CHANGED
663        | EVENT_ANIMATION
664        | EVENT_POINTER
665        | EVENT_REDUCED_MOTION_CHANGED
666        | EVENT_ZOOM_CHANGED => {
667            return Err(ProtocolError::InvalidRecordLength);
668        }
669        other => return Err(ProtocolError::InvalidTag(other)),
670    };
671    Ok(HostEvent {
672        node_id,
673        handler_id,
674        payload,
675    })
676}
677
678/// Test/mock helper for constructing a Renderer-to-Host event.
679pub fn encode_event(event: &HostEvent<'_>, output: &mut Vec<u8>) -> Result<(), ProtocolError> {
680    output.clear();
681    if let crate::schema::EventPayload::KeyDown {
682        key,
683        shift_key,
684        ctrl_key,
685        alt_key,
686        meta_key,
687    } = event.payload
688    {
689        output.extend_from_slice(&EVENT_KEY_DOWN.to_le_bytes());
690        output.extend_from_slice(&20_u16.to_le_bytes());
691        output.extend_from_slice(&event.node_id.to_le_bytes());
692        output.extend_from_slice(&event.handler_id.to_le_bytes());
693        output.extend_from_slice(&(key as u16).to_le_bytes());
694        let modifiers = (u8::from(shift_key) * MODIFIER_SHIFT)
695            | (u8::from(ctrl_key) * MODIFIER_CTRL)
696            | (u8::from(alt_key) * MODIFIER_ALT)
697            | (u8::from(meta_key) * MODIFIER_META);
698        output.push(modifiers);
699        output.push(0);
700        return Ok(());
701    }
702    if let crate::schema::EventPayload::ValueChanged(value) = event.payload {
703        output.extend_from_slice(&EVENT_VALUE_CHANGED.to_le_bytes());
704        output.extend_from_slice(&24_u16.to_le_bytes());
705        output.extend_from_slice(&event.node_id.to_le_bytes());
706        output.extend_from_slice(&event.handler_id.to_le_bytes());
707        output.extend_from_slice(&value.to_bits().to_le_bytes());
708        return Ok(());
709    }
710    if let crate::schema::EventPayload::WindowSizeChanged {
711        width_dp,
712        height_dp,
713        class,
714        height_class,
715    } = event.payload
716    {
717        output.extend_from_slice(&EVENT_WINDOW_SIZE_CHANGED.to_le_bytes());
718        output.extend_from_slice(&32_u16.to_le_bytes());
719        output.extend_from_slice(&event.node_id.to_le_bytes());
720        output.extend_from_slice(&event.handler_id.to_le_bytes());
721        output.extend_from_slice(&width_dp.to_bits().to_le_bytes());
722        output.extend_from_slice(&height_dp.to_bits().to_le_bytes());
723        output.extend_from_slice(&u32::from(u16::from(class)).to_le_bytes());
724        output.extend_from_slice(&u32::from(u16::from(height_class)).to_le_bytes());
725        return Ok(());
726    }
727    if let crate::schema::EventPayload::DesignSystemResolved(system) = event.payload {
728        output.extend_from_slice(&EVENT_DESIGN_SYSTEM_RESOLVED.to_le_bytes());
729        output.extend_from_slice(&20_u16.to_le_bytes());
730        output.extend_from_slice(&event.node_id.to_le_bytes());
731        output.extend_from_slice(&event.handler_id.to_le_bytes());
732        // The tag in a word, which keeps the record a multiple of four the way every
733        // other record here is.
734        output.extend_from_slice(&u32::from(u16::from(system)).to_le_bytes());
735        return Ok(());
736    }
737    if let crate::schema::EventPayload::NotificationPermissionChanged(state) = event.payload {
738        output.extend_from_slice(&EVENT_NOTIFICATION_PERMISSION_CHANGED.to_le_bytes());
739        output.extend_from_slice(&20_u16.to_le_bytes());
740        output.extend_from_slice(&event.node_id.to_le_bytes());
741        output.extend_from_slice(&event.handler_id.to_le_bytes());
742        output.extend_from_slice(&u32::from(u16::from(state)).to_le_bytes());
743        return Ok(());
744    }
745    if let crate::schema::EventPayload::AnimationEvent {
746        animation_id,
747        kind,
748        property,
749        slot,
750        iteration,
751        elapsed_ms,
752        time_nanos,
753    } = event.payload
754    {
755        output.extend_from_slice(&EVENT_ANIMATION.to_le_bytes());
756        output.extend_from_slice(&40_u16.to_le_bytes());
757        output.extend_from_slice(&event.node_id.to_le_bytes());
758        output.extend_from_slice(&event.handler_id.to_le_bytes());
759        output.extend_from_slice(&animation_id.to_le_bytes());
760        output.push(kind as u8);
761        output.push(property as u8);
762        output.push(slot);
763        output.push(0);
764        output.extend_from_slice(&iteration.to_le_bytes());
765        output.extend_from_slice(&elapsed_ms.to_bits().to_le_bytes());
766        output.extend_from_slice(&time_nanos.to_le_bytes());
767        return Ok(());
768    }
769    if let crate::schema::EventPayload::Pointer {
770        kind,
771        pointer_id,
772        pointer_type,
773        x,
774        y,
775        buttons,
776        button,
777        modifiers,
778        click_count,
779        delta_x,
780        delta_y,
781        delta_mode,
782        timestamp_nanos,
783    } = event.payload
784    {
785        output.extend_from_slice(&EVENT_POINTER.to_le_bytes());
786        output.extend_from_slice(&(POINTER_RECORD_LEN as u16).to_le_bytes());
787        output.extend_from_slice(&event.node_id.to_le_bytes());
788        output.extend_from_slice(&event.handler_id.to_le_bytes());
789        output.extend_from_slice(&u32::from(u16::from(kind)).to_le_bytes());
790        output.extend_from_slice(&pointer_id.to_le_bytes());
791        output.extend_from_slice(&u32::from(u16::from(pointer_type)).to_le_bytes());
792        output.extend_from_slice(&x.to_bits().to_le_bytes());
793        output.extend_from_slice(&y.to_bits().to_le_bytes());
794        output.extend_from_slice(&buttons.to_le_bytes());
795        output.extend_from_slice(&button.to_le_bytes());
796        output.extend_from_slice(&modifiers.to_le_bytes());
797        output.extend_from_slice(&click_count.to_le_bytes());
798        output.extend_from_slice(&delta_x.to_bits().to_le_bytes());
799        output.extend_from_slice(&delta_y.to_bits().to_le_bytes());
800        output.extend_from_slice(&u32::from(u16::from(delta_mode)).to_le_bytes());
801        output.extend_from_slice(&timestamp_nanos.to_le_bytes());
802        return Ok(());
803    }
804    if let crate::schema::EventPayload::ReducedMotionChanged(state) = event.payload {
805        output.extend_from_slice(&EVENT_REDUCED_MOTION_CHANGED.to_le_bytes());
806        output.extend_from_slice(&20_u16.to_le_bytes());
807        output.extend_from_slice(&event.node_id.to_le_bytes());
808        output.extend_from_slice(&event.handler_id.to_le_bytes());
809        output.extend_from_slice(&u32::from(u16::from(state)).to_le_bytes());
810        return Ok(());
811    }
812    match event.payload {
813        crate::schema::EventPayload::CodeChanged {
814            version,
815            start_line,
816            start_column,
817            end_line,
818            end_column,
819            text,
820        } => {
821            put_event_header(output, EVENT_CODE_CHANGED, 44, event);
822            for value in [version, start_line, start_column, end_line, end_column] {
823                output.extend_from_slice(&value.to_le_bytes());
824            }
825            let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
826            output.extend_from_slice(&44_u32.to_le_bytes());
827            output.extend_from_slice(&len.to_le_bytes());
828            output.extend_from_slice(text.as_bytes());
829            return Ok(());
830        }
831        crate::schema::EventPayload::CodeEditRejected {
832            request_id,
833            base_version,
834            current_version,
835            start_line,
836            start_column,
837            end_line,
838            end_column,
839        } => {
840            put_event_header(output, EVENT_CODE_EDIT_REJECTED, 44, event);
841            for value in [
842                request_id,
843                base_version,
844                current_version,
845                start_line,
846                start_column,
847                end_line,
848                end_column,
849            ] {
850                output.extend_from_slice(&value.to_le_bytes());
851            }
852            return Ok(());
853        }
854        crate::schema::EventPayload::CodeHovered {
855            decoration,
856            line,
857            column,
858            phase,
859        } => {
860            put_event_header(output, EVENT_CODE_HOVERED, 36, event);
861            output.extend_from_slice(&decoration.to_le_bytes());
862            output.extend_from_slice(&line.to_le_bytes());
863            output.extend_from_slice(&column.to_le_bytes());
864            output.extend_from_slice(&u32::from(u16::from(phase)).to_le_bytes());
865            return Ok(());
866        }
867        crate::schema::EventPayload::CodeSaveRequested { version } => {
868            put_event_header(output, EVENT_CODE_SAVE_REQUESTED, 24, event);
869            output.extend_from_slice(&version.to_le_bytes());
870            // The padding that keeps the record a multiple of four bytes.
871            output.extend_from_slice(&0_u32.to_le_bytes());
872            return Ok(());
873        }
874        crate::schema::EventPayload::DecorationActivated { decoration } => {
875            put_event_header(output, EVENT_DECORATION_ACTIVATED, 24, event);
876            output.extend_from_slice(&decoration.to_le_bytes());
877            return Ok(());
878        }
879        crate::schema::EventPayload::SelectionChanged {
880            version,
881            anchor_line,
882            anchor_column,
883            active_line,
884            active_column,
885        } => {
886            put_event_header(output, EVENT_SELECTION_CHANGED, 36, event);
887            for value in [
888                version,
889                anchor_line,
890                anchor_column,
891                active_line,
892                active_column,
893            ] {
894                output.extend_from_slice(&value.to_le_bytes());
895            }
896            return Ok(());
897        }
898        crate::schema::EventPayload::VisibleRangeChanged {
899            version,
900            first_line,
901            last_line,
902        } => {
903            put_event_header(output, EVENT_VISIBLE_RANGE_CHANGED, 28, event);
904            for value in [version, first_line, last_line] {
905                output.extend_from_slice(&value.to_le_bytes());
906            }
907            return Ok(());
908        }
909        crate::schema::EventPayload::EditorFocusChanged { focused } => {
910            put_event_header(output, EVENT_EDITOR_FOCUS_CHANGED, 24, event);
911            output.extend_from_slice(&u32::from(focused).to_le_bytes());
912            output.extend_from_slice(&0_u32.to_le_bytes());
913            return Ok(());
914        }
915        crate::schema::EventPayload::FoldChanged {
916            start_line,
917            collapsed,
918        } => {
919            put_event_header(output, EVENT_FOLD_CHANGED, 24, event);
920            output.extend_from_slice(&start_line.to_le_bytes());
921            output.extend_from_slice(&u32::from(collapsed).to_le_bytes());
922            return Ok(());
923        }
924        crate::schema::EventPayload::CompositionChanged { composing } => {
925            put_event_header(output, EVENT_COMPOSITION_CHANGED, 24, event);
926            output.extend_from_slice(&u32::from(composing).to_le_bytes());
927            output.extend_from_slice(&0_u32.to_le_bytes());
928            return Ok(());
929        }
930        _ => {}
931    }
932    match event.payload {
933        crate::schema::EventPayload::WindowFocusChanged { focused } => {
934            put_event_header(output, EVENT_WINDOW_FOCUS_CHANGED, 20, event);
935            output.extend_from_slice(&u32::from(focused).to_le_bytes());
936            return Ok(());
937        }
938        crate::schema::EventPayload::WindowMoved {
939            x,
940            y,
941            width,
942            height,
943        } => {
944            put_event_header(output, EVENT_WINDOW_MOVED, 32, event);
945            for value in [x, y, width, height] {
946                output.extend_from_slice(&value.to_bits().to_le_bytes());
947            }
948            return Ok(());
949        }
950        crate::schema::EventPayload::WindowDrag {
951            phase,
952            screen_x,
953            screen_y,
954            over_window,
955        } => {
956            put_event_header(output, EVENT_WINDOW_DRAG, 32, event);
957            output.extend_from_slice(&phase.to_le_bytes());
958            output.extend_from_slice(&screen_x.to_bits().to_le_bytes());
959            output.extend_from_slice(&screen_y.to_bits().to_le_bytes());
960            output.extend_from_slice(&over_window.to_le_bytes());
961            return Ok(());
962        }
963        crate::schema::EventPayload::WindowDragOver { kind, window, x, y } => {
964            put_event_header(output, EVENT_WINDOW_DRAG_OVER, 32, event);
965            output.extend_from_slice(&kind.to_le_bytes());
966            output.extend_from_slice(&window.to_le_bytes());
967            output.extend_from_slice(&x.to_bits().to_le_bytes());
968            output.extend_from_slice(&y.to_bits().to_le_bytes());
969            return Ok(());
970        }
971        _ => {}
972    }
973    if let crate::schema::EventPayload::ZoomChanged { k, os, level } = event.payload {
974        output.extend_from_slice(&EVENT_ZOOM_CHANGED.to_le_bytes());
975        output.extend_from_slice(&28_u16.to_le_bytes());
976        output.extend_from_slice(&event.node_id.to_le_bytes());
977        output.extend_from_slice(&event.handler_id.to_le_bytes());
978        output.extend_from_slice(&k.to_bits().to_le_bytes());
979        output.extend_from_slice(&os.to_bits().to_le_bytes());
980        output.extend_from_slice(&level.to_le_bytes());
981        return Ok(());
982    }
983    if let crate::schema::EventPayload::RangeRequested { start, count } = event.payload {
984        output.extend_from_slice(&EVENT_RANGE_REQUESTED.to_le_bytes());
985        output.extend_from_slice(&24_u16.to_le_bytes());
986        output.extend_from_slice(&event.node_id.to_le_bytes());
987        output.extend_from_slice(&event.handler_id.to_le_bytes());
988        output.extend_from_slice(&start.to_le_bytes());
989        output.extend_from_slice(&count.to_le_bytes());
990        return Ok(());
991    }
992    let (tag, record_len, text, error_code) = match &event.payload {
993        crate::schema::EventPayload::Clicked => (EVENT_CLICK, 16_u16, None, None),
994        crate::schema::EventPayload::TextChanged(value) => {
995            (EVENT_TEXT_CHANGED, 24, Some(*value), None)
996        }
997        crate::schema::EventPayload::TextSubmitted(value) => {
998            (EVENT_TEXT_SUBMITTED, 24, Some(*value), None)
999        }
1000        crate::schema::EventPayload::FocusLost => (EVENT_FOCUS_LOST, 16, None, None),
1001        crate::schema::EventPayload::FilesEntered => (EVENT_FILES_ENTERED, 16, None, None),
1002        crate::schema::EventPayload::FilesDropped(paths) => {
1003            (EVENT_FILES_DROPPED, 24, Some(*paths), None)
1004        }
1005        crate::schema::EventPayload::Resync => (EVENT_RESYNC, 16, None, None),
1006        crate::schema::EventPayload::WindowCloseRequested => {
1007            (EVENT_WINDOW_CLOSE_REQUESTED, 16, None, None)
1008        }
1009        crate::schema::EventPayload::WindowUnavailable => {
1010            (EVENT_WINDOW_UNAVAILABLE, 16, None, None)
1011        }
1012        crate::schema::EventPayload::WindowClosed => (EVENT_WINDOW_CLOSED, 16, None, None),
1013        crate::schema::EventPayload::ApplicationReopen => {
1014            (EVENT_APPLICATION_REOPEN, 16, None, None)
1015        }
1016        // The shape of a protocol error: one word, then one string.
1017        crate::schema::EventPayload::WindowRequested { key, request } => {
1018            (EVENT_WINDOW_REQUESTED, 28, Some(*key), Some(*request))
1019        }
1020        crate::schema::EventPayload::LifecycleStart => (EVENT_LIFECYCLE_START, 16, None, None),
1021        crate::schema::EventPayload::LifecycleStop => (EVENT_LIFECYCLE_STOP, 16, None, None),
1022        crate::schema::EventPayload::ProtocolError { code, message } => {
1023            (EVENT_PROTOCOL_ERROR, 28, Some(*message), Some(*code))
1024        }
1025        // The same shape as a protocol error: one word, then one string.
1026        crate::schema::EventPayload::NotificationActivated { action, key } => {
1027            (EVENT_NOTIFICATION_ACTIVATED, 28, Some(*key), Some(*action))
1028        }
1029        crate::schema::EventPayload::KeyDown { .. }
1030        | crate::schema::EventPayload::RangeRequested { .. }
1031        | crate::schema::EventPayload::ValueChanged(_)
1032        | crate::schema::EventPayload::WindowSizeChanged { .. }
1033        | crate::schema::EventPayload::DesignSystemResolved(_)
1034        | crate::schema::EventPayload::NotificationPermissionChanged(_)
1035        | crate::schema::EventPayload::CodeChanged { .. }
1036        | crate::schema::EventPayload::CodeEditRejected { .. }
1037        | crate::schema::EventPayload::CodeHovered { .. }
1038        | crate::schema::EventPayload::CodeSaveRequested { .. }
1039        | crate::schema::EventPayload::DecorationActivated { .. }
1040        | crate::schema::EventPayload::SelectionChanged { .. }
1041        | crate::schema::EventPayload::VisibleRangeChanged { .. }
1042        | crate::schema::EventPayload::EditorFocusChanged { .. }
1043        | crate::schema::EventPayload::FoldChanged { .. }
1044        | crate::schema::EventPayload::CompositionChanged { .. }
1045        | crate::schema::EventPayload::WindowFocusChanged { .. }
1046        | crate::schema::EventPayload::WindowMoved { .. }
1047        | crate::schema::EventPayload::WindowDrag { .. }
1048        | crate::schema::EventPayload::WindowDragOver { .. } => unreachable!(),
1049        crate::schema::EventPayload::AnimationEvent { .. }
1050        | crate::schema::EventPayload::Pointer { .. }
1051        | crate::schema::EventPayload::ReducedMotionChanged(_)
1052        | crate::schema::EventPayload::ZoomChanged { .. } => unreachable!(),
1053    };
1054    output.extend_from_slice(&tag.to_le_bytes());
1055    output.extend_from_slice(&record_len.to_le_bytes());
1056    output.extend_from_slice(&event.node_id.to_le_bytes());
1057    output.extend_from_slice(&event.handler_id.to_le_bytes());
1058    if let Some(code) = error_code {
1059        output.extend_from_slice(&code.to_le_bytes());
1060    }
1061    if let Some(text) = text {
1062        let offset = u32::from(record_len);
1063        let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1064        output.extend_from_slice(&offset.to_le_bytes());
1065        output.extend_from_slice(&len.to_le_bytes());
1066        output.extend_from_slice(text.as_bytes());
1067    }
1068    Ok(())
1069}
1070
1071/// The sixteen bytes every event starts with: the tag, the record's length, the node and
1072/// the handler.
1073fn put_event_header(output: &mut Vec<u8>, tag: u16, record_len: u16, event: &HostEvent<'_>) {
1074    output.extend_from_slice(&tag.to_le_bytes());
1075    output.extend_from_slice(&record_len.to_le_bytes());
1076    output.extend_from_slice(&event.node_id.to_le_bytes());
1077    output.extend_from_slice(&event.handler_id.to_le_bytes());
1078}
1079
1080impl fmt::Display for ProtocolError {
1081    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1082        write!(formatter, "protocol error: {self:?}")
1083    }
1084}
1085
1086impl std::error::Error for ProtocolError {}
1087
1088/// Reusable encoder. Once capacities are warmed, `clear` and `finish` allocate no memory.
1089pub struct BatchEncoder {
1090    records: Vec<u8>,
1091    strings: Vec<u8>,
1092    string_fixups: Vec<usize>,
1093    arena: Vec<u8>,
1094    record_count: u32,
1095}
1096
1097impl Default for BatchEncoder {
1098    fn default() -> Self {
1099        Self::with_capacity(4096, 1024, 64)
1100    }
1101}
1102
1103impl BatchEncoder {
1104    pub fn with_capacity(record_bytes: usize, string_bytes: usize, string_count: usize) -> Self {
1105        let mut encoder = Self {
1106            records: Vec::with_capacity(record_bytes),
1107            strings: Vec::with_capacity(string_bytes),
1108            string_fixups: Vec::with_capacity(string_count),
1109            arena: Vec::with_capacity(record_bytes + string_bytes),
1110            record_count: 0,
1111        };
1112        encoder.clear();
1113        encoder
1114    }
1115
1116    pub fn clear(&mut self) {
1117        self.records.clear();
1118        self.strings.clear();
1119        self.string_fixups.clear();
1120        self.arena.clear();
1121        self.record_count = 0;
1122        self.begin_record(TAG_ENVELOPE, 8);
1123        self.put_u32(0);
1124        self.put_u32(0);
1125    }
1126
1127    pub fn encode(&mut self, mutation: &Mutation<'_>) -> Result<(), ProtocolError> {
1128        match mutation {
1129            Mutation::Create { node_id, widget } => {
1130                self.begin_record(TAG_CREATE, 8);
1131                self.put_u32(*node_id);
1132                self.put_u16(*widget as u16);
1133                self.put_u16(0);
1134            }
1135            Mutation::SetProp {
1136                node_id,
1137                property,
1138                value,
1139            } => {
1140                self.begin_record(TAG_SET_PROP, 20);
1141                self.put_u32(*node_id);
1142                self.put_u16(*property as u16);
1143                match value {
1144                    PropertyValue::None => {
1145                        self.put_u16(VALUE_NONE);
1146                        self.put_u64(0);
1147                        self.put_u32(0);
1148                    }
1149                    PropertyValue::String(value) => {
1150                        self.put_u16(VALUE_STRING);
1151                        self.put_string_ref(value)?;
1152                        self.put_u32(0);
1153                    }
1154                    PropertyValue::Bool(value) => {
1155                        self.put_u16(VALUE_BOOL);
1156                        self.put_u64(u64::from(*value));
1157                        self.put_u32(0);
1158                    }
1159                    PropertyValue::Integer(value) => {
1160                        self.put_u16(VALUE_I64);
1161                        self.put_u64(*value as u64);
1162                        self.put_u32(0);
1163                    }
1164                    PropertyValue::Float(value) => {
1165                        self.put_u16(VALUE_F32);
1166                        self.put_u64(u64::from(value.to_bits()));
1167                        self.put_u32(0);
1168                    }
1169                    PropertyValue::Bytes(value) => {
1170                        self.put_u16(VALUE_BYTES);
1171                        self.put_bytes_ref(value)?;
1172                        self.put_u32(0);
1173                    }
1174                }
1175            }
1176            Mutation::SetModifier {
1177                node_id,
1178                index,
1179                modifier,
1180            } => {
1181                let (tag, words) = modifier_fields(modifier);
1182                // The tag fixes how long the value is, so the record is as long as the
1183                // schema says that tag's records are and every other modifier keeps the
1184                // 28 bytes it always had.
1185                let extra = modifier_extra_words(tag).ok_or(ProtocolError::InvalidModifier(tag))?;
1186                self.begin_record(TAG_SET_MODIFIER, 24 + 8 * u16::from(extra));
1187                self.put_u32(*node_id);
1188                self.put_u16(*index);
1189                self.put_u16(tag);
1190                for word in &words[..2 + usize::from(extra)] {
1191                    self.put_u64(*word);
1192                }
1193            }
1194            Mutation::Insert {
1195                parent_id,
1196                node_id,
1197                index,
1198            } => {
1199                self.begin_record(TAG_INSERT, 12);
1200                self.put_u32(*parent_id);
1201                self.put_u32(*node_id);
1202                self.put_u32(*index);
1203            }
1204            Mutation::Move {
1205                parent_id,
1206                node_id,
1207                index,
1208            } => {
1209                self.begin_record(TAG_MOVE, 12);
1210                self.put_u32(*parent_id);
1211                self.put_u32(*node_id);
1212                self.put_u32(*index);
1213            }
1214            Mutation::Remove { node_id } => {
1215                self.begin_record(TAG_REMOVE, 4);
1216                self.put_u32(*node_id);
1217            }
1218            Mutation::SetText {
1219                node_id,
1220                text,
1221                selection,
1222            } => {
1223                self.begin_record(TAG_SET_TEXT, 20);
1224                self.put_u32(*node_id);
1225                self.put_string_ref(text)?;
1226                let (start, end) =
1227                    selection.map_or((u32::MAX, u32::MAX), |value| (value.start, value.end));
1228                self.put_u32(start);
1229                self.put_u32(end);
1230            }
1231            Mutation::AppendText { node_id, text } => {
1232                self.begin_record(TAG_APPEND_TEXT, 12);
1233                self.put_u32(*node_id);
1234                self.put_string_ref(text)?;
1235            }
1236            Mutation::RegisterAsset {
1237                asset_id,
1238                kind,
1239                bytes,
1240            } => {
1241                self.begin_record(TAG_REGISTER_ASSET, 16);
1242                self.put_u32(*asset_id);
1243                self.put_u16(*kind as u16);
1244                self.put_u16(0);
1245                self.put_bytes_ref(bytes)?;
1246            }
1247            Mutation::ReleaseAsset { asset_id } => {
1248                self.begin_record(TAG_RELEASE_ASSET, 4);
1249                self.put_u32(*asset_id);
1250            }
1251            Mutation::ShowMessage {
1252                handler_id,
1253                text,
1254                action,
1255                duration,
1256            } => {
1257                self.begin_record(TAG_SHOW_MESSAGE, 28);
1258                self.put_u64(*handler_id);
1259                self.put_string_ref(text)?;
1260                self.put_string_ref(action)?;
1261                self.put_u16(*duration as u16);
1262                self.put_u16(0);
1263            }
1264            Mutation::EditCode {
1265                node_id,
1266                request_id,
1267                base_version,
1268                start_line,
1269                start_column,
1270                end_line,
1271                end_column,
1272                text,
1273            } => {
1274                self.begin_record(TAG_EDIT_CODE, 36);
1275                self.put_u32(*node_id);
1276                self.put_u32(*request_id);
1277                self.put_u32(*base_version);
1278                self.put_u32(*start_line);
1279                self.put_u32(*start_column);
1280                self.put_u32(*end_line);
1281                self.put_u32(*end_column);
1282                self.put_string_ref(text)?;
1283            }
1284            Mutation::SetSelection {
1285                node_id,
1286                base_version,
1287                anchor_line,
1288                anchor_column,
1289                active_line,
1290                active_column,
1291            } => {
1292                self.begin_record(TAG_SET_SELECTION, 24);
1293                self.put_u32(*node_id);
1294                self.put_u32(*base_version);
1295                self.put_u32(*anchor_line);
1296                self.put_u32(*anchor_column);
1297                self.put_u32(*active_line);
1298                self.put_u32(*active_column);
1299            }
1300            Mutation::RevealRange {
1301                node_id,
1302                base_version,
1303                start_line,
1304                start_column,
1305                end_line,
1306                end_column,
1307                align,
1308            } => {
1309                self.begin_record(TAG_REVEAL_RANGE, 28);
1310                self.put_u32(*node_id);
1311                self.put_u32(*base_version);
1312                self.put_u32(*start_line);
1313                self.put_u32(*start_column);
1314                self.put_u32(*end_line);
1315                self.put_u32(*end_column);
1316                self.put_u32(u32::from(*align as u16));
1317            }
1318            Mutation::SetFolded {
1319                node_id,
1320                start_line,
1321                collapsed,
1322            } => {
1323                self.begin_record(TAG_SET_FOLDED, 12);
1324                self.put_u32(*node_id);
1325                self.put_u32(*start_line);
1326                self.put_u32(u32::from(*collapsed));
1327            }
1328            Mutation::SetTheme(theme) => {
1329                self.begin_record(TAG_SET_THEME, THEME_PAYLOAD_LEN as u16);
1330                self.put_theme(theme)?;
1331            }
1332            Mutation::SetWindowTheme { node_id, theme } => {
1333                self.begin_record(TAG_SET_WINDOW_THEME, 4 + THEME_PAYLOAD_LEN as u16);
1334                self.put_u32(*node_id);
1335                self.put_theme(theme)?;
1336            }
1337            Mutation::FocusWindow { node_id } => {
1338                self.begin_record(TAG_FOCUS_WINDOW, 4);
1339                self.put_u32(*node_id);
1340            }
1341            Mutation::SetWindow(window) => {
1342                self.begin_record(TAG_SET_WINDOW, 28);
1343                self.put_u16(window.chrome as u16);
1344                self.put_u16(window.title_bar as u16);
1345                self.put_u16(window.width);
1346                self.put_u16(window.height);
1347                self.put_u16(window.min_width);
1348                self.put_u16(window.min_height);
1349                // Bit 0 is `resizable`. The mode and the last window policy ride in the
1350                // bits above it that were always zero, so a window that asks for one
1351                // window writes the bytes it always wrote.
1352                self.put_u16(
1353                    u16::from(window.resizable)
1354                        | ((window.mode as u16) << WINDOW_MODE_SHIFT)
1355                        | ((window.last_window_closed as u16) << WINDOW_POLICY_SHIFT),
1356                );
1357                // The application's zoom level, in what used to be the padding that keeps
1358                // the record a multiple of four: a byte saying whether there is one, then
1359                // the level as a signed byte. Zero is "none", so a window that asks for
1360                // nothing reads exactly as it did before the level existed.
1361                self.put_u16(match window.zoom_level {
1362                    None => 0,
1363                    Some(level) => 1 | (u16::from(crate::zoom::clamp_level(level) as u8) << 8),
1364                });
1365                self.put_string_ref(window.title)?;
1366                self.put_u32(window.icon);
1367            }
1368            Mutation::PostNotification {
1369                key,
1370                title,
1371                body,
1372                channel,
1373                action_1,
1374                action_2,
1375                importance,
1376                presentation,
1377            } => {
1378                self.begin_record(TAG_POST_NOTIFICATION, 52);
1379                self.put_string_ref(key)?;
1380                self.put_string_ref(title)?;
1381                self.put_string_ref(body)?;
1382                self.put_string_ref(channel)?;
1383                self.put_string_ref(action_1)?;
1384                self.put_string_ref(action_2)?;
1385                self.put_u16(*importance as u16);
1386                self.put_u16(*presentation as u16);
1387            }
1388            Mutation::WithdrawNotification { key } => {
1389                self.begin_record(TAG_WITHDRAW_NOTIFICATION, 8);
1390                self.put_string_ref(key)?;
1391            }
1392            Mutation::RequestNotificationPermission => {
1393                self.begin_record(TAG_REQUEST_NOTIFICATION_PERMISSION, 0);
1394            }
1395            Mutation::StartAnimation(animation) => self.put_animation(animation)?,
1396            Mutation::ControlAnimation {
1397                node_id,
1398                animation_id,
1399                property,
1400                slot,
1401                op,
1402                at_time_nanos,
1403            } => {
1404                self.begin_record(TAG_CONTROL_ANIMATION, 20);
1405                self.put_u32(*node_id);
1406                self.put_u32(*animation_id);
1407                self.records.push(*property as u8);
1408                self.records.push(*slot);
1409                self.records.push(*op as u8);
1410                self.records.push(0);
1411                self.put_u64(*at_time_nanos);
1412            }
1413        }
1414        self.record_count = self
1415            .record_count
1416            .checked_add(1)
1417            .ok_or(ProtocolError::LengthOverflow)?;
1418        Ok(())
1419    }
1420
1421    pub fn finish(&mut self) -> Result<&[u8], ProtocolError> {
1422        let records_len =
1423            u32::try_from(self.records.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1424        self.records[4..8].copy_from_slice(&records_len.to_le_bytes());
1425        self.records[8..12].copy_from_slice(&self.record_count.to_le_bytes());
1426        let total = self
1427            .records
1428            .len()
1429            .checked_add(self.strings.len())
1430            .ok_or(ProtocolError::LengthOverflow)?;
1431        self.arena.clear();
1432        if self.arena.capacity() < total {
1433            self.arena.reserve(total);
1434        }
1435        self.arena.extend_from_slice(&self.records);
1436        self.arena.extend_from_slice(&self.strings);
1437        for &position in &self.string_fixups {
1438            let relative = read_u32(&self.arena, position)?;
1439            let absolute = records_len
1440                .checked_add(relative)
1441                .ok_or(ProtocolError::LengthOverflow)?;
1442            self.arena[position..position + 4].copy_from_slice(&absolute.to_le_bytes());
1443        }
1444        Ok(&self.arena)
1445    }
1446
1447    /// Where the arena a finished batch lives in starts, and how much of it is mapped.
1448    ///
1449    /// A runtime that cannot read the Host's memory directly wraps this range once and
1450    /// keeps the view: every batch is a prefix of the arena, so it is read where it lies
1451    /// and no byte is copied out. Growing the arena moves it, which is why the address is
1452    /// reported again on every call rather than once at startup.
1453    pub fn arena(&self) -> (*const u8, usize) {
1454        (self.arena.as_ptr(), self.arena.capacity())
1455    }
1456
1457    pub fn capacity(&self) -> usize {
1458        self.records.capacity() + self.strings.capacity() + self.arena.capacity()
1459    }
1460
1461    /// Writes a `StartAnimation`: the 52 byte head, then each keyframe with its value.
1462    ///
1463    /// The bytes are written as they are given; whether they make an animation is the
1464    /// decoder's question, asked on both sides, so a Host that sends a bad one hears about
1465    /// it as a protocol error rather than not at all.
1466    fn put_animation(
1467        &mut self,
1468        animation: &crate::schema::Animation<'_>,
1469    ) -> Result<(), ProtocolError> {
1470        use crate::schema::{KeyframeValue, Timing};
1471        let keyframes_len: usize = animation
1472            .keyframes
1473            .iter()
1474            .map(|keyframe| {
1475                KEYFRAME_HEAD_LEN
1476                    + match &keyframe.value {
1477                        KeyframeValue::Alpha(_) => 4,
1478                        KeyframeValue::Paint(_) => 8,
1479                        KeyframeValue::Transform(functions) => {
1480                            4 + TRANSFORM_FUNCTION_LEN * functions.len()
1481                        }
1482                    }
1483            })
1484            .sum();
1485        let payload = u16::try_from(ANIMATION_HEADER_LEN - 4 + keyframes_len)
1486            .ok()
1487            .filter(|payload| *payload <= u16::MAX - 4)
1488            .ok_or(ProtocolError::LengthOverflow)?;
1489        let keyframe_count =
1490            u16::try_from(animation.keyframes.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1491        self.begin_record(TAG_START_ANIMATION, payload);
1492        self.put_u32(animation.node_id);
1493        self.put_u32(animation.animation_id);
1494        self.records.push(animation.property as u8);
1495        self.records.push(animation.slot);
1496        self.records.push(animation.direction as u8);
1497        self.records.push(animation.fill as u8);
1498        self.records.push(animation.play_state as u8);
1499        self.records
1500            .push(animation.interpolation.map_or(0, |value| value as u8));
1501        self.put_u16(keyframe_count);
1502        self.put_u64(animation.start_time_nanos);
1503        self.put_u32(animation.delay_ms.to_bits());
1504        self.put_u32(animation.duration_ms.to_bits());
1505        self.put_u32(animation.iterations.to_bits());
1506        self.put_u32(animation.origin_x.to_bits());
1507        self.put_u32(animation.origin_y.to_bits());
1508        self.records.push(animation.events.0);
1509        self.records.extend_from_slice(&[0, 0, 0]);
1510        for keyframe in animation.keyframes.iter() {
1511            self.put_u32(keyframe.offset.to_bits());
1512            let (kind, position, numbers) = match keyframe.timing {
1513                Timing::Linear => (1_u8, 0_u8, [0_u32; 4]),
1514                Timing::CubicBezier { x1, y1, x2, y2 } => (
1515                    2,
1516                    0,
1517                    [x1.to_bits(), y1.to_bits(), x2.to_bits(), y2.to_bits()],
1518                ),
1519                Timing::Steps { count, position } => (3, position as u8, [count, 0, 0, 0]),
1520            };
1521            self.records.push(kind);
1522            self.records.push(position);
1523            self.put_u16(if keyframe.from_presented {
1524                KEYFRAME_FROM_PRESENTED
1525            } else {
1526                0
1527            });
1528            for number in numbers {
1529                self.put_u32(number);
1530            }
1531            match &keyframe.value {
1532                KeyframeValue::Alpha(value) => self.put_u32(value.to_bits()),
1533                KeyframeValue::Paint(paint) => self.put_u64(paint.to_bits()),
1534                KeyframeValue::Transform(functions) => {
1535                    let count = u16::try_from(functions.len())
1536                        .map_err(|_| ProtocolError::LengthOverflow)?;
1537                    self.put_u16(count);
1538                    self.put_u16(0);
1539                    for function in functions.iter() {
1540                        self.put_u32(u32::from(u16::from(function.kind)));
1541                        for value in function.values {
1542                            self.put_u32(value.to_bits());
1543                        }
1544                    }
1545                }
1546            }
1547        }
1548        Ok(())
1549    }
1550
1551    /// The body of a `SetTheme`, which `SetWindowTheme` carries after its node id.
1552    fn put_theme(&mut self, theme: &Theme) -> Result<(), ProtocolError> {
1553        self.put_u16(theme.design_system as u16);
1554        self.put_u16(theme.fallback as u16);
1555        self.put_u16(theme.color_scheme as u16);
1556        self.put_u16(u16::from(theme.adaptive));
1557        // One slot per type role, zero where the role keeps the system font. Sent
1558        // whether or not any of them is filled, because the record is fixed
1559        // layout and a theme change is one record rather than a per frame cost.
1560        for asset in theme.fonts {
1561            self.put_u32(asset);
1562        }
1563        // The application's colours, as a run of eight byte entries behind the
1564        // records the way a string is. Zero long where there is no palette, so a
1565        // theme without one costs the eight bytes of the reference and nothing
1566        // else.
1567        self.put_palette_ref(theme.palette)
1568    }
1569
1570    fn begin_record(&mut self, tag: u16, payload_len: u16) {
1571        self.put_u16(tag);
1572        self.put_u16(payload_len + 4);
1573    }
1574
1575    /// Writes a palette's entries straight into the trailing region, so encoding a theme
1576    /// allocates nothing beyond what that region already has.
1577    fn put_palette_ref(
1578        &mut self,
1579        palette: Option<&crate::palette::Palette>,
1580    ) -> Result<(), ProtocolError> {
1581        let start = self.strings.len();
1582        let offset = u32::try_from(start).map_err(|_| ProtocolError::LengthOverflow)?;
1583        if let Some(palette) = palette {
1584            palette.write_into(&mut self.strings);
1585        }
1586        let len =
1587            u32::try_from(self.strings.len() - start).map_err(|_| ProtocolError::LengthOverflow)?;
1588        self.string_fixups.push(self.records.len());
1589        self.put_u32(offset);
1590        self.put_u32(len);
1591        Ok(())
1592    }
1593
1594    fn put_string_ref(&mut self, value: &str) -> Result<(), ProtocolError> {
1595        self.put_bytes_ref(value.as_bytes())
1596    }
1597
1598    /// Asset bytes ride in the same trailing region as strings, with the same
1599    /// `(offset, len)` reference and the same fixup, so the record stays fixed layout.
1600    fn put_bytes_ref(&mut self, value: &[u8]) -> Result<(), ProtocolError> {
1601        let offset =
1602            u32::try_from(self.strings.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1603        let len = u32::try_from(value.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1604        self.string_fixups.push(self.records.len());
1605        self.put_u32(offset);
1606        self.put_u32(len);
1607        self.strings.extend_from_slice(value);
1608        Ok(())
1609    }
1610
1611    fn put_u16(&mut self, value: u16) {
1612        self.records.extend_from_slice(&value.to_le_bytes());
1613    }
1614
1615    fn put_u32(&mut self, value: u32) {
1616        self.records.extend_from_slice(&value.to_le_bytes());
1617    }
1618
1619    fn put_u64(&mut self, value: u64) {
1620        self.records.extend_from_slice(&value.to_le_bytes());
1621    }
1622}
1623
1624/// Reads the body of a `SetTheme` that starts at `payload`, which `SetWindowTheme` also
1625/// carries, after its node id.
1626fn decode_theme(bytes: &[u8], payload: usize, records_len: usize) -> Result<Theme, ProtocolError> {
1627    let design_system = read_u16(bytes, payload)?;
1628    let fallback = read_u16(bytes, payload + 2)?;
1629    let color_scheme = read_u16(bytes, payload + 4)?;
1630    let adaptive = read_u16(bytes, payload + 6)?;
1631    if adaptive > 1 {
1632        return Err(ProtocolError::InvalidTheme(adaptive));
1633    }
1634    let mut fonts = [0u32; TYPE_ROLE_COUNT];
1635    for (index, slot) in fonts.iter_mut().enumerate() {
1636        *slot = read_u32(bytes, payload + 8 + 4 * index)?;
1637    }
1638    // Entries that are wrong are left out and the rest stand, which is what
1639    // the Renderer does with them as well. Leaked like the window's title: a
1640    // theme holds its palette for the life of the program, and this decoder
1641    // is what tests and the mock renderer read a batch back with.
1642    let palette_bytes = read_bytes(bytes, payload + 8 + 4 * TYPE_ROLE_COUNT, records_len)?;
1643    let palette = if palette_bytes.is_empty() {
1644        None
1645    } else {
1646        let (palette, _problems) = crate::palette::decode_palette(palette_bytes);
1647        Some(&*Box::leak(Box::new(palette)))
1648    };
1649    Ok(Theme {
1650        design_system: DesignSystem::try_from(design_system)
1651            .map_err(|()| ProtocolError::InvalidTheme(design_system))?,
1652        fallback: DesignSystem::try_from(fallback)
1653            .map_err(|()| ProtocolError::InvalidTheme(fallback))?,
1654        color_scheme: ColorScheme::try_from(color_scheme)
1655            .map_err(|()| ProtocolError::InvalidTheme(color_scheme))?,
1656        adaptive: adaptive == 1,
1657        fonts,
1658        palette,
1659    })
1660}
1661
1662pub fn decode_batch(bytes: &[u8]) -> Result<Vec<Mutation<'_>>, ProtocolError> {
1663    if bytes.len() < ENVELOPE_LEN
1664        || read_u16(bytes, 0)? != TAG_ENVELOPE
1665        || read_u16(bytes, 2)? != 12
1666    {
1667        return Err(ProtocolError::InvalidEnvelope);
1668    }
1669    let records_len =
1670        usize::try_from(read_u32(bytes, 4)?).map_err(|_| ProtocolError::LengthOverflow)?;
1671    let count = usize::try_from(read_u32(bytes, 8)?).map_err(|_| ProtocolError::LengthOverflow)?;
1672    if records_len < ENVELOPE_LEN || records_len > bytes.len() || records_len % 4 != 0 {
1673        return Err(ProtocolError::InvalidEnvelope);
1674    }
1675    // `count` is attacker-controlled, so it only sizes the buffer up to what the
1676    // record region could actually hold. The shortest mutation record is a bare header at
1677    // 4 bytes, so that is the ceiling. Reserving `count` directly let a 12-byte message ask
1678    // for a >100 GB allocation.
1679    let capacity = count.min((records_len - ENVELOPE_LEN) / MIN_RECORD_LEN);
1680    let mut output = Vec::with_capacity(capacity);
1681    let mut position = ENVELOPE_LEN;
1682    while position < records_len {
1683        let tag = read_u16(bytes, position)?;
1684        let len = usize::from(read_u16(bytes, position + 2)?);
1685        if len < 4 || len % 4 != 0 || position + len > records_len {
1686            return Err(ProtocolError::InvalidRecordLength);
1687        }
1688        let payload = position + 4;
1689        let mutation = match tag {
1690            TAG_CREATE if len == 12 => Mutation::Create {
1691                node_id: read_u32(bytes, payload)?,
1692                widget: WidgetKind::try_from(read_u16(bytes, payload + 4)?).map_err(|()| {
1693                    ProtocolError::InvalidWidget(read_u16(bytes, payload + 4).unwrap_or(0))
1694                })?,
1695            },
1696            TAG_SET_PROP if len == 24 => {
1697                let property_raw = read_u16(bytes, payload + 4)?;
1698                let value_kind = read_u16(bytes, payload + 6)?;
1699                let value = match value_kind {
1700                    VALUE_NONE => PropertyValue::None,
1701                    VALUE_STRING => {
1702                        PropertyValue::String(read_string(bytes, payload + 8, records_len)?)
1703                    }
1704                    VALUE_BOOL => PropertyValue::Bool(read_u64(bytes, payload + 8)? != 0),
1705                    VALUE_I64 => PropertyValue::Integer(read_u64(bytes, payload + 8)? as i64),
1706                    VALUE_F32 => {
1707                        PropertyValue::Float(f32::from_bits(read_u64(bytes, payload + 8)? as u32))
1708                    }
1709                    VALUE_BYTES => {
1710                        PropertyValue::Bytes(read_bytes(bytes, payload + 8, records_len)?)
1711                    }
1712                    other => return Err(ProtocolError::InvalidValueKind(other)),
1713                };
1714                Mutation::SetProp {
1715                    node_id: read_u32(bytes, payload)?,
1716                    property: PropertyKind::try_from(property_raw)
1717                        .map_err(|()| ProtocolError::InvalidProperty(property_raw))?,
1718                    value,
1719                }
1720            }
1721            TAG_SET_MODIFIER if len >= 28 => {
1722                // The modifier's tag says how long its record is: 28 bytes and eight more
1723                // for each word the schema gives it beyond the first two. A record of any
1724                // other length is refused before its value is read.
1725                let modifier_tag = read_u16(bytes, payload + 6)?;
1726                let extra = usize::from(
1727                    modifier_extra_words(modifier_tag)
1728                        .ok_or(ProtocolError::InvalidModifier(modifier_tag))?,
1729                );
1730                if len != 28 + 8 * extra {
1731                    return Err(ProtocolError::InvalidRecordLength);
1732                }
1733                let mut words = [0_u64; MAX_MODIFIER_WORDS];
1734                for (word_index, word) in words.iter_mut().take(2 + extra).enumerate() {
1735                    *word = read_u64(bytes, payload + 8 + 8 * word_index)?;
1736                }
1737                Mutation::SetModifier {
1738                    node_id: read_u32(bytes, payload)?,
1739                    index: read_u16(bytes, payload + 4)?,
1740                    modifier: decode_modifier(modifier_tag, &words)?,
1741                }
1742            }
1743            TAG_INSERT if len == 16 => Mutation::Insert {
1744                parent_id: read_u32(bytes, payload)?,
1745                node_id: read_u32(bytes, payload + 4)?,
1746                index: read_u32(bytes, payload + 8)?,
1747            },
1748            TAG_MOVE if len == 16 => Mutation::Move {
1749                parent_id: read_u32(bytes, payload)?,
1750                node_id: read_u32(bytes, payload + 4)?,
1751                index: read_u32(bytes, payload + 8)?,
1752            },
1753            TAG_REMOVE if len == 8 => Mutation::Remove {
1754                node_id: read_u32(bytes, payload)?,
1755            },
1756            TAG_SET_TEXT if len == 24 => {
1757                let start = read_u32(bytes, payload + 12)?;
1758                let end = read_u32(bytes, payload + 16)?;
1759                Mutation::SetText {
1760                    node_id: read_u32(bytes, payload)?,
1761                    text: read_string(bytes, payload + 4, records_len)?,
1762                    selection: (start != u32::MAX || end != u32::MAX)
1763                        .then_some(Selection { start, end }),
1764                }
1765            }
1766            TAG_APPEND_TEXT if len == 16 => Mutation::AppendText {
1767                node_id: read_u32(bytes, payload)?,
1768                text: read_string(bytes, payload + 4, records_len)?,
1769            },
1770            TAG_SET_THEME if len == 4 + THEME_PAYLOAD_LEN => {
1771                Mutation::SetTheme(decode_theme(bytes, payload, records_len)?)
1772            }
1773            TAG_SET_WINDOW_THEME if len == 8 + THEME_PAYLOAD_LEN => Mutation::SetWindowTheme {
1774                node_id: read_u32(bytes, payload)?,
1775                theme: decode_theme(bytes, payload + 4, records_len)?,
1776            },
1777            TAG_FOCUS_WINDOW if len == 8 => Mutation::FocusWindow {
1778                node_id: read_u32(bytes, payload)?,
1779            },
1780            TAG_SET_WINDOW if len == 32 => {
1781                let chrome = read_u16(bytes, payload)?;
1782                let title_bar = read_u16(bytes, payload + 2)?;
1783                // Bit 0 is `resizable`; the mode and the last window policy are the
1784                // nibbles above the second byte. Anything else standing is a record the
1785                // sides disagree on.
1786                let flags = read_u16(bytes, payload + 12)?;
1787                if flags & 0x00FE != 0 {
1788                    return Err(ProtocolError::InvalidValueKind(flags));
1789                }
1790                let resizable = flags & 1;
1791                let mode_tag = (flags >> WINDOW_MODE_SHIFT) & 0xF;
1792                let policy_tag = flags >> WINDOW_POLICY_SHIFT;
1793                let zoom = read_u16(bytes, payload + 14)?;
1794                let zoom_level = match zoom & 0xFF {
1795                    0 => None,
1796                    1 => {
1797                        let level = (zoom >> 8) as u8 as i8;
1798                        if !(crate::zoom::MIN_ZOOM_LEVEL..=crate::zoom::MAX_ZOOM_LEVEL)
1799                            .contains(&level)
1800                        {
1801                            return Err(ProtocolError::InvalidValueKind(zoom));
1802                        }
1803                        Some(level)
1804                    }
1805                    _ => return Err(ProtocolError::InvalidValueKind(zoom)),
1806                };
1807                Mutation::SetWindow(crate::schema::Window {
1808                    chrome: crate::schema::Chrome::try_from(chrome)
1809                        .map_err(|()| ProtocolError::InvalidValueKind(chrome))?,
1810                    title_bar: crate::schema::TitleBar::try_from(title_bar)
1811                        .map_err(|()| ProtocolError::InvalidValueKind(title_bar))?,
1812                    width: read_u16(bytes, payload + 4)?,
1813                    height: read_u16(bytes, payload + 6)?,
1814                    min_width: read_u16(bytes, payload + 8)?,
1815                    min_height: read_u16(bytes, payload + 10)?,
1816                    resizable: resizable == 1,
1817                    zoom_level,
1818                    mode: crate::schema::WindowMode::try_from(mode_tag)
1819                        .map_err(|()| ProtocolError::InvalidValueKind(flags))?,
1820                    last_window_closed: crate::schema::LastWindowClosed::try_from(policy_tag)
1821                        .map_err(|()| ProtocolError::InvalidValueKind(flags))?,
1822                    // Leaked on purpose, once per window. The record's owner is the
1823                    // window, the window outlives the batch it arrived in, and there is
1824                    // one of these per process.
1825                    icon: read_u32(bytes, payload + 24)?,
1826                    title: Box::leak(
1827                        read_string(bytes, payload + 16, records_len)?
1828                            .to_owned()
1829                            .into_boxed_str(),
1830                    ),
1831                })
1832            }
1833            TAG_REGISTER_ASSET if len == 20 => {
1834                let raw_kind = read_u16(bytes, payload + 4)?;
1835                Mutation::RegisterAsset {
1836                    asset_id: read_u32(bytes, payload)?,
1837                    kind: AssetKind::try_from(raw_kind)
1838                        .map_err(|()| ProtocolError::InvalidAssetKind(raw_kind))?,
1839                    bytes: read_bytes(bytes, payload + 8, records_len)?,
1840                }
1841            }
1842            TAG_RELEASE_ASSET if len == 8 => Mutation::ReleaseAsset {
1843                asset_id: read_u32(bytes, payload)?,
1844            },
1845            TAG_SHOW_MESSAGE if len == 32 => {
1846                let raw_duration = read_u16(bytes, payload + 24)?;
1847                Mutation::ShowMessage {
1848                    handler_id: read_u64(bytes, payload)?,
1849                    text: read_string(bytes, payload + 8, records_len)?,
1850                    action: read_string(bytes, payload + 16, records_len)?,
1851                    duration: MessageDuration::try_from(raw_duration)
1852                        .map_err(|()| ProtocolError::InvalidMessageDuration(raw_duration))?,
1853                }
1854            }
1855            TAG_EDIT_CODE if len == 40 => Mutation::EditCode {
1856                node_id: read_u32(bytes, payload)?,
1857                request_id: read_u32(bytes, payload + 4)?,
1858                base_version: read_u32(bytes, payload + 8)?,
1859                start_line: read_u32(bytes, payload + 12)?,
1860                start_column: read_u32(bytes, payload + 16)?,
1861                end_line: read_u32(bytes, payload + 20)?,
1862                end_column: read_u32(bytes, payload + 24)?,
1863                text: read_string(bytes, payload + 28, records_len)?,
1864            },
1865            TAG_POST_NOTIFICATION if len == 56 => {
1866                let raw_importance = read_u16(bytes, payload + 48)?;
1867                let raw_presentation = read_u16(bytes, payload + 50)?;
1868                Mutation::PostNotification {
1869                    key: read_string(bytes, payload, records_len)?,
1870                    title: read_string(bytes, payload + 8, records_len)?,
1871                    body: read_string(bytes, payload + 16, records_len)?,
1872                    channel: read_string(bytes, payload + 24, records_len)?,
1873                    action_1: read_string(bytes, payload + 32, records_len)?,
1874                    action_2: read_string(bytes, payload + 40, records_len)?,
1875                    importance: NotificationImportance::try_from(raw_importance)
1876                        .map_err(|()| ProtocolError::InvalidNotification(raw_importance))?,
1877                    presentation: NotificationPresentation::try_from(raw_presentation)
1878                        .map_err(|()| ProtocolError::InvalidNotification(raw_presentation))?,
1879                }
1880            }
1881            TAG_WITHDRAW_NOTIFICATION if len == 12 => Mutation::WithdrawNotification {
1882                key: read_string(bytes, payload, records_len)?,
1883            },
1884            TAG_REQUEST_NOTIFICATION_PERMISSION if len == 4 => {
1885                Mutation::RequestNotificationPermission
1886            }
1887            TAG_START_ANIMATION if len >= ANIMATION_HEADER_LEN => {
1888                // A keyframe that runs past the end of its record is a count that does not
1889                // match the length, which is the record being wrong rather than the batch.
1890                Mutation::StartAnimation(
1891                    decode_animation(&bytes[position..position + len]).map_err(|error| {
1892                        if error == ProtocolError::Truncated {
1893                            ProtocolError::InvalidAnimation
1894                        } else {
1895                            error
1896                        }
1897                    })?,
1898                )
1899            }
1900            TAG_CONTROL_ANIMATION if len == 24 => {
1901                let raw_property = u16::from(read_u8(bytes, payload + 8)?);
1902                let raw_op = u16::from(read_u8(bytes, payload + 10)?);
1903                Mutation::ControlAnimation {
1904                    node_id: read_u32(bytes, payload)?,
1905                    animation_id: read_u32(bytes, payload + 4)?,
1906                    property: crate::schema::AnimatedProperty::try_from(raw_property)
1907                        .map_err(|()| ProtocolError::InvalidAnimation)?,
1908                    slot: read_u8(bytes, payload + 9)?,
1909                    op: crate::schema::AnimationControl::try_from(raw_op)
1910                        .map_err(|()| ProtocolError::InvalidAnimation)?,
1911                    at_time_nanos: read_u64(bytes, payload + 12)?,
1912                }
1913            }
1914            TAG_SET_SELECTION if len == 28 => Mutation::SetSelection {
1915                node_id: read_u32(bytes, payload)?,
1916                base_version: read_u32(bytes, payload + 4)?,
1917                anchor_line: read_u32(bytes, payload + 8)?,
1918                anchor_column: read_u32(bytes, payload + 12)?,
1919                active_line: read_u32(bytes, payload + 16)?,
1920                active_column: read_u32(bytes, payload + 20)?,
1921            },
1922            TAG_REVEAL_RANGE if len == 32 => {
1923                let raw_align = read_u32(bytes, payload + 24)?;
1924                Mutation::RevealRange {
1925                    node_id: read_u32(bytes, payload)?,
1926                    base_version: read_u32(bytes, payload + 4)?,
1927                    start_line: read_u32(bytes, payload + 8)?,
1928                    start_column: read_u32(bytes, payload + 12)?,
1929                    end_line: read_u32(bytes, payload + 16)?,
1930                    end_column: read_u32(bytes, payload + 20)?,
1931                    align: u16::try_from(raw_align)
1932                        .ok()
1933                        .and_then(|tag| crate::schema::RevealAlign::try_from(tag).ok())
1934                        .ok_or(ProtocolError::InvalidValueKind(
1935                            raw_align.min(u32::from(u16::MAX)) as u16,
1936                        ))?,
1937                }
1938            }
1939            TAG_SET_FOLDED if len == 16 => Mutation::SetFolded {
1940                node_id: read_u32(bytes, payload)?,
1941                start_line: read_u32(bytes, payload + 4)?,
1942                collapsed: read_flag(bytes, payload + 8)?,
1943            },
1944            TAG_CREATE..=TAG_EDIT_CODE
1945            | TAG_START_ANIMATION
1946            | TAG_CONTROL_ANIMATION
1947            | TAG_SET_SELECTION..=TAG_SET_FOLDED
1948            | TAG_SET_WINDOW_THEME..=TAG_FOCUS_WINDOW => {
1949                return Err(ProtocolError::InvalidRecordLength);
1950            }
1951            other => return Err(ProtocolError::InvalidTag(other)),
1952        };
1953        output.push(mutation);
1954        position += len;
1955    }
1956    if output.len() != count {
1957        return Err(ProtocolError::InvalidEnvelope);
1958    }
1959    Ok(output)
1960}
1961
1962/// Reads one `StartAnimation` record, given whole, and refuses anything that is not an
1963/// animation the Renderer could play. The Renderer asks the same questions of the same
1964/// bytes, so a record one side refuses is refused on the other as well.
1965fn decode_animation(record: &[u8]) -> Result<crate::schema::Animation<'static>, ProtocolError> {
1966    use crate::schema::{
1967        AnimatedProperty, Animation, AnimationEvents, ColorInterpolation, FillMode, Keyframe,
1968        KeyframeValue, PlayState, PlaybackDirection, StepPosition, Timing, TransformFunction,
1969        TransformFunctionKind,
1970    };
1971    let bad = ProtocolError::InvalidAnimation;
1972    fn byte_enum<T: TryFrom<u16, Error = ()>>(
1973        record: &[u8],
1974        at: usize,
1975    ) -> Result<T, ProtocolError> {
1976        T::try_from(u16::from(read_u8(record, at)?)).map_err(|()| ProtocolError::InvalidAnimation)
1977    }
1978    let float = |at: usize| read_u32(record, at).map(f32::from_bits);
1979    let property: AnimatedProperty = byte_enum(record, 12)?;
1980    let direction: PlaybackDirection = byte_enum(record, 14)?;
1981    let fill: FillMode = byte_enum(record, 15)?;
1982    let play_state: PlayState = byte_enum(record, 16)?;
1983    let colour = matches!(
1984        property,
1985        AnimatedProperty::Color | AnimatedProperty::Background | AnimatedProperty::BorderColor
1986    );
1987    let interpolation = match (colour, read_u8(record, 17)?) {
1988        (false, 0) => None,
1989        (true, raw) => Some(
1990            ColorInterpolation::try_from(u16::from(raw))
1991                .map_err(|()| ProtocolError::InvalidAnimation)?,
1992        ),
1993        (false, _) => return Err(bad),
1994    };
1995    let keyframe_count = usize::from(read_u16(record, 18)?);
1996    let delay_ms = float(28)?;
1997    let duration_ms = float(32)?;
1998    let iterations = float(36)?;
1999    let origin_x = float(40)?;
2000    let origin_y = float(44)?;
2001    let events = read_u8(record, 48)?;
2002    if keyframe_count < 2
2003        || !delay_ms.is_finite()
2004        || !(duration_ms.is_finite() && duration_ms >= 0.0)
2005        || iterations.is_nan()
2006        || iterations < 0.0
2007        || !(origin_x.is_finite() && origin_y.is_finite())
2008        || events & !AnimationEvents::ALL.0 != 0
2009    {
2010        return Err(bad);
2011    }
2012    let mut keyframes = Vec::with_capacity(keyframe_count);
2013    let mut at = ANIMATION_HEADER_LEN;
2014    for index in 0..keyframe_count {
2015        let offset = float(at)?;
2016        let raw_timing = read_u8(record, at + 4)?;
2017        let raw_position = read_u8(record, at + 5)?;
2018        let flags = read_u16(record, at + 6)?;
2019        let timing = match raw_timing {
2020            1 => Timing::Linear,
2021            2 => {
2022                let (x1, y1, x2, y2) = (
2023                    float(at + 8)?,
2024                    float(at + 12)?,
2025                    float(at + 16)?,
2026                    float(at + 20)?,
2027                );
2028                if ![x1, y1, x2, y2].iter().all(|value| value.is_finite())
2029                    || !(0.0..=1.0).contains(&x1)
2030                    || !(0.0..=1.0).contains(&x2)
2031                {
2032                    return Err(bad);
2033                }
2034                Timing::CubicBezier { x1, y1, x2, y2 }
2035            }
2036            3 => {
2037                let count = read_u32(record, at + 8)?;
2038                let position =
2039                    StepPosition::try_from(u16::from(raw_position)).map_err(|()| bad.clone())?;
2040                let least = if position == StepPosition::JumpNone {
2041                    2
2042                } else {
2043                    1
2044                };
2045                if count < least {
2046                    return Err(bad);
2047                }
2048                Timing::Steps { count, position }
2049            }
2050            _ => return Err(bad),
2051        };
2052        if flags & !KEYFRAME_FROM_PRESENTED != 0
2053            || (index > 0 && flags & KEYFRAME_FROM_PRESENTED != 0)
2054            || !(0.0..=1.0).contains(&offset)
2055        {
2056            return Err(bad);
2057        }
2058        let value_at = at + KEYFRAME_HEAD_LEN;
2059        let (value, value_len) = match property {
2060            AnimatedProperty::Alpha => {
2061                let value = float(value_at)?;
2062                if !value.is_finite() {
2063                    return Err(bad);
2064                }
2065                (KeyframeValue::Alpha(value), 4)
2066            }
2067            AnimatedProperty::Color
2068            | AnimatedProperty::Background
2069            | AnimatedProperty::BorderColor => {
2070                // A colour can be mixed with another colour; a brush cannot.
2071                let paint = Paint::from_bits(read_u64(record, value_at)?)
2072                    .filter(|paint| !matches!(paint, Paint::Asset(_)))
2073                    .ok_or(bad.clone())?;
2074                (KeyframeValue::Paint(paint), 8)
2075            }
2076            AnimatedProperty::Transform => {
2077                let count = usize::from(read_u16(record, value_at)?);
2078                let mut functions = Vec::with_capacity(count);
2079                for function in 0..count {
2080                    let function_at = value_at + 4 + TRANSFORM_FUNCTION_LEN * function;
2081                    let raw_kind = read_u32(record, function_at)?;
2082                    let kind = u16::try_from(raw_kind)
2083                        .ok()
2084                        .and_then(|kind| TransformFunctionKind::try_from(kind).ok())
2085                        .ok_or(bad.clone())?;
2086                    let mut values = [0.0_f32; 6];
2087                    for (slot, value) in values.iter_mut().enumerate() {
2088                        *value = float(function_at + 4 + 4 * slot)?;
2089                    }
2090                    if !values.iter().all(|value| value.is_finite()) {
2091                        return Err(bad);
2092                    }
2093                    functions.push(TransformFunction { kind, values });
2094                }
2095                (
2096                    KeyframeValue::Transform(std::borrow::Cow::Owned(functions)),
2097                    4 + TRANSFORM_FUNCTION_LEN * count,
2098                )
2099            }
2100        };
2101        keyframes.push(Keyframe {
2102            offset,
2103            timing,
2104            from_presented: flags & KEYFRAME_FROM_PRESENTED != 0,
2105            value,
2106        });
2107        at = value_at + value_len;
2108    }
2109    if at != record.len() {
2110        return Err(bad);
2111    }
2112    // Offsets start at 0, end at 1 and never go back, and every transform list has the
2113    // same kinds in the same order. A list the Host did not line up cannot be interpolated
2114    // one function to the next, which is the only way the Renderer interpolates them.
2115    let first = keyframes.first().map(|keyframe| keyframe.offset);
2116    let last = keyframes.last().map(|keyframe| keyframe.offset);
2117    if first != Some(0.0)
2118        || last != Some(1.0)
2119        || keyframes
2120            .windows(2)
2121            .any(|pair| pair[1].offset < pair[0].offset)
2122    {
2123        return Err(bad);
2124    }
2125    if let Some(KeyframeValue::Transform(shape)) = keyframes.first().map(|keyframe| &keyframe.value)
2126    {
2127        let kinds: Vec<_> = shape.iter().map(|function| function.kind).collect();
2128        let lined_up = keyframes.iter().all(|keyframe| match &keyframe.value {
2129            KeyframeValue::Transform(functions) => functions
2130                .iter()
2131                .map(|function| function.kind)
2132                .eq(kinds.iter().copied()),
2133            _ => false,
2134        });
2135        if !lined_up {
2136            return Err(bad);
2137        }
2138    }
2139    Ok(Animation {
2140        node_id: read_u32(record, 4)?,
2141        animation_id: read_u32(record, 8)?,
2142        property,
2143        slot: read_u8(record, 13)?,
2144        direction,
2145        fill,
2146        play_state,
2147        interpolation,
2148        start_time_nanos: read_u64(record, 20)?,
2149        delay_ms,
2150        duration_ms,
2151        iterations,
2152        origin_x,
2153        origin_y,
2154        events: AnimationEvents(events),
2155        keyframes: std::borrow::Cow::Owned(keyframes),
2156    })
2157}
2158
2159/// Two `f32` share one `u64`, with the first value in the low 32 bits.
2160const fn pack_floats(low: f32, high: f32) -> u64 {
2161    (low.to_bits() as u64) | ((high.to_bits() as u64) << 32)
2162}
2163
2164const fn unpack_low(word: u64) -> f32 {
2165    f32::from_bits(word as u32)
2166}
2167
2168const fn unpack_high(word: u64) -> f32 {
2169    f32::from_bits((word >> 32) as u32)
2170}
2171
2172/// The value's words with nothing past the first two, which is every modifier but the few
2173/// whose schema entry gives them more.
2174const fn two_words(first: u64, second: u64) -> [u64; MAX_MODIFIER_WORDS] {
2175    [first, second, 0, 0, 0, 0]
2176}
2177
2178fn float_word(value: f32) -> u64 {
2179    u64::from(value.to_bits())
2180}
2181
2182/// A modifier's tag and the words of its value. Only as many words as the schema gives
2183/// that tag are written; the rest are zero and never leave this function.
2184fn modifier_fields(modifier: &Modifier) -> (u16, [u64; MAX_MODIFIER_WORDS]) {
2185    match modifier {
2186        Modifier::Empty => (0, two_words(0, 0)),
2187        Modifier::Padding(value) => (1, two_words(float_word(*value), 0)),
2188        Modifier::FillMaxWidth => (2, two_words(0, 0)),
2189        Modifier::FillMaxHeight => (3, two_words(0, 0)),
2190        Modifier::Width(value) => (4, two_words(float_word(*value), 0)),
2191        Modifier::Height(value) => (5, two_words(float_word(*value), 0)),
2192        Modifier::Size { width, height } => (6, two_words(float_word(*width), float_word(*height))),
2193        Modifier::Background(paint) => (7, two_words(paint.to_bits(), 0)),
2194        Modifier::Clickable { handler_id } => (8, two_words(*handler_id, 0)),
2195        Modifier::PaddingRole(role) => (9, two_words(*role as u64, 0)),
2196        Modifier::PaddingEach {
2197            start,
2198            top,
2199            end,
2200            bottom,
2201        } => (
2202            10,
2203            two_words(pack_floats(*start, *top), pack_floats(*end, *bottom)),
2204        ),
2205        Modifier::Weight(value) => (11, two_words(float_word(*value), 0)),
2206        Modifier::Shape {
2207            top_start,
2208            top_end,
2209            bottom_end,
2210            bottom_start,
2211        } => (
2212            12,
2213            two_words(
2214                pack_floats(*top_start, *top_end),
2215                pack_floats(*bottom_end, *bottom_start),
2216            ),
2217        ),
2218        Modifier::ShapeRole(role) => (13, two_words(*role as u64, 0)),
2219        Modifier::Border { width, paint } => (14, two_words(float_word(*width), paint.to_bits())),
2220        Modifier::Elevation(dp) => (15, two_words(float_word(*dp), 0)),
2221        Modifier::ObserveSize { token } => (16, two_words(u64::from(*token), 0)),
2222        Modifier::Motion(role) => (17, two_words(*role as u64, 0)),
2223        Modifier::Material(role) => (18, two_words(*role as u64, 0)),
2224        Modifier::Offset { x, y } => (19, two_words(float_word(*x), float_word(*y))),
2225        Modifier::RequiredSize { width, height } => {
2226            (20, two_words(float_word(*width), float_word(*height)))
2227        }
2228        Modifier::BorderEach {
2229            top,
2230            right,
2231            bottom,
2232            left,
2233            top_paint,
2234            right_paint,
2235            bottom_paint,
2236            left_paint,
2237        } => (
2238            21,
2239            [
2240                pack_floats(*top, *right),
2241                pack_floats(*bottom, *left),
2242                top_paint.to_bits(),
2243                right_paint.to_bits(),
2244                bottom_paint.to_bits(),
2245                left_paint.to_bits(),
2246            ],
2247        ),
2248        Modifier::CornerEach {
2249            top_left,
2250            top_right,
2251            bottom_right,
2252            bottom_left,
2253        } => (
2254            22,
2255            two_words(
2256                pack_floats(*top_left, *top_right),
2257                pack_floats(*bottom_right, *bottom_left),
2258            ),
2259        ),
2260        Modifier::Shadow {
2261            x,
2262            y,
2263            blur,
2264            spread,
2265            paint,
2266        } => (
2267            23,
2268            [
2269                pack_floats(*x, *y),
2270                pack_floats(*blur, *spread),
2271                paint.to_bits(),
2272                0,
2273                0,
2274                0,
2275            ],
2276        ),
2277        Modifier::Clip(enabled) => (24, two_words(u64::from(*enabled), 0)),
2278        Modifier::Alpha(value) => (25, two_words(float_word(*value), 0)),
2279        Modifier::Transform {
2280            a,
2281            b,
2282            c,
2283            d,
2284            e,
2285            f,
2286            origin_x,
2287            origin_y,
2288        } => (
2289            26,
2290            [
2291                pack_floats(*a, *b),
2292                pack_floats(*c, *d),
2293                pack_floats(*e, *f),
2294                pack_floats(*origin_x, *origin_y),
2295                0,
2296                0,
2297            ],
2298        ),
2299    }
2300}
2301
2302fn decode_role<T: TryFrom<u16, Error = ()>>(first: u64) -> Result<T, ProtocolError> {
2303    let tag = u16::try_from(first).map_err(|_| ProtocolError::InvalidModifier(0))?;
2304    T::try_from(tag).map_err(|()| ProtocolError::InvalidModifier(tag))
2305}
2306
2307fn decode_paint(bits: u64) -> Result<Paint, ProtocolError> {
2308    Paint::from_bits(bits).ok_or(ProtocolError::InvalidModifier(0))
2309}
2310
2311fn decode_modifier(tag: u16, words: &[u64; MAX_MODIFIER_WORDS]) -> Result<Modifier, ProtocolError> {
2312    let [first, second, third, fourth, fifth, sixth] = *words;
2313    match tag {
2314        0 => Ok(Modifier::Empty),
2315        1 => Ok(Modifier::Padding(f32::from_bits(first as u32))),
2316        2 => Ok(Modifier::FillMaxWidth),
2317        3 => Ok(Modifier::FillMaxHeight),
2318        4 => Ok(Modifier::Width(f32::from_bits(first as u32))),
2319        5 => Ok(Modifier::Height(f32::from_bits(first as u32))),
2320        6 => Ok(Modifier::Size {
2321            width: f32::from_bits(first as u32),
2322            height: f32::from_bits(second as u32),
2323        }),
2324        7 => Ok(Modifier::Background(decode_paint(first)?)),
2325        8 => Ok(Modifier::Clickable { handler_id: first }),
2326        9 => Ok(Modifier::PaddingRole(decode_role::<SpaceRole>(first)?)),
2327        10 => Ok(Modifier::PaddingEach {
2328            start: unpack_low(first),
2329            top: unpack_high(first),
2330            end: unpack_low(second),
2331            bottom: unpack_high(second),
2332        }),
2333        11 => Ok(Modifier::Weight(f32::from_bits(first as u32))),
2334        12 => Ok(Modifier::Shape {
2335            top_start: unpack_low(first),
2336            top_end: unpack_high(first),
2337            bottom_end: unpack_low(second),
2338            bottom_start: unpack_high(second),
2339        }),
2340        13 => Ok(Modifier::ShapeRole(decode_role::<ShapeRole>(first)?)),
2341        14 => Ok(Modifier::Border {
2342            width: f32::from_bits(first as u32),
2343            paint: decode_paint(second)?,
2344        }),
2345        15 => Ok(Modifier::Elevation(f32::from_bits(first as u32))),
2346        16 => Ok(Modifier::ObserveSize {
2347            token: first as u32,
2348        }),
2349        17 => Ok(Modifier::Motion(decode_role::<MotionRole>(first)?)),
2350        18 => Ok(Modifier::Material(decode_role::<MaterialRole>(first)?)),
2351        19 => Ok(Modifier::Offset {
2352            x: f32::from_bits(first as u32),
2353            y: f32::from_bits(second as u32),
2354        }),
2355        20 => Ok(Modifier::RequiredSize {
2356            width: f32::from_bits(first as u32),
2357            height: f32::from_bits(second as u32),
2358        }),
2359        21 => Ok(Modifier::BorderEach {
2360            top: unpack_low(first),
2361            right: unpack_high(first),
2362            bottom: unpack_low(second),
2363            left: unpack_high(second),
2364            top_paint: decode_paint(third)?,
2365            right_paint: decode_paint(fourth)?,
2366            bottom_paint: decode_paint(fifth)?,
2367            left_paint: decode_paint(sixth)?,
2368        }),
2369        22 => Ok(Modifier::CornerEach {
2370            top_left: unpack_low(first),
2371            top_right: unpack_high(first),
2372            bottom_right: unpack_low(second),
2373            bottom_left: unpack_high(second),
2374        }),
2375        23 => Ok(Modifier::Shadow {
2376            x: unpack_low(first),
2377            y: unpack_high(first),
2378            blur: unpack_low(second),
2379            spread: unpack_high(second),
2380            paint: decode_paint(third)?,
2381        }),
2382        // A flag is 0 or 1. Anything else is a record the two sides disagree about, and
2383        // reading it as "on" would hide that.
2384        24 => match first {
2385            0 => Ok(Modifier::Clip(false)),
2386            1 => Ok(Modifier::Clip(true)),
2387            _ => Err(ProtocolError::InvalidModifier(24)),
2388        },
2389        25 => Ok(Modifier::Alpha(f32::from_bits(first as u32))),
2390        26 => {
2391            let values = [
2392                unpack_low(first),
2393                unpack_high(first),
2394                unpack_low(second),
2395                unpack_high(second),
2396                unpack_low(third),
2397                unpack_high(third),
2398                unpack_low(fourth),
2399                unpack_high(fourth),
2400            ];
2401            // A NaN or an infinity in a matrix makes everything under it undrawable, and
2402            // the two sides would not even agree on how. Refused here, as the Renderer
2403            // refuses it.
2404            if values.iter().any(|value| !value.is_finite()) {
2405                return Err(ProtocolError::InvalidModifier(26));
2406            }
2407            let [a, b, c, d, e, f, origin_x, origin_y] = values;
2408            Ok(Modifier::Transform {
2409                a,
2410                b,
2411                c,
2412                d,
2413                e,
2414                f,
2415                origin_x,
2416                origin_y,
2417            })
2418        }
2419        other => Err(ProtocolError::InvalidModifier(other)),
2420    }
2421}
2422
2423/// Reads a `(offset: u32, len: u32)` string reference at `position`.
2424///
2425/// Strings live in the arena that follows the records, so `arena_start` is the
2426/// first byte a string may legally point at. Without that floor a hostile Renderer can
2427/// aim a string at the record region and have the decoder reinterpret record headers as
2428/// text, garbage decoding into a valid-looking mutation.
2429fn read_string(bytes: &[u8], position: usize, arena_start: usize) -> Result<&str, ProtocolError> {
2430    let value = read_bytes(bytes, position, arena_start)?;
2431    std::str::from_utf8(value).map_err(|_| ProtocolError::InvalidUtf8)
2432}
2433
2434/// The same arena range a string uses, without the UTF-8 requirement.
2435fn read_bytes(bytes: &[u8], position: usize, arena_start: usize) -> Result<&[u8], ProtocolError> {
2436    let offset =
2437        usize::try_from(read_u32(bytes, position)?).map_err(|_| ProtocolError::LengthOverflow)?;
2438    let len = usize::try_from(read_u32(bytes, position + 4)?)
2439        .map_err(|_| ProtocolError::LengthOverflow)?;
2440    let end = offset
2441        .checked_add(len)
2442        .ok_or(ProtocolError::InvalidStringRange)?;
2443    if offset < arena_start {
2444        return Err(ProtocolError::InvalidStringRange);
2445    }
2446    bytes
2447        .get(offset..end)
2448        .ok_or(ProtocolError::InvalidStringRange)
2449}
2450
2451fn read_u8(bytes: &[u8], position: usize) -> Result<u8, ProtocolError> {
2452    bytes.get(position).copied().ok_or(ProtocolError::Truncated)
2453}
2454
2455fn read_u16(bytes: &[u8], position: usize) -> Result<u16, ProtocolError> {
2456    let value = bytes
2457        .get(position..position + 2)
2458        .ok_or(ProtocolError::Truncated)?;
2459    Ok(u16::from_le_bytes([value[0], value[1]]))
2460}
2461
2462fn read_u32(bytes: &[u8], position: usize) -> Result<u32, ProtocolError> {
2463    let value = bytes
2464        .get(position..position + 4)
2465        .ok_or(ProtocolError::Truncated)?;
2466    Ok(u32::from_le_bytes([value[0], value[1], value[2], value[3]]))
2467}
2468
2469/// A word that is 0 or 1. Any other value is a protocol error, not a third state.
2470fn read_flag(bytes: &[u8], position: usize) -> Result<bool, ProtocolError> {
2471    match read_u32(bytes, position)? {
2472        0 => Ok(false),
2473        1 => Ok(true),
2474        other => Err(ProtocolError::InvalidValueKind(
2475            other.min(u32::from(u16::MAX)) as u16,
2476        )),
2477    }
2478}
2479
2480fn read_u64(bytes: &[u8], position: usize) -> Result<u64, ProtocolError> {
2481    let value = bytes
2482        .get(position..position + 8)
2483        .ok_or(ProtocolError::Truncated)?;
2484    Ok(u64::from_le_bytes(
2485        value.try_into().map_err(|_| ProtocolError::Truncated)?,
2486    ))
2487}
2488
2489#[cfg(test)]
2490mod tests {
2491    use super::*;
2492    use crate::schema::TypeRole;
2493
2494    #[test]
2495    fn round_trips_all_m0_mutations() {
2496        let mutations = [
2497            Mutation::Create {
2498                node_id: 1,
2499                widget: WidgetKind::Column,
2500            },
2501            Mutation::SetProp {
2502                node_id: 2,
2503                property: PropertyKind::Text,
2504                value: PropertyValue::String("안녕"),
2505            },
2506            Mutation::SetProp {
2507                node_id: 3,
2508                property: PropertyKind::Enabled,
2509                value: PropertyValue::Bool(true),
2510            },
2511            Mutation::SetModifier {
2512                node_id: 1,
2513                index: 0,
2514                modifier: Modifier::Padding(16.0),
2515            },
2516            Mutation::Insert {
2517                parent_id: 1,
2518                node_id: 2,
2519                index: 0,
2520            },
2521            Mutation::Move {
2522                parent_id: 1,
2523                node_id: 2,
2524                index: 1,
2525            },
2526            Mutation::Remove { node_id: 3 },
2527            Mutation::SetText {
2528                node_id: 4,
2529                text: "compose",
2530                selection: Some(Selection { start: 1, end: 4 }),
2531            },
2532            Mutation::AppendText {
2533                node_id: 4,
2534                text: " token",
2535            },
2536        ];
2537        let mut encoder = BatchEncoder::default();
2538        for mutation in &mutations {
2539            encoder.encode(mutation).unwrap();
2540        }
2541        let decoded = decode_batch(encoder.finish().unwrap()).unwrap();
2542        assert_eq!(decoded, mutations);
2543    }
2544
2545    /// A role Paint and a literal Paint occupy the same record length and
2546    /// decode back to the value that was encoded.
2547    #[test]
2548    fn fr13_paint_role_and_literal_share_one_record_length() {
2549        let encode_one = |modifier| {
2550            let mut encoder = BatchEncoder::default();
2551            encoder
2552                .encode(&Mutation::SetModifier {
2553                    node_id: 1,
2554                    index: 0,
2555                    modifier,
2556                })
2557                .unwrap();
2558            encoder.finish().unwrap().to_vec()
2559        };
2560        let role = encode_one(Modifier::Background(Paint::Role(
2561            crate::schema::ColorRole::Surface,
2562        )));
2563        let literal = encode_one(Modifier::Background(Paint::Literal(
2564            crate::schema::Color::rgb(0x1b1b1f),
2565        )));
2566        assert_eq!(role.len(), literal.len());
2567        assert_eq!(
2568            decode_batch(&role).unwrap(),
2569            [Mutation::SetModifier {
2570                node_id: 1,
2571                index: 0,
2572                modifier: Modifier::Background(Paint::Role(crate::schema::ColorRole::Surface)),
2573            }]
2574        );
2575        assert_eq!(
2576            decode_batch(&literal).unwrap(),
2577            [Mutation::SetModifier {
2578                node_id: 1,
2579                index: 0,
2580                modifier: Modifier::Background(Paint::Literal(crate::schema::Color::rgb(0x1b1b1f))),
2581            }]
2582        );
2583    }
2584
2585    /// Every design primitive fits in `(tag, u64, u64)` and survives a round trip.
2586    #[test]
2587    fn fr13_design_modifiers_round_trip_in_two_words() {
2588        let modifiers = [
2589            Modifier::PaddingRole(SpaceRole::Md),
2590            Modifier::PaddingEach {
2591                start: 1.0,
2592                top: 2.0,
2593                end: 3.0,
2594                bottom: 4.0,
2595            },
2596            Modifier::Weight(0.25),
2597            Modifier::Shape {
2598                top_start: 4.0,
2599                top_end: 8.0,
2600                bottom_end: 12.0,
2601                bottom_start: 16.0,
2602            },
2603            Modifier::ShapeRole(ShapeRole::Full),
2604            Modifier::Border {
2605                width: 2.0,
2606                paint: Paint::Role(crate::schema::ColorRole::Outline),
2607            },
2608            Modifier::Elevation(6.0),
2609        ];
2610        let mut encoder = BatchEncoder::default();
2611        let expected: Vec<_> = modifiers
2612            .iter()
2613            .enumerate()
2614            .map(|(index, modifier)| Mutation::SetModifier {
2615                node_id: 1,
2616                index: index as u16,
2617                modifier: modifier.clone(),
2618            })
2619            .collect();
2620        for mutation in &expected {
2621            encoder.encode(mutation).unwrap();
2622        }
2623        let bytes = encoder.finish().unwrap();
2624        // Every modifier record is the same fixed 28 bytes.
2625        assert_eq!(bytes.len(), ENVELOPE_LEN + expected.len() * 28);
2626        assert_eq!(decode_batch(bytes).unwrap(), expected);
2627    }
2628
2629    /// A node's shadows are several `Shadow` records at consecutive chain positions,
2630    /// each with its own offset, blur, spread and paint (alpha included), and every one
2631    /// survives a round trip in the order it was written.
2632    #[test]
2633    fn fr42_shadow_a_list_of_shadows_round_trips_in_order() {
2634        let shadows = [
2635            Modifier::Shadow {
2636                x: 0.0,
2637                y: 1.0,
2638                blur: 2.0,
2639                spread: 0.0,
2640                paint: Paint::Literal(crate::schema::Color::argb(0x1400_0000)),
2641            },
2642            Modifier::Shadow {
2643                x: 0.0,
2644                y: 4.0,
2645                blur: 12.0,
2646                spread: -2.0,
2647                paint: Paint::Literal(crate::schema::Color::argb(0x2900_0000)),
2648            },
2649            Modifier::Shadow {
2650                x: -3.5,
2651                y: 24.0,
2652                blur: 48.0,
2653                spread: 4.0,
2654                paint: Paint::Role(crate::schema::ColorRole::Outline),
2655            },
2656        ];
2657        let expected: Vec<_> = shadows
2658            .iter()
2659            .enumerate()
2660            .map(|(index, modifier)| Mutation::SetModifier {
2661                node_id: 7,
2662                index: 9 + index as u16,
2663                modifier: modifier.clone(),
2664            })
2665            .collect();
2666        let mut encoder = BatchEncoder::default();
2667        for mutation in &expected {
2668            encoder.encode(mutation).unwrap();
2669        }
2670        let bytes = encoder.finish().unwrap();
2671        assert_eq!(decode_batch(bytes).unwrap(), expected);
2672    }
2673
2674    /// `SetTheme` is four `u16` fields and one font slot per type role.
2675    ///
2676    /// Fixed length whether or not any font is named, because the record is fixed layout
2677    /// and a theme is sent once rather than per frame. The slots cost 36 bytes on a
2678    /// message that appears once in a session.
2679    #[test]
2680    fn fr23_set_theme_round_trips_with_a_slot_for_every_type_role() {
2681        let theme = Theme::adaptive(DesignSystem::Cupertino)
2682            .with_color_scheme(ColorScheme::Dark)
2683            .with_font(TypeRole::Display, 7);
2684        let length = 20 + 4 * TYPE_ROLE_COUNT as u16;
2685        let mut encoder = BatchEncoder::default();
2686        encoder.encode(&Mutation::SetTheme(theme)).unwrap();
2687        let bytes = encoder.finish().unwrap();
2688        assert_eq!(bytes.len(), ENVELOPE_LEN + length as usize);
2689        assert_eq!(read_u16(bytes, ENVELOPE_LEN).unwrap(), TAG_SET_THEME);
2690        assert_eq!(read_u16(bytes, ENVELOPE_LEN + 2).unwrap(), length);
2691        assert_eq!(decode_batch(bytes).unwrap(), [Mutation::SetTheme(theme)]);
2692    }
2693
2694    /// A font named for one role leaves every other role on the system font.
2695    #[test]
2696    fn fr23_a_font_is_named_for_one_role_and_no_others() {
2697        let theme = Theme::unified(DesignSystem::Material3).with_font(TypeRole::Display, 3);
2698        assert_eq!(theme.font(TypeRole::Display), Some(3));
2699        for role in [
2700            TypeRole::Body,
2701            TypeRole::Title,
2702            TypeRole::Mono,
2703            TypeRole::Caption,
2704        ] {
2705            assert_eq!(theme.font(role), None);
2706        }
2707    }
2708
2709    /// One font for the whole screen: every role but code takes it, and code stays
2710    /// monospaced until a font is named for it.
2711    #[test]
2712    fn fr52_a_default_font_names_every_role_but_mono() {
2713        let theme = Theme::unified(DesignSystem::Material3).with_default_font(5);
2714        for role in [
2715            TypeRole::Display,
2716            TypeRole::Headline,
2717            TypeRole::Title,
2718            TypeRole::Subtitle,
2719            TypeRole::Body,
2720            TypeRole::BodyStrong,
2721            TypeRole::Label,
2722            TypeRole::Caption,
2723        ] {
2724            assert_eq!(theme.font(role), Some(5), "{role:?}");
2725        }
2726        assert_eq!(theme.font(TypeRole::Mono), None);
2727        assert_eq!(theme.font(TypeRole::None), None);
2728
2729        let both = theme
2730            .with_font(TypeRole::Mono, 9)
2731            .with_font(TypeRole::Display, 3);
2732        assert_eq!(both.font(TypeRole::Mono), Some(9));
2733        assert_eq!(
2734            both.font(TypeRole::Display),
2735            Some(3),
2736            "the role named last wins"
2737        );
2738        assert_eq!(both.font(TypeRole::Body), Some(5));
2739
2740        let mut encoder = BatchEncoder::default();
2741        encoder.encode(&Mutation::SetTheme(theme)).unwrap();
2742        let bytes = encoder.finish().unwrap();
2743        assert_eq!(decode_batch(bytes).unwrap(), [Mutation::SetTheme(theme)]);
2744    }
2745
2746    /// An out-of-range theme tag is an error, never a panic.
2747    #[test]
2748    fn fr14_rejects_unknown_theme_tags() {
2749        let mut encoder = BatchEncoder::default();
2750        encoder
2751            .encode(&Mutation::SetTheme(Theme::default()))
2752            .unwrap();
2753        let mut bytes = encoder.finish().unwrap().to_vec();
2754        bytes[ENVELOPE_LEN + 4..ENVELOPE_LEN + 6].copy_from_slice(&99_u16.to_le_bytes());
2755        assert_eq!(decode_batch(&bytes), Err(ProtocolError::InvalidTheme(99)));
2756    }
2757
2758    #[test]
2759    fn rejects_unknown_tags_without_panicking() {
2760        let mut bytes = vec![0, 0, 12, 0, 20, 0, 0, 0, 1, 0, 0, 0];
2761        bytes.extend_from_slice(&999_u16.to_le_bytes());
2762        bytes.extend_from_slice(&8_u16.to_le_bytes());
2763        bytes.extend_from_slice(&0_u32.to_le_bytes());
2764        assert_eq!(decode_batch(&bytes), Err(ProtocolError::InvalidTag(999)));
2765    }
2766
2767    #[test]
2768    fn pr4_renderer_events_round_trip_fixed_layout() {
2769        let events = [
2770            HostEvent {
2771                node_id: 7,
2772                handler_id: 11,
2773                payload: crate::EventPayload::Clicked,
2774            },
2775            HostEvent {
2776                node_id: 8,
2777                handler_id: 12,
2778                payload: crate::EventPayload::TextChanged("한글"),
2779            },
2780            HostEvent {
2781                node_id: 8,
2782                handler_id: 13,
2783                payload: crate::EventPayload::TextSubmitted("done"),
2784            },
2785            HostEvent {
2786                node_id: 8,
2787                handler_id: 14,
2788                payload: crate::EventPayload::FocusLost,
2789            },
2790            HostEvent {
2791                node_id: 9,
2792                handler_id: 16,
2793                payload: crate::EventPayload::RangeRequested {
2794                    start: 100,
2795                    count: 20,
2796                },
2797            },
2798            HostEvent {
2799                node_id: 8,
2800                handler_id: 15,
2801                payload: crate::EventPayload::KeyDown {
2802                    key: Key::Enter,
2803                    shift_key: true,
2804                    ctrl_key: false,
2805                    alt_key: true,
2806                    meta_key: false,
2807                },
2808            },
2809        ];
2810        let mut bytes = Vec::new();
2811        for event in events {
2812            encode_event(&event, &mut bytes).unwrap();
2813            assert_eq!(decode_event(&bytes).unwrap(), event);
2814        }
2815    }
2816
2817    #[test]
2818    fn fr20_window_size_changed_round_trips_in_thirty_two_bytes() {
2819        let event = HostEvent {
2820            node_id: 0,
2821            handler_id: 0,
2822            payload: crate::EventPayload::WindowSizeChanged {
2823                width_dp: 841.5,
2824                height_dp: 600.25,
2825                class: crate::WindowSizeClass::Expanded,
2826                height_class: crate::WindowHeightClass::Medium,
2827            },
2828        };
2829        let mut bytes = Vec::new();
2830        encode_event(&event, &mut bytes).unwrap();
2831        assert_eq!(bytes.len(), 32);
2832        assert_eq!(decode_event(&bytes).unwrap(), event);
2833    }
2834
2835    #[test]
2836    fn fr20_unknown_window_size_class_is_a_protocol_error() {
2837        let event = HostEvent {
2838            node_id: 0,
2839            handler_id: 0,
2840            payload: crate::EventPayload::WindowSizeChanged {
2841                width_dp: 320.0,
2842                height_dp: 640.0,
2843                class: crate::WindowSizeClass::Compact,
2844                height_class: crate::WindowHeightClass::Medium,
2845            },
2846        };
2847        let mut bytes = Vec::new();
2848        encode_event(&event, &mut bytes).unwrap();
2849        bytes[24..28].copy_from_slice(&99_u32.to_le_bytes());
2850        assert_eq!(
2851            decode_event(&bytes),
2852            Err(ProtocolError::InvalidValueKind(99))
2853        );
2854    }
2855
2856    #[test]
2857    fn fr20_unknown_window_height_class_is_a_protocol_error() {
2858        let event = HostEvent {
2859            node_id: 0,
2860            handler_id: 0,
2861            payload: crate::EventPayload::WindowSizeChanged {
2862                width_dp: 320.0,
2863                height_dp: 640.0,
2864                class: crate::WindowSizeClass::Compact,
2865                height_class: crate::WindowHeightClass::Medium,
2866            },
2867        };
2868        let mut bytes = Vec::new();
2869        encode_event(&event, &mut bytes).unwrap();
2870        bytes[28..32].copy_from_slice(&99_u32.to_le_bytes());
2871        assert_eq!(
2872            decode_event(&bytes),
2873            Err(ProtocolError::InvalidValueKind(99))
2874        );
2875    }
2876
2877    #[test]
2878    fn fr20_window_size_classes_follow_the_material_boundaries() {
2879        use crate::WindowSizeClass;
2880        assert_eq!(
2881            WindowSizeClass::from_width_dp(599.9),
2882            WindowSizeClass::Compact
2883        );
2884        assert_eq!(
2885            WindowSizeClass::from_width_dp(600.0),
2886            WindowSizeClass::Medium
2887        );
2888        assert_eq!(
2889            WindowSizeClass::from_width_dp(839.9),
2890            WindowSizeClass::Medium
2891        );
2892        assert_eq!(
2893            WindowSizeClass::from_width_dp(840.0),
2894            WindowSizeClass::Expanded
2895        );
2896    }
2897
2898    #[test]
2899    fn fr20_window_height_classes_follow_the_material_boundaries() {
2900        use crate::WindowHeightClass;
2901        assert_eq!(
2902            WindowHeightClass::from_height_dp(479.9),
2903            WindowHeightClass::Compact
2904        );
2905        assert_eq!(
2906            WindowHeightClass::from_height_dp(480.0),
2907            WindowHeightClass::Medium
2908        );
2909        assert_eq!(
2910            WindowHeightClass::from_height_dp(899.9),
2911            WindowHeightClass::Medium
2912        );
2913        assert_eq!(
2914            WindowHeightClass::from_height_dp(900.0),
2915            WindowHeightClass::Expanded
2916        );
2917    }
2918
2919    fn post_notification() -> Mutation<'static> {
2920        Mutation::PostNotification {
2921            key: "session/7",
2922            title: "세션이 끝났습니다",
2923            body: "refactor-parser: 테스트 214개 통과",
2924            channel: "세션",
2925            action_1: "열기",
2926            action_2: "",
2927            importance: NotificationImportance::Urgent,
2928            presentation: NotificationPresentation::WhenInactive,
2929        }
2930    }
2931
2932    /// The three notification commands keep the lengths the wire fixes for them: six string
2933    /// references and two closed enums, one string reference, and a bare header.
2934    #[test]
2935    fn fr36_notification_commands_round_trip_at_their_fixed_lengths() {
2936        let mutations = [
2937            post_notification(),
2938            Mutation::WithdrawNotification { key: "session/7" },
2939            Mutation::RequestNotificationPermission,
2940        ];
2941        let mut lengths = Vec::new();
2942        let mut encoder = BatchEncoder::default();
2943        for mutation in &mutations {
2944            encoder.encode(mutation).unwrap();
2945        }
2946        let bytes = encoder.finish().unwrap();
2947        let mut position = ENVELOPE_LEN;
2948        for _ in &mutations {
2949            let length = read_u16(bytes, position + 2).unwrap();
2950            lengths.push((read_u16(bytes, position).unwrap(), length));
2951            position += usize::from(length);
2952        }
2953        assert_eq!(
2954            lengths,
2955            [
2956                (TAG_POST_NOTIFICATION, 56),
2957                (TAG_WITHDRAW_NOTIFICATION, 12),
2958                (TAG_REQUEST_NOTIFICATION_PERMISSION, 4),
2959            ]
2960        );
2961        assert_eq!(decode_batch(bytes).unwrap(), mutations);
2962    }
2963
2964    /// An importance or a presentation this side does not know is an error, never a guess
2965    /// and never a panic.
2966    #[test]
2967    fn fr36_unknown_importance_and_presentation_are_protocol_errors() {
2968        for (offset, expected) in [(52, 9_u16), (54, 7_u16)] {
2969            let mut encoder = BatchEncoder::default();
2970            encoder.encode(&post_notification()).unwrap();
2971            let mut bytes = encoder.finish().unwrap().to_vec();
2972            bytes[ENVELOPE_LEN + offset..ENVELOPE_LEN + offset + 2]
2973                .copy_from_slice(&expected.to_le_bytes());
2974            assert_eq!(
2975                decode_batch(&bytes),
2976                Err(ProtocolError::InvalidNotification(expected))
2977            );
2978        }
2979    }
2980
2981    /// A known notification tag at the wrong length is a length error, the same as any
2982    /// other known record.
2983    #[test]
2984    fn fr36_a_notification_record_of_the_wrong_length_is_rejected() {
2985        let mut bytes = vec![0, 0, 12, 0, 20, 0, 0, 0, 1, 0, 0, 0];
2986        bytes.extend_from_slice(&TAG_REQUEST_NOTIFICATION_PERMISSION.to_le_bytes());
2987        bytes.extend_from_slice(&8_u16.to_le_bytes());
2988        bytes.extend_from_slice(&0_u32.to_le_bytes());
2989        assert_eq!(
2990            decode_batch(&bytes),
2991            Err(ProtocolError::InvalidRecordLength)
2992        );
2993    }
2994
2995    /// The two notification events: a word and a string after the common head, and a word
2996    /// on its own.
2997    #[test]
2998    fn fr36_notification_events_round_trip_fixed_layout() {
2999        let activated = HostEvent {
3000            node_id: 0,
3001            handler_id: 0,
3002            payload: crate::EventPayload::NotificationActivated {
3003                action: 2,
3004                key: "session/7",
3005            },
3006        };
3007        let mut bytes = Vec::new();
3008        encode_event(&activated, &mut bytes).unwrap();
3009        assert_eq!(read_u16(&bytes, 0).unwrap(), EVENT_NOTIFICATION_ACTIVATED);
3010        assert_eq!(read_u16(&bytes, 2).unwrap(), 28);
3011        assert_eq!(bytes.len(), 28 + "session/7".len());
3012        assert_eq!(decode_event(&bytes).unwrap(), activated);
3013
3014        for state in [
3015            NotificationPermission::NotDetermined,
3016            NotificationPermission::Granted,
3017            NotificationPermission::Denied,
3018            NotificationPermission::Unsupported,
3019        ] {
3020            let changed = HostEvent {
3021                node_id: 0,
3022                handler_id: 0,
3023                payload: crate::EventPayload::NotificationPermissionChanged(state),
3024            };
3025            encode_event(&changed, &mut bytes).unwrap();
3026            assert_eq!(bytes.len(), 20);
3027            assert_eq!(decode_event(&bytes).unwrap(), changed);
3028        }
3029    }
3030
3031    /// A permission state outside the four is reported, not mapped onto one of them.
3032    #[test]
3033    fn fr36_unknown_permission_state_is_a_protocol_error() {
3034        let mut bytes = Vec::new();
3035        encode_event(
3036            &HostEvent {
3037                node_id: 0,
3038                handler_id: 0,
3039                payload: crate::EventPayload::NotificationPermissionChanged(
3040                    NotificationPermission::Granted,
3041                ),
3042            },
3043            &mut bytes,
3044        )
3045        .unwrap();
3046        bytes[16..20].copy_from_slice(&5_u32.to_le_bytes());
3047        assert_eq!(
3048            decode_event(&bytes),
3049            Err(ProtocolError::InvalidValueKind(5))
3050        );
3051    }
3052
3053    /// A notification has a body and at most two buttons, so an activation naming a third
3054    /// is a malformed event.
3055    #[test]
3056    fn fr36_an_activation_naming_a_third_button_is_a_protocol_error() {
3057        let mut bytes = Vec::new();
3058        encode_event(
3059            &HostEvent {
3060                node_id: 0,
3061                handler_id: 0,
3062                payload: crate::EventPayload::NotificationActivated {
3063                    action: 1,
3064                    key: "k",
3065                },
3066            },
3067            &mut bytes,
3068        )
3069        .unwrap();
3070        bytes[16..20].copy_from_slice(&3_u32.to_le_bytes());
3071        assert_eq!(
3072            decode_event(&bytes),
3073            Err(ProtocolError::InvalidValueKind(3))
3074        );
3075    }
3076
3077    /// How large the Renderer is drawing: two floats and a signed level after the head.
3078    #[test]
3079    fn fr43_zoom_changed_round_trips_fixed_layout() {
3080        let event = HostEvent {
3081            node_id: 0,
3082            handler_id: 0,
3083            payload: crate::EventPayload::ZoomChanged {
3084                k: 1.25 * 1.44,
3085                os: 1.25,
3086                level: 2,
3087            },
3088        };
3089        let mut bytes = Vec::new();
3090        encode_event(&event, &mut bytes).unwrap();
3091        assert_eq!(read_u16(&bytes, 0).unwrap(), 33);
3092        assert_eq!(read_u16(&bytes, 2).unwrap(), 28);
3093        assert_eq!(bytes.len(), 28);
3094        assert_eq!(decode_event(&bytes).unwrap(), event);
3095
3096        let smaller = HostEvent {
3097            node_id: 0,
3098            handler_id: 0,
3099            payload: crate::EventPayload::ZoomChanged {
3100                k: 0.5,
3101                os: 1.0,
3102                level: -4,
3103            },
3104        };
3105        encode_event(&smaller, &mut bytes).unwrap();
3106        assert_eq!(decode_event(&bytes).unwrap(), smaller);
3107    }
3108
3109    /// A level outside the range the Renderer can be at is a protocol error.
3110    #[test]
3111    fn fr43_a_zoom_level_out_of_range_is_rejected() {
3112        let event = HostEvent {
3113            node_id: 0,
3114            handler_id: 0,
3115            payload: crate::EventPayload::ZoomChanged {
3116                k: 1.0,
3117                os: 1.0,
3118                level: 9,
3119            },
3120        };
3121        let mut bytes = Vec::new();
3122        encode_event(&event, &mut bytes).unwrap();
3123        assert_eq!(
3124            decode_event(&bytes),
3125            Err(ProtocolError::InvalidValueKind(9))
3126        );
3127        bytes[2..4].copy_from_slice(&24_u16.to_le_bytes());
3128        assert_eq!(
3129            decode_event(&bytes),
3130            Err(ProtocolError::InvalidRecordLength)
3131        );
3132    }
3133
3134    /// The window record carries the application's zoom level where it asked for one, and
3135    /// a window that asked for nothing is byte for byte what it was before the level.
3136    #[test]
3137    fn fr43_the_window_record_carries_a_zoom_level() {
3138        for level in [None, Some(-8), Some(0), Some(2), Some(8)] {
3139            let mut window = crate::schema::Window::new().with_title("zoom");
3140            window.zoom_level = level;
3141            let mut encoder = BatchEncoder::default();
3142            encoder.encode(&Mutation::SetWindow(window)).unwrap();
3143            let bytes = encoder.finish().unwrap().to_vec();
3144            if level.is_none() {
3145                assert_eq!(read_u16(&bytes, ENVELOPE_LEN + 18).unwrap(), 0);
3146            }
3147            let decoded = decode_batch(&bytes).unwrap();
3148            assert_eq!(decoded, vec![Mutation::SetWindow(window)]);
3149        }
3150        assert_eq!(
3151            crate::schema::Window::new().with_zoom_level(20).zoom_level,
3152            Some(8)
3153        );
3154    }
3155}