Skip to main content

compose_rust/
protocol.rs

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