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