Skip to main content

compose_rust/
protocol.rs

1//! Fixed-layout little-endian boundary protocol.
2
3use crate::schema::{
4    AssetKind, ColorScheme, DesignSystem, FileDelivery, FileDialogKind, FileDialogStatus, FileOp,
5    IoStatus, Key, MAX_MODIFIER_WORDS, MaterialRole, MessageDuration, Modifier, MotionRole,
6    NotificationImportance, NotificationPermission, NotificationPresentation, Paint, PropertyKind,
7    SEMANTICS_STATE_MASK, Selection, SemanticsRole, ShapeRole, SpaceRole, TYPE_ROLE_COUNT, Theme,
8    WidgetKind, modifier_extra_words,
9};
10use core::fmt;
11
12const TAG_ENVELOPE: u16 = 0;
13const TAG_CREATE: u16 = 1;
14const TAG_SET_PROP: u16 = 2;
15const TAG_SET_MODIFIER: u16 = 3;
16const TAG_INSERT: u16 = 4;
17const TAG_MOVE: u16 = 5;
18const TAG_REMOVE: u16 = 6;
19const TAG_SET_TEXT: u16 = 7;
20const TAG_APPEND_TEXT: u16 = 8;
21const TAG_SET_THEME: u16 = 9;
22const TAG_REGISTER_ASSET: u16 = 10;
23const TAG_RELEASE_ASSET: u16 = 11;
24const TAG_SHOW_MESSAGE: u16 = 12;
25const TAG_SET_WINDOW: u16 = 13;
26const TAG_POST_NOTIFICATION: u16 = 14;
27const TAG_WITHDRAW_NOTIFICATION: u16 = 15;
28const TAG_REQUEST_NOTIFICATION_PERMISSION: u16 = 16;
29const TAG_EDIT_CODE: u16 = 17;
30const TAG_START_ANIMATION: u16 = 18;
31const TAG_CONTROL_ANIMATION: u16 = 19;
32// 20 to 22 are the code editor's: the selection, a range to bring into view, a fold.
33const TAG_SET_SELECTION: u16 = 20;
34const TAG_REVEAL_RANGE: u16 = 21;
35const TAG_SET_FOLDED: u16 = 22;
36// 23 and 24 are the windows': a theme for one window, and bringing one forward.
37const TAG_SET_WINDOW_THEME: u16 = 23;
38const TAG_FOCUS_WINDOW: u16 = 24;
39const TAG_REQUEST_FOCUS: u16 = 25;
40// 26 asks for a platform file dialog. Its answer is the event numbered 50.
41const TAG_SHOW_FILE_DIALOG: u16 = 26;
42/// `ShowFileDialog` after its header: the request, the window, the kind and the default
43/// filter, then four string references. 44 bytes, so the record is 48.
44const SHOW_FILE_DIALOG_PAYLOAD_LEN: usize = 4 + 4 + 2 + 2 + 4 * 8;
45// 27 sets application-level overlay theme tokens for Dialog, Menu, Dropdown and Sheet.
46const TAG_SET_OVERLAY_TOKENS: u16 = 27;
47// 28 and 29 are numbered for commands that are not here yet.
48const TAG_SET_SCROLL_OFFSET: u16 = 30;
49/// 9 u16 fields (18 bytes) + 1 u16 reserved (2 bytes) + 1 u32 scrim (4 bytes) = 24 bytes.
50const OVERLAY_TOKENS_PAYLOAD_LEN: usize = 24;
51// 29 puts plain text on the platform clipboard. Nothing answers it. 28 is held for the
52// code editor's grouped edit.
53const TAG_SET_CLIPBOARD_TEXT: u16 = 29;
54// 30 is held for the code editor's scroll offset. 31 asks for the clipboard's plain text,
55// answered by one `ClipboardText` event carrying the same request number.
56const TAG_READ_CLIPBOARD: u16 = 31;
57// 32 asks the Renderer to carry out one storage operation, answered by one `IoAnswer`
58// event carrying the same request number.
59const TAG_STORAGE_CALL: u16 = 32;
60/// `StorageCall` after its header: the request, the operation, a reserved half word, then
61/// the store, the key and the bytes as three references. 32 bytes, so the record is 36.
62const STORAGE_CALL_PAYLOAD_LEN: usize = 4 + 2 + 2 + 3 * 8;
63// 33 asks something of a file the Renderer holds a handle for, answered by one `IoAnswer`
64// or `FileHandles` event carrying the same request number. 34 lets go of such a handle and
65// is not answered.
66const TAG_FILE_CALL: u16 = 33;
67/// `FileCall` after its header: the request, the operation, the create flag and the handle,
68/// then the name's and the bytes' references. 28 bytes, so the record is 32.
69const FILE_CALL_PAYLOAD_LEN: usize = 4 + 2 + 2 + 4 + 8 + 8;
70const TAG_RELEASE_FILE_HANDLE: u16 = 34;
71// 35 asks a scroll
72// container to go to an offset and 36 to bring a rectangle into view. Both are requests:
73// the Renderer owns the position and reports what it did in `ScrollChanged`.
74const TAG_SCROLL_TO: u16 = 35;
75const TAG_SCROLL_INTO_VIEW: u16 = 36;
76/// `ScrollTo` in full: the 4 byte head, the node, the item, the flags and two `f64`.
77const SCROLL_TO_LEN: usize = 32;
78/// `ScrollIntoView` in full: the 4 byte head, the node, the target, four `u16` and four `f64`.
79const SCROLL_INTO_VIEW_LEN: usize = 52;
80/// The head of a `StartAnimation` record, before its keyframes.
81const ANIMATION_HEADER_LEN: usize = 52;
82/// A keyframe before its value: offset, timing, step position, flags and four numbers.
83const KEYFRAME_HEAD_LEN: usize = 24;
84/// One transform function: its kind and six numbers.
85const TRANSFORM_FUNCTION_LEN: usize = 28;
86const KEYFRAME_FROM_PRESENTED: u16 = 1;
87const ENVELOPE_LEN: usize = 12;
88/// `SetTheme` after its header: four `u16` fields, one font slot per type role, and the
89/// palette's `(offset, len)` reference. 52 bytes, so the record is 56.
90pub const THEME_PAYLOAD_LEN: usize = 8 + 4 * TYPE_ROLE_COUNT + 8;
91/// The shortest mutation record on the wire (`RequestNotificationPermission`: the 4-byte
92/// header and nothing else).
93const MIN_RECORD_LEN: usize = 4;
94
95const VALUE_NONE: u16 = 0;
96const VALUE_STRING: u16 = 1;
97const VALUE_BOOL: u16 = 2;
98const VALUE_I64: u16 = 3;
99const VALUE_F32: u16 = 4;
100/// An opaque byte run in the arena. Same `(offset, length)` layout as a string, with no
101/// UTF-8 check, because a Canvas command list is not text.
102const VALUE_BYTES: u16 = 5;
103
104#[derive(Clone, Debug, PartialEq)]
105pub enum PropertyValue<'a> {
106    None,
107    String(&'a str),
108    Bool(bool),
109    Integer(i64),
110    Float(f32),
111    Bytes(&'a [u8]),
112}
113
114#[derive(Clone, Debug, PartialEq)]
115pub enum Mutation<'a> {
116    Create {
117        node_id: u32,
118        widget: WidgetKind,
119    },
120    SetProp {
121        node_id: u32,
122        property: PropertyKind,
123        value: PropertyValue<'a>,
124    },
125    SetModifier {
126        node_id: u32,
127        index: u16,
128        modifier: Modifier,
129    },
130    Insert {
131        parent_id: u32,
132        node_id: u32,
133        index: u32,
134    },
135    Move {
136        parent_id: u32,
137        node_id: u32,
138        index: u32,
139    },
140    Remove {
141        node_id: u32,
142    },
143    SetText {
144        node_id: u32,
145        text: &'a str,
146        selection: Option<Selection>,
147    },
148    /// Appends the streamed tail to a Text node instead of resending the whole string.
149    AppendText {
150        node_id: u32,
151        text: &'a str,
152    },
153    /// The root theme. Sent once as the first record of the initial batch.
154    SetTheme(Theme),
155    /// What the application asked of its own window.
156    ///
157    /// Written once per rebuild beside the theme, and read by the Renderer before it
158    /// stands the window up: `compose_rust_host_init` answers with the first batch, so
159    /// the setting is in hand before there is a window to apply it to. Platforms where
160    /// the window is not ours ignore it, which is not an error but an absence of anywhere
161    /// to put it.
162    SetWindow(crate::schema::Window),
163    /// Hands the Renderer the bytes of one asset. The Renderer copies them into its own
164    /// cache inside this call, because the batch buffer is only valid for the call that
165    /// carries it and an image has to outlive the frame that draws it.
166    RegisterAsset {
167        asset_id: u32,
168        kind: AssetKind,
169        bytes: &'a [u8],
170    },
171    /// Drops an asset from the Renderer's cache. The Host owns the lifetime.
172    ReleaseAsset {
173        asset_id: u32,
174    },
175    /// Says a sentence to the user once. It is not a node, because the Host would then
176    /// have to hold "showing until four seconds from now" and run a render to take it
177    /// away again. How long it stays, where it sits and what happens when a second one
178    /// arrives while the first is up are all the Renderer's.
179    ///
180    /// `handler_id` is what the action label reports when it is pressed, or 0 when the
181    /// message has no action, in which case `action` is empty too.
182    ShowMessage {
183        handler_id: u64,
184        text: &'a str,
185        action: &'a str,
186        duration: MessageDuration,
187    },
188    /// Asks the platform to show one notification outside the window.
189    ///
190    /// Not a node, for the same reason a message is not: it outlives the component that
191    /// posted it, and when it goes away depends on what the user does in the notification
192    /// centre, which the Host never hears about. Posting again under the same `key`
193    /// replaces it. An empty `key` asks the Renderer to name it, and such a notification
194    /// can be neither replaced nor withdrawn. An empty action label means no button.
195    PostNotification {
196        key: &'a str,
197        title: &'a str,
198        body: &'a str,
199        channel: &'a str,
200        action_1: &'a str,
201        action_2: &'a str,
202        importance: NotificationImportance,
203        presentation: NotificationPresentation,
204    },
205    /// Takes back the notification posted under `key`, if it is still showing.
206    WithdrawNotification {
207        key: &'a str,
208    },
209    /// Asks the platform for permission to show notifications. It carries nothing: the
210    /// answer comes back as a permission event.
211    RequestNotificationPermission,
212    /// Replaces a range of a code editor's document, as it stood at `base_version`, with
213    /// `text`.
214    ///
215    /// The Renderer owns the document, so this is a request and not an assignment: where
216    /// the reader has typed since that version the range is moved along with what they
217    /// typed, and where they typed in the same place the edit is refused and
218    /// `CodeEditRejected` names `request_id`. An edit that is applied comes back as an
219    /// ordinary `CodeChanged`, so the version stays one sequence whoever made the edit.
220    EditCode {
221        node_id: u32,
222        request_id: u32,
223        base_version: u32,
224        start_line: u32,
225        start_column: u32,
226        end_line: u32,
227        end_column: u32,
228        text: &'a str,
229    },
230    /// Hands the Renderer one animation to play on its own frame clock until it ends.
231    /// Nothing crosses the boundary while it plays unless it asked for events.
232    StartAnimation(crate::schema::Animation<'a>),
233    /// Pauses, resumes or cancels one animation the Renderer is playing. A key the Renderer
234    /// does not hold, or an id that is not the one it holds, is left alone: an animation
235    /// that ended on its own is not an error to stop.
236    ControlAnimation {
237        node_id: u32,
238        animation_id: u32,
239        property: crate::schema::AnimatedProperty,
240        slot: u8,
241        op: crate::schema::AnimationControl,
242        /// On the Renderer's frame clock. Zero means the first frame that draws it.
243        at_time_nanos: u64,
244    },
245    /// Puts a code editor's selection between two places in the document as it stood at
246    /// `base_version`. Both ends are moved along with whatever was typed since, and an end
247    /// that is outside the document, or between the two halves of a pair, drops the
248    /// command and comes back as a protocol error.
249    SetSelection {
250        node_id: u32,
251        base_version: u32,
252        anchor_line: u32,
253        anchor_column: u32,
254        active_line: u32,
255        active_column: u32,
256    },
257    /// Scrolls a code editor until a range of the document, as it stood at `base_version`,
258    /// is on screen. A range inside a folded stretch unfolds it first.
259    RevealRange {
260        node_id: u32,
261        base_version: u32,
262        start_line: u32,
263        start_column: u32,
264        end_line: u32,
265        end_column: u32,
266        align: crate::schema::RevealAlign,
267    },
268    /// Folds or unfolds the stretch of a code editor that starts on `start_line`.
269    SetFolded {
270        node_id: u32,
271        start_line: u32,
272        collapsed: bool,
273    },
274    /// A theme for one `Window` node, over the application's. The payload is
275    /// `SetTheme`'s, after the node id. A window without one follows the application's.
276    SetWindowTheme {
277        node_id: u32,
278        theme: Theme,
279    },
280    /// Brings a window forward and makes it the key window. A command and not a
281    /// property: it happens once.
282    FocusWindow {
283        node_id: u32,
284    },
285    /// Moves input focus to `node_id`, or clears focus when `node_id` is 0.
286    RequestFocus {
287        node_id: u32,
288    },
289    /// Asks the platform to show a file dialog on a window and answers later, once, with a
290    /// `FileDialogResult` event that carries `request`.
291    ///
292    /// `window` is a `Window` node id, or 0 for the implicit root window. `filters` is one
293    /// string: a label and its comma-joined extensions as a pair, pairs joined by NUL
294    /// (`"Images\0png,jpg\0Docs\0pdf,txt"`). `default_filter` is the position of the
295    /// filter selected first.
296    ShowFileDialog {
297        request: u32,
298        window: u32,
299        kind: FileDialogKind,
300        default_filter: u16,
301        title: &'a str,
302        directory: &'a str,
303        name: &'a str,
304        filters: &'a str,
305    },
306    /// Application-level overlay theme tokens for Dialog, Menu, Dropdown and Sheet.
307    SetOverlayTokens(crate::schema::OverlayTokens),
308    /// Puts `text` on the platform clipboard as plain text.
309    ///
310    /// Not a node and not answered: a Renderer with no clipboard drops it.
311    SetClipboardText {
312        text: &'a str,
313    },
314    /// Asks for the plain text on the platform clipboard. Answered exactly once, later, by a
315    /// `ClipboardText` event that carries `request`.
316    ReadClipboard {
317        request: u32,
318    },
319    /// Scrolls a code editor so that `offset_px` physical pixels of its document are above
320    /// the top of its view. The Renderer keeps the offset inside what the document allows,
321    /// and reports where it ended up through `CodeScrolled` like any other scroll.
322    SetScrollOffset {
323        node_id: u32,
324        offset_px: f64,
325    },
326    /// Asks the Renderer to carry out one operation on the application's storage, where the
327    /// Renderer is the side that can reach it (a browser's IndexedDB). Answered exactly
328    /// once, later, by an `IoAnswer` event that carries `request`.
329    ///
330    /// `store` is the application identifier, `key` the value's name (empty for `Keys`),
331    /// and `data` the value to write (empty for every operation but `Write`).
332    StorageCall {
333        request: u32,
334        op: crate::schema::StorageOp,
335        store: &'a str,
336        key: &'a str,
337        data: &'a [u8],
338    },
339    /// Asks something of a file the Renderer holds a handle for: reading it, writing
340    /// `data` to it, listing a folder, or finding (and with `create`, making) the child
341    /// called `name`. Answered exactly once, later: a read or a write by an `IoAnswer`, a
342    /// listing or a child by a `FileHandles`, and any of them that failed by an `IoAnswer`
343    /// with the reason. `name` is empty but for `Child`, `data` but for `Write`.
344    FileCall {
345        request: u32,
346        op: FileOp,
347        create: bool,
348        handle: u32,
349        name: &'a str,
350        data: &'a [u8],
351    },
352    /// The Host holds nothing that names `handle` any more, so the Renderer may let go of
353    /// the file behind it. Not answered. A handle the Renderer does not hold is ignored.
354    ReleaseFileHandle {
355        handle: u32,
356    },
357    /// Asks the scroll container `node_id` to go to an offset in physical pixels. `flags`
358    /// is the wire word (`schema::scroll_to_flags`): animate, apply `x_px`, apply `y_px`.
359    /// An axis whose bit is off is not touched. `item` is the lazy list item the offset is
360    /// measured from, and 0 elsewhere.
361    ScrollTo {
362        node_id: u32,
363        item: u32,
364        flags: u32,
365        x_px: f64,
366        y_px: f64,
367    },
368    /// Asks the scroll container `node_id` to bring a rectangle into view. The fields are
369    /// the wire's: `target_kind` from `schema::scroll_target_kind`, the two alignments as
370    /// `schema::ScrollAlign` values, and `flags` from `schema::scroll_into_view_flags`.
371    ScrollIntoView {
372        node_id: u32,
373        target: u32,
374        target_kind: u16,
375        align_x: u16,
376        align_y: u16,
377        flags: u16,
378        rect: crate::schema::Rect,
379    },
380}
381
382#[derive(Clone, Debug, Eq, PartialEq)]
383pub enum ProtocolError {
384    Truncated,
385    InvalidEnvelope,
386    InvalidRecordLength,
387    InvalidTag(u16),
388    InvalidWidget(u16),
389    InvalidProperty(u16),
390    InvalidValueKind(u16),
391    InvalidModifier(u16),
392    InvalidTheme(u16),
393    InvalidAssetKind(u16),
394    InvalidMessageDuration(u16),
395    /// A notification importance or presentation this decoder does not know.
396    InvalidNotification(u16),
397    InvalidStringRange,
398    InvalidUtf8,
399    LengthOverflow,
400    /// The Renderer refused a whole measure call with this status: its buffer could not be
401    /// read, it came from the wrong thread, or there was nothing to measure with yet.
402    MeasureRefused(i32),
403    /// An animation record that is well formed bytes but not an animation: an unknown
404    /// value, fewer than two keyframes, offsets that do not run from 0 to 1, a negative or
405    /// non-numeric duration or iteration count, keyframes whose values or transform lists
406    /// do not match, or a length that does not hold its keyframes.
407    InvalidAnimation,
408    /// A `Window` node that is not a child of the root, or a node moved from one window
409    /// into another. Compose keeps one composition per window and has no
410    /// way to hand a node from one to the next.
411    MisplacedWindow,
412}
413
414#[derive(Clone, Debug, PartialEq)]
415pub struct HostEvent<'a> {
416    pub node_id: u32,
417    pub handler_id: u64,
418    pub payload: crate::schema::EventPayload<'a>,
419}
420
421const EVENT_CLICK: u16 = 1;
422const EVENT_TEXT_CHANGED: u16 = 2;
423const EVENT_TEXT_SUBMITTED: u16 = 3;
424const EVENT_FOCUS_LOST: u16 = 4;
425const EVENT_PROTOCOL_ERROR: u16 = 5;
426const EVENT_KEY_DOWN: u16 = 6;
427const EVENT_RANGE_REQUESTED: u16 = 7;
428// Tag 8 is the pointer event and 9 to 15 are reserved for the gesture events, so this one
429// starts at 16 and the window size report continues from 17.
430const EVENT_POINTER: u16 = 8;
431const POINTER_RECORD_LEN: usize = 72;
432const EVENT_VALUE_CHANGED: u16 = 16;
433const EVENT_WINDOW_SIZE_CHANGED: u16 = 17;
434const EVENT_RESYNC: u16 = 18;
435const EVENT_LIFECYCLE_START: u16 = 19;
436const EVENT_LIFECYCLE_STOP: u16 = 20;
437const EVENT_DESIGN_SYSTEM_RESOLVED: u16 = 21;
438const EVENT_FILES_ENTERED: u16 = 22;
439const EVENT_FILES_DROPPED: u16 = 23;
440const EVENT_NOTIFICATION_ACTIVATED: u16 = 24;
441const EVENT_NOTIFICATION_PERMISSION_CHANGED: u16 = 25;
442const EVENT_CODE_CHANGED: u16 = 26;
443const EVENT_CODE_EDIT_REJECTED: u16 = 27;
444const EVENT_CODE_HOVERED: u16 = 28;
445const EVENT_CODE_SAVE_REQUESTED: u16 = 29;
446const EVENT_DECORATION_ACTIVATED: u16 = 30;
447const EVENT_ANIMATION: u16 = 31;
448const EVENT_REDUCED_MOTION_CHANGED: u16 = 32;
449// Tags 26 to 32 belong to events other requirements have numbered.
450const EVENT_ZOOM_CHANGED: u16 = 33;
451// 33 is the zoom event's. 34 to 38 are the code editor's.
452const EVENT_SELECTION_CHANGED: u16 = 34;
453const EVENT_VISIBLE_RANGE_CHANGED: u16 = 35;
454const EVENT_EDITOR_FOCUS_CHANGED: u16 = 36;
455const EVENT_FOLD_CHANGED: u16 = 37;
456const EVENT_COMPOSITION_CHANGED: u16 = 38;
457// 39 to 47 are the windows'.
458const EVENT_WINDOW_CLOSE_REQUESTED: u16 = 39;
459const EVENT_WINDOW_FOCUS_CHANGED: u16 = 40;
460const EVENT_WINDOW_MOVED: u16 = 41;
461const EVENT_WINDOW_DRAG: u16 = 42;
462const EVENT_WINDOW_DRAG_OVER: u16 = 43;
463const EVENT_WINDOW_UNAVAILABLE: u16 = 44;
464const EVENT_WINDOW_CLOSED: u16 = 45;
465const EVENT_WINDOW_REQUESTED: u16 = 46;
466const EVENT_APPLICATION_REOPEN: u16 = 47;
467// 48 and 49 are the node focus-gained and key-up events.
468const EVENT_FOCUS_GAINED: u16 = 48;
469const EVENT_KEY_UP: u16 = 49;
470const EVENT_FILE_DIALOG_RESULT: u16 = 50;
471// 51 to 53 are numbered for events that are not here yet. 54 and 55 are the code editor's
472// row height and scroll offset, each one `f64` of physical pixels.
473const EVENT_CODE_METRICS: u16 = 54;
474const EVENT_CODE_SCROLLED: u16 = 55;
475/// `FileDialogResult` in full: the 16 byte header, three words and the paths' reference.
476const FILE_DIALOG_RESULT_LEN: usize = 36;
477// 51 to 55 are held by events other requirements have numbered.
478const EVENT_CLIPBOARD_TEXT: u16 = 56;
479/// `ClipboardText` in full: the 16 byte header, three words and the text's reference.
480const CLIPBOARD_TEXT_LEN: usize = 36;
481const EVENT_IO_ANSWER: u16 = 57;
482/// `IoAnswer` in full: the 16 byte header, two words and the data's reference.
483const IO_ANSWER_LEN: usize = 32;
484const EVENT_FILE_HANDLES: u16 = 58;
485/// `FileHandles` in full: the 16 byte header, five words and two string references.
486const FILE_HANDLES_LEN: usize = 52;
487const EVENT_STORAGE_LOCATION: u16 = 59;
488const EVENT_SCROLL_CHANGED: u16 = 60;
489/// `ScrollChanged` in full: the 16 byte header, the item, the flags and six `f64`.
490const SCROLL_CHANGED_LEN: usize = 72;
491
492/// Where `SetWindow` keeps the mode and the last window policy: in the bits of the
493/// `resizable` word above the flag.
494const WINDOW_MODE_SHIFT: u32 = 8;
495const WINDOW_POLICY_SHIFT: u32 = 12;
496
497/// The phases of a `WindowDrag`.
498pub const WINDOW_DRAG_MOVE: u32 = 1;
499pub const WINDOW_DRAG_END: u32 = 2;
500pub const WINDOW_DRAG_CANCEL: u32 = 3;
501/// The kinds of a `WindowDragOver`.
502pub const WINDOW_DRAG_OVER_ENTER: u32 = 1;
503pub const WINDOW_DRAG_OVER_MOVE: u32 = 2;
504pub const WINDOW_DRAG_OVER_LEAVE: u32 = 3;
505pub const WINDOW_DRAG_OVER_DROP: u32 = 4;
506
507const MODIFIER_SHIFT: u8 = 1 << 0;
508const MODIFIER_CTRL: u8 = 1 << 1;
509const MODIFIER_ALT: u8 = 1 << 2;
510const MODIFIER_META: u8 = 1 << 3;
511
512/// Reads a pointer enum's tag out of a `u32` word. A word that does not fit a tag, or names
513/// none, is a value the sides disagree on.
514fn pointer_tag<T: TryFrom<u16, Error = ()>>(
515    bytes: &[u8],
516    offset: usize,
517) -> Result<T, ProtocolError> {
518    let raw = read_u32(bytes, offset)?;
519    u16::try_from(raw)
520        .ok()
521        .and_then(|tag| T::try_from(tag).ok())
522        .ok_or(ProtocolError::InvalidValueKind(
523            raw.min(u32::from(u16::MAX)) as u16,
524        ))
525}
526
527/// The 72-byte pointer record: see the field table of the pointer event.
528fn decode_pointer(bytes: &[u8]) -> Result<crate::schema::EventPayload<'_>, ProtocolError> {
529    Ok(crate::schema::EventPayload::Pointer {
530        kind: pointer_tag(bytes, 16)?,
531        pointer_id: read_u32(bytes, 20)?,
532        pointer_type: pointer_tag(bytes, 24)?,
533        x: f32::from_bits(read_u32(bytes, 28)?),
534        y: f32::from_bits(read_u32(bytes, 32)?),
535        buttons: read_u32(bytes, 36)?,
536        button: read_u32(bytes, 40)?,
537        modifiers: read_u32(bytes, 44)?,
538        click_count: read_u32(bytes, 48)?,
539        delta_x: f32::from_bits(read_u32(bytes, 52)?),
540        delta_y: f32::from_bits(read_u32(bytes, 56)?),
541        delta_mode: pointer_tag(bytes, 60)?,
542        timestamp_nanos: read_u64(bytes, 64)?,
543    })
544}
545
546/// Decode one Renderer-to-Host event record.
547pub fn decode_event(bytes: &[u8]) -> Result<HostEvent<'_>, ProtocolError> {
548    if bytes.len() < 16 {
549        return Err(ProtocolError::Truncated);
550    }
551    let tag = read_u16(bytes, 0)?;
552    let record_len = usize::from(read_u16(bytes, 2)?);
553    if record_len < 16 || record_len % 4 != 0 || record_len > bytes.len() {
554        return Err(ProtocolError::InvalidRecordLength);
555    }
556    let node_id = read_u32(bytes, 4)?;
557    let handler_id = read_u64(bytes, 8)?;
558    let payload = match tag {
559        EVENT_CLICK if record_len == 16 => crate::schema::EventPayload::Clicked,
560        EVENT_TEXT_CHANGED if record_len == 24 => {
561            crate::schema::EventPayload::TextChanged(read_string(bytes, 16, record_len)?)
562        }
563        EVENT_TEXT_SUBMITTED if record_len == 24 => {
564            crate::schema::EventPayload::TextSubmitted(read_string(bytes, 16, record_len)?)
565        }
566        EVENT_FOCUS_LOST if record_len == 16 => crate::schema::EventPayload::FocusLost,
567        EVENT_FOCUS_GAINED if record_len == 16 => crate::schema::EventPayload::FocusGained,
568        EVENT_PROTOCOL_ERROR if record_len == 28 => crate::schema::EventPayload::ProtocolError {
569            code: read_u32(bytes, 16)?,
570            message: read_string(bytes, 20, record_len)?,
571        },
572        EVENT_KEY_DOWN if record_len == 32 => {
573            let raw_key = read_u16(bytes, 16)?;
574            let modifiers = *bytes.get(18).ok_or(ProtocolError::Truncated)?;
575            let repeat = *bytes.get(19).ok_or(ProtocolError::Truncated)? != 0;
576            crate::schema::EventPayload::KeyDown {
577                key: Key::try_from(raw_key)
578                    .map_err(|()| ProtocolError::InvalidValueKind(raw_key))?,
579                shift_key: modifiers & MODIFIER_SHIFT != 0,
580                ctrl_key: modifiers & MODIFIER_CTRL != 0,
581                alt_key: modifiers & MODIFIER_ALT != 0,
582                meta_key: modifiers & MODIFIER_META != 0,
583                repeat,
584                code: read_u32(bytes, 20)?,
585                text: read_string(bytes, 24, record_len)?,
586            }
587        }
588        // A key-up has the key-down's layout, so a Host handler reads both the same way.
589        EVENT_KEY_UP if record_len == 32 => {
590            let raw_key = read_u16(bytes, 16)?;
591            let modifiers = *bytes.get(18).ok_or(ProtocolError::Truncated)?;
592            let repeat = *bytes.get(19).ok_or(ProtocolError::Truncated)? != 0;
593            crate::schema::EventPayload::KeyUp {
594                key: Key::try_from(raw_key)
595                    .map_err(|()| ProtocolError::InvalidValueKind(raw_key))?,
596                shift_key: modifiers & MODIFIER_SHIFT != 0,
597                ctrl_key: modifiers & MODIFIER_CTRL != 0,
598                alt_key: modifiers & MODIFIER_ALT != 0,
599                meta_key: modifiers & MODIFIER_META != 0,
600                repeat,
601                code: read_u32(bytes, 20)?,
602                text: read_string(bytes, 24, record_len)?,
603            }
604        }
605        EVENT_RANGE_REQUESTED if record_len == 24 => crate::schema::EventPayload::RangeRequested {
606            start: read_u32(bytes, 16)?,
607            count: read_u32(bytes, 20)?,
608        },
609        EVENT_VALUE_CHANGED if record_len == 24 => {
610            crate::schema::EventPayload::ValueChanged(f64::from_bits(read_u64(bytes, 16)?))
611        }
612        EVENT_WINDOW_SIZE_CHANGED if record_len == 44 => {
613            let raw_class = read_u32(bytes, 24)?;
614            let class = u16::try_from(raw_class)
615                .ok()
616                .and_then(|value| crate::schema::WindowSizeClass::try_from(value).ok())
617                .ok_or(ProtocolError::InvalidValueKind(raw_class as u16))?;
618            let raw_height_class = read_u32(bytes, 28)?;
619            let height_class = u16::try_from(raw_height_class)
620                .ok()
621                .and_then(|value| crate::schema::WindowHeightClass::try_from(value).ok())
622                .ok_or(ProtocolError::InvalidValueKind(raw_height_class as u16))?;
623            crate::schema::EventPayload::WindowSizeChanged {
624                width_dp: f32::from_bits(read_u32(bytes, 16)?),
625                height_dp: f32::from_bits(read_u32(bytes, 20)?),
626                class,
627                height_class,
628                safe_area_top: f32::from_bits(read_u32(bytes, 32)?),
629                safe_area_leading: f32::from_bits(read_u32(bytes, 36)?),
630                safe_area_trailing: f32::from_bits(read_u32(bytes, 40)?),
631            }
632        }
633        EVENT_DESIGN_SYSTEM_RESOLVED if record_len == 20 => {
634            let raw = read_u32(bytes, 16)?;
635            let system = u16::try_from(raw)
636                .ok()
637                .and_then(|tag| crate::schema::DesignSystem::try_from(tag).ok())
638                .ok_or(ProtocolError::InvalidValueKind(raw as u16))?;
639            crate::schema::EventPayload::DesignSystemResolved(system)
640        }
641        EVENT_FILES_ENTERED if record_len == 16 => crate::schema::EventPayload::FilesEntered,
642        EVENT_FILES_DROPPED if record_len == 24 => {
643            crate::schema::EventPayload::FilesDropped(read_string(bytes, 16, record_len)?)
644        }
645        EVENT_NOTIFICATION_ACTIVATED if record_len == 28 => {
646            let action = read_u32(bytes, 16)?;
647            // Zero is the body and one and two are the two buttons a notification can have.
648            // Anything else names a button that cannot exist.
649            if action > 2 {
650                return Err(ProtocolError::InvalidValueKind(
651                    action.min(u32::from(u16::MAX)) as u16,
652                ));
653            }
654            crate::schema::EventPayload::NotificationActivated {
655                action,
656                key: read_string(bytes, 20, record_len)?,
657            }
658        }
659        EVENT_NOTIFICATION_PERMISSION_CHANGED if record_len == 20 => {
660            let raw = read_u32(bytes, 16)?;
661            let state = u16::try_from(raw)
662                .ok()
663                .and_then(|tag| NotificationPermission::try_from(tag).ok())
664                .ok_or(ProtocolError::InvalidValueKind(
665                    raw.min(u32::from(u16::MAX)) as u16,
666                ))?;
667            crate::schema::EventPayload::NotificationPermissionChanged(state)
668        }
669        EVENT_ANIMATION if record_len == 40 => {
670            let raw_kind = u16::from(read_u8(bytes, 20)?);
671            let raw_property = u16::from(read_u8(bytes, 21)?);
672            crate::schema::EventPayload::AnimationEvent {
673                animation_id: read_u32(bytes, 16)?,
674                kind: crate::schema::AnimationEventKind::try_from(raw_kind)
675                    .map_err(|()| ProtocolError::InvalidValueKind(raw_kind))?,
676                property: crate::schema::AnimatedProperty::try_from(raw_property)
677                    .map_err(|()| ProtocolError::InvalidValueKind(raw_property))?,
678                slot: read_u8(bytes, 22)?,
679                iteration: read_u32(bytes, 24)?,
680                elapsed_ms: f32::from_bits(read_u32(bytes, 28)?),
681                time_nanos: read_u64(bytes, 32)?,
682            }
683        }
684        EVENT_POINTER if record_len == POINTER_RECORD_LEN => decode_pointer(bytes)?,
685        EVENT_REDUCED_MOTION_CHANGED if record_len == 20 => {
686            let raw = read_u32(bytes, 16)?;
687            let state = u16::try_from(raw)
688                .ok()
689                .and_then(|tag| crate::schema::ReducedMotion::try_from(tag).ok())
690                .ok_or(ProtocolError::InvalidValueKind(
691                    raw.min(u32::from(u16::MAX)) as u16,
692                ))?;
693            crate::schema::EventPayload::ReducedMotionChanged(state)
694        }
695        EVENT_CODE_CHANGED if record_len == 44 => crate::schema::EventPayload::CodeChanged {
696            version: read_u32(bytes, 16)?,
697            start_line: read_u32(bytes, 20)?,
698            start_column: read_u32(bytes, 24)?,
699            end_line: read_u32(bytes, 28)?,
700            end_column: read_u32(bytes, 32)?,
701            text: read_string(bytes, 36, record_len)?,
702        },
703        EVENT_CODE_EDIT_REJECTED if record_len == 44 => {
704            crate::schema::EventPayload::CodeEditRejected {
705                request_id: read_u32(bytes, 16)?,
706                base_version: read_u32(bytes, 20)?,
707                current_version: read_u32(bytes, 24)?,
708                start_line: read_u32(bytes, 28)?,
709                start_column: read_u32(bytes, 32)?,
710                end_line: read_u32(bytes, 36)?,
711                end_column: read_u32(bytes, 40)?,
712            }
713        }
714        EVENT_CODE_HOVERED if record_len == 36 => {
715            let raw_phase = read_u32(bytes, 32)?;
716            let phase = u16::try_from(raw_phase)
717                .ok()
718                .and_then(|tag| crate::schema::HoverPhase::try_from(tag).ok())
719                .ok_or(ProtocolError::InvalidValueKind(raw_phase as u16))?;
720            crate::schema::EventPayload::CodeHovered {
721                decoration: read_u64(bytes, 16)?,
722                line: read_u32(bytes, 24)?,
723                column: read_u32(bytes, 28)?,
724                phase,
725            }
726        }
727        EVENT_CODE_SAVE_REQUESTED if record_len == 24 => {
728            crate::schema::EventPayload::CodeSaveRequested {
729                version: read_u32(bytes, 16)?,
730            }
731        }
732        EVENT_DECORATION_ACTIVATED if record_len == 24 => {
733            crate::schema::EventPayload::DecorationActivated {
734                decoration: read_u64(bytes, 16)?,
735            }
736        }
737        EVENT_CODE_CHANGED..=EVENT_DECORATION_ACTIVATED => {
738            return Err(ProtocolError::InvalidRecordLength);
739        }
740        EVENT_SELECTION_CHANGED if record_len == 36 => {
741            crate::schema::EventPayload::SelectionChanged {
742                version: read_u32(bytes, 16)?,
743                anchor_line: read_u32(bytes, 20)?,
744                anchor_column: read_u32(bytes, 24)?,
745                active_line: read_u32(bytes, 28)?,
746                active_column: read_u32(bytes, 32)?,
747            }
748        }
749        EVENT_VISIBLE_RANGE_CHANGED if record_len == 28 => {
750            crate::schema::EventPayload::VisibleRangeChanged {
751                version: read_u32(bytes, 16)?,
752                first_line: read_u32(bytes, 20)?,
753                last_line: read_u32(bytes, 24)?,
754            }
755        }
756        EVENT_EDITOR_FOCUS_CHANGED if record_len == 24 => {
757            crate::schema::EventPayload::EditorFocusChanged {
758                focused: read_flag(bytes, 16)?,
759            }
760        }
761        EVENT_FOLD_CHANGED if record_len == 24 => crate::schema::EventPayload::FoldChanged {
762            start_line: read_u32(bytes, 16)?,
763            collapsed: read_flag(bytes, 20)?,
764        },
765        EVENT_COMPOSITION_CHANGED if record_len == 24 => {
766            crate::schema::EventPayload::CompositionChanged {
767                composing: read_flag(bytes, 16)?,
768            }
769        }
770        EVENT_SELECTION_CHANGED..=EVENT_COMPOSITION_CHANGED => {
771            return Err(ProtocolError::InvalidRecordLength);
772        }
773        EVENT_WINDOW_CLOSE_REQUESTED if record_len == 16 => {
774            crate::schema::EventPayload::WindowCloseRequested
775        }
776        EVENT_WINDOW_FOCUS_CHANGED if record_len == 20 => {
777            crate::schema::EventPayload::WindowFocusChanged {
778                focused: read_flag(bytes, 16)?,
779            }
780        }
781        EVENT_WINDOW_MOVED if record_len == 32 => crate::schema::EventPayload::WindowMoved {
782            x: f32::from_bits(read_u32(bytes, 16)?),
783            y: f32::from_bits(read_u32(bytes, 20)?),
784            width: f32::from_bits(read_u32(bytes, 24)?),
785            height: f32::from_bits(read_u32(bytes, 28)?),
786        },
787        EVENT_WINDOW_DRAG if record_len == 32 => {
788            let phase = read_u32(bytes, 16)?;
789            if !(WINDOW_DRAG_MOVE..=WINDOW_DRAG_CANCEL).contains(&phase) {
790                return Err(ProtocolError::InvalidValueKind(
791                    phase.min(u32::from(u16::MAX)) as u16,
792                ));
793            }
794            crate::schema::EventPayload::WindowDrag {
795                phase,
796                screen_x: f32::from_bits(read_u32(bytes, 20)?),
797                screen_y: f32::from_bits(read_u32(bytes, 24)?),
798                over_window: read_u32(bytes, 28)?,
799            }
800        }
801        EVENT_WINDOW_DRAG_OVER if record_len == 32 => {
802            let kind = read_u32(bytes, 16)?;
803            if !(WINDOW_DRAG_OVER_ENTER..=WINDOW_DRAG_OVER_DROP).contains(&kind) {
804                return Err(ProtocolError::InvalidValueKind(
805                    kind.min(u32::from(u16::MAX)) as u16,
806                ));
807            }
808            crate::schema::EventPayload::WindowDragOver {
809                kind,
810                window: read_u32(bytes, 20)?,
811                x: f32::from_bits(read_u32(bytes, 24)?),
812                y: f32::from_bits(read_u32(bytes, 28)?),
813            }
814        }
815        EVENT_WINDOW_UNAVAILABLE if record_len == 16 => {
816            crate::schema::EventPayload::WindowUnavailable
817        }
818        EVENT_WINDOW_CLOSED if record_len == 16 => crate::schema::EventPayload::WindowClosed,
819        EVENT_WINDOW_REQUESTED if record_len == 28 => {
820            crate::schema::EventPayload::WindowRequested {
821                request: read_u32(bytes, 16)?,
822                key: read_string(bytes, 20, record_len)?,
823            }
824        }
825        EVENT_APPLICATION_REOPEN if record_len == 16 => {
826            crate::schema::EventPayload::ApplicationReopen
827        }
828        EVENT_FILE_DIALOG_RESULT if record_len == FILE_DIALOG_RESULT_LEN => {
829            let raw_status = read_u32(bytes, 20)?;
830            let status = u16::try_from(raw_status)
831                .ok()
832                .and_then(|tag| FileDialogStatus::try_from(tag).ok())
833                .ok_or(ProtocolError::InvalidValueKind(
834                    raw_status.min(u32::from(u16::MAX)) as u16,
835                ))?;
836            crate::schema::EventPayload::FileDialogResult {
837                request: read_u32(bytes, 16)?,
838                status,
839                filter: read_u32(bytes, 24)?,
840                paths: read_string(bytes, 28, record_len)?,
841            }
842        }
843        EVENT_FILE_DIALOG_RESULT => return Err(ProtocolError::InvalidRecordLength),
844        EVENT_CLIPBOARD_TEXT if record_len == CLIPBOARD_TEXT_LEN => {
845            crate::schema::EventPayload::ClipboardText {
846                request: read_u32(bytes, 16)?,
847                status: read_u32(bytes, 20)?,
848                len: read_u32(bytes, 24)?,
849                text: read_string(bytes, 28, record_len)?,
850            }
851        }
852        EVENT_CLIPBOARD_TEXT => return Err(ProtocolError::InvalidRecordLength),
853        EVENT_CODE_METRICS if record_len == 24 => crate::schema::EventPayload::CodeMetrics {
854            line_height_px: f64::from_bits(read_u64(bytes, 16)?),
855        },
856        EVENT_CODE_SCROLLED if record_len == 24 => crate::schema::EventPayload::CodeScrolled {
857            offset_px: f64::from_bits(read_u64(bytes, 16)?),
858        },
859        EVENT_CODE_METRICS | EVENT_CODE_SCROLLED => {
860            return Err(ProtocolError::InvalidRecordLength);
861        }
862        EVENT_IO_ANSWER if record_len == IO_ANSWER_LEN => {
863            let raw_status = read_u32(bytes, 20)?;
864            let status = u16::try_from(raw_status)
865                .ok()
866                .and_then(|tag| IoStatus::try_from(tag).ok())
867                .ok_or(ProtocolError::InvalidValueKind(
868                    raw_status.min(u32::from(u16::MAX)) as u16,
869                ))?;
870            crate::schema::EventPayload::IoAnswer {
871                request: read_u32(bytes, 16)?,
872                status,
873                data: read_bytes(bytes, 24, record_len)?,
874            }
875        }
876        EVENT_IO_ANSWER => return Err(ProtocolError::InvalidRecordLength),
877        EVENT_FILE_HANDLES if record_len == FILE_HANDLES_LEN => {
878            let raw_status = read_u32(bytes, 20)?;
879            let status = u16::try_from(raw_status)
880                .ok()
881                .and_then(|tag| FileDialogStatus::try_from(tag).ok())
882                .ok_or(ProtocolError::InvalidValueKind(
883                    raw_status.min(u32::from(u16::MAX)) as u16,
884                ))?;
885            let raw_delivery = read_u32(bytes, 28)?;
886            let delivery = u16::try_from(raw_delivery)
887                .ok()
888                .and_then(|tag| FileDelivery::try_from(tag).ok())
889                .ok_or(ProtocolError::InvalidValueKind(
890                    raw_delivery.min(u32::from(u16::MAX)) as u16,
891                ))?;
892            let names = read_string(bytes, 36, record_len)?;
893            let kinds = read_string(bytes, 44, record_len)?;
894            // One letter per name, and only the two letters there are. A list whose kinds do
895            // not line up with its names would hand the application a file as a folder.
896            if let Some(unknown) = kinds.bytes().find(|kind| *kind != b'f' && *kind != b'd') {
897                return Err(ProtocolError::InvalidValueKind(u16::from(unknown)));
898            }
899            let count = if names.is_empty() {
900                0
901            } else {
902                names.split('\0').count()
903            };
904            if count != kinds.len() || names.split('\0').any(|name| count != 0 && name.is_empty()) {
905                return Err(ProtocolError::InvalidStringRange);
906            }
907            crate::schema::EventPayload::FileHandles {
908                request: read_u32(bytes, 16)?,
909                status,
910                filter: read_u32(bytes, 24)?,
911                delivery,
912                first_handle: read_u32(bytes, 32)?,
913                names,
914                kinds,
915            }
916        }
917        EVENT_FILE_HANDLES => return Err(ProtocolError::InvalidRecordLength),
918        EVENT_STORAGE_LOCATION if record_len == 24 => {
919            crate::schema::EventPayload::StorageLocation(read_string(bytes, 16, record_len)?)
920        }
921        EVENT_STORAGE_LOCATION => return Err(ProtocolError::InvalidRecordLength),
922        EVENT_SCROLL_CHANGED if record_len == SCROLL_CHANGED_LEN => {
923            use crate::schema::scroll_changed_flags as flag;
924            let flags = read_u32(bytes, 20)?;
925            if flags & !flag::KNOWN != 0 {
926                // A bit with no meaning here is a newer or a broken Renderer, and guessing
927                // what it says about the position would be worse than refusing the report.
928                return Err(ProtocolError::InvalidValueKind(
929                    (flags & !flag::KNOWN).trailing_zeros() as u16,
930                ));
931            }
932            crate::schema::EventPayload::ScrollChanged {
933                item: read_u32(bytes, 16)?,
934                settled: flags & flag::SETTLED != 0,
935                can_scroll_backward: flags & flag::CAN_SCROLL_BACKWARD != 0,
936                can_scroll_forward: flags & flag::CAN_SCROLL_FORWARD != 0,
937                x_px: f64::from_bits(read_u64(bytes, 24)?),
938                y_px: f64::from_bits(read_u64(bytes, 32)?),
939                max_x_px: f64::from_bits(read_u64(bytes, 40)?),
940                max_y_px: f64::from_bits(read_u64(bytes, 48)?),
941                viewport_width_px: f64::from_bits(read_u64(bytes, 56)?),
942                viewport_height_px: f64::from_bits(read_u64(bytes, 64)?),
943            }
944        }
945        EVENT_SCROLL_CHANGED => return Err(ProtocolError::InvalidRecordLength),
946        EVENT_WINDOW_CLOSE_REQUESTED..=EVENT_APPLICATION_REOPEN => {
947            return Err(ProtocolError::InvalidRecordLength);
948        }
949        EVENT_ZOOM_CHANGED if record_len == 28 => {
950            let level = read_u32(bytes, 24)? as i32;
951            if !(i32::from(crate::zoom::MIN_ZOOM_LEVEL)..=i32::from(crate::zoom::MAX_ZOOM_LEVEL))
952                .contains(&level)
953            {
954                return Err(ProtocolError::InvalidValueKind(level as u16));
955            }
956            crate::schema::EventPayload::ZoomChanged {
957                k: f32::from_bits(read_u32(bytes, 16)?),
958                os: f32::from_bits(read_u32(bytes, 20)?),
959                level,
960            }
961        }
962        EVENT_RESYNC if record_len == 16 => crate::schema::EventPayload::Resync,
963        EVENT_LIFECYCLE_START if record_len == 16 => crate::schema::EventPayload::LifecycleStart,
964        EVENT_LIFECYCLE_STOP if record_len == 16 => crate::schema::EventPayload::LifecycleStop,
965        EVENT_CLICK..=EVENT_RANGE_REQUESTED
966        | EVENT_VALUE_CHANGED..=EVENT_NOTIFICATION_PERMISSION_CHANGED
967        | EVENT_ANIMATION
968        | EVENT_POINTER
969        | EVENT_REDUCED_MOTION_CHANGED
970        | EVENT_ZOOM_CHANGED
971        | EVENT_FOCUS_GAINED
972        | EVENT_KEY_UP => {
973            return Err(ProtocolError::InvalidRecordLength);
974        }
975        other => return Err(ProtocolError::InvalidTag(other)),
976    };
977    Ok(HostEvent {
978        node_id,
979        handler_id,
980        payload,
981    })
982}
983
984/// Test/mock helper for constructing a Renderer-to-Host event.
985pub fn encode_event(event: &HostEvent<'_>, output: &mut Vec<u8>) -> Result<(), ProtocolError> {
986    output.clear();
987    if let crate::schema::EventPayload::KeyDown {
988        key,
989        shift_key,
990        ctrl_key,
991        alt_key,
992        meta_key,
993        repeat,
994        code,
995        text,
996    } = event.payload
997    {
998        output.extend_from_slice(&EVENT_KEY_DOWN.to_le_bytes());
999        output.extend_from_slice(&32_u16.to_le_bytes());
1000        output.extend_from_slice(&event.node_id.to_le_bytes());
1001        output.extend_from_slice(&event.handler_id.to_le_bytes());
1002        output.extend_from_slice(&(key as u16).to_le_bytes());
1003        let modifiers = (u8::from(shift_key) * MODIFIER_SHIFT)
1004            | (u8::from(ctrl_key) * MODIFIER_CTRL)
1005            | (u8::from(alt_key) * MODIFIER_ALT)
1006            | (u8::from(meta_key) * MODIFIER_META);
1007        output.push(modifiers);
1008        output.push(u8::from(repeat));
1009        output.extend_from_slice(&code.to_le_bytes());
1010        let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1011        output.extend_from_slice(&32_u32.to_le_bytes());
1012        output.extend_from_slice(&len.to_le_bytes());
1013        output.extend_from_slice(text.as_bytes());
1014        return Ok(());
1015    }
1016    if let crate::schema::EventPayload::KeyUp {
1017        key,
1018        shift_key,
1019        ctrl_key,
1020        alt_key,
1021        meta_key,
1022        repeat,
1023        code,
1024        text,
1025    } = event.payload
1026    {
1027        output.extend_from_slice(&EVENT_KEY_UP.to_le_bytes());
1028        output.extend_from_slice(&32_u16.to_le_bytes());
1029        output.extend_from_slice(&event.node_id.to_le_bytes());
1030        output.extend_from_slice(&event.handler_id.to_le_bytes());
1031        output.extend_from_slice(&(key as u16).to_le_bytes());
1032        let modifiers = (u8::from(shift_key) * MODIFIER_SHIFT)
1033            | (u8::from(ctrl_key) * MODIFIER_CTRL)
1034            | (u8::from(alt_key) * MODIFIER_ALT)
1035            | (u8::from(meta_key) * MODIFIER_META);
1036        output.push(modifiers);
1037        output.push(u8::from(repeat));
1038        output.extend_from_slice(&code.to_le_bytes());
1039        let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1040        output.extend_from_slice(&32_u32.to_le_bytes());
1041        output.extend_from_slice(&len.to_le_bytes());
1042        output.extend_from_slice(text.as_bytes());
1043        return Ok(());
1044    }
1045    if let crate::schema::EventPayload::ValueChanged(value) = event.payload {
1046        output.extend_from_slice(&EVENT_VALUE_CHANGED.to_le_bytes());
1047        output.extend_from_slice(&24_u16.to_le_bytes());
1048        output.extend_from_slice(&event.node_id.to_le_bytes());
1049        output.extend_from_slice(&event.handler_id.to_le_bytes());
1050        output.extend_from_slice(&value.to_bits().to_le_bytes());
1051        return Ok(());
1052    }
1053    if let crate::schema::EventPayload::WindowSizeChanged {
1054        width_dp,
1055        height_dp,
1056        class,
1057        height_class,
1058        safe_area_top,
1059        safe_area_leading,
1060        safe_area_trailing,
1061    } = event.payload
1062    {
1063        output.extend_from_slice(&EVENT_WINDOW_SIZE_CHANGED.to_le_bytes());
1064        output.extend_from_slice(&44_u16.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        output.extend_from_slice(&width_dp.to_bits().to_le_bytes());
1068        output.extend_from_slice(&height_dp.to_bits().to_le_bytes());
1069        output.extend_from_slice(&u32::from(u16::from(class)).to_le_bytes());
1070        output.extend_from_slice(&u32::from(u16::from(height_class)).to_le_bytes());
1071        output.extend_from_slice(&safe_area_top.to_bits().to_le_bytes());
1072        output.extend_from_slice(&safe_area_leading.to_bits().to_le_bytes());
1073        output.extend_from_slice(&safe_area_trailing.to_bits().to_le_bytes());
1074        return Ok(());
1075    }
1076    if let crate::schema::EventPayload::DesignSystemResolved(system) = event.payload {
1077        output.extend_from_slice(&EVENT_DESIGN_SYSTEM_RESOLVED.to_le_bytes());
1078        output.extend_from_slice(&20_u16.to_le_bytes());
1079        output.extend_from_slice(&event.node_id.to_le_bytes());
1080        output.extend_from_slice(&event.handler_id.to_le_bytes());
1081        // The tag in a word, which keeps the record a multiple of four the way every
1082        // other record here is.
1083        output.extend_from_slice(&u32::from(u16::from(system)).to_le_bytes());
1084        return Ok(());
1085    }
1086    if let crate::schema::EventPayload::NotificationPermissionChanged(state) = event.payload {
1087        output.extend_from_slice(&EVENT_NOTIFICATION_PERMISSION_CHANGED.to_le_bytes());
1088        output.extend_from_slice(&20_u16.to_le_bytes());
1089        output.extend_from_slice(&event.node_id.to_le_bytes());
1090        output.extend_from_slice(&event.handler_id.to_le_bytes());
1091        output.extend_from_slice(&u32::from(u16::from(state)).to_le_bytes());
1092        return Ok(());
1093    }
1094    if let crate::schema::EventPayload::AnimationEvent {
1095        animation_id,
1096        kind,
1097        property,
1098        slot,
1099        iteration,
1100        elapsed_ms,
1101        time_nanos,
1102    } = event.payload
1103    {
1104        output.extend_from_slice(&EVENT_ANIMATION.to_le_bytes());
1105        output.extend_from_slice(&40_u16.to_le_bytes());
1106        output.extend_from_slice(&event.node_id.to_le_bytes());
1107        output.extend_from_slice(&event.handler_id.to_le_bytes());
1108        output.extend_from_slice(&animation_id.to_le_bytes());
1109        output.push(kind as u8);
1110        output.push(property as u8);
1111        output.push(slot);
1112        output.push(0);
1113        output.extend_from_slice(&iteration.to_le_bytes());
1114        output.extend_from_slice(&elapsed_ms.to_bits().to_le_bytes());
1115        output.extend_from_slice(&time_nanos.to_le_bytes());
1116        return Ok(());
1117    }
1118    if let crate::schema::EventPayload::Pointer {
1119        kind,
1120        pointer_id,
1121        pointer_type,
1122        x,
1123        y,
1124        buttons,
1125        button,
1126        modifiers,
1127        click_count,
1128        delta_x,
1129        delta_y,
1130        delta_mode,
1131        timestamp_nanos,
1132    } = event.payload
1133    {
1134        output.extend_from_slice(&EVENT_POINTER.to_le_bytes());
1135        output.extend_from_slice(&(POINTER_RECORD_LEN as u16).to_le_bytes());
1136        output.extend_from_slice(&event.node_id.to_le_bytes());
1137        output.extend_from_slice(&event.handler_id.to_le_bytes());
1138        output.extend_from_slice(&u32::from(u16::from(kind)).to_le_bytes());
1139        output.extend_from_slice(&pointer_id.to_le_bytes());
1140        output.extend_from_slice(&u32::from(u16::from(pointer_type)).to_le_bytes());
1141        output.extend_from_slice(&x.to_bits().to_le_bytes());
1142        output.extend_from_slice(&y.to_bits().to_le_bytes());
1143        output.extend_from_slice(&buttons.to_le_bytes());
1144        output.extend_from_slice(&button.to_le_bytes());
1145        output.extend_from_slice(&modifiers.to_le_bytes());
1146        output.extend_from_slice(&click_count.to_le_bytes());
1147        output.extend_from_slice(&delta_x.to_bits().to_le_bytes());
1148        output.extend_from_slice(&delta_y.to_bits().to_le_bytes());
1149        output.extend_from_slice(&u32::from(u16::from(delta_mode)).to_le_bytes());
1150        output.extend_from_slice(&timestamp_nanos.to_le_bytes());
1151        return Ok(());
1152    }
1153    if let crate::schema::EventPayload::ReducedMotionChanged(state) = event.payload {
1154        output.extend_from_slice(&EVENT_REDUCED_MOTION_CHANGED.to_le_bytes());
1155        output.extend_from_slice(&20_u16.to_le_bytes());
1156        output.extend_from_slice(&event.node_id.to_le_bytes());
1157        output.extend_from_slice(&event.handler_id.to_le_bytes());
1158        output.extend_from_slice(&u32::from(u16::from(state)).to_le_bytes());
1159        return Ok(());
1160    }
1161    match event.payload {
1162        crate::schema::EventPayload::CodeChanged {
1163            version,
1164            start_line,
1165            start_column,
1166            end_line,
1167            end_column,
1168            text,
1169        } => {
1170            put_event_header(output, EVENT_CODE_CHANGED, 44, event);
1171            for value in [version, start_line, start_column, end_line, end_column] {
1172                output.extend_from_slice(&value.to_le_bytes());
1173            }
1174            let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1175            output.extend_from_slice(&44_u32.to_le_bytes());
1176            output.extend_from_slice(&len.to_le_bytes());
1177            output.extend_from_slice(text.as_bytes());
1178            return Ok(());
1179        }
1180        crate::schema::EventPayload::CodeEditRejected {
1181            request_id,
1182            base_version,
1183            current_version,
1184            start_line,
1185            start_column,
1186            end_line,
1187            end_column,
1188        } => {
1189            put_event_header(output, EVENT_CODE_EDIT_REJECTED, 44, event);
1190            for value in [
1191                request_id,
1192                base_version,
1193                current_version,
1194                start_line,
1195                start_column,
1196                end_line,
1197                end_column,
1198            ] {
1199                output.extend_from_slice(&value.to_le_bytes());
1200            }
1201            return Ok(());
1202        }
1203        crate::schema::EventPayload::CodeHovered {
1204            decoration,
1205            line,
1206            column,
1207            phase,
1208        } => {
1209            put_event_header(output, EVENT_CODE_HOVERED, 36, event);
1210            output.extend_from_slice(&decoration.to_le_bytes());
1211            output.extend_from_slice(&line.to_le_bytes());
1212            output.extend_from_slice(&column.to_le_bytes());
1213            output.extend_from_slice(&u32::from(u16::from(phase)).to_le_bytes());
1214            return Ok(());
1215        }
1216        crate::schema::EventPayload::CodeSaveRequested { version } => {
1217            put_event_header(output, EVENT_CODE_SAVE_REQUESTED, 24, event);
1218            output.extend_from_slice(&version.to_le_bytes());
1219            // The padding that keeps the record a multiple of four bytes.
1220            output.extend_from_slice(&0_u32.to_le_bytes());
1221            return Ok(());
1222        }
1223        crate::schema::EventPayload::DecorationActivated { decoration } => {
1224            put_event_header(output, EVENT_DECORATION_ACTIVATED, 24, event);
1225            output.extend_from_slice(&decoration.to_le_bytes());
1226            return Ok(());
1227        }
1228        crate::schema::EventPayload::SelectionChanged {
1229            version,
1230            anchor_line,
1231            anchor_column,
1232            active_line,
1233            active_column,
1234        } => {
1235            put_event_header(output, EVENT_SELECTION_CHANGED, 36, event);
1236            for value in [
1237                version,
1238                anchor_line,
1239                anchor_column,
1240                active_line,
1241                active_column,
1242            ] {
1243                output.extend_from_slice(&value.to_le_bytes());
1244            }
1245            return Ok(());
1246        }
1247        crate::schema::EventPayload::VisibleRangeChanged {
1248            version,
1249            first_line,
1250            last_line,
1251        } => {
1252            put_event_header(output, EVENT_VISIBLE_RANGE_CHANGED, 28, event);
1253            for value in [version, first_line, last_line] {
1254                output.extend_from_slice(&value.to_le_bytes());
1255            }
1256            return Ok(());
1257        }
1258        crate::schema::EventPayload::EditorFocusChanged { focused } => {
1259            put_event_header(output, EVENT_EDITOR_FOCUS_CHANGED, 24, event);
1260            output.extend_from_slice(&u32::from(focused).to_le_bytes());
1261            output.extend_from_slice(&0_u32.to_le_bytes());
1262            return Ok(());
1263        }
1264        crate::schema::EventPayload::FoldChanged {
1265            start_line,
1266            collapsed,
1267        } => {
1268            put_event_header(output, EVENT_FOLD_CHANGED, 24, event);
1269            output.extend_from_slice(&start_line.to_le_bytes());
1270            output.extend_from_slice(&u32::from(collapsed).to_le_bytes());
1271            return Ok(());
1272        }
1273        crate::schema::EventPayload::CompositionChanged { composing } => {
1274            put_event_header(output, EVENT_COMPOSITION_CHANGED, 24, event);
1275            output.extend_from_slice(&u32::from(composing).to_le_bytes());
1276            output.extend_from_slice(&0_u32.to_le_bytes());
1277            return Ok(());
1278        }
1279        _ => {}
1280    }
1281    match event.payload {
1282        crate::schema::EventPayload::WindowFocusChanged { focused } => {
1283            put_event_header(output, EVENT_WINDOW_FOCUS_CHANGED, 20, event);
1284            output.extend_from_slice(&u32::from(focused).to_le_bytes());
1285            return Ok(());
1286        }
1287        crate::schema::EventPayload::WindowMoved {
1288            x,
1289            y,
1290            width,
1291            height,
1292        } => {
1293            put_event_header(output, EVENT_WINDOW_MOVED, 32, event);
1294            for value in [x, y, width, height] {
1295                output.extend_from_slice(&value.to_bits().to_le_bytes());
1296            }
1297            return Ok(());
1298        }
1299        crate::schema::EventPayload::WindowDrag {
1300            phase,
1301            screen_x,
1302            screen_y,
1303            over_window,
1304        } => {
1305            put_event_header(output, EVENT_WINDOW_DRAG, 32, event);
1306            output.extend_from_slice(&phase.to_le_bytes());
1307            output.extend_from_slice(&screen_x.to_bits().to_le_bytes());
1308            output.extend_from_slice(&screen_y.to_bits().to_le_bytes());
1309            output.extend_from_slice(&over_window.to_le_bytes());
1310            return Ok(());
1311        }
1312        crate::schema::EventPayload::WindowDragOver { kind, window, x, y } => {
1313            put_event_header(output, EVENT_WINDOW_DRAG_OVER, 32, event);
1314            output.extend_from_slice(&kind.to_le_bytes());
1315            output.extend_from_slice(&window.to_le_bytes());
1316            output.extend_from_slice(&x.to_bits().to_le_bytes());
1317            output.extend_from_slice(&y.to_bits().to_le_bytes());
1318            return Ok(());
1319        }
1320        _ => {}
1321    }
1322    if let crate::schema::EventPayload::FileDialogResult {
1323        request,
1324        status,
1325        filter,
1326        paths,
1327    } = event.payload
1328    {
1329        put_event_header(
1330            output,
1331            EVENT_FILE_DIALOG_RESULT,
1332            FILE_DIALOG_RESULT_LEN as u16,
1333            event,
1334        );
1335        output.extend_from_slice(&request.to_le_bytes());
1336        output.extend_from_slice(&u32::from(status as u16).to_le_bytes());
1337        output.extend_from_slice(&filter.to_le_bytes());
1338        let len = u32::try_from(paths.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1339        output.extend_from_slice(&(FILE_DIALOG_RESULT_LEN as u32).to_le_bytes());
1340        output.extend_from_slice(&len.to_le_bytes());
1341        output.extend_from_slice(paths.as_bytes());
1342        return Ok(());
1343    }
1344    if let crate::schema::EventPayload::ClipboardText {
1345        request,
1346        status,
1347        len,
1348        text,
1349    } = event.payload
1350    {
1351        put_event_header(
1352            output,
1353            EVENT_CLIPBOARD_TEXT,
1354            CLIPBOARD_TEXT_LEN as u16,
1355            event,
1356        );
1357        output.extend_from_slice(&request.to_le_bytes());
1358        output.extend_from_slice(&status.to_le_bytes());
1359        output.extend_from_slice(&len.to_le_bytes());
1360        let text_len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1361        output.extend_from_slice(&(CLIPBOARD_TEXT_LEN as u32).to_le_bytes());
1362        output.extend_from_slice(&text_len.to_le_bytes());
1363        output.extend_from_slice(text.as_bytes());
1364        return Ok(());
1365    }
1366    match event.payload {
1367        crate::schema::EventPayload::CodeMetrics { line_height_px } => {
1368            put_event_header(output, EVENT_CODE_METRICS, 24, event);
1369            output.extend_from_slice(&line_height_px.to_bits().to_le_bytes());
1370            return Ok(());
1371        }
1372        crate::schema::EventPayload::CodeScrolled { offset_px } => {
1373            put_event_header(output, EVENT_CODE_SCROLLED, 24, event);
1374            output.extend_from_slice(&offset_px.to_bits().to_le_bytes());
1375            return Ok(());
1376        }
1377        _ => {}
1378    }
1379    if let crate::schema::EventPayload::IoAnswer {
1380        request,
1381        status,
1382        data,
1383    } = event.payload
1384    {
1385        put_event_header(output, EVENT_IO_ANSWER, IO_ANSWER_LEN as u16, event);
1386        output.extend_from_slice(&request.to_le_bytes());
1387        output.extend_from_slice(&u32::from(u16::from(status)).to_le_bytes());
1388        let data_len = u32::try_from(data.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1389        output.extend_from_slice(&(IO_ANSWER_LEN as u32).to_le_bytes());
1390        output.extend_from_slice(&data_len.to_le_bytes());
1391        output.extend_from_slice(data);
1392        return Ok(());
1393    }
1394    if let crate::schema::EventPayload::FileHandles {
1395        request,
1396        status,
1397        filter,
1398        delivery,
1399        first_handle,
1400        names,
1401        kinds,
1402    } = event.payload
1403    {
1404        put_event_header(output, EVENT_FILE_HANDLES, FILE_HANDLES_LEN as u16, event);
1405        output.extend_from_slice(&request.to_le_bytes());
1406        output.extend_from_slice(&u32::from(status as u16).to_le_bytes());
1407        output.extend_from_slice(&filter.to_le_bytes());
1408        output.extend_from_slice(&u32::from(delivery as u16).to_le_bytes());
1409        output.extend_from_slice(&first_handle.to_le_bytes());
1410        let names_len = u32::try_from(names.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1411        let kinds_len = u32::try_from(kinds.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1412        let kinds_at = (FILE_HANDLES_LEN as u32)
1413            .checked_add(names_len)
1414            .ok_or(ProtocolError::LengthOverflow)?;
1415        output.extend_from_slice(&(FILE_HANDLES_LEN as u32).to_le_bytes());
1416        output.extend_from_slice(&names_len.to_le_bytes());
1417        output.extend_from_slice(&kinds_at.to_le_bytes());
1418        output.extend_from_slice(&kinds_len.to_le_bytes());
1419        output.extend_from_slice(names.as_bytes());
1420        output.extend_from_slice(kinds.as_bytes());
1421        return Ok(());
1422    }
1423    if let crate::schema::EventPayload::ScrollChanged {
1424        item,
1425        settled,
1426        can_scroll_backward,
1427        can_scroll_forward,
1428        x_px,
1429        y_px,
1430        max_x_px,
1431        max_y_px,
1432        viewport_width_px,
1433        viewport_height_px,
1434    } = event.payload
1435    {
1436        use crate::schema::scroll_changed_flags as flag;
1437        put_event_header(
1438            output,
1439            EVENT_SCROLL_CHANGED,
1440            SCROLL_CHANGED_LEN as u16,
1441            event,
1442        );
1443        let mut flags = 0_u32;
1444        if settled {
1445            flags |= flag::SETTLED;
1446        }
1447        if can_scroll_backward {
1448            flags |= flag::CAN_SCROLL_BACKWARD;
1449        }
1450        if can_scroll_forward {
1451            flags |= flag::CAN_SCROLL_FORWARD;
1452        }
1453        output.extend_from_slice(&item.to_le_bytes());
1454        output.extend_from_slice(&flags.to_le_bytes());
1455        for value in [
1456            x_px,
1457            y_px,
1458            max_x_px,
1459            max_y_px,
1460            viewport_width_px,
1461            viewport_height_px,
1462        ] {
1463            output.extend_from_slice(&value.to_bits().to_le_bytes());
1464        }
1465        return Ok(());
1466    }
1467    if let crate::schema::EventPayload::ZoomChanged { k, os, level } = event.payload {
1468        output.extend_from_slice(&EVENT_ZOOM_CHANGED.to_le_bytes());
1469        output.extend_from_slice(&28_u16.to_le_bytes());
1470        output.extend_from_slice(&event.node_id.to_le_bytes());
1471        output.extend_from_slice(&event.handler_id.to_le_bytes());
1472        output.extend_from_slice(&k.to_bits().to_le_bytes());
1473        output.extend_from_slice(&os.to_bits().to_le_bytes());
1474        output.extend_from_slice(&level.to_le_bytes());
1475        return Ok(());
1476    }
1477    if let crate::schema::EventPayload::RangeRequested { start, count } = event.payload {
1478        output.extend_from_slice(&EVENT_RANGE_REQUESTED.to_le_bytes());
1479        output.extend_from_slice(&24_u16.to_le_bytes());
1480        output.extend_from_slice(&event.node_id.to_le_bytes());
1481        output.extend_from_slice(&event.handler_id.to_le_bytes());
1482        output.extend_from_slice(&start.to_le_bytes());
1483        output.extend_from_slice(&count.to_le_bytes());
1484        return Ok(());
1485    }
1486    let (tag, record_len, text, error_code) = match &event.payload {
1487        crate::schema::EventPayload::Clicked => (EVENT_CLICK, 16_u16, None, None),
1488        crate::schema::EventPayload::TextChanged(value) => {
1489            (EVENT_TEXT_CHANGED, 24, Some(*value), None)
1490        }
1491        crate::schema::EventPayload::TextSubmitted(value) => {
1492            (EVENT_TEXT_SUBMITTED, 24, Some(*value), None)
1493        }
1494        crate::schema::EventPayload::FocusLost => (EVENT_FOCUS_LOST, 16, None, None),
1495        crate::schema::EventPayload::FocusGained => (EVENT_FOCUS_GAINED, 16, None, None),
1496        crate::schema::EventPayload::FilesEntered => (EVENT_FILES_ENTERED, 16, None, None),
1497        crate::schema::EventPayload::FilesDropped(paths) => {
1498            (EVENT_FILES_DROPPED, 24, Some(*paths), None)
1499        }
1500        crate::schema::EventPayload::StorageLocation(path) => {
1501            (EVENT_STORAGE_LOCATION, 24, Some(*path), None)
1502        }
1503        crate::schema::EventPayload::Resync => (EVENT_RESYNC, 16, None, None),
1504        crate::schema::EventPayload::WindowCloseRequested => {
1505            (EVENT_WINDOW_CLOSE_REQUESTED, 16, None, None)
1506        }
1507        crate::schema::EventPayload::WindowUnavailable => {
1508            (EVENT_WINDOW_UNAVAILABLE, 16, None, None)
1509        }
1510        crate::schema::EventPayload::WindowClosed => (EVENT_WINDOW_CLOSED, 16, None, None),
1511        crate::schema::EventPayload::ApplicationReopen => {
1512            (EVENT_APPLICATION_REOPEN, 16, None, None)
1513        }
1514        // The shape of a protocol error: one word, then one string.
1515        crate::schema::EventPayload::WindowRequested { key, request } => {
1516            (EVENT_WINDOW_REQUESTED, 28, Some(*key), Some(*request))
1517        }
1518        crate::schema::EventPayload::LifecycleStart => (EVENT_LIFECYCLE_START, 16, None, None),
1519        crate::schema::EventPayload::LifecycleStop => (EVENT_LIFECYCLE_STOP, 16, None, None),
1520        crate::schema::EventPayload::ProtocolError { code, message } => {
1521            (EVENT_PROTOCOL_ERROR, 28, Some(*message), Some(*code))
1522        }
1523        // The same shape as a protocol error: one word, then one string.
1524        crate::schema::EventPayload::NotificationActivated { action, key } => {
1525            (EVENT_NOTIFICATION_ACTIVATED, 28, Some(*key), Some(*action))
1526        }
1527        crate::schema::EventPayload::KeyDown { .. }
1528        | crate::schema::EventPayload::KeyUp { .. }
1529        | crate::schema::EventPayload::RangeRequested { .. }
1530        | crate::schema::EventPayload::ValueChanged(_)
1531        | crate::schema::EventPayload::WindowSizeChanged { .. }
1532        | crate::schema::EventPayload::DesignSystemResolved(_)
1533        | crate::schema::EventPayload::NotificationPermissionChanged(_)
1534        | crate::schema::EventPayload::CodeChanged { .. }
1535        | crate::schema::EventPayload::CodeEditRejected { .. }
1536        | crate::schema::EventPayload::CodeHovered { .. }
1537        | crate::schema::EventPayload::CodeSaveRequested { .. }
1538        | crate::schema::EventPayload::DecorationActivated { .. }
1539        | crate::schema::EventPayload::SelectionChanged { .. }
1540        | crate::schema::EventPayload::VisibleRangeChanged { .. }
1541        | crate::schema::EventPayload::EditorFocusChanged { .. }
1542        | crate::schema::EventPayload::FoldChanged { .. }
1543        | crate::schema::EventPayload::CompositionChanged { .. }
1544        | crate::schema::EventPayload::WindowFocusChanged { .. }
1545        | crate::schema::EventPayload::WindowMoved { .. }
1546        | crate::schema::EventPayload::WindowDrag { .. }
1547        | crate::schema::EventPayload::WindowDragOver { .. }
1548        | crate::schema::EventPayload::CodeMetrics { .. }
1549        | crate::schema::EventPayload::CodeScrolled { .. }
1550        | crate::schema::EventPayload::FileDialogResult { .. }
1551        | crate::schema::EventPayload::ClipboardText { .. }
1552        | crate::schema::EventPayload::IoAnswer { .. }
1553        | crate::schema::EventPayload::FileHandles { .. }
1554        | crate::schema::EventPayload::ScrollChanged { .. } => unreachable!(),
1555        crate::schema::EventPayload::AnimationEvent { .. }
1556        | crate::schema::EventPayload::Pointer { .. }
1557        | crate::schema::EventPayload::ReducedMotionChanged(_)
1558        | crate::schema::EventPayload::ZoomChanged { .. } => unreachable!(),
1559    };
1560    output.extend_from_slice(&tag.to_le_bytes());
1561    output.extend_from_slice(&record_len.to_le_bytes());
1562    output.extend_from_slice(&event.node_id.to_le_bytes());
1563    output.extend_from_slice(&event.handler_id.to_le_bytes());
1564    if let Some(code) = error_code {
1565        output.extend_from_slice(&code.to_le_bytes());
1566    }
1567    if let Some(text) = text {
1568        let offset = u32::from(record_len);
1569        let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1570        output.extend_from_slice(&offset.to_le_bytes());
1571        output.extend_from_slice(&len.to_le_bytes());
1572        output.extend_from_slice(text.as_bytes());
1573    }
1574    Ok(())
1575}
1576
1577/// The sixteen bytes every event starts with: the tag, the record's length, the node and
1578/// the handler.
1579fn put_event_header(output: &mut Vec<u8>, tag: u16, record_len: u16, event: &HostEvent<'_>) {
1580    output.extend_from_slice(&tag.to_le_bytes());
1581    output.extend_from_slice(&record_len.to_le_bytes());
1582    output.extend_from_slice(&event.node_id.to_le_bytes());
1583    output.extend_from_slice(&event.handler_id.to_le_bytes());
1584}
1585
1586impl fmt::Display for ProtocolError {
1587    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1588        write!(formatter, "protocol error: {self:?}")
1589    }
1590}
1591
1592impl std::error::Error for ProtocolError {}
1593
1594/// Reusable encoder. Once capacities are warmed, `clear` and `finish` allocate no memory.
1595pub struct BatchEncoder {
1596    records: Vec<u8>,
1597    strings: Vec<u8>,
1598    string_fixups: Vec<usize>,
1599    arena: Vec<u8>,
1600    record_count: u32,
1601}
1602
1603impl Default for BatchEncoder {
1604    fn default() -> Self {
1605        Self::with_capacity(4096, 1024, 64)
1606    }
1607}
1608
1609impl BatchEncoder {
1610    pub fn with_capacity(record_bytes: usize, string_bytes: usize, string_count: usize) -> Self {
1611        let mut encoder = Self {
1612            records: Vec::with_capacity(record_bytes),
1613            strings: Vec::with_capacity(string_bytes),
1614            string_fixups: Vec::with_capacity(string_count),
1615            arena: Vec::with_capacity(record_bytes + string_bytes),
1616            record_count: 0,
1617        };
1618        encoder.clear();
1619        encoder
1620    }
1621
1622    pub fn clear(&mut self) {
1623        self.records.clear();
1624        self.strings.clear();
1625        self.string_fixups.clear();
1626        self.arena.clear();
1627        self.record_count = 0;
1628        self.begin_record(TAG_ENVELOPE, 8);
1629        self.put_u32(0);
1630        self.put_u32(0);
1631    }
1632
1633    pub fn encode(&mut self, mutation: &Mutation<'_>) -> Result<(), ProtocolError> {
1634        match mutation {
1635            Mutation::Create { node_id, widget } => {
1636                self.begin_record(TAG_CREATE, 8);
1637                self.put_u32(*node_id);
1638                self.put_u16(*widget as u16);
1639                self.put_u16(0);
1640            }
1641            Mutation::SetProp {
1642                node_id,
1643                property,
1644                value,
1645            } => {
1646                self.begin_record(TAG_SET_PROP, 20);
1647                self.put_u32(*node_id);
1648                self.put_u16(*property as u16);
1649                match value {
1650                    PropertyValue::None => {
1651                        self.put_u16(VALUE_NONE);
1652                        self.put_u64(0);
1653                        self.put_u32(0);
1654                    }
1655                    PropertyValue::String(value) => {
1656                        self.put_u16(VALUE_STRING);
1657                        self.put_string_ref(value)?;
1658                        self.put_u32(0);
1659                    }
1660                    PropertyValue::Bool(value) => {
1661                        self.put_u16(VALUE_BOOL);
1662                        self.put_u64(u64::from(*value));
1663                        self.put_u32(0);
1664                    }
1665                    PropertyValue::Integer(value) => {
1666                        self.put_u16(VALUE_I64);
1667                        self.put_u64(*value as u64);
1668                        self.put_u32(0);
1669                    }
1670                    PropertyValue::Float(value) => {
1671                        self.put_u16(VALUE_F32);
1672                        self.put_u64(u64::from(value.to_bits()));
1673                        self.put_u32(0);
1674                    }
1675                    PropertyValue::Bytes(value) => {
1676                        self.put_u16(VALUE_BYTES);
1677                        self.put_bytes_ref(value)?;
1678                        self.put_u32(0);
1679                    }
1680                }
1681            }
1682            Mutation::SetModifier {
1683                node_id,
1684                index,
1685                modifier,
1686            } => {
1687                let (tag, words) = modifier_fields(modifier);
1688                // The tag fixes how long the value is, so the record is as long as the
1689                // schema says that tag's records are and every other modifier keeps the
1690                // 28 bytes it always had.
1691                let extra = modifier_extra_words(tag).ok_or(ProtocolError::InvalidModifier(tag))?;
1692                self.begin_record(TAG_SET_MODIFIER, 24 + 8 * u16::from(extra));
1693                self.put_u32(*node_id);
1694                self.put_u16(*index);
1695                self.put_u16(tag);
1696                for word in &words[..2 + usize::from(extra)] {
1697                    self.put_u64(*word);
1698                }
1699            }
1700            Mutation::Insert {
1701                parent_id,
1702                node_id,
1703                index,
1704            } => {
1705                self.begin_record(TAG_INSERT, 12);
1706                self.put_u32(*parent_id);
1707                self.put_u32(*node_id);
1708                self.put_u32(*index);
1709            }
1710            Mutation::Move {
1711                parent_id,
1712                node_id,
1713                index,
1714            } => {
1715                self.begin_record(TAG_MOVE, 12);
1716                self.put_u32(*parent_id);
1717                self.put_u32(*node_id);
1718                self.put_u32(*index);
1719            }
1720            Mutation::Remove { node_id } => {
1721                self.begin_record(TAG_REMOVE, 4);
1722                self.put_u32(*node_id);
1723            }
1724            Mutation::SetText {
1725                node_id,
1726                text,
1727                selection,
1728            } => {
1729                self.begin_record(TAG_SET_TEXT, 20);
1730                self.put_u32(*node_id);
1731                self.put_string_ref(text)?;
1732                let (start, end) =
1733                    selection.map_or((u32::MAX, u32::MAX), |value| (value.start, value.end));
1734                self.put_u32(start);
1735                self.put_u32(end);
1736            }
1737            Mutation::AppendText { node_id, text } => {
1738                self.begin_record(TAG_APPEND_TEXT, 12);
1739                self.put_u32(*node_id);
1740                self.put_string_ref(text)?;
1741            }
1742            Mutation::RegisterAsset {
1743                asset_id,
1744                kind,
1745                bytes,
1746            } => {
1747                self.begin_record(TAG_REGISTER_ASSET, 16);
1748                self.put_u32(*asset_id);
1749                self.put_u16(*kind as u16);
1750                self.put_u16(0);
1751                self.put_bytes_ref(bytes)?;
1752            }
1753            Mutation::ReleaseAsset { asset_id } => {
1754                self.begin_record(TAG_RELEASE_ASSET, 4);
1755                self.put_u32(*asset_id);
1756            }
1757            Mutation::ShowMessage {
1758                handler_id,
1759                text,
1760                action,
1761                duration,
1762            } => {
1763                self.begin_record(TAG_SHOW_MESSAGE, 28);
1764                self.put_u64(*handler_id);
1765                self.put_string_ref(text)?;
1766                self.put_string_ref(action)?;
1767                self.put_u16(*duration as u16);
1768                self.put_u16(0);
1769            }
1770            Mutation::EditCode {
1771                node_id,
1772                request_id,
1773                base_version,
1774                start_line,
1775                start_column,
1776                end_line,
1777                end_column,
1778                text,
1779            } => {
1780                self.begin_record(TAG_EDIT_CODE, 36);
1781                self.put_u32(*node_id);
1782                self.put_u32(*request_id);
1783                self.put_u32(*base_version);
1784                self.put_u32(*start_line);
1785                self.put_u32(*start_column);
1786                self.put_u32(*end_line);
1787                self.put_u32(*end_column);
1788                self.put_string_ref(text)?;
1789            }
1790            Mutation::SetSelection {
1791                node_id,
1792                base_version,
1793                anchor_line,
1794                anchor_column,
1795                active_line,
1796                active_column,
1797            } => {
1798                self.begin_record(TAG_SET_SELECTION, 24);
1799                self.put_u32(*node_id);
1800                self.put_u32(*base_version);
1801                self.put_u32(*anchor_line);
1802                self.put_u32(*anchor_column);
1803                self.put_u32(*active_line);
1804                self.put_u32(*active_column);
1805            }
1806            Mutation::RevealRange {
1807                node_id,
1808                base_version,
1809                start_line,
1810                start_column,
1811                end_line,
1812                end_column,
1813                align,
1814            } => {
1815                self.begin_record(TAG_REVEAL_RANGE, 28);
1816                self.put_u32(*node_id);
1817                self.put_u32(*base_version);
1818                self.put_u32(*start_line);
1819                self.put_u32(*start_column);
1820                self.put_u32(*end_line);
1821                self.put_u32(*end_column);
1822                self.put_u32(u32::from(*align as u16));
1823            }
1824            Mutation::SetFolded {
1825                node_id,
1826                start_line,
1827                collapsed,
1828            } => {
1829                self.begin_record(TAG_SET_FOLDED, 12);
1830                self.put_u32(*node_id);
1831                self.put_u32(*start_line);
1832                self.put_u32(u32::from(*collapsed));
1833            }
1834            Mutation::SetTheme(theme) => {
1835                self.begin_record(TAG_SET_THEME, THEME_PAYLOAD_LEN as u16);
1836                self.put_theme(theme)?;
1837            }
1838            Mutation::SetWindowTheme { node_id, theme } => {
1839                self.begin_record(TAG_SET_WINDOW_THEME, 4 + THEME_PAYLOAD_LEN as u16);
1840                self.put_u32(*node_id);
1841                self.put_theme(theme)?;
1842            }
1843            Mutation::FocusWindow { node_id } => {
1844                self.begin_record(TAG_FOCUS_WINDOW, 4);
1845                self.put_u32(*node_id);
1846            }
1847            Mutation::RequestFocus { node_id } => {
1848                self.begin_record(TAG_REQUEST_FOCUS, 4);
1849                self.put_u32(*node_id);
1850            }
1851            Mutation::SetScrollOffset { node_id, offset_px } => {
1852                self.begin_record(TAG_SET_SCROLL_OFFSET, 12);
1853                self.put_u32(*node_id);
1854                self.put_u64(offset_px.to_bits());
1855            }
1856            Mutation::ShowFileDialog {
1857                request,
1858                window,
1859                kind,
1860                default_filter,
1861                title,
1862                directory,
1863                name,
1864                filters,
1865            } => {
1866                self.begin_record(TAG_SHOW_FILE_DIALOG, SHOW_FILE_DIALOG_PAYLOAD_LEN as u16);
1867                self.put_u32(*request);
1868                self.put_u32(*window);
1869                self.put_u16(*kind as u16);
1870                self.put_u16(*default_filter);
1871                self.put_string_ref(title)?;
1872                self.put_string_ref(directory)?;
1873                self.put_string_ref(name)?;
1874                self.put_string_ref(filters)?;
1875            }
1876            Mutation::SetOverlayTokens(overlay) => {
1877                self.begin_record(TAG_SET_OVERLAY_TOKENS, OVERLAY_TOKENS_PAYLOAD_LEN as u16);
1878                self.put_u16(overlay.sheet_corner_radius);
1879                self.put_u16(overlay.dialog_corner_radius);
1880                self.put_u16(overlay.popup_corner_radius);
1881                self.put_u16(overlay.sheet_elevation);
1882                self.put_u16(overlay.popup_elevation);
1883                self.put_u16(overlay.dialog_elevation);
1884                self.put_u16(overlay.sheet_padding);
1885                self.put_u16(overlay.dialog_padding);
1886                self.put_u16(overlay.popup_padding);
1887                self.put_u16(0);
1888                self.put_u32(overlay.scrim);
1889            }
1890            Mutation::SetClipboardText { text } => {
1891                self.begin_record(TAG_SET_CLIPBOARD_TEXT, 8);
1892                self.put_string_ref(text)?;
1893            }
1894            Mutation::ReadClipboard { request } => {
1895                self.begin_record(TAG_READ_CLIPBOARD, 4);
1896                self.put_u32(*request);
1897            }
1898            Mutation::StorageCall {
1899                request,
1900                op,
1901                store,
1902                key,
1903                data,
1904            } => {
1905                self.begin_record(TAG_STORAGE_CALL, STORAGE_CALL_PAYLOAD_LEN as u16);
1906                self.put_u32(*request);
1907                self.put_u16(u16::from(*op));
1908                self.put_u16(0);
1909                self.put_string_ref(store)?;
1910                self.put_string_ref(key)?;
1911                self.put_bytes_ref(data)?;
1912            }
1913            Mutation::FileCall {
1914                request,
1915                op,
1916                create,
1917                handle,
1918                name,
1919                data,
1920            } => {
1921                self.begin_record(TAG_FILE_CALL, FILE_CALL_PAYLOAD_LEN as u16);
1922                self.put_u32(*request);
1923                self.put_u16(*op as u16);
1924                self.put_u16(u16::from(*create));
1925                self.put_u32(*handle);
1926                self.put_string_ref(name)?;
1927                self.put_bytes_ref(data)?;
1928            }
1929            Mutation::ReleaseFileHandle { handle } => {
1930                self.begin_record(TAG_RELEASE_FILE_HANDLE, 4);
1931                self.put_u32(*handle);
1932            }
1933            Mutation::ScrollTo {
1934                node_id,
1935                item,
1936                flags,
1937                x_px,
1938                y_px,
1939            } => {
1940                self.begin_record(TAG_SCROLL_TO, (SCROLL_TO_LEN - 4) as u16);
1941                self.put_u32(*node_id);
1942                self.put_u32(*item);
1943                self.put_u32(*flags);
1944                self.put_u64(x_px.to_bits());
1945                self.put_u64(y_px.to_bits());
1946            }
1947            Mutation::ScrollIntoView {
1948                node_id,
1949                target,
1950                target_kind,
1951                align_x,
1952                align_y,
1953                flags,
1954                rect,
1955            } => {
1956                self.begin_record(TAG_SCROLL_INTO_VIEW, (SCROLL_INTO_VIEW_LEN - 4) as u16);
1957                self.put_u32(*node_id);
1958                self.put_u32(*target);
1959                self.put_u16(*target_kind);
1960                self.put_u16(*align_x);
1961                self.put_u16(*align_y);
1962                self.put_u16(*flags);
1963                self.put_u64(rect.x_px.to_bits());
1964                self.put_u64(rect.y_px.to_bits());
1965                self.put_u64(rect.width_px.to_bits());
1966                self.put_u64(rect.height_px.to_bits());
1967            }
1968            Mutation::SetWindow(window) => {
1969                self.begin_record(TAG_SET_WINDOW, 28);
1970                self.put_u16(window.chrome as u16);
1971                self.put_u16(window.title_bar as u16);
1972                self.put_u16(window.width);
1973                self.put_u16(window.height);
1974                self.put_u16(window.min_width);
1975                self.put_u16(window.min_height);
1976                // Bit 0 is `resizable` and bit 1 asks for every whole-dp size change. The
1977                // mode and the last window policy ride in the bits above them that were
1978                // always zero, so a window that asks for none writes the bytes it always
1979                // wrote.
1980                self.put_u16(
1981                    u16::from(window.resizable)
1982                        | (u16::from(window.report_size) << 1)
1983                        | ((window.mode as u16) << WINDOW_MODE_SHIFT)
1984                        | ((window.last_window_closed as u16) << WINDOW_POLICY_SHIFT),
1985                );
1986                // The application's zoom level, in what used to be the padding that keeps
1987                // the record a multiple of four: a byte saying whether there is one, then
1988                // the level as a signed byte. Zero is "none", so a window that asks for
1989                // nothing reads exactly as it did before the level existed.
1990                self.put_u16(match window.zoom_level {
1991                    None => 0,
1992                    Some(level) => 1 | (u16::from(crate::zoom::clamp_level(level) as u8) << 8),
1993                });
1994                self.put_string_ref(window.title)?;
1995                self.put_u32(window.icon);
1996            }
1997            Mutation::PostNotification {
1998                key,
1999                title,
2000                body,
2001                channel,
2002                action_1,
2003                action_2,
2004                importance,
2005                presentation,
2006            } => {
2007                self.begin_record(TAG_POST_NOTIFICATION, 52);
2008                self.put_string_ref(key)?;
2009                self.put_string_ref(title)?;
2010                self.put_string_ref(body)?;
2011                self.put_string_ref(channel)?;
2012                self.put_string_ref(action_1)?;
2013                self.put_string_ref(action_2)?;
2014                self.put_u16(*importance as u16);
2015                self.put_u16(*presentation as u16);
2016            }
2017            Mutation::WithdrawNotification { key } => {
2018                self.begin_record(TAG_WITHDRAW_NOTIFICATION, 8);
2019                self.put_string_ref(key)?;
2020            }
2021            Mutation::RequestNotificationPermission => {
2022                self.begin_record(TAG_REQUEST_NOTIFICATION_PERMISSION, 0);
2023            }
2024            Mutation::StartAnimation(animation) => self.put_animation(animation)?,
2025            Mutation::ControlAnimation {
2026                node_id,
2027                animation_id,
2028                property,
2029                slot,
2030                op,
2031                at_time_nanos,
2032            } => {
2033                self.begin_record(TAG_CONTROL_ANIMATION, 20);
2034                self.put_u32(*node_id);
2035                self.put_u32(*animation_id);
2036                self.records.push(*property as u8);
2037                self.records.push(*slot);
2038                self.records.push(*op as u8);
2039                self.records.push(0);
2040                self.put_u64(*at_time_nanos);
2041            }
2042        }
2043        self.record_count = self
2044            .record_count
2045            .checked_add(1)
2046            .ok_or(ProtocolError::LengthOverflow)?;
2047        Ok(())
2048    }
2049
2050    pub fn finish(&mut self) -> Result<&[u8], ProtocolError> {
2051        let records_len =
2052            u32::try_from(self.records.len()).map_err(|_| ProtocolError::LengthOverflow)?;
2053        self.records[4..8].copy_from_slice(&records_len.to_le_bytes());
2054        self.records[8..12].copy_from_slice(&self.record_count.to_le_bytes());
2055        let total = self
2056            .records
2057            .len()
2058            .checked_add(self.strings.len())
2059            .ok_or(ProtocolError::LengthOverflow)?;
2060        self.arena.clear();
2061        if self.arena.capacity() < total {
2062            self.arena.reserve(total);
2063        }
2064        self.arena.extend_from_slice(&self.records);
2065        self.arena.extend_from_slice(&self.strings);
2066        for &position in &self.string_fixups {
2067            let relative = read_u32(&self.arena, position)?;
2068            let absolute = records_len
2069                .checked_add(relative)
2070                .ok_or(ProtocolError::LengthOverflow)?;
2071            self.arena[position..position + 4].copy_from_slice(&absolute.to_le_bytes());
2072        }
2073        Ok(&self.arena)
2074    }
2075
2076    /// Where the arena a finished batch lives in starts, and how much of it is mapped.
2077    ///
2078    /// A runtime that cannot read the Host's memory directly wraps this range once and
2079    /// keeps the view: every batch is a prefix of the arena, so it is read where it lies
2080    /// and no byte is copied out. Growing the arena moves it, which is why the address is
2081    /// reported again on every call rather than once at startup.
2082    pub fn arena(&self) -> (*const u8, usize) {
2083        (self.arena.as_ptr(), self.arena.capacity())
2084    }
2085
2086    pub fn capacity(&self) -> usize {
2087        self.records.capacity() + self.strings.capacity() + self.arena.capacity()
2088    }
2089
2090    /// Writes a `StartAnimation`: the 52 byte head, then each keyframe with its value.
2091    ///
2092    /// The bytes are written as they are given; whether they make an animation is the
2093    /// decoder's question, asked on both sides, so a Host that sends a bad one hears about
2094    /// it as a protocol error rather than not at all.
2095    fn put_animation(
2096        &mut self,
2097        animation: &crate::schema::Animation<'_>,
2098    ) -> Result<(), ProtocolError> {
2099        use crate::schema::{KeyframeValue, Timing};
2100        let keyframes_len: usize = animation
2101            .keyframes
2102            .iter()
2103            .map(|keyframe| {
2104                KEYFRAME_HEAD_LEN
2105                    + match &keyframe.value {
2106                        KeyframeValue::Alpha(_) => 4,
2107                        KeyframeValue::Paint(_) => 8,
2108                        KeyframeValue::Transform(functions) => {
2109                            4 + TRANSFORM_FUNCTION_LEN * functions.len()
2110                        }
2111                    }
2112            })
2113            .sum();
2114        let payload = u16::try_from(ANIMATION_HEADER_LEN - 4 + keyframes_len)
2115            .ok()
2116            .filter(|payload| *payload <= u16::MAX - 4)
2117            .ok_or(ProtocolError::LengthOverflow)?;
2118        let keyframe_count =
2119            u16::try_from(animation.keyframes.len()).map_err(|_| ProtocolError::LengthOverflow)?;
2120        self.begin_record(TAG_START_ANIMATION, payload);
2121        self.put_u32(animation.node_id);
2122        self.put_u32(animation.animation_id);
2123        self.records.push(animation.property as u8);
2124        self.records.push(animation.slot);
2125        self.records.push(animation.direction as u8);
2126        self.records.push(animation.fill as u8);
2127        self.records.push(animation.play_state as u8);
2128        self.records
2129            .push(animation.interpolation.map_or(0, |value| value as u8));
2130        self.put_u16(keyframe_count);
2131        self.put_u64(animation.start_time_nanos);
2132        self.put_u32(animation.delay_ms.to_bits());
2133        self.put_u32(animation.duration_ms.to_bits());
2134        self.put_u32(animation.iterations.to_bits());
2135        self.put_u32(animation.origin_x.to_bits());
2136        self.put_u32(animation.origin_y.to_bits());
2137        self.records.push(animation.events.0);
2138        self.records.extend_from_slice(&[0, 0, 0]);
2139        for keyframe in animation.keyframes.iter() {
2140            self.put_u32(keyframe.offset.to_bits());
2141            let (kind, position, numbers) = match keyframe.timing {
2142                Timing::Linear => (1_u8, 0_u8, [0_u32; 4]),
2143                Timing::CubicBezier { x1, y1, x2, y2 } => (
2144                    2,
2145                    0,
2146                    [x1.to_bits(), y1.to_bits(), x2.to_bits(), y2.to_bits()],
2147                ),
2148                Timing::Steps { count, position } => (3, position as u8, [count, 0, 0, 0]),
2149            };
2150            self.records.push(kind);
2151            self.records.push(position);
2152            self.put_u16(if keyframe.from_presented {
2153                KEYFRAME_FROM_PRESENTED
2154            } else {
2155                0
2156            });
2157            for number in numbers {
2158                self.put_u32(number);
2159            }
2160            match &keyframe.value {
2161                KeyframeValue::Alpha(value) => self.put_u32(value.to_bits()),
2162                KeyframeValue::Paint(paint) => self.put_u64(paint.to_bits()),
2163                KeyframeValue::Transform(functions) => {
2164                    let count = u16::try_from(functions.len())
2165                        .map_err(|_| ProtocolError::LengthOverflow)?;
2166                    self.put_u16(count);
2167                    self.put_u16(0);
2168                    for function in functions.iter() {
2169                        self.put_u32(u32::from(u16::from(function.kind)));
2170                        for value in function.values {
2171                            self.put_u32(value.to_bits());
2172                        }
2173                    }
2174                }
2175            }
2176        }
2177        Ok(())
2178    }
2179
2180    /// The body of a `SetTheme`, which `SetWindowTheme` carries after its node id.
2181    fn put_theme(&mut self, theme: &Theme) -> Result<(), ProtocolError> {
2182        self.put_u16(theme.design_system as u16);
2183        self.put_u16(theme.fallback as u16);
2184        self.put_u16(theme.color_scheme as u16);
2185        self.put_u16(u16::from(theme.adaptive));
2186        // One slot per type role, zero where the role keeps the system font. Sent
2187        // whether or not any of them is filled, because the record is fixed
2188        // layout and a theme change is one record rather than a per frame cost.
2189        for asset in theme.fonts {
2190            self.put_u32(asset);
2191        }
2192        // The application's colours, as a run of eight byte entries behind the
2193        // records the way a string is. Zero long where there is no palette, so a
2194        // theme without one costs the eight bytes of the reference and nothing
2195        // else.
2196        self.put_palette_ref(theme.palette)
2197    }
2198
2199    fn begin_record(&mut self, tag: u16, payload_len: u16) {
2200        self.put_u16(tag);
2201        self.put_u16(payload_len + 4);
2202    }
2203
2204    /// Writes a palette's entries straight into the trailing region, so encoding a theme
2205    /// allocates nothing beyond what that region already has.
2206    fn put_palette_ref(
2207        &mut self,
2208        palette: Option<&crate::palette::Palette>,
2209    ) -> Result<(), ProtocolError> {
2210        let start = self.strings.len();
2211        let offset = u32::try_from(start).map_err(|_| ProtocolError::LengthOverflow)?;
2212        if let Some(palette) = palette {
2213            palette.write_into(&mut self.strings);
2214        }
2215        let len =
2216            u32::try_from(self.strings.len() - start).map_err(|_| ProtocolError::LengthOverflow)?;
2217        self.string_fixups.push(self.records.len());
2218        self.put_u32(offset);
2219        self.put_u32(len);
2220        Ok(())
2221    }
2222
2223    fn put_string_ref(&mut self, value: &str) -> Result<(), ProtocolError> {
2224        self.put_bytes_ref(value.as_bytes())
2225    }
2226
2227    /// Asset bytes ride in the same trailing region as strings, with the same
2228    /// `(offset, len)` reference and the same fixup, so the record stays fixed layout.
2229    fn put_bytes_ref(&mut self, value: &[u8]) -> Result<(), ProtocolError> {
2230        let offset =
2231            u32::try_from(self.strings.len()).map_err(|_| ProtocolError::LengthOverflow)?;
2232        let len = u32::try_from(value.len()).map_err(|_| ProtocolError::LengthOverflow)?;
2233        self.string_fixups.push(self.records.len());
2234        self.put_u32(offset);
2235        self.put_u32(len);
2236        self.strings.extend_from_slice(value);
2237        Ok(())
2238    }
2239
2240    fn put_u16(&mut self, value: u16) {
2241        self.records.extend_from_slice(&value.to_le_bytes());
2242    }
2243
2244    fn put_u32(&mut self, value: u32) {
2245        self.records.extend_from_slice(&value.to_le_bytes());
2246    }
2247
2248    fn put_u64(&mut self, value: u64) {
2249        self.records.extend_from_slice(&value.to_le_bytes());
2250    }
2251}
2252
2253/// Reads the body of a `SetTheme` that starts at `payload`, which `SetWindowTheme` also
2254/// carries, after its node id.
2255fn decode_theme(bytes: &[u8], payload: usize, records_len: usize) -> Result<Theme, ProtocolError> {
2256    let design_system = read_u16(bytes, payload)?;
2257    let fallback = read_u16(bytes, payload + 2)?;
2258    let color_scheme = read_u16(bytes, payload + 4)?;
2259    let adaptive = read_u16(bytes, payload + 6)?;
2260    if adaptive > 1 {
2261        return Err(ProtocolError::InvalidTheme(adaptive));
2262    }
2263    let mut fonts = [0u32; TYPE_ROLE_COUNT];
2264    for (index, slot) in fonts.iter_mut().enumerate() {
2265        *slot = read_u32(bytes, payload + 8 + 4 * index)?;
2266    }
2267    // Entries that are wrong are left out and the rest stand, which is what
2268    // the Renderer does with them as well. Leaked like the window's title: a
2269    // theme holds its palette for the life of the program, and this decoder
2270    // is what tests and the mock renderer read a batch back with.
2271    let palette_bytes = read_bytes(bytes, payload + 8 + 4 * TYPE_ROLE_COUNT, records_len)?;
2272    let palette = if palette_bytes.is_empty() {
2273        None
2274    } else {
2275        let (palette, _problems) = crate::palette::decode_palette(palette_bytes);
2276        Some(&*Box::leak(Box::new(palette)))
2277    };
2278    Ok(Theme {
2279        design_system: DesignSystem::try_from(design_system)
2280            .map_err(|()| ProtocolError::InvalidTheme(design_system))?,
2281        fallback: DesignSystem::try_from(fallback)
2282            .map_err(|()| ProtocolError::InvalidTheme(fallback))?,
2283        color_scheme: ColorScheme::try_from(color_scheme)
2284            .map_err(|()| ProtocolError::InvalidTheme(color_scheme))?,
2285        adaptive: adaptive == 1,
2286        fonts,
2287        palette,
2288        overlay: None,
2289    })
2290}
2291
2292pub fn decode_batch(bytes: &[u8]) -> Result<Vec<Mutation<'_>>, ProtocolError> {
2293    if bytes.len() < ENVELOPE_LEN
2294        || read_u16(bytes, 0)? != TAG_ENVELOPE
2295        || read_u16(bytes, 2)? != 12
2296    {
2297        return Err(ProtocolError::InvalidEnvelope);
2298    }
2299    let records_len =
2300        usize::try_from(read_u32(bytes, 4)?).map_err(|_| ProtocolError::LengthOverflow)?;
2301    let count = usize::try_from(read_u32(bytes, 8)?).map_err(|_| ProtocolError::LengthOverflow)?;
2302    if records_len < ENVELOPE_LEN || records_len > bytes.len() || records_len % 4 != 0 {
2303        return Err(ProtocolError::InvalidEnvelope);
2304    }
2305    // `count` is attacker-controlled, so it only sizes the buffer up to what the
2306    // record region could actually hold. The shortest mutation record is a bare header at
2307    // 4 bytes, so that is the ceiling. Reserving `count` directly let a 12-byte message ask
2308    // for a >100 GB allocation.
2309    let capacity = count.min((records_len - ENVELOPE_LEN) / MIN_RECORD_LEN);
2310    let mut output = Vec::with_capacity(capacity);
2311    let mut position = ENVELOPE_LEN;
2312    while position < records_len {
2313        let tag = read_u16(bytes, position)?;
2314        let len = usize::from(read_u16(bytes, position + 2)?);
2315        if len < 4 || len % 4 != 0 || position + len > records_len {
2316            return Err(ProtocolError::InvalidRecordLength);
2317        }
2318        let payload = position + 4;
2319        let mutation = match tag {
2320            TAG_CREATE if len == 12 => Mutation::Create {
2321                node_id: read_u32(bytes, payload)?,
2322                widget: WidgetKind::try_from(read_u16(bytes, payload + 4)?).map_err(|()| {
2323                    ProtocolError::InvalidWidget(read_u16(bytes, payload + 4).unwrap_or(0))
2324                })?,
2325            },
2326            TAG_SET_PROP if len == 24 => {
2327                let property_raw = read_u16(bytes, payload + 4)?;
2328                let value_kind = read_u16(bytes, payload + 6)?;
2329                let value = match value_kind {
2330                    VALUE_NONE => PropertyValue::None,
2331                    VALUE_STRING => {
2332                        PropertyValue::String(read_string(bytes, payload + 8, records_len)?)
2333                    }
2334                    VALUE_BOOL => PropertyValue::Bool(read_u64(bytes, payload + 8)? != 0),
2335                    VALUE_I64 => PropertyValue::Integer(read_u64(bytes, payload + 8)? as i64),
2336                    VALUE_F32 => {
2337                        PropertyValue::Float(f32::from_bits(read_u64(bytes, payload + 8)? as u32))
2338                    }
2339                    VALUE_BYTES => {
2340                        PropertyValue::Bytes(read_bytes(bytes, payload + 8, records_len)?)
2341                    }
2342                    other => return Err(ProtocolError::InvalidValueKind(other)),
2343                };
2344                Mutation::SetProp {
2345                    node_id: read_u32(bytes, payload)?,
2346                    property: PropertyKind::try_from(property_raw)
2347                        .map_err(|()| ProtocolError::InvalidProperty(property_raw))?,
2348                    value,
2349                }
2350            }
2351            TAG_SET_MODIFIER if len >= 28 => {
2352                // The modifier's tag says how long its record is: 28 bytes and eight more
2353                // for each word the schema gives it beyond the first two. A record of any
2354                // other length is refused before its value is read.
2355                let modifier_tag = read_u16(bytes, payload + 6)?;
2356                let extra = usize::from(
2357                    modifier_extra_words(modifier_tag)
2358                        .ok_or(ProtocolError::InvalidModifier(modifier_tag))?,
2359                );
2360                if len != 28 + 8 * extra {
2361                    return Err(ProtocolError::InvalidRecordLength);
2362                }
2363                let mut words = [0_u64; MAX_MODIFIER_WORDS];
2364                for (word_index, word) in words.iter_mut().take(2 + extra).enumerate() {
2365                    *word = read_u64(bytes, payload + 8 + 8 * word_index)?;
2366                }
2367                Mutation::SetModifier {
2368                    node_id: read_u32(bytes, payload)?,
2369                    index: read_u16(bytes, payload + 4)?,
2370                    modifier: decode_modifier(modifier_tag, &words)?,
2371                }
2372            }
2373            TAG_INSERT if len == 16 => Mutation::Insert {
2374                parent_id: read_u32(bytes, payload)?,
2375                node_id: read_u32(bytes, payload + 4)?,
2376                index: read_u32(bytes, payload + 8)?,
2377            },
2378            TAG_MOVE if len == 16 => Mutation::Move {
2379                parent_id: read_u32(bytes, payload)?,
2380                node_id: read_u32(bytes, payload + 4)?,
2381                index: read_u32(bytes, payload + 8)?,
2382            },
2383            TAG_REMOVE if len == 8 => Mutation::Remove {
2384                node_id: read_u32(bytes, payload)?,
2385            },
2386            TAG_SET_TEXT if len == 24 => {
2387                let start = read_u32(bytes, payload + 12)?;
2388                let end = read_u32(bytes, payload + 16)?;
2389                Mutation::SetText {
2390                    node_id: read_u32(bytes, payload)?,
2391                    text: read_string(bytes, payload + 4, records_len)?,
2392                    selection: (start != u32::MAX || end != u32::MAX)
2393                        .then_some(Selection { start, end }),
2394                }
2395            }
2396            TAG_APPEND_TEXT if len == 16 => Mutation::AppendText {
2397                node_id: read_u32(bytes, payload)?,
2398                text: read_string(bytes, payload + 4, records_len)?,
2399            },
2400            TAG_SET_THEME if len == 4 + THEME_PAYLOAD_LEN => {
2401                Mutation::SetTheme(decode_theme(bytes, payload, records_len)?)
2402            }
2403            TAG_SET_WINDOW_THEME if len == 8 + THEME_PAYLOAD_LEN => Mutation::SetWindowTheme {
2404                node_id: read_u32(bytes, payload)?,
2405                theme: decode_theme(bytes, payload + 4, records_len)?,
2406            },
2407            TAG_FOCUS_WINDOW if len == 8 => Mutation::FocusWindow {
2408                node_id: read_u32(bytes, payload)?,
2409            },
2410            TAG_REQUEST_FOCUS if len == 8 => Mutation::RequestFocus {
2411                node_id: read_u32(bytes, payload)?,
2412            },
2413            TAG_SHOW_FILE_DIALOG if len == 4 + SHOW_FILE_DIALOG_PAYLOAD_LEN => {
2414                let raw_kind = read_u16(bytes, payload + 8)?;
2415                Mutation::ShowFileDialog {
2416                    request: read_u32(bytes, payload)?,
2417                    window: read_u32(bytes, payload + 4)?,
2418                    kind: FileDialogKind::try_from(raw_kind)
2419                        .map_err(|()| ProtocolError::InvalidValueKind(raw_kind))?,
2420                    default_filter: read_u16(bytes, payload + 10)?,
2421                    title: read_string(bytes, payload + 12, records_len)?,
2422                    directory: read_string(bytes, payload + 20, records_len)?,
2423                    name: read_string(bytes, payload + 28, records_len)?,
2424                    filters: read_string(bytes, payload + 36, records_len)?,
2425                }
2426            }
2427            TAG_SHOW_FILE_DIALOG => return Err(ProtocolError::InvalidRecordLength),
2428            TAG_SET_OVERLAY_TOKENS if len == 4 + OVERLAY_TOKENS_PAYLOAD_LEN => {
2429                let sheet_corner_radius = read_u16(bytes, payload)?;
2430                let dialog_corner_radius = read_u16(bytes, payload + 2)?;
2431                let popup_corner_radius = read_u16(bytes, payload + 4)?;
2432                let sheet_elevation = read_u16(bytes, payload + 6)?;
2433                let popup_elevation = read_u16(bytes, payload + 8)?;
2434                let dialog_elevation = read_u16(bytes, payload + 10)?;
2435                let sheet_padding = read_u16(bytes, payload + 12)?;
2436                let dialog_padding = read_u16(bytes, payload + 14)?;
2437                let popup_padding = read_u16(bytes, payload + 16)?;
2438                let scrim = read_u32(bytes, payload + 20)?;
2439                Mutation::SetOverlayTokens(crate::schema::OverlayTokens {
2440                    sheet_corner_radius,
2441                    dialog_corner_radius,
2442                    popup_corner_radius,
2443                    sheet_elevation,
2444                    popup_elevation,
2445                    dialog_elevation,
2446                    sheet_padding,
2447                    dialog_padding,
2448                    popup_padding,
2449                    scrim,
2450                })
2451            }
2452            TAG_SET_OVERLAY_TOKENS => return Err(ProtocolError::InvalidRecordLength),
2453            TAG_SET_CLIPBOARD_TEXT if len == 12 => Mutation::SetClipboardText {
2454                text: read_string(bytes, payload, records_len)?,
2455            },
2456            TAG_READ_CLIPBOARD if len == 8 => Mutation::ReadClipboard {
2457                request: read_u32(bytes, payload)?,
2458            },
2459            TAG_STORAGE_CALL if len == 4 + STORAGE_CALL_PAYLOAD_LEN => {
2460                let raw_op = read_u16(bytes, payload + 4)?;
2461                Mutation::StorageCall {
2462                    request: read_u32(bytes, payload)?,
2463                    op: crate::schema::StorageOp::try_from(raw_op)
2464                        .map_err(|()| ProtocolError::InvalidValueKind(raw_op))?,
2465                    store: read_string(bytes, payload + 8, records_len)?,
2466                    key: read_string(bytes, payload + 16, records_len)?,
2467                    data: read_bytes(bytes, payload + 24, records_len)?,
2468                }
2469            }
2470            TAG_FILE_CALL if len == 4 + FILE_CALL_PAYLOAD_LEN => {
2471                let raw_op = read_u16(bytes, payload + 4)?;
2472                let raw_create = read_u16(bytes, payload + 6)?;
2473                if raw_create > 1 {
2474                    return Err(ProtocolError::InvalidValueKind(raw_create));
2475                }
2476                Mutation::FileCall {
2477                    request: read_u32(bytes, payload)?,
2478                    op: FileOp::try_from(raw_op)
2479                        .map_err(|()| ProtocolError::InvalidValueKind(raw_op))?,
2480                    create: raw_create == 1,
2481                    handle: read_u32(bytes, payload + 8)?,
2482                    name: read_string(bytes, payload + 12, records_len)?,
2483                    data: read_bytes(bytes, payload + 20, records_len)?,
2484                }
2485            }
2486            TAG_RELEASE_FILE_HANDLE if len == 8 => Mutation::ReleaseFileHandle {
2487                handle: read_u32(bytes, payload)?,
2488            },
2489            TAG_SCROLL_TO if len == SCROLL_TO_LEN => Mutation::ScrollTo {
2490                node_id: read_u32(bytes, payload)?,
2491                item: read_u32(bytes, payload + 4)?,
2492                flags: read_u32(bytes, payload + 8)?,
2493                x_px: f64::from_bits(read_u64(bytes, payload + 12)?),
2494                y_px: f64::from_bits(read_u64(bytes, payload + 20)?),
2495            },
2496            TAG_SCROLL_INTO_VIEW if len == SCROLL_INTO_VIEW_LEN => Mutation::ScrollIntoView {
2497                node_id: read_u32(bytes, payload)?,
2498                target: read_u32(bytes, payload + 4)?,
2499                target_kind: read_u16(bytes, payload + 8)?,
2500                align_x: read_u16(bytes, payload + 10)?,
2501                align_y: read_u16(bytes, payload + 12)?,
2502                flags: read_u16(bytes, payload + 14)?,
2503                rect: crate::schema::Rect {
2504                    x_px: f64::from_bits(read_u64(bytes, payload + 16)?),
2505                    y_px: f64::from_bits(read_u64(bytes, payload + 24)?),
2506                    width_px: f64::from_bits(read_u64(bytes, payload + 32)?),
2507                    height_px: f64::from_bits(read_u64(bytes, payload + 40)?),
2508                },
2509            },
2510            TAG_SET_WINDOW if len == 32 => {
2511                let chrome = read_u16(bytes, payload)?;
2512                let title_bar = read_u16(bytes, payload + 2)?;
2513                // Bit 0 is `resizable`; the mode and the last window policy are the
2514                // nibbles above the second byte. Anything else standing is a record the
2515                // sides disagree on.
2516                let flags = read_u16(bytes, payload + 12)?;
2517                if flags & 0x00FC != 0 {
2518                    return Err(ProtocolError::InvalidValueKind(flags));
2519                }
2520                let resizable = flags & 1;
2521                let report_size = flags & 0x0002 != 0;
2522                let mode_tag = (flags >> WINDOW_MODE_SHIFT) & 0xF;
2523                let policy_tag = flags >> WINDOW_POLICY_SHIFT;
2524                let zoom = read_u16(bytes, payload + 14)?;
2525                let zoom_level = match zoom & 0xFF {
2526                    0 => None,
2527                    1 => {
2528                        let level = (zoom >> 8) as u8 as i8;
2529                        if !(crate::zoom::MIN_ZOOM_LEVEL..=crate::zoom::MAX_ZOOM_LEVEL)
2530                            .contains(&level)
2531                        {
2532                            return Err(ProtocolError::InvalidValueKind(zoom));
2533                        }
2534                        Some(level)
2535                    }
2536                    _ => return Err(ProtocolError::InvalidValueKind(zoom)),
2537                };
2538                Mutation::SetWindow(crate::schema::Window {
2539                    chrome: crate::schema::Chrome::try_from(chrome)
2540                        .map_err(|()| ProtocolError::InvalidValueKind(chrome))?,
2541                    title_bar: crate::schema::TitleBar::try_from(title_bar)
2542                        .map_err(|()| ProtocolError::InvalidValueKind(title_bar))?,
2543                    width: read_u16(bytes, payload + 4)?,
2544                    height: read_u16(bytes, payload + 6)?,
2545                    min_width: read_u16(bytes, payload + 8)?,
2546                    min_height: read_u16(bytes, payload + 10)?,
2547                    resizable: resizable == 1,
2548                    zoom_level,
2549                    report_size,
2550                    mode: crate::schema::WindowMode::try_from(mode_tag)
2551                        .map_err(|()| ProtocolError::InvalidValueKind(flags))?,
2552                    last_window_closed: crate::schema::LastWindowClosed::try_from(policy_tag)
2553                        .map_err(|()| ProtocolError::InvalidValueKind(flags))?,
2554                    // Leaked on purpose, once per window. The record's owner is the
2555                    // window, the window outlives the batch it arrived in, and there is
2556                    // one of these per process.
2557                    icon: read_u32(bytes, payload + 24)?,
2558                    title: Box::leak(
2559                        read_string(bytes, payload + 16, records_len)?
2560                            .to_owned()
2561                            .into_boxed_str(),
2562                    ),
2563                })
2564            }
2565            TAG_REGISTER_ASSET if len == 20 => {
2566                let raw_kind = read_u16(bytes, payload + 4)?;
2567                Mutation::RegisterAsset {
2568                    asset_id: read_u32(bytes, payload)?,
2569                    kind: AssetKind::try_from(raw_kind)
2570                        .map_err(|()| ProtocolError::InvalidAssetKind(raw_kind))?,
2571                    bytes: read_bytes(bytes, payload + 8, records_len)?,
2572                }
2573            }
2574            TAG_RELEASE_ASSET if len == 8 => Mutation::ReleaseAsset {
2575                asset_id: read_u32(bytes, payload)?,
2576            },
2577            TAG_SHOW_MESSAGE if len == 32 => {
2578                let raw_duration = read_u16(bytes, payload + 24)?;
2579                Mutation::ShowMessage {
2580                    handler_id: read_u64(bytes, payload)?,
2581                    text: read_string(bytes, payload + 8, records_len)?,
2582                    action: read_string(bytes, payload + 16, records_len)?,
2583                    duration: MessageDuration::try_from(raw_duration)
2584                        .map_err(|()| ProtocolError::InvalidMessageDuration(raw_duration))?,
2585                }
2586            }
2587            TAG_EDIT_CODE if len == 40 => Mutation::EditCode {
2588                node_id: read_u32(bytes, payload)?,
2589                request_id: read_u32(bytes, payload + 4)?,
2590                base_version: read_u32(bytes, payload + 8)?,
2591                start_line: read_u32(bytes, payload + 12)?,
2592                start_column: read_u32(bytes, payload + 16)?,
2593                end_line: read_u32(bytes, payload + 20)?,
2594                end_column: read_u32(bytes, payload + 24)?,
2595                text: read_string(bytes, payload + 28, records_len)?,
2596            },
2597            TAG_POST_NOTIFICATION if len == 56 => {
2598                let raw_importance = read_u16(bytes, payload + 48)?;
2599                let raw_presentation = read_u16(bytes, payload + 50)?;
2600                Mutation::PostNotification {
2601                    key: read_string(bytes, payload, records_len)?,
2602                    title: read_string(bytes, payload + 8, records_len)?,
2603                    body: read_string(bytes, payload + 16, records_len)?,
2604                    channel: read_string(bytes, payload + 24, records_len)?,
2605                    action_1: read_string(bytes, payload + 32, records_len)?,
2606                    action_2: read_string(bytes, payload + 40, records_len)?,
2607                    importance: NotificationImportance::try_from(raw_importance)
2608                        .map_err(|()| ProtocolError::InvalidNotification(raw_importance))?,
2609                    presentation: NotificationPresentation::try_from(raw_presentation)
2610                        .map_err(|()| ProtocolError::InvalidNotification(raw_presentation))?,
2611                }
2612            }
2613            TAG_WITHDRAW_NOTIFICATION if len == 12 => Mutation::WithdrawNotification {
2614                key: read_string(bytes, payload, records_len)?,
2615            },
2616            TAG_REQUEST_NOTIFICATION_PERMISSION if len == 4 => {
2617                Mutation::RequestNotificationPermission
2618            }
2619            TAG_START_ANIMATION if len >= ANIMATION_HEADER_LEN => {
2620                // A keyframe that runs past the end of its record is a count that does not
2621                // match the length, which is the record being wrong rather than the batch.
2622                Mutation::StartAnimation(
2623                    decode_animation(&bytes[position..position + len]).map_err(|error| {
2624                        if error == ProtocolError::Truncated {
2625                            ProtocolError::InvalidAnimation
2626                        } else {
2627                            error
2628                        }
2629                    })?,
2630                )
2631            }
2632            TAG_CONTROL_ANIMATION if len == 24 => {
2633                let raw_property = u16::from(read_u8(bytes, payload + 8)?);
2634                let raw_op = u16::from(read_u8(bytes, payload + 10)?);
2635                Mutation::ControlAnimation {
2636                    node_id: read_u32(bytes, payload)?,
2637                    animation_id: read_u32(bytes, payload + 4)?,
2638                    property: crate::schema::AnimatedProperty::try_from(raw_property)
2639                        .map_err(|()| ProtocolError::InvalidAnimation)?,
2640                    slot: read_u8(bytes, payload + 9)?,
2641                    op: crate::schema::AnimationControl::try_from(raw_op)
2642                        .map_err(|()| ProtocolError::InvalidAnimation)?,
2643                    at_time_nanos: read_u64(bytes, payload + 12)?,
2644                }
2645            }
2646            TAG_SET_SELECTION if len == 28 => Mutation::SetSelection {
2647                node_id: read_u32(bytes, payload)?,
2648                base_version: read_u32(bytes, payload + 4)?,
2649                anchor_line: read_u32(bytes, payload + 8)?,
2650                anchor_column: read_u32(bytes, payload + 12)?,
2651                active_line: read_u32(bytes, payload + 16)?,
2652                active_column: read_u32(bytes, payload + 20)?,
2653            },
2654            TAG_REVEAL_RANGE if len == 32 => {
2655                let raw_align = read_u32(bytes, payload + 24)?;
2656                Mutation::RevealRange {
2657                    node_id: read_u32(bytes, payload)?,
2658                    base_version: read_u32(bytes, payload + 4)?,
2659                    start_line: read_u32(bytes, payload + 8)?,
2660                    start_column: read_u32(bytes, payload + 12)?,
2661                    end_line: read_u32(bytes, payload + 16)?,
2662                    end_column: read_u32(bytes, payload + 20)?,
2663                    align: u16::try_from(raw_align)
2664                        .ok()
2665                        .and_then(|tag| crate::schema::RevealAlign::try_from(tag).ok())
2666                        .ok_or(ProtocolError::InvalidValueKind(
2667                            raw_align.min(u32::from(u16::MAX)) as u16,
2668                        ))?,
2669                }
2670            }
2671            TAG_SET_FOLDED if len == 16 => Mutation::SetFolded {
2672                node_id: read_u32(bytes, payload)?,
2673                start_line: read_u32(bytes, payload + 4)?,
2674                collapsed: read_flag(bytes, payload + 8)?,
2675            },
2676            TAG_SET_SCROLL_OFFSET if len == 16 => Mutation::SetScrollOffset {
2677                node_id: read_u32(bytes, payload)?,
2678                offset_px: f64::from_bits(read_u64(bytes, payload + 4)?),
2679            },
2680            TAG_SET_SCROLL_OFFSET => return Err(ProtocolError::InvalidRecordLength),
2681            TAG_CREATE..=TAG_EDIT_CODE
2682            | TAG_START_ANIMATION
2683            | TAG_CONTROL_ANIMATION
2684            | TAG_SET_SELECTION..=TAG_SET_FOLDED
2685            | TAG_SET_WINDOW_THEME..=TAG_SET_CLIPBOARD_TEXT
2686            | TAG_READ_CLIPBOARD
2687            | TAG_STORAGE_CALL
2688            | TAG_FILE_CALL
2689            | TAG_RELEASE_FILE_HANDLE
2690            | TAG_SCROLL_TO
2691            | TAG_SCROLL_INTO_VIEW => {
2692                return Err(ProtocolError::InvalidRecordLength);
2693            }
2694            other => return Err(ProtocolError::InvalidTag(other)),
2695        };
2696        output.push(mutation);
2697        position += len;
2698    }
2699    if output.len() != count {
2700        return Err(ProtocolError::InvalidEnvelope);
2701    }
2702    Ok(output)
2703}
2704
2705/// Reads one `StartAnimation` record, given whole, and refuses anything that is not an
2706/// animation the Renderer could play. The Renderer asks the same questions of the same
2707/// bytes, so a record one side refuses is refused on the other as well.
2708fn decode_animation(record: &[u8]) -> Result<crate::schema::Animation<'static>, ProtocolError> {
2709    use crate::schema::{
2710        AnimatedProperty, Animation, AnimationEvents, ColorInterpolation, FillMode, Keyframe,
2711        KeyframeValue, PlayState, PlaybackDirection, StepPosition, Timing, TransformFunction,
2712        TransformFunctionKind,
2713    };
2714    let bad = ProtocolError::InvalidAnimation;
2715    fn byte_enum<T: TryFrom<u16, Error = ()>>(
2716        record: &[u8],
2717        at: usize,
2718    ) -> Result<T, ProtocolError> {
2719        T::try_from(u16::from(read_u8(record, at)?)).map_err(|()| ProtocolError::InvalidAnimation)
2720    }
2721    let float = |at: usize| read_u32(record, at).map(f32::from_bits);
2722    let property: AnimatedProperty = byte_enum(record, 12)?;
2723    let direction: PlaybackDirection = byte_enum(record, 14)?;
2724    let fill: FillMode = byte_enum(record, 15)?;
2725    let play_state: PlayState = byte_enum(record, 16)?;
2726    let colour = matches!(
2727        property,
2728        AnimatedProperty::Color | AnimatedProperty::Background | AnimatedProperty::BorderColor
2729    );
2730    let interpolation = match (colour, read_u8(record, 17)?) {
2731        (false, 0) => None,
2732        (true, raw) => Some(
2733            ColorInterpolation::try_from(u16::from(raw))
2734                .map_err(|()| ProtocolError::InvalidAnimation)?,
2735        ),
2736        (false, _) => return Err(bad),
2737    };
2738    let keyframe_count = usize::from(read_u16(record, 18)?);
2739    let delay_ms = float(28)?;
2740    let duration_ms = float(32)?;
2741    let iterations = float(36)?;
2742    let origin_x = float(40)?;
2743    let origin_y = float(44)?;
2744    let events = read_u8(record, 48)?;
2745    if keyframe_count < 2
2746        || !delay_ms.is_finite()
2747        || !(duration_ms.is_finite() && duration_ms >= 0.0)
2748        || iterations.is_nan()
2749        || iterations < 0.0
2750        || !(origin_x.is_finite() && origin_y.is_finite())
2751        || events & !AnimationEvents::ALL.0 != 0
2752    {
2753        return Err(bad);
2754    }
2755    let mut keyframes = Vec::with_capacity(keyframe_count);
2756    let mut at = ANIMATION_HEADER_LEN;
2757    for index in 0..keyframe_count {
2758        let offset = float(at)?;
2759        let raw_timing = read_u8(record, at + 4)?;
2760        let raw_position = read_u8(record, at + 5)?;
2761        let flags = read_u16(record, at + 6)?;
2762        let timing = match raw_timing {
2763            1 => Timing::Linear,
2764            2 => {
2765                let (x1, y1, x2, y2) = (
2766                    float(at + 8)?,
2767                    float(at + 12)?,
2768                    float(at + 16)?,
2769                    float(at + 20)?,
2770                );
2771                if ![x1, y1, x2, y2].iter().all(|value| value.is_finite())
2772                    || !(0.0..=1.0).contains(&x1)
2773                    || !(0.0..=1.0).contains(&x2)
2774                {
2775                    return Err(bad);
2776                }
2777                Timing::CubicBezier { x1, y1, x2, y2 }
2778            }
2779            3 => {
2780                let count = read_u32(record, at + 8)?;
2781                let position =
2782                    StepPosition::try_from(u16::from(raw_position)).map_err(|()| bad.clone())?;
2783                let least = if position == StepPosition::JumpNone {
2784                    2
2785                } else {
2786                    1
2787                };
2788                if count < least {
2789                    return Err(bad);
2790                }
2791                Timing::Steps { count, position }
2792            }
2793            _ => return Err(bad),
2794        };
2795        if flags & !KEYFRAME_FROM_PRESENTED != 0
2796            || (index > 0 && flags & KEYFRAME_FROM_PRESENTED != 0)
2797            || !(0.0..=1.0).contains(&offset)
2798        {
2799            return Err(bad);
2800        }
2801        let value_at = at + KEYFRAME_HEAD_LEN;
2802        let (value, value_len) = match property {
2803            AnimatedProperty::Alpha => {
2804                let value = float(value_at)?;
2805                if !value.is_finite() {
2806                    return Err(bad);
2807                }
2808                (KeyframeValue::Alpha(value), 4)
2809            }
2810            AnimatedProperty::Color
2811            | AnimatedProperty::Background
2812            | AnimatedProperty::BorderColor => {
2813                // A colour can be mixed with another colour; a brush cannot.
2814                let paint = Paint::from_bits(read_u64(record, value_at)?)
2815                    .filter(|paint| !matches!(paint, Paint::Asset(_)))
2816                    .ok_or(bad.clone())?;
2817                (KeyframeValue::Paint(paint), 8)
2818            }
2819            AnimatedProperty::Transform => {
2820                let count = usize::from(read_u16(record, value_at)?);
2821                let mut functions = Vec::with_capacity(count);
2822                for function in 0..count {
2823                    let function_at = value_at + 4 + TRANSFORM_FUNCTION_LEN * function;
2824                    let raw_kind = read_u32(record, function_at)?;
2825                    let kind = u16::try_from(raw_kind)
2826                        .ok()
2827                        .and_then(|kind| TransformFunctionKind::try_from(kind).ok())
2828                        .ok_or(bad.clone())?;
2829                    let mut values = [0.0_f32; 6];
2830                    for (slot, value) in values.iter_mut().enumerate() {
2831                        *value = float(function_at + 4 + 4 * slot)?;
2832                    }
2833                    if !values.iter().all(|value| value.is_finite()) {
2834                        return Err(bad);
2835                    }
2836                    functions.push(TransformFunction { kind, values });
2837                }
2838                (
2839                    KeyframeValue::Transform(std::borrow::Cow::Owned(functions)),
2840                    4 + TRANSFORM_FUNCTION_LEN * count,
2841                )
2842            }
2843        };
2844        keyframes.push(Keyframe {
2845            offset,
2846            timing,
2847            from_presented: flags & KEYFRAME_FROM_PRESENTED != 0,
2848            value,
2849        });
2850        at = value_at + value_len;
2851    }
2852    if at != record.len() {
2853        return Err(bad);
2854    }
2855    // Offsets start at 0, end at 1 and never go back, and every transform list has the
2856    // same kinds in the same order. A list the Host did not line up cannot be interpolated
2857    // one function to the next, which is the only way the Renderer interpolates them.
2858    let first = keyframes.first().map(|keyframe| keyframe.offset);
2859    let last = keyframes.last().map(|keyframe| keyframe.offset);
2860    if first != Some(0.0)
2861        || last != Some(1.0)
2862        || keyframes
2863            .windows(2)
2864            .any(|pair| pair[1].offset < pair[0].offset)
2865    {
2866        return Err(bad);
2867    }
2868    if let Some(KeyframeValue::Transform(shape)) = keyframes.first().map(|keyframe| &keyframe.value)
2869    {
2870        let kinds: Vec<_> = shape.iter().map(|function| function.kind).collect();
2871        let lined_up = keyframes.iter().all(|keyframe| match &keyframe.value {
2872            KeyframeValue::Transform(functions) => functions
2873                .iter()
2874                .map(|function| function.kind)
2875                .eq(kinds.iter().copied()),
2876            _ => false,
2877        });
2878        if !lined_up {
2879            return Err(bad);
2880        }
2881    }
2882    Ok(Animation {
2883        node_id: read_u32(record, 4)?,
2884        animation_id: read_u32(record, 8)?,
2885        property,
2886        slot: read_u8(record, 13)?,
2887        direction,
2888        fill,
2889        play_state,
2890        interpolation,
2891        start_time_nanos: read_u64(record, 20)?,
2892        delay_ms,
2893        duration_ms,
2894        iterations,
2895        origin_x,
2896        origin_y,
2897        events: AnimationEvents(events),
2898        keyframes: std::borrow::Cow::Owned(keyframes),
2899    })
2900}
2901
2902/// Two `f32` share one `u64`, with the first value in the low 32 bits.
2903const fn pack_floats(low: f32, high: f32) -> u64 {
2904    (low.to_bits() as u64) | ((high.to_bits() as u64) << 32)
2905}
2906
2907const fn unpack_low(word: u64) -> f32 {
2908    f32::from_bits(word as u32)
2909}
2910
2911const fn unpack_high(word: u64) -> f32 {
2912    f32::from_bits((word >> 32) as u32)
2913}
2914
2915/// The value's words with nothing past the first two, which is every modifier but the few
2916/// whose schema entry gives them more.
2917const fn two_words(first: u64, second: u64) -> [u64; MAX_MODIFIER_WORDS] {
2918    [first, second, 0, 0, 0, 0, 0, 0, 0, 0]
2919}
2920
2921fn float_word(value: f32) -> u64 {
2922    u64::from(value.to_bits())
2923}
2924
2925/// A modifier's tag and the words of its value. Only as many words as the schema gives
2926/// that tag are written; the rest are zero and never leave this function.
2927fn modifier_fields(modifier: &Modifier) -> (u16, [u64; MAX_MODIFIER_WORDS]) {
2928    match modifier {
2929        Modifier::Empty => (0, two_words(0, 0)),
2930        Modifier::Padding(value) => (1, two_words(float_word(*value), 0)),
2931        Modifier::FillMaxWidth => (2, two_words(0, 0)),
2932        Modifier::MatchParentSize => (31, two_words(0, 0)),
2933        Modifier::FillMaxHeight => (3, two_words(0, 0)),
2934        Modifier::Width(value) => (4, two_words(float_word(*value), 0)),
2935        Modifier::Height(value) => (5, two_words(float_word(*value), 0)),
2936        Modifier::Size { width, height } => (6, two_words(float_word(*width), float_word(*height))),
2937        Modifier::Background(paint) => (7, two_words(paint.to_bits(), 0)),
2938        Modifier::Clickable { handler_id } => (8, two_words(*handler_id, 0)),
2939        Modifier::PaddingRole(role) => (9, two_words(*role as u64, 0)),
2940        Modifier::PaddingEach {
2941            start,
2942            top,
2943            end,
2944            bottom,
2945        } => (
2946            10,
2947            two_words(pack_floats(*start, *top), pack_floats(*end, *bottom)),
2948        ),
2949        Modifier::Weight(value) => (11, two_words(float_word(*value), 0)),
2950        Modifier::Shape {
2951            top_start,
2952            top_end,
2953            bottom_end,
2954            bottom_start,
2955        } => (
2956            12,
2957            two_words(
2958                pack_floats(*top_start, *top_end),
2959                pack_floats(*bottom_end, *bottom_start),
2960            ),
2961        ),
2962        Modifier::ShapeRole(role) => (13, two_words(*role as u64, 0)),
2963        Modifier::Border { width, paint } => (14, two_words(float_word(*width), paint.to_bits())),
2964        Modifier::Elevation(dp) => (15, two_words(float_word(*dp), 0)),
2965        Modifier::ObserveSize { token, every_dp } => {
2966            (16, two_words(u64::from(*token), u64::from(*every_dp)))
2967        }
2968        Modifier::Motion(role) => (17, two_words(*role as u64, 0)),
2969        Modifier::Material(role) => (18, two_words(*role as u64, 0)),
2970        Modifier::Offset { x, y } => (19, two_words(float_word(*x), float_word(*y))),
2971        Modifier::RequiredSize { width, height } => {
2972            (20, two_words(float_word(*width), float_word(*height)))
2973        }
2974        Modifier::BorderEach {
2975            top,
2976            right,
2977            bottom,
2978            left,
2979            top_paint,
2980            right_paint,
2981            bottom_paint,
2982            left_paint,
2983        } => (
2984            21,
2985            [
2986                pack_floats(*top, *right),
2987                pack_floats(*bottom, *left),
2988                top_paint.to_bits(),
2989                right_paint.to_bits(),
2990                bottom_paint.to_bits(),
2991                left_paint.to_bits(),
2992                0,
2993                0,
2994                0,
2995                0,
2996            ],
2997        ),
2998        Modifier::CornerEach {
2999            top_left,
3000            top_right,
3001            bottom_right,
3002            bottom_left,
3003        } => (
3004            22,
3005            two_words(
3006                pack_floats(*top_left, *top_right),
3007                pack_floats(*bottom_right, *bottom_left),
3008            ),
3009        ),
3010        Modifier::Shadow {
3011            x,
3012            y,
3013            blur,
3014            spread,
3015            paint,
3016        } => (
3017            23,
3018            [
3019                pack_floats(*x, *y),
3020                pack_floats(*blur, *spread),
3021                paint.to_bits(),
3022                0,
3023                0,
3024                0,
3025                0,
3026                0,
3027                0,
3028                0,
3029            ],
3030        ),
3031        Modifier::WindowDrag(enabled) => (32, two_words(u64::from(*enabled), 0)),
3032        Modifier::Clip(enabled) => (24, two_words(u64::from(*enabled), 0)),
3033        Modifier::Alpha(value) => (25, two_words(float_word(*value), 0)),
3034        Modifier::PressFeedback { scale, tint_alpha } => {
3035            (33, two_words(pack_floats(*scale, *tint_alpha), 0))
3036        }
3037        Modifier::Semantics { role, state } => (34, two_words(*role as u64, u64::from(*state))),
3038        Modifier::CornerCurve { smoothing } => (35, two_words(float_word(*smoothing), 0)),
3039        Modifier::Transform {
3040            a,
3041            b,
3042            c,
3043            d,
3044            e,
3045            f,
3046            origin_x,
3047            origin_y,
3048        } => (
3049            26,
3050            [
3051                pack_floats(*a, *b),
3052                pack_floats(*c, *d),
3053                pack_floats(*e, *f),
3054                pack_floats(*origin_x, *origin_y),
3055                0,
3056                0,
3057                0,
3058                0,
3059                0,
3060                0,
3061            ],
3062        ),
3063        Modifier::Transform3D {
3064            m,
3065            origin_x,
3066            origin_y,
3067            backface_hidden,
3068        } => (
3069            27,
3070            [
3071                pack_floats(m[0], m[1]),
3072                pack_floats(m[2], m[3]),
3073                pack_floats(m[4], m[5]),
3074                pack_floats(m[6], m[7]),
3075                pack_floats(m[8], m[9]),
3076                pack_floats(m[10], m[11]),
3077                pack_floats(m[12], m[13]),
3078                pack_floats(m[14], m[15]),
3079                pack_floats(*origin_x, *origin_y),
3080                u64::from(*backface_hidden),
3081            ],
3082        ),
3083    }
3084}
3085
3086fn decode_role<T: TryFrom<u16, Error = ()>>(first: u64) -> Result<T, ProtocolError> {
3087    let tag = u16::try_from(first).map_err(|_| ProtocolError::InvalidModifier(0))?;
3088    T::try_from(tag).map_err(|()| ProtocolError::InvalidModifier(tag))
3089}
3090
3091fn decode_paint(bits: u64) -> Result<Paint, ProtocolError> {
3092    Paint::from_bits(bits).ok_or(ProtocolError::InvalidModifier(0))
3093}
3094
3095fn decode_modifier(tag: u16, words: &[u64; MAX_MODIFIER_WORDS]) -> Result<Modifier, ProtocolError> {
3096    let [
3097        first,
3098        second,
3099        third,
3100        fourth,
3101        fifth,
3102        sixth,
3103        seventh,
3104        eighth,
3105        ninth,
3106        tenth,
3107    ] = *words;
3108    match tag {
3109        0 => Ok(Modifier::Empty),
3110        1 => Ok(Modifier::Padding(f32::from_bits(first as u32))),
3111        2 => Ok(Modifier::FillMaxWidth),
3112        3 => Ok(Modifier::FillMaxHeight),
3113        4 => Ok(Modifier::Width(f32::from_bits(first as u32))),
3114        5 => Ok(Modifier::Height(f32::from_bits(first as u32))),
3115        6 => Ok(Modifier::Size {
3116            width: f32::from_bits(first as u32),
3117            height: f32::from_bits(second as u32),
3118        }),
3119        7 => Ok(Modifier::Background(decode_paint(first)?)),
3120        8 => Ok(Modifier::Clickable { handler_id: first }),
3121        9 => Ok(Modifier::PaddingRole(decode_role::<SpaceRole>(first)?)),
3122        10 => Ok(Modifier::PaddingEach {
3123            start: unpack_low(first),
3124            top: unpack_high(first),
3125            end: unpack_low(second),
3126            bottom: unpack_high(second),
3127        }),
3128        11 => Ok(Modifier::Weight(f32::from_bits(first as u32))),
3129        12 => Ok(Modifier::Shape {
3130            top_start: unpack_low(first),
3131            top_end: unpack_high(first),
3132            bottom_end: unpack_low(second),
3133            bottom_start: unpack_high(second),
3134        }),
3135        13 => Ok(Modifier::ShapeRole(decode_role::<ShapeRole>(first)?)),
3136        14 => Ok(Modifier::Border {
3137            width: f32::from_bits(first as u32),
3138            paint: decode_paint(second)?,
3139        }),
3140        15 => Ok(Modifier::Elevation(f32::from_bits(first as u32))),
3141        16 => Ok(Modifier::ObserveSize {
3142            token: first as u32,
3143            every_dp: match second {
3144                0 => false,
3145                1 => true,
3146                _ => return Err(ProtocolError::InvalidModifier(16)),
3147            },
3148        }),
3149        17 => Ok(Modifier::Motion(decode_role::<MotionRole>(first)?)),
3150        18 => Ok(Modifier::Material(decode_role::<MaterialRole>(first)?)),
3151        19 => Ok(Modifier::Offset {
3152            x: f32::from_bits(first as u32),
3153            y: f32::from_bits(second as u32),
3154        }),
3155        20 => Ok(Modifier::RequiredSize {
3156            width: f32::from_bits(first as u32),
3157            height: f32::from_bits(second as u32),
3158        }),
3159        21 => Ok(Modifier::BorderEach {
3160            top: unpack_low(first),
3161            right: unpack_high(first),
3162            bottom: unpack_low(second),
3163            left: unpack_high(second),
3164            top_paint: decode_paint(third)?,
3165            right_paint: decode_paint(fourth)?,
3166            bottom_paint: decode_paint(fifth)?,
3167            left_paint: decode_paint(sixth)?,
3168        }),
3169        22 => Ok(Modifier::CornerEach {
3170            top_left: unpack_low(first),
3171            top_right: unpack_high(first),
3172            bottom_right: unpack_low(second),
3173            bottom_left: unpack_high(second),
3174        }),
3175        23 => Ok(Modifier::Shadow {
3176            x: unpack_low(first),
3177            y: unpack_high(first),
3178            blur: unpack_low(second),
3179            spread: unpack_high(second),
3180            paint: decode_paint(third)?,
3181        }),
3182        // A flag is 0 or 1. Anything else is a record the two sides disagree about, and
3183        // reading it as "on" would hide that.
3184        24 => match first {
3185            0 => Ok(Modifier::Clip(false)),
3186            1 => Ok(Modifier::Clip(true)),
3187            _ => Err(ProtocolError::InvalidModifier(24)),
3188        },
3189        32 => match first {
3190            0 => Ok(Modifier::WindowDrag(false)),
3191            1 => Ok(Modifier::WindowDrag(true)),
3192            _ => Err(ProtocolError::InvalidModifier(32)),
3193        },
3194        25 => Ok(Modifier::Alpha(f32::from_bits(first as u32))),
3195        26 => {
3196            let values = [
3197                unpack_low(first),
3198                unpack_high(first),
3199                unpack_low(second),
3200                unpack_high(second),
3201                unpack_low(third),
3202                unpack_high(third),
3203                unpack_low(fourth),
3204                unpack_high(fourth),
3205            ];
3206            // A NaN or an infinity in a matrix makes everything under it undrawable, and
3207            // the two sides would not even agree on how. Refused here, as the Renderer
3208            // refuses it.
3209            if values.iter().any(|value| !value.is_finite()) {
3210                return Err(ProtocolError::InvalidModifier(26));
3211            }
3212            let [a, b, c, d, e, f, origin_x, origin_y] = values;
3213            Ok(Modifier::Transform {
3214                a,
3215                b,
3216                c,
3217                d,
3218                e,
3219                f,
3220                origin_x,
3221                origin_y,
3222            })
3223        }
3224        27 => {
3225            let values = [
3226                unpack_low(first),
3227                unpack_high(first),
3228                unpack_low(second),
3229                unpack_high(second),
3230                unpack_low(third),
3231                unpack_high(third),
3232                unpack_low(fourth),
3233                unpack_high(fourth),
3234                unpack_low(fifth),
3235                unpack_high(fifth),
3236                unpack_low(sixth),
3237                unpack_high(sixth),
3238                unpack_low(seventh),
3239                unpack_high(seventh),
3240                unpack_low(eighth),
3241                unpack_high(eighth),
3242                unpack_low(ninth),
3243                unpack_high(ninth),
3244            ];
3245            // The same refusal as the 2D matrix: a NaN or an infinity would draw nothing
3246            // on one side and something on the other.
3247            if values.iter().any(|value| !value.is_finite()) {
3248                return Err(ProtocolError::InvalidModifier(27));
3249            }
3250            let backface_hidden = match tenth {
3251                0 => false,
3252                1 => true,
3253                _ => return Err(ProtocolError::InvalidModifier(27)),
3254            };
3255            let mut m = [0.0; 16];
3256            m.copy_from_slice(&values[..16]);
3257            Ok(Modifier::Transform3D {
3258                m,
3259                origin_x: values[16],
3260                origin_y: values[17],
3261                backface_hidden,
3262            })
3263        }
3264        31 => Ok(Modifier::MatchParentSize),
3265        33 => Ok(Modifier::PressFeedback {
3266            scale: unpack_low(first),
3267            tint_alpha: unpack_high(first),
3268        }),
3269        34 => {
3270            let state = u32::try_from(second)
3271                .ok()
3272                .filter(|state| state & !SEMANTICS_STATE_MASK == 0)
3273                .ok_or(ProtocolError::InvalidModifier(34))?;
3274            Ok(Modifier::Semantics {
3275                role: decode_role::<SemanticsRole>(first)?,
3276                state,
3277            })
3278        }
3279        // Carried as sent. An out-of-range value is not an error; the Renderer draws it as
3280        // the nearer end of 0..=1.
3281        35 => Ok(Modifier::CornerCurve {
3282            smoothing: unpack_low(first),
3283        }),
3284        other => Err(ProtocolError::InvalidModifier(other)),
3285    }
3286}
3287
3288/// Reads a `(offset: u32, len: u32)` string reference at `position`.
3289///
3290/// Strings live in the arena that follows the records, so `arena_start` is the
3291/// first byte a string may legally point at. Without that floor a hostile Renderer can
3292/// aim a string at the record region and have the decoder reinterpret record headers as
3293/// text, garbage decoding into a valid-looking mutation.
3294fn read_string(bytes: &[u8], position: usize, arena_start: usize) -> Result<&str, ProtocolError> {
3295    let value = read_bytes(bytes, position, arena_start)?;
3296    std::str::from_utf8(value).map_err(|_| ProtocolError::InvalidUtf8)
3297}
3298
3299/// The same arena range a string uses, without the UTF-8 requirement.
3300fn read_bytes(bytes: &[u8], position: usize, arena_start: usize) -> Result<&[u8], ProtocolError> {
3301    let offset =
3302        usize::try_from(read_u32(bytes, position)?).map_err(|_| ProtocolError::LengthOverflow)?;
3303    let len = usize::try_from(read_u32(bytes, position + 4)?)
3304        .map_err(|_| ProtocolError::LengthOverflow)?;
3305    let end = offset
3306        .checked_add(len)
3307        .ok_or(ProtocolError::InvalidStringRange)?;
3308    if offset < arena_start {
3309        return Err(ProtocolError::InvalidStringRange);
3310    }
3311    bytes
3312        .get(offset..end)
3313        .ok_or(ProtocolError::InvalidStringRange)
3314}
3315
3316fn read_u8(bytes: &[u8], position: usize) -> Result<u8, ProtocolError> {
3317    bytes.get(position).copied().ok_or(ProtocolError::Truncated)
3318}
3319
3320fn read_u16(bytes: &[u8], position: usize) -> Result<u16, ProtocolError> {
3321    let value = bytes
3322        .get(position..position + 2)
3323        .ok_or(ProtocolError::Truncated)?;
3324    Ok(u16::from_le_bytes([value[0], value[1]]))
3325}
3326
3327fn read_u32(bytes: &[u8], position: usize) -> Result<u32, ProtocolError> {
3328    let value = bytes
3329        .get(position..position + 4)
3330        .ok_or(ProtocolError::Truncated)?;
3331    Ok(u32::from_le_bytes([value[0], value[1], value[2], value[3]]))
3332}
3333
3334/// A word that is 0 or 1. Any other value is a protocol error, not a third state.
3335fn read_flag(bytes: &[u8], position: usize) -> Result<bool, ProtocolError> {
3336    match read_u32(bytes, position)? {
3337        0 => Ok(false),
3338        1 => Ok(true),
3339        other => Err(ProtocolError::InvalidValueKind(
3340            other.min(u32::from(u16::MAX)) as u16,
3341        )),
3342    }
3343}
3344
3345fn read_u64(bytes: &[u8], position: usize) -> Result<u64, ProtocolError> {
3346    let value = bytes
3347        .get(position..position + 8)
3348        .ok_or(ProtocolError::Truncated)?;
3349    Ok(u64::from_le_bytes(
3350        value.try_into().map_err(|_| ProtocolError::Truncated)?,
3351    ))
3352}
3353
3354#[cfg(test)]
3355mod tests {
3356    use super::*;
3357    use crate::schema::TypeRole;
3358
3359    #[test]
3360    fn round_trips_all_m0_mutations() {
3361        let mutations = [
3362            Mutation::Create {
3363                node_id: 1,
3364                widget: WidgetKind::Column,
3365            },
3366            Mutation::SetProp {
3367                node_id: 2,
3368                property: PropertyKind::Text,
3369                value: PropertyValue::String("안녕"),
3370            },
3371            Mutation::SetProp {
3372                node_id: 3,
3373                property: PropertyKind::Enabled,
3374                value: PropertyValue::Bool(true),
3375            },
3376            Mutation::SetModifier {
3377                node_id: 1,
3378                index: 0,
3379                modifier: Modifier::Padding(16.0),
3380            },
3381            Mutation::Insert {
3382                parent_id: 1,
3383                node_id: 2,
3384                index: 0,
3385            },
3386            Mutation::Move {
3387                parent_id: 1,
3388                node_id: 2,
3389                index: 1,
3390            },
3391            Mutation::Remove { node_id: 3 },
3392            Mutation::SetText {
3393                node_id: 4,
3394                text: "compose",
3395                selection: Some(Selection { start: 1, end: 4 }),
3396            },
3397            Mutation::AppendText {
3398                node_id: 4,
3399                text: " token",
3400            },
3401        ];
3402        let mut encoder = BatchEncoder::default();
3403        for mutation in &mutations {
3404            encoder.encode(mutation).unwrap();
3405        }
3406        let decoded = decode_batch(encoder.finish().unwrap()).unwrap();
3407        assert_eq!(decoded, mutations);
3408    }
3409
3410    /// A role Paint and a literal Paint occupy the same record length and
3411    /// decode back to the value that was encoded.
3412    #[test]
3413    fn fr13_paint_role_and_literal_share_one_record_length() {
3414        let encode_one = |modifier| {
3415            let mut encoder = BatchEncoder::default();
3416            encoder
3417                .encode(&Mutation::SetModifier {
3418                    node_id: 1,
3419                    index: 0,
3420                    modifier,
3421                })
3422                .unwrap();
3423            encoder.finish().unwrap().to_vec()
3424        };
3425        let role = encode_one(Modifier::Background(Paint::Role(
3426            crate::schema::ColorRole::Surface,
3427        )));
3428        let literal = encode_one(Modifier::Background(Paint::Literal(
3429            crate::schema::Color::rgb(0x1b1b1f),
3430        )));
3431        assert_eq!(role.len(), literal.len());
3432        assert_eq!(
3433            decode_batch(&role).unwrap(),
3434            [Mutation::SetModifier {
3435                node_id: 1,
3436                index: 0,
3437                modifier: Modifier::Background(Paint::Role(crate::schema::ColorRole::Surface)),
3438            }]
3439        );
3440        assert_eq!(
3441            decode_batch(&literal).unwrap(),
3442            [Mutation::SetModifier {
3443                node_id: 1,
3444                index: 0,
3445                modifier: Modifier::Background(Paint::Literal(crate::schema::Color::rgb(0x1b1b1f))),
3446            }]
3447        );
3448    }
3449
3450    #[test]
3451    fn fr68_semantics_round_trips_and_refuses_unknown_state_bits() {
3452        let expected = Modifier::Semantics {
3453            role: SemanticsRole::Switch,
3454            state: (2 << crate::schema::SEMANTICS_CHECKED_SHIFT)
3455                | (1 << crate::schema::SEMANTICS_SELECTED_SHIFT),
3456        };
3457        let mutation = Mutation::SetModifier {
3458            node_id: 1,
3459            index: 0,
3460            modifier: expected.clone(),
3461        };
3462        let mut encoder = BatchEncoder::default();
3463        encoder.encode(&mutation).unwrap();
3464        assert_eq!(decode_batch(encoder.finish().unwrap()).unwrap(), [mutation]);
3465
3466        let words = [SemanticsRole::Button as u64, 0x2000, 0, 0, 0, 0, 0, 0, 0, 0];
3467        assert_eq!(
3468            decode_modifier(34, &words),
3469            Err(ProtocolError::InvalidModifier(34))
3470        );
3471    }
3472
3473    #[test]
3474    fn fr71_every_new_role_round_trips_and_is_exposed() {
3475        use crate::schema::*;
3476        let roles = [
3477            (SemanticsRole::TabList, 9),
3478            (SemanticsRole::Tree, 10),
3479            (SemanticsRole::TreeItem, 11),
3480            (SemanticsRole::Menu, 12),
3481            (SemanticsRole::MenuItem, 13),
3482            (SemanticsRole::TextBox, 14),
3483            (SemanticsRole::Heading, 15),
3484            (SemanticsRole::Navigation, 16),
3485            (SemanticsRole::List, 17),
3486            (SemanticsRole::ListItem, 18),
3487            (SemanticsRole::Group, 19),
3488        ];
3489        let state = (2 << SEMANTICS_EXPANDED_SHIFT)
3490            | (2 << SEMANTICS_DISABLED_SHIFT)
3491            | (1 << SEMANTICS_FOCUSED_SHIFT)
3492            | SEMANTICS_HIDDEN_BIT
3493            | SEMANTICS_CLICK_ACTION_BIT
3494            | SEMANTICS_FOCUS_ACTION_BIT;
3495        assert_eq!(state & !SEMANTICS_STATE_MASK, 0);
3496        for (role, value) in roles {
3497            assert_eq!(role as u64, value, "{role:?} changed its wire value");
3498            let mutation = Mutation::SetModifier {
3499                node_id: 1,
3500                index: 0,
3501                modifier: Modifier::Semantics { role, state },
3502            };
3503            let mut encoder = BatchEncoder::default();
3504            encoder.encode(&mutation).unwrap();
3505            assert_eq!(decode_batch(encoder.finish().unwrap()).unwrap(), [mutation]);
3506        }
3507        assert!(
3508            ROLE_ENUM_SCHEMA
3509                .iter()
3510                .any(|e| e.name == "SemanticsRole" && e.variants.len() == 19),
3511            "the schema must list all 19 roles"
3512        );
3513    }
3514
3515    #[test]
3516    fn nfr7_unknown_semantics_bit_is_a_protocol_error() {
3517        // Bit 13 is the first one above the mask.
3518        let words = [SemanticsRole::Group as u64, 1 << 13, 0, 0, 0, 0, 0, 0, 0, 0];
3519        assert_eq!(
3520            decode_modifier(34, &words),
3521            Err(ProtocolError::InvalidModifier(34))
3522        );
3523        // The highest bit the mask allows still decodes.
3524        let words = [SemanticsRole::Group as u64, 1 << 12, 0, 0, 0, 0, 0, 0, 0, 0];
3525        assert!(decode_modifier(34, &words).is_ok());
3526    }
3527
3528    #[test]
3529    fn fr51_press_feedback_round_trips() {
3530        let expected = [Mutation::SetModifier {
3531            node_id: 1,
3532            index: 0,
3533            modifier: Modifier::PressFeedback {
3534                scale: 0.975,
3535                tint_alpha: 0.07,
3536            },
3537        }];
3538        let mut encoder = BatchEncoder::default();
3539        encoder.encode(&expected[0]).unwrap();
3540        assert_eq!(decode_batch(encoder.finish().unwrap()).unwrap(), expected);
3541    }
3542
3543    #[test]
3544    fn fr50_corner_curve_round_trips() {
3545        for smoothing in [0.0, 0.6, 1.0, -2.5, 7.0] {
3546            let expected = [Mutation::SetModifier {
3547                node_id: 1,
3548                index: 0,
3549                modifier: Modifier::CornerCurve { smoothing },
3550            }];
3551            let mut encoder = BatchEncoder::default();
3552            encoder.encode(&expected[0]).unwrap();
3553            let bytes = encoder.finish().unwrap().to_vec();
3554            assert_eq!(u16::from_le_bytes([bytes[22], bytes[23]]), 35);
3555            assert_eq!(decode_batch(&bytes).unwrap(), expected);
3556        }
3557    }
3558
3559    /// Every design primitive fits in `(tag, u64, u64)` and survives a round trip.
3560    #[test]
3561    fn fr13_design_modifiers_round_trip_in_two_words() {
3562        let modifiers = [
3563            Modifier::PaddingRole(SpaceRole::Md),
3564            Modifier::PaddingEach {
3565                start: 1.0,
3566                top: 2.0,
3567                end: 3.0,
3568                bottom: 4.0,
3569            },
3570            Modifier::Weight(0.25),
3571            Modifier::Shape {
3572                top_start: 4.0,
3573                top_end: 8.0,
3574                bottom_end: 12.0,
3575                bottom_start: 16.0,
3576            },
3577            Modifier::ShapeRole(ShapeRole::Full),
3578            Modifier::Border {
3579                width: 2.0,
3580                paint: Paint::Role(crate::schema::ColorRole::Outline),
3581            },
3582            Modifier::Elevation(6.0),
3583        ];
3584        let mut encoder = BatchEncoder::default();
3585        let expected: Vec<_> = modifiers
3586            .iter()
3587            .enumerate()
3588            .map(|(index, modifier)| Mutation::SetModifier {
3589                node_id: 1,
3590                index: index as u16,
3591                modifier: modifier.clone(),
3592            })
3593            .collect();
3594        for mutation in &expected {
3595            encoder.encode(mutation).unwrap();
3596        }
3597        let bytes = encoder.finish().unwrap();
3598        // Every modifier record is the same fixed 28 bytes.
3599        assert_eq!(bytes.len(), ENVELOPE_LEN + expected.len() * 28);
3600        assert_eq!(decode_batch(bytes).unwrap(), expected);
3601    }
3602
3603    /// A node's shadows are several `Shadow` records at consecutive chain positions,
3604    /// each with its own offset, blur, spread and paint (alpha included), and every one
3605    /// survives a round trip in the order it was written.
3606    #[test]
3607    fn fr42_shadow_a_list_of_shadows_round_trips_in_order() {
3608        let shadows = [
3609            Modifier::Shadow {
3610                x: 0.0,
3611                y: 1.0,
3612                blur: 2.0,
3613                spread: 0.0,
3614                paint: Paint::Literal(crate::schema::Color::argb(0x1400_0000)),
3615            },
3616            Modifier::Shadow {
3617                x: 0.0,
3618                y: 4.0,
3619                blur: 12.0,
3620                spread: -2.0,
3621                paint: Paint::Literal(crate::schema::Color::argb(0x2900_0000)),
3622            },
3623            Modifier::Shadow {
3624                x: -3.5,
3625                y: 24.0,
3626                blur: 48.0,
3627                spread: 4.0,
3628                paint: Paint::Role(crate::schema::ColorRole::Outline),
3629            },
3630        ];
3631        let expected: Vec<_> = shadows
3632            .iter()
3633            .enumerate()
3634            .map(|(index, modifier)| Mutation::SetModifier {
3635                node_id: 7,
3636                index: 9 + index as u16,
3637                modifier: modifier.clone(),
3638            })
3639            .collect();
3640        let mut encoder = BatchEncoder::default();
3641        for mutation in &expected {
3642            encoder.encode(mutation).unwrap();
3643        }
3644        let bytes = encoder.finish().unwrap();
3645        assert_eq!(decode_batch(bytes).unwrap(), expected);
3646    }
3647
3648    /// `SetTheme` is four `u16` fields and one font slot per type role.
3649    ///
3650    /// Fixed length whether or not any font is named, because the record is fixed layout
3651    /// and a theme is sent once rather than per frame. The slots cost 36 bytes on a
3652    /// message that appears once in a session.
3653    #[test]
3654    fn fr23_set_theme_round_trips_with_a_slot_for_every_type_role() {
3655        let theme = Theme::adaptive(DesignSystem::Cupertino)
3656            .with_color_scheme(ColorScheme::Dark)
3657            .with_font(TypeRole::Display, 7);
3658        let length = 20 + 4 * TYPE_ROLE_COUNT as u16;
3659        let mut encoder = BatchEncoder::default();
3660        encoder.encode(&Mutation::SetTheme(theme)).unwrap();
3661        let bytes = encoder.finish().unwrap();
3662        assert_eq!(bytes.len(), ENVELOPE_LEN + length as usize);
3663        assert_eq!(read_u16(bytes, ENVELOPE_LEN).unwrap(), TAG_SET_THEME);
3664        assert_eq!(read_u16(bytes, ENVELOPE_LEN + 2).unwrap(), length);
3665        assert_eq!(decode_batch(bytes).unwrap(), [Mutation::SetTheme(theme)]);
3666    }
3667
3668    /// A font named for one role leaves every other role on the system font.
3669    #[test]
3670    fn fr23_a_font_is_named_for_one_role_and_no_others() {
3671        let theme = Theme::unified(DesignSystem::Material3).with_font(TypeRole::Display, 3);
3672        assert_eq!(theme.font(TypeRole::Display), Some(3));
3673        for role in [
3674            TypeRole::Body,
3675            TypeRole::Title,
3676            TypeRole::Mono,
3677            TypeRole::Caption,
3678        ] {
3679            assert_eq!(theme.font(role), None);
3680        }
3681    }
3682
3683    /// One font for the whole screen: every role but code takes it, and code stays
3684    /// monospaced until a font is named for it.
3685    #[test]
3686    fn fr52_a_default_font_names_every_role_but_mono() {
3687        let theme = Theme::unified(DesignSystem::Material3).with_default_font(5);
3688        for role in [
3689            TypeRole::Display,
3690            TypeRole::Headline,
3691            TypeRole::Title,
3692            TypeRole::Subtitle,
3693            TypeRole::Body,
3694            TypeRole::BodyStrong,
3695            TypeRole::Label,
3696            TypeRole::Caption,
3697        ] {
3698            assert_eq!(theme.font(role), Some(5), "{role:?}");
3699        }
3700        assert_eq!(theme.font(TypeRole::Mono), None);
3701        assert_eq!(theme.font(TypeRole::None), None);
3702
3703        let both = theme
3704            .with_font(TypeRole::Mono, 9)
3705            .with_font(TypeRole::Display, 3);
3706        assert_eq!(both.font(TypeRole::Mono), Some(9));
3707        assert_eq!(
3708            both.font(TypeRole::Display),
3709            Some(3),
3710            "the role named last wins"
3711        );
3712        assert_eq!(both.font(TypeRole::Body), Some(5));
3713
3714        let mut encoder = BatchEncoder::default();
3715        encoder.encode(&Mutation::SetTheme(theme)).unwrap();
3716        let bytes = encoder.finish().unwrap();
3717        assert_eq!(decode_batch(bytes).unwrap(), [Mutation::SetTheme(theme)]);
3718    }
3719
3720    /// An out-of-range theme tag is an error, never a panic.
3721    #[test]
3722    fn fr14_rejects_unknown_theme_tags() {
3723        let mut encoder = BatchEncoder::default();
3724        encoder
3725            .encode(&Mutation::SetTheme(Theme::default()))
3726            .unwrap();
3727        let mut bytes = encoder.finish().unwrap().to_vec();
3728        bytes[ENVELOPE_LEN + 4..ENVELOPE_LEN + 6].copy_from_slice(&99_u16.to_le_bytes());
3729        assert_eq!(decode_batch(&bytes), Err(ProtocolError::InvalidTheme(99)));
3730    }
3731
3732    #[test]
3733    fn rejects_unknown_tags_without_panicking() {
3734        let mut bytes = vec![0, 0, 12, 0, 20, 0, 0, 0, 1, 0, 0, 0];
3735        bytes.extend_from_slice(&999_u16.to_le_bytes());
3736        bytes.extend_from_slice(&8_u16.to_le_bytes());
3737        bytes.extend_from_slice(&0_u32.to_le_bytes());
3738        assert_eq!(decode_batch(&bytes), Err(ProtocolError::InvalidTag(999)));
3739    }
3740
3741    #[test]
3742    fn pr4_renderer_events_round_trip_fixed_layout() {
3743        let events = [
3744            HostEvent {
3745                node_id: 7,
3746                handler_id: 11,
3747                payload: crate::EventPayload::Clicked,
3748            },
3749            HostEvent {
3750                node_id: 8,
3751                handler_id: 12,
3752                payload: crate::EventPayload::TextChanged("한글"),
3753            },
3754            HostEvent {
3755                node_id: 8,
3756                handler_id: 13,
3757                payload: crate::EventPayload::TextSubmitted("done"),
3758            },
3759            HostEvent {
3760                node_id: 8,
3761                handler_id: 14,
3762                payload: crate::EventPayload::FocusLost,
3763            },
3764            HostEvent {
3765                node_id: 9,
3766                handler_id: 16,
3767                payload: crate::EventPayload::RangeRequested {
3768                    start: 100,
3769                    count: 20,
3770                },
3771            },
3772            HostEvent {
3773                node_id: 8,
3774                handler_id: 15,
3775                payload: crate::EventPayload::KeyDown {
3776                    key: Key::Enter,
3777                    shift_key: true,
3778                    ctrl_key: false,
3779                    alt_key: true,
3780                    meta_key: false,
3781                    repeat: true,
3782                    code: 0x28,
3783                    text: "Enter",
3784                },
3785            },
3786        ];
3787        let mut bytes = Vec::new();
3788        for event in events {
3789            encode_event(&event, &mut bytes).unwrap();
3790            assert_eq!(decode_event(&bytes).unwrap(), event);
3791        }
3792    }
3793
3794    #[test]
3795    fn fr19_9_window_safe_area_round_trips() {
3796        let event = HostEvent {
3797            node_id: 0,
3798            handler_id: 0,
3799            payload: crate::EventPayload::WindowSizeChanged {
3800                width_dp: 841.5,
3801                height_dp: 600.25,
3802                class: crate::WindowSizeClass::Expanded,
3803                height_class: crate::WindowHeightClass::Medium,
3804                safe_area_top: 28.5,
3805                safe_area_leading: 72.0,
3806                safe_area_trailing: 13.0,
3807            },
3808        };
3809        let mut bytes = Vec::new();
3810        encode_event(&event, &mut bytes).unwrap();
3811        assert_eq!(bytes.len(), 44);
3812        assert_eq!(decode_event(&bytes).unwrap(), event);
3813    }
3814
3815    #[test]
3816    fn nfr7_malformed_key_down_text_is_a_protocol_error() {
3817        let event = HostEvent {
3818            node_id: 1,
3819            handler_id: 2,
3820            payload: crate::EventPayload::KeyDown {
3821                key: Key::Enter,
3822                shift_key: false,
3823                ctrl_key: false,
3824                alt_key: false,
3825                meta_key: false,
3826                repeat: false,
3827                code: 0x51,
3828                text: "ArrowDown",
3829            },
3830        };
3831        let mut bytes = Vec::new();
3832        encode_event(&event, &mut bytes).unwrap();
3833        assert_eq!(decode_event(&bytes).unwrap(), event);
3834        // The text points past the end of the record.
3835        let mut past_end = bytes.clone();
3836        past_end[28..32].copy_from_slice(&200_u32.to_le_bytes());
3837        assert_eq!(
3838            decode_event(&past_end),
3839            Err(ProtocolError::InvalidStringRange)
3840        );
3841        // The text starts inside the header, before the string area.
3842        let mut inside_header = bytes.clone();
3843        inside_header[24..28].copy_from_slice(&8_u32.to_le_bytes());
3844        assert_eq!(
3845            decode_event(&inside_header),
3846            Err(ProtocolError::InvalidStringRange)
3847        );
3848        // The text is not UTF-8.
3849        let mut not_utf8 = bytes;
3850        not_utf8[32] = 0xff;
3851        assert_eq!(decode_event(&not_utf8), Err(ProtocolError::InvalidUtf8));
3852    }
3853
3854    #[test]
3855    fn fr19_9_short_window_size_record_is_a_protocol_error() {
3856        let mut bytes = vec![0; 32];
3857        bytes[0..2].copy_from_slice(&EVENT_WINDOW_SIZE_CHANGED.to_le_bytes());
3858        bytes[2..4].copy_from_slice(&32_u16.to_le_bytes());
3859        assert!(decode_event(&bytes).is_err());
3860        bytes[2..4].copy_from_slice(&44_u16.to_le_bytes());
3861        assert!(decode_event(&bytes).is_err());
3862    }
3863
3864    #[test]
3865    fn fr20_unknown_window_size_class_is_a_protocol_error() {
3866        let event = HostEvent {
3867            node_id: 0,
3868            handler_id: 0,
3869            payload: crate::EventPayload::WindowSizeChanged {
3870                width_dp: 320.0,
3871                height_dp: 640.0,
3872                class: crate::WindowSizeClass::Compact,
3873                height_class: crate::WindowHeightClass::Medium,
3874                safe_area_top: 0.0,
3875                safe_area_leading: 0.0,
3876                safe_area_trailing: 0.0,
3877            },
3878        };
3879        let mut bytes = Vec::new();
3880        encode_event(&event, &mut bytes).unwrap();
3881        bytes[24..28].copy_from_slice(&99_u32.to_le_bytes());
3882        assert_eq!(
3883            decode_event(&bytes),
3884            Err(ProtocolError::InvalidValueKind(99))
3885        );
3886    }
3887
3888    #[test]
3889    fn fr20_unknown_window_height_class_is_a_protocol_error() {
3890        let event = HostEvent {
3891            node_id: 0,
3892            handler_id: 0,
3893            payload: crate::EventPayload::WindowSizeChanged {
3894                width_dp: 320.0,
3895                height_dp: 640.0,
3896                class: crate::WindowSizeClass::Compact,
3897                height_class: crate::WindowHeightClass::Medium,
3898                safe_area_top: 0.0,
3899                safe_area_leading: 0.0,
3900                safe_area_trailing: 0.0,
3901            },
3902        };
3903        let mut bytes = Vec::new();
3904        encode_event(&event, &mut bytes).unwrap();
3905        bytes[28..32].copy_from_slice(&99_u32.to_le_bytes());
3906        assert_eq!(
3907            decode_event(&bytes),
3908            Err(ProtocolError::InvalidValueKind(99))
3909        );
3910    }
3911
3912    #[test]
3913    fn fr20_window_size_classes_follow_the_material_boundaries() {
3914        use crate::WindowSizeClass;
3915        assert_eq!(
3916            WindowSizeClass::from_width_dp(599.9),
3917            WindowSizeClass::Compact
3918        );
3919        assert_eq!(
3920            WindowSizeClass::from_width_dp(600.0),
3921            WindowSizeClass::Medium
3922        );
3923        assert_eq!(
3924            WindowSizeClass::from_width_dp(839.9),
3925            WindowSizeClass::Medium
3926        );
3927        assert_eq!(
3928            WindowSizeClass::from_width_dp(840.0),
3929            WindowSizeClass::Expanded
3930        );
3931    }
3932
3933    #[test]
3934    fn fr20_window_height_classes_follow_the_material_boundaries() {
3935        use crate::WindowHeightClass;
3936        assert_eq!(
3937            WindowHeightClass::from_height_dp(479.9),
3938            WindowHeightClass::Compact
3939        );
3940        assert_eq!(
3941            WindowHeightClass::from_height_dp(480.0),
3942            WindowHeightClass::Medium
3943        );
3944        assert_eq!(
3945            WindowHeightClass::from_height_dp(899.9),
3946            WindowHeightClass::Medium
3947        );
3948        assert_eq!(
3949            WindowHeightClass::from_height_dp(900.0),
3950            WindowHeightClass::Expanded
3951        );
3952    }
3953
3954    fn post_notification() -> Mutation<'static> {
3955        Mutation::PostNotification {
3956            key: "session/7",
3957            title: "세션이 끝났습니다",
3958            body: "refactor-parser: 테스트 214개 통과",
3959            channel: "세션",
3960            action_1: "열기",
3961            action_2: "",
3962            importance: NotificationImportance::Urgent,
3963            presentation: NotificationPresentation::WhenInactive,
3964        }
3965    }
3966
3967    /// The three notification commands keep the lengths the wire fixes for them: six string
3968    /// references and two closed enums, one string reference, and a bare header.
3969    #[test]
3970    fn fr36_notification_commands_round_trip_at_their_fixed_lengths() {
3971        let mutations = [
3972            post_notification(),
3973            Mutation::WithdrawNotification { key: "session/7" },
3974            Mutation::RequestNotificationPermission,
3975        ];
3976        let mut lengths = Vec::new();
3977        let mut encoder = BatchEncoder::default();
3978        for mutation in &mutations {
3979            encoder.encode(mutation).unwrap();
3980        }
3981        let bytes = encoder.finish().unwrap();
3982        let mut position = ENVELOPE_LEN;
3983        for _ in &mutations {
3984            let length = read_u16(bytes, position + 2).unwrap();
3985            lengths.push((read_u16(bytes, position).unwrap(), length));
3986            position += usize::from(length);
3987        }
3988        assert_eq!(
3989            lengths,
3990            [
3991                (TAG_POST_NOTIFICATION, 56),
3992                (TAG_WITHDRAW_NOTIFICATION, 12),
3993                (TAG_REQUEST_NOTIFICATION_PERMISSION, 4),
3994            ]
3995        );
3996        assert_eq!(decode_batch(bytes).unwrap(), mutations);
3997    }
3998
3999    /// An importance or a presentation this side does not know is an error, never a guess
4000    /// and never a panic.
4001    #[test]
4002    fn fr36_unknown_importance_and_presentation_are_protocol_errors() {
4003        for (offset, expected) in [(52, 9_u16), (54, 7_u16)] {
4004            let mut encoder = BatchEncoder::default();
4005            encoder.encode(&post_notification()).unwrap();
4006            let mut bytes = encoder.finish().unwrap().to_vec();
4007            bytes[ENVELOPE_LEN + offset..ENVELOPE_LEN + offset + 2]
4008                .copy_from_slice(&expected.to_le_bytes());
4009            assert_eq!(
4010                decode_batch(&bytes),
4011                Err(ProtocolError::InvalidNotification(expected))
4012            );
4013        }
4014    }
4015
4016    /// A known notification tag at the wrong length is a length error, the same as any
4017    /// other known record.
4018    #[test]
4019    fn fr36_a_notification_record_of_the_wrong_length_is_rejected() {
4020        let mut bytes = vec![0, 0, 12, 0, 20, 0, 0, 0, 1, 0, 0, 0];
4021        bytes.extend_from_slice(&TAG_REQUEST_NOTIFICATION_PERMISSION.to_le_bytes());
4022        bytes.extend_from_slice(&8_u16.to_le_bytes());
4023        bytes.extend_from_slice(&0_u32.to_le_bytes());
4024        assert_eq!(
4025            decode_batch(&bytes),
4026            Err(ProtocolError::InvalidRecordLength)
4027        );
4028    }
4029
4030    /// The two notification events: a word and a string after the common head, and a word
4031    /// on its own.
4032    #[test]
4033    fn fr36_notification_events_round_trip_fixed_layout() {
4034        let activated = HostEvent {
4035            node_id: 0,
4036            handler_id: 0,
4037            payload: crate::EventPayload::NotificationActivated {
4038                action: 2,
4039                key: "session/7",
4040            },
4041        };
4042        let mut bytes = Vec::new();
4043        encode_event(&activated, &mut bytes).unwrap();
4044        assert_eq!(read_u16(&bytes, 0).unwrap(), EVENT_NOTIFICATION_ACTIVATED);
4045        assert_eq!(read_u16(&bytes, 2).unwrap(), 28);
4046        assert_eq!(bytes.len(), 28 + "session/7".len());
4047        assert_eq!(decode_event(&bytes).unwrap(), activated);
4048
4049        for state in [
4050            NotificationPermission::NotDetermined,
4051            NotificationPermission::Granted,
4052            NotificationPermission::Denied,
4053            NotificationPermission::Unsupported,
4054        ] {
4055            let changed = HostEvent {
4056                node_id: 0,
4057                handler_id: 0,
4058                payload: crate::EventPayload::NotificationPermissionChanged(state),
4059            };
4060            encode_event(&changed, &mut bytes).unwrap();
4061            assert_eq!(bytes.len(), 20);
4062            assert_eq!(decode_event(&bytes).unwrap(), changed);
4063        }
4064    }
4065
4066    /// A permission state outside the four is reported, not mapped onto one of them.
4067    #[test]
4068    fn fr36_unknown_permission_state_is_a_protocol_error() {
4069        let mut bytes = Vec::new();
4070        encode_event(
4071            &HostEvent {
4072                node_id: 0,
4073                handler_id: 0,
4074                payload: crate::EventPayload::NotificationPermissionChanged(
4075                    NotificationPermission::Granted,
4076                ),
4077            },
4078            &mut bytes,
4079        )
4080        .unwrap();
4081        bytes[16..20].copy_from_slice(&5_u32.to_le_bytes());
4082        assert_eq!(
4083            decode_event(&bytes),
4084            Err(ProtocolError::InvalidValueKind(5))
4085        );
4086    }
4087
4088    /// A notification has a body and at most two buttons, so an activation naming a third
4089    /// is a malformed event.
4090    #[test]
4091    fn fr36_an_activation_naming_a_third_button_is_a_protocol_error() {
4092        let mut bytes = Vec::new();
4093        encode_event(
4094            &HostEvent {
4095                node_id: 0,
4096                handler_id: 0,
4097                payload: crate::EventPayload::NotificationActivated {
4098                    action: 1,
4099                    key: "k",
4100                },
4101            },
4102            &mut bytes,
4103        )
4104        .unwrap();
4105        bytes[16..20].copy_from_slice(&3_u32.to_le_bytes());
4106        assert_eq!(
4107            decode_event(&bytes),
4108            Err(ProtocolError::InvalidValueKind(3))
4109        );
4110    }
4111
4112    /// How large the Renderer is drawing: two floats and a signed level after the head.
4113    #[test]
4114    fn fr43_zoom_changed_round_trips_fixed_layout() {
4115        let event = HostEvent {
4116            node_id: 0,
4117            handler_id: 0,
4118            payload: crate::EventPayload::ZoomChanged {
4119                k: 1.25 * 1.44,
4120                os: 1.25,
4121                level: 2,
4122            },
4123        };
4124        let mut bytes = Vec::new();
4125        encode_event(&event, &mut bytes).unwrap();
4126        assert_eq!(read_u16(&bytes, 0).unwrap(), 33);
4127        assert_eq!(read_u16(&bytes, 2).unwrap(), 28);
4128        assert_eq!(bytes.len(), 28);
4129        assert_eq!(decode_event(&bytes).unwrap(), event);
4130
4131        let smaller = HostEvent {
4132            node_id: 0,
4133            handler_id: 0,
4134            payload: crate::EventPayload::ZoomChanged {
4135                k: 0.5,
4136                os: 1.0,
4137                level: -4,
4138            },
4139        };
4140        encode_event(&smaller, &mut bytes).unwrap();
4141        assert_eq!(decode_event(&bytes).unwrap(), smaller);
4142    }
4143
4144    /// A level outside the range the Renderer can be at is a protocol error.
4145    #[test]
4146    fn fr43_a_zoom_level_out_of_range_is_rejected() {
4147        let event = HostEvent {
4148            node_id: 0,
4149            handler_id: 0,
4150            payload: crate::EventPayload::ZoomChanged {
4151                k: 1.0,
4152                os: 1.0,
4153                level: 9,
4154            },
4155        };
4156        let mut bytes = Vec::new();
4157        encode_event(&event, &mut bytes).unwrap();
4158        assert_eq!(
4159            decode_event(&bytes),
4160            Err(ProtocolError::InvalidValueKind(9))
4161        );
4162        bytes[2..4].copy_from_slice(&24_u16.to_le_bytes());
4163        assert_eq!(
4164            decode_event(&bytes),
4165            Err(ProtocolError::InvalidRecordLength)
4166        );
4167    }
4168
4169    /// The window record carries the application's zoom level where it asked for one, and
4170    /// a window that asked for nothing is byte for byte what it was before the level.
4171    #[test]
4172    fn fr43_the_window_record_carries_a_zoom_level() {
4173        for level in [None, Some(-8), Some(0), Some(2), Some(8)] {
4174            let mut window = crate::schema::Window::new().with_title("zoom");
4175            window.zoom_level = level;
4176            let mut encoder = BatchEncoder::default();
4177            encoder.encode(&Mutation::SetWindow(window)).unwrap();
4178            let bytes = encoder.finish().unwrap().to_vec();
4179            if level.is_none() {
4180                assert_eq!(read_u16(&bytes, ENVELOPE_LEN + 18).unwrap(), 0);
4181            }
4182            let decoded = decode_batch(&bytes).unwrap();
4183            assert_eq!(decoded, vec![Mutation::SetWindow(window)]);
4184        }
4185        assert_eq!(
4186            crate::schema::Window::new().with_zoom_level(20).zoom_level,
4187            Some(8)
4188        );
4189    }
4190
4191    /// Bit 1 of the flags word asks the implicit window for every whole-dp size change. A
4192    /// window that does not ask encodes the bytes it always did.
4193    #[test]
4194    fn fr20_8_set_window_report_size_bit_roundtrips() {
4195        for report in [false, true] {
4196            let window = crate::schema::Window::new()
4197                .with_title("size")
4198                .with_report_size(report);
4199            let mut encoder = BatchEncoder::default();
4200            encoder.encode(&Mutation::SetWindow(window)).unwrap();
4201            let bytes = encoder.finish().unwrap().to_vec();
4202            let flags = read_u16(&bytes, ENVELOPE_LEN + 16).unwrap();
4203            assert_eq!(flags, 1 | (u16::from(report) << 1));
4204            assert_eq!(
4205                decode_batch(&bytes).unwrap(),
4206                vec![Mutation::SetWindow(window)]
4207            );
4208        }
4209        // The default window, byte by byte from the record layout.
4210        let window = crate::schema::Window::new();
4211        let mut encoder = BatchEncoder::default();
4212        encoder.encode(&Mutation::SetWindow(window)).unwrap();
4213        let bytes = encoder.finish().unwrap().to_vec();
4214        let record = &bytes[ENVELOPE_LEN..];
4215        assert_eq!(record.len(), 32);
4216        assert_eq!(read_u16(record, 0).unwrap(), TAG_SET_WINDOW);
4217        assert_eq!(read_u16(record, 2).unwrap(), 32);
4218        assert_eq!(read_u16(record, 4).unwrap(), window.chrome as u16);
4219        assert_eq!(read_u16(record, 6).unwrap(), window.title_bar as u16);
4220        for at in [8, 10, 12, 14] {
4221            assert_eq!(read_u16(record, at).unwrap(), 0);
4222        }
4223        // Resizable only: the mode and the policy are the defaults for the platform.
4224        let expected_flags = 1
4225            | ((window.mode as u16) << WINDOW_MODE_SHIFT)
4226            | ((window.last_window_closed as u16) << WINDOW_POLICY_SHIFT);
4227        assert_eq!(read_u16(record, 16).unwrap(), expected_flags);
4228        assert_eq!(read_u16(record, 18).unwrap(), 0);
4229        assert_eq!(read_u32(record, 28).unwrap(), 0);
4230    }
4231
4232    /// The other low bits of the flags word are still nobody's, so a record that sets
4233    /// one is a protocol error.
4234    #[test]
4235    fn fr20_8_set_window_rejects_unknown_flag_bits() {
4236        let mut encoder = BatchEncoder::default();
4237        encoder
4238            .encode(&Mutation::SetWindow(crate::schema::Window::new()))
4239            .unwrap();
4240        let bytes = encoder.finish().unwrap().to_vec();
4241        let flags = read_u16(&bytes, ENVELOPE_LEN + 16).unwrap();
4242        for bit in 2..8 {
4243            let bad = flags | (1 << bit);
4244            let mut copy = bytes.clone();
4245            copy[ENVELOPE_LEN + 16..ENVELOPE_LEN + 18].copy_from_slice(&bad.to_le_bytes());
4246            assert_eq!(
4247                decode_batch(&copy),
4248                Err(ProtocolError::InvalidValueKind(bad)),
4249                "bit {bit}"
4250            );
4251        }
4252    }
4253
4254    #[test]
4255    fn fr65_request_focus_round_trips_in_eight_bytes() {
4256        for node_id in [0_u32, 7, u32::MAX] {
4257            let mut encoder = BatchEncoder::default();
4258            encoder.encode(&Mutation::RequestFocus { node_id }).unwrap();
4259            let bytes = encoder.finish().unwrap().to_vec();
4260            assert_eq!(bytes.len(), ENVELOPE_LEN + 8);
4261            assert_eq!(read_u16(&bytes, ENVELOPE_LEN).unwrap(), TAG_REQUEST_FOCUS);
4262            assert_eq!(read_u16(&bytes, ENVELOPE_LEN + 2).unwrap(), 8);
4263            assert_eq!(read_u32(&bytes, ENVELOPE_LEN + 4).unwrap(), node_id);
4264            assert_eq!(
4265                decode_batch(&bytes).unwrap(),
4266                vec![Mutation::RequestFocus { node_id }]
4267            );
4268        }
4269        let mut encoder = BatchEncoder::default();
4270        encoder
4271            .encode(&Mutation::RequestFocus { node_id: 7 })
4272            .unwrap();
4273        let mut bad = encoder.finish().unwrap().to_vec();
4274        bad.extend_from_slice(&[0; 4]);
4275        let records_len = u32::try_from(bad.len()).unwrap();
4276        bad[4..8].copy_from_slice(&records_len.to_le_bytes());
4277        bad[ENVELOPE_LEN + 2..ENVELOPE_LEN + 4].copy_from_slice(&12_u16.to_le_bytes());
4278        assert_eq!(decode_batch(&bad), Err(ProtocolError::InvalidRecordLength));
4279    }
4280
4281    /// The request and the result are fixed records. Both survive a round trip with
4282    /// Korean text and NUL separated lists in them, an unknown kind or status is a
4283    /// protocol error, and no prefix of either record decodes or panics.
4284    #[test]
4285    fn fr72_file_dialog_records_round_trip_and_unknown_enums_are_errors() {
4286        let filters = "이미지\0png,jpg\0Docs\0pdf,txt";
4287        for kind in [
4288            FileDialogKind::OpenFile,
4289            FileDialogKind::OpenFiles,
4290            FileDialogKind::OpenFolder,
4291            FileDialogKind::OpenFolders,
4292            FileDialogKind::Save,
4293        ] {
4294            let request = Mutation::ShowFileDialog {
4295                request: 7,
4296                window: 20,
4297                kind,
4298                default_filter: 1,
4299                title: "파일 고르기",
4300                directory: "/home/한글",
4301                name: "보고서.txt",
4302                filters,
4303            };
4304            let mut encoder = BatchEncoder::default();
4305            encoder.encode(&request).unwrap();
4306            let bytes = encoder.finish().unwrap().to_vec();
4307            assert_eq!(
4308                read_u16(&bytes, ENVELOPE_LEN).unwrap(),
4309                TAG_SHOW_FILE_DIALOG
4310            );
4311            assert_eq!(read_u16(&bytes, ENVELOPE_LEN + 2).unwrap(), 48);
4312            assert_eq!(decode_batch(&bytes).unwrap(), vec![request]);
4313        }
4314
4315        let empty = Mutation::ShowFileDialog {
4316            request: u32::MAX,
4317            window: 0,
4318            kind: FileDialogKind::Save,
4319            default_filter: 0,
4320            title: "",
4321            directory: "",
4322            name: "",
4323            filters: "",
4324        };
4325        let mut encoder = BatchEncoder::default();
4326        encoder.encode(&empty).unwrap();
4327        assert_eq!(
4328            decode_batch(encoder.finish().unwrap()).unwrap(),
4329            vec![empty]
4330        );
4331
4332        let full = Mutation::ShowFileDialog {
4333            request: 1,
4334            window: 2,
4335            kind: FileDialogKind::OpenFile,
4336            default_filter: 3,
4337            title: "t",
4338            directory: "/d",
4339            name: "n",
4340            filters,
4341        };
4342        let mut encoder = BatchEncoder::default();
4343        encoder.encode(&full).unwrap();
4344        let bytes = encoder.finish().unwrap().to_vec();
4345        for raw_kind in [0_u16, 6, 9, u16::MAX] {
4346            let mut bad = bytes.clone();
4347            bad[ENVELOPE_LEN + 12..ENVELOPE_LEN + 14].copy_from_slice(&raw_kind.to_le_bytes());
4348            assert_eq!(
4349                decode_batch(&bad),
4350                Err(ProtocolError::InvalidValueKind(raw_kind))
4351            );
4352        }
4353        let mut short = bytes.clone();
4354        short[ENVELOPE_LEN + 2..ENVELOPE_LEN + 4].copy_from_slice(&44_u16.to_le_bytes());
4355        assert_eq!(
4356            decode_batch(&short),
4357            Err(ProtocolError::InvalidRecordLength)
4358        );
4359        for cut in 0..bytes.len() {
4360            assert!(
4361                decode_batch(&bytes[..cut]).is_err(),
4362                "a batch cut at {cut} bytes decoded"
4363            );
4364        }
4365
4366        let paths = "/Users/한글/문서.txt\0/tmp/b c.png";
4367        for (status, filter) in [
4368            (FileDialogStatus::Chosen, 1_u32),
4369            (FileDialogStatus::Cancelled, u32::MAX),
4370            (FileDialogStatus::Unavailable, u32::MAX),
4371        ] {
4372            let result = HostEvent {
4373                node_id: 0,
4374                handler_id: 0,
4375                payload: crate::EventPayload::FileDialogResult {
4376                    request: 41,
4377                    status,
4378                    filter,
4379                    paths: if status == FileDialogStatus::Chosen {
4380                        paths
4381                    } else {
4382                        ""
4383                    },
4384                },
4385            };
4386            let mut bytes = Vec::new();
4387            encode_event(&result, &mut bytes).unwrap();
4388            assert_eq!(read_u16(&bytes, 0).unwrap(), EVENT_FILE_DIALOG_RESULT);
4389            assert_eq!(
4390                usize::from(read_u16(&bytes, 2).unwrap()),
4391                FILE_DIALOG_RESULT_LEN
4392            );
4393            assert_eq!(decode_event(&bytes).unwrap(), result);
4394        }
4395
4396        let result = HostEvent {
4397            node_id: 0,
4398            handler_id: 0,
4399            payload: crate::EventPayload::FileDialogResult {
4400                request: 3,
4401                status: FileDialogStatus::Chosen,
4402                filter: 0,
4403                paths,
4404            },
4405        };
4406        let mut bytes = Vec::new();
4407        encode_event(&result, &mut bytes).unwrap();
4408        for raw_status in [0_u32, 4, 0x1_0000, u32::MAX] {
4409            let mut bad = bytes.clone();
4410            bad[20..24].copy_from_slice(&raw_status.to_le_bytes());
4411            assert_eq!(
4412                decode_event(&bad),
4413                Err(ProtocolError::InvalidValueKind(
4414                    raw_status.min(u32::from(u16::MAX)) as u16
4415                ))
4416            );
4417        }
4418        let mut wrong_length = bytes.clone();
4419        wrong_length[2..4].copy_from_slice(&32_u16.to_le_bytes());
4420        assert_eq!(
4421            decode_event(&wrong_length),
4422            Err(ProtocolError::InvalidRecordLength)
4423        );
4424        let mut not_utf8 = bytes.clone();
4425        let last = not_utf8.len() - 1;
4426        not_utf8[last] = 0xFF;
4427        assert_eq!(decode_event(&not_utf8), Err(ProtocolError::InvalidUtf8));
4428        for cut in 0..bytes.len() {
4429            assert!(
4430                decode_event(&bytes[..cut]).is_err(),
4431                "an event cut at {cut} bytes decoded"
4432            );
4433        }
4434    }
4435
4436    #[test]
4437    fn fr65_text_field_on_composition_change_delivers_bool() {
4438        use std::cell::RefCell;
4439        use std::rc::Rc;
4440        use std::sync::Arc;
4441        use std::sync::atomic::{AtomicUsize, Ordering};
4442
4443        let seen: Rc<RefCell<Vec<bool>>> = Rc::new(RefCell::new(Vec::new()));
4444        let calls = Arc::new(AtomicUsize::new(0));
4445        let calls_for_root = Arc::clone(&calls);
4446        let mut host = crate::ComposeHost::with_content(move || {
4447            let calls_inner = Arc::clone(&calls_for_root);
4448            crate::ui::TextField().on_composition_change(move |composing| {
4449                calls_inner.fetch_add(if composing { 10 } else { 1 }, Ordering::SeqCst);
4450            });
4451        });
4452        let init = host.rebuild().unwrap().to_vec();
4453        let mutations = decode_batch(&init).unwrap();
4454        let prop = mutations
4455            .iter()
4456            .find_map(|mutation| match mutation {
4457                Mutation::SetProp {
4458                    node_id,
4459                    property: PropertyKind::OnCompositionChange,
4460                    value: PropertyValue::Integer(handler),
4461                } => Some((*node_id, *handler as u64)),
4462                _ => None,
4463            })
4464            .expect("TextField with on_composition_change must emit OnCompositionChange");
4465        let mut wire = Vec::new();
4466        encode_event(
4467            &HostEvent {
4468                node_id: prop.0,
4469                handler_id: prop.1,
4470                payload: crate::EventPayload::CompositionChanged { composing: true },
4471            },
4472            &mut wire,
4473        )
4474        .unwrap();
4475        host.dispatch_event(&wire).unwrap();
4476        encode_event(
4477            &HostEvent {
4478                node_id: prop.0,
4479                handler_id: prop.1,
4480                payload: crate::EventPayload::CompositionChanged { composing: false },
4481            },
4482            &mut wire,
4483        )
4484        .unwrap();
4485        host.dispatch_event(&wire).unwrap();
4486        assert_eq!(calls.load(Ordering::SeqCst), 11);
4487        drop(seen);
4488    }
4489
4490    #[test]
4491    fn fr45_focus_gained_and_lost_pair() {
4492        use std::sync::Arc;
4493        use std::sync::Mutex;
4494
4495        let trace: Arc<Mutex<Vec<&'static str>>> = Arc::new(Mutex::new(Vec::new()));
4496        let trace_for_root = Arc::clone(&trace);
4497        let mut host = crate::ComposeHost::with_content(move || {
4498            let gained_trace = Arc::clone(&trace_for_root);
4499            let lost_trace = Arc::clone(&trace_for_root);
4500            crate::ui::TextField()
4501                .on_focus_gained(move || {
4502                    gained_trace.lock().unwrap().push("gained");
4503                })
4504                .on_focus_lost(move || {
4505                    lost_trace.lock().unwrap().push("lost");
4506                });
4507        });
4508        let init = host.rebuild().unwrap().to_vec();
4509        let mutations = decode_batch(&init).unwrap();
4510        let gained_prop = mutations
4511            .iter()
4512            .find_map(|mutation| match mutation {
4513                Mutation::SetProp {
4514                    node_id,
4515                    property: PropertyKind::OnFocusGained,
4516                    value: PropertyValue::Integer(handler),
4517                } => Some((*node_id, *handler as u64)),
4518                _ => None,
4519            })
4520            .expect("TextField with on_focus_gained must emit OnFocusGained");
4521        let lost_prop = mutations
4522            .iter()
4523            .find_map(|mutation| match mutation {
4524                Mutation::SetProp {
4525                    node_id,
4526                    property: PropertyKind::OnFocusLost,
4527                    value: PropertyValue::Integer(handler),
4528                } => Some((*node_id, *handler as u64)),
4529                _ => None,
4530            })
4531            .expect("TextField with on_focus_lost must emit OnFocusLost");
4532        assert_eq!(gained_prop.0, lost_prop.0);
4533        assert_eq!(PropertyKind::OnFocusGained as u16, 117);
4534
4535        let gained_event = HostEvent {
4536            node_id: gained_prop.0,
4537            handler_id: gained_prop.1,
4538            payload: crate::EventPayload::FocusGained,
4539        };
4540        let mut wire = Vec::new();
4541        encode_event(&gained_event, &mut wire).unwrap();
4542        assert_eq!(wire.len(), 16);
4543        assert_eq!(read_u16(&wire, 0).unwrap(), EVENT_FOCUS_GAINED);
4544        assert_eq!(EVENT_FOCUS_GAINED, 48);
4545        assert_eq!(decode_event(&wire).unwrap(), gained_event);
4546        host.dispatch_event(&wire).unwrap();
4547
4548        let lost_event = HostEvent {
4549            node_id: lost_prop.0,
4550            handler_id: lost_prop.1,
4551            payload: crate::EventPayload::FocusLost,
4552        };
4553        encode_event(&lost_event, &mut wire).unwrap();
4554        assert_eq!(decode_event(&wire).unwrap(), lost_event);
4555        host.dispatch_event(&wire).unwrap();
4556
4557        assert_eq!(*trace.lock().unwrap(), vec!["gained", "lost"]);
4558    }
4559
4560    #[test]
4561    fn fr45_key_up_follows_key_down() {
4562        use std::sync::Arc;
4563        use std::sync::Mutex;
4564
4565        // What each key event carried: which handler, the key, then the four modifiers.
4566        type KeyTrace = Vec<(&'static str, Key, bool, bool, bool, bool)>;
4567        let trace: Arc<Mutex<KeyTrace>> = Arc::new(Mutex::new(Vec::new()));
4568        let trace_for_root = Arc::clone(&trace);
4569        let mut host = crate::ComposeHost::with_content(move || {
4570            let down_trace = Arc::clone(&trace_for_root);
4571            let up_trace = Arc::clone(&trace_for_root);
4572            crate::ui::TextField()
4573                .on_key_down(move |event| {
4574                    down_trace.lock().unwrap().push((
4575                        "down",
4576                        event.key(),
4577                        event.shift_key(),
4578                        event.ctrl_key(),
4579                        event.alt_key(),
4580                        event.meta_key(),
4581                    ));
4582                })
4583                .on_key_up(move |event| {
4584                    up_trace.lock().unwrap().push((
4585                        "up",
4586                        event.key(),
4587                        event.shift_key(),
4588                        event.ctrl_key(),
4589                        event.alt_key(),
4590                        event.meta_key(),
4591                    ));
4592                });
4593        });
4594        let init = host.rebuild().unwrap().to_vec();
4595        let mutations = decode_batch(&init).unwrap();
4596        let down_prop = mutations
4597            .iter()
4598            .find_map(|mutation| match mutation {
4599                Mutation::SetProp {
4600                    node_id,
4601                    property: PropertyKind::OnKeyDown,
4602                    value: PropertyValue::Integer(handler),
4603                } => Some((*node_id, *handler as u64)),
4604                _ => None,
4605            })
4606            .expect("TextField with on_key_down must emit OnKeyDown");
4607        let up_prop = mutations
4608            .iter()
4609            .find_map(|mutation| match mutation {
4610                Mutation::SetProp {
4611                    node_id,
4612                    property: PropertyKind::OnKeyUp,
4613                    value: PropertyValue::Integer(handler),
4614                } => Some((*node_id, *handler as u64)),
4615                _ => None,
4616            })
4617            .expect("TextField with on_key_up must emit OnKeyUp");
4618        assert_eq!(down_prop.0, up_prop.0);
4619        assert_eq!(PropertyKind::OnKeyUp as u16, 118);
4620
4621        let down_event = HostEvent {
4622            node_id: down_prop.0,
4623            handler_id: down_prop.1,
4624            payload: crate::EventPayload::KeyDown {
4625                key: Key::Enter,
4626                shift_key: true,
4627                ctrl_key: false,
4628                alt_key: true,
4629                meta_key: false,
4630                repeat: false,
4631                code: 0x28,
4632                text: "Enter",
4633            },
4634        };
4635        let mut wire = Vec::new();
4636        encode_event(&down_event, &mut wire).unwrap();
4637        assert_eq!(decode_event(&wire).unwrap(), down_event);
4638        host.dispatch_event(&wire).unwrap();
4639
4640        let up_event = HostEvent {
4641            node_id: up_prop.0,
4642            handler_id: up_prop.1,
4643            payload: crate::EventPayload::KeyUp {
4644                key: Key::Enter,
4645                shift_key: true,
4646                ctrl_key: false,
4647                alt_key: true,
4648                meta_key: false,
4649                repeat: false,
4650                code: 0x28,
4651                text: "Enter",
4652            },
4653        };
4654        encode_event(&up_event, &mut wire).unwrap();
4655        assert_eq!(wire.len(), 37);
4656        assert_eq!(read_u16(&wire, 0).unwrap(), EVENT_KEY_UP);
4657        assert_eq!(EVENT_KEY_UP, 49);
4658        assert_eq!(decode_event(&wire).unwrap(), up_event);
4659        host.dispatch_event(&wire).unwrap();
4660
4661        assert_eq!(
4662            *trace.lock().unwrap(),
4663            vec![
4664                ("down", Key::Enter, true, false, true, false),
4665                ("up", Key::Enter, true, false, true, false),
4666            ]
4667        );
4668    }
4669
4670    #[test]
4671    fn fr45_unregistered_nodes_get_no_events() {
4672        let mut host = crate::ComposeHost::with_content(|| {
4673            crate::ui::TextField();
4674            crate::ui::Box().content(|| {});
4675        });
4676        let init = host.rebuild().unwrap().to_vec();
4677        let mutations = decode_batch(&init).unwrap();
4678        for mutation in &mutations {
4679            if let Mutation::SetProp { property, .. } = mutation {
4680                assert_ne!(
4681                    *property,
4682                    PropertyKind::OnFocusGained,
4683                    "unregistered node must not emit OnFocusGained"
4684                );
4685                assert_ne!(
4686                    *property,
4687                    PropertyKind::OnKeyUp,
4688                    "unregistered node must not emit OnKeyUp"
4689                );
4690            }
4691        }
4692    }
4693
4694    /// The keys menus, lists and overlays act on have their own name, appended after
4695    /// `Other` so no earlier tag moves, and each one survives the wire.
4696    #[test]
4697    fn fr12_named_keys_are_appended_and_round_trip() {
4698        let named = [
4699            (Key::Enter, 1u16, 0x28u32, "Enter"),
4700            (Key::Other, 2, 0x04, "a"),
4701            (Key::Escape, 3, 0x29, "Escape"),
4702            (Key::Tab, 4, 0x2B, "Tab"),
4703            (Key::Backspace, 5, 0x2A, "Backspace"),
4704            (Key::Delete, 6, 0x4C, "Delete"),
4705            (Key::ArrowUp, 7, 0x52, "ArrowUp"),
4706            (Key::ArrowDown, 8, 0x51, "ArrowDown"),
4707            (Key::ArrowLeft, 9, 0x50, "ArrowLeft"),
4708            (Key::ArrowRight, 10, 0x4F, "ArrowRight"),
4709            (Key::Home, 11, 0x4A, "Home"),
4710            (Key::End, 12, 0x4D, "End"),
4711            (Key::PageUp, 13, 0x4B, "PageUp"),
4712            (Key::PageDown, 14, 0x4E, "PageDown"),
4713        ];
4714        let mut wire = Vec::new();
4715        for (key, tag, code, text) in named {
4716            assert_eq!(Key::try_from(tag), Ok(key), "tag {tag} names {text}");
4717            let event = HostEvent {
4718                node_id: 4,
4719                handler_id: 9,
4720                payload: crate::EventPayload::KeyDown {
4721                    key,
4722                    shift_key: false,
4723                    ctrl_key: false,
4724                    alt_key: false,
4725                    meta_key: false,
4726                    repeat: false,
4727                    code,
4728                    text,
4729                },
4730            };
4731            encode_event(&event, &mut wire).unwrap();
4732            assert_eq!(read_u16(&wire, 16).unwrap(), tag);
4733            assert_eq!(decode_event(&wire).unwrap(), event);
4734        }
4735        assert_eq!(Key::try_from(15), Err(()));
4736    }
4737
4738    /// A drawer or a popup menu is a container, not a `Dialog` or a `Sheet`, so the
4739    /// Renderer does not dismiss it. Its own `on_key_down` sees Escape by its DOM key
4740    /// string and HID usage, and consuming it is the call's result.
4741    #[test]
4742    fn fr45_container_key_down_sees_escape_and_consumes_it() {
4743        use std::sync::{Arc, Mutex};
4744
4745        let seen: Arc<Mutex<Vec<(String, u32)>>> = Arc::new(Mutex::new(Vec::new()));
4746        let seen_for_root = Arc::clone(&seen);
4747        let mut host = crate::ComposeHost::with_content(move || {
4748            let seen = Arc::clone(&seen_for_root);
4749            crate::ui::Column()
4750                .on_key_down(move |event| {
4751                    seen.lock()
4752                        .unwrap()
4753                        .push((event.text().to_owned(), event.code()));
4754                    if event.text() == "Escape" {
4755                        event.consume();
4756                    }
4757                })
4758                .content(|| {
4759                    crate::ui::Text("session list");
4760                });
4761        });
4762        let init = host.rebuild().unwrap().to_vec();
4763        let mutations = decode_batch(&init).unwrap();
4764        let (node_id, handler_id) = mutations
4765            .iter()
4766            .find_map(|mutation| match mutation {
4767                Mutation::SetProp {
4768                    node_id,
4769                    property: PropertyKind::OnKeyDown,
4770                    value: PropertyValue::Integer(handler),
4771                } => Some((*node_id, *handler as u64)),
4772                _ => None,
4773            })
4774            .expect("a Column with on_key_down must emit OnKeyDown");
4775        let created_column = mutations.iter().any(|mutation| {
4776            matches!(
4777                mutation,
4778                Mutation::Create { node_id: id, widget: crate::WidgetKind::Column, .. } if *id == node_id
4779            )
4780        });
4781        assert!(
4782            created_column,
4783            "the key handler belongs to the Column itself"
4784        );
4785
4786        let mut wire = Vec::new();
4787        let mut press = |code: u32, text: &'static str| {
4788            encode_event(
4789                &HostEvent {
4790                    node_id,
4791                    handler_id,
4792                    payload: crate::EventPayload::KeyDown {
4793                        key: if code == 0x29 {
4794                            Key::Escape
4795                        } else {
4796                            Key::ArrowDown
4797                        },
4798                        shift_key: false,
4799                        ctrl_key: false,
4800                        alt_key: false,
4801                        meta_key: false,
4802                        repeat: false,
4803                        code,
4804                        text,
4805                    },
4806                },
4807                &mut wire,
4808            )
4809            .unwrap();
4810            host.dispatch_event(&wire).unwrap().1
4811        };
4812        assert_ne!(
4813            press(0x29, "Escape"),
4814            0,
4815            "Escape was consumed by the container"
4816        );
4817        assert_eq!(
4818            press(0x51, "ArrowDown"),
4819            0,
4820            "an unhandled key is left to the Renderer"
4821        );
4822
4823        assert_eq!(
4824            *seen.lock().unwrap(),
4825            vec![("Escape".to_owned(), 0x29), ("ArrowDown".to_owned(), 0x51)]
4826        );
4827    }
4828}