Skip to main content

compose_rust/
protocol.rs

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