Skip to main content

vst_audio_protocol/
lib.rs

1#![forbid(unsafe_code)]
2
3//! Control framing and deterministic host supervision from ADR 0021.
4
5use std::collections::VecDeque;
6use std::fmt;
7use std::io::{self, Read, Write};
8use std::time::Duration;
9
10pub mod editor;
11pub mod layout;
12pub mod processing;
13pub use editor::{
14    EditorAction, EditorImage, EditorPlatform, EditorReply, EditorScaling, EditorStatus,
15    ParentWindow,
16};
17pub use processing::{BusConfiguration, Capabilities, ProcessingSetup, SampleFormat};
18pub mod state;
19#[derive(Clone, Debug, thiserror::Error)]
20#[error("{0}")]
21pub struct PluginError(pub String);
22
23pub const MAGIC: [u8; 4] = *b"SSRP";
24pub const PROTOCOL_MAJOR: u16 = 4;
25pub const PROTOCOL_MINOR: u16 = 1;
26pub const MAX_MESSAGE_BYTES: usize = 64 * 1024;
27pub const MAX_STATE_BYTES: usize = 64 * 1024 * 1024;
28/// Longest UTF-8 string in a message. Windows long paths are why it is above
29/// MAX_PATH.
30pub const MAX_STRING_BYTES: usize = 4096;
31const FRAME_META_BYTES: usize = 8;
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34#[repr(u16)]
35pub enum PluginFormat {
36    Vst2 = 2,
37    Vst3 = 3,
38}
39
40#[derive(Clone, Debug, Eq, PartialEq)]
41pub struct Bus {
42    pub channels: u16,
43    pub arrangement: u64,
44}
45
46/// What a declared audio bus is for.
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48pub enum BusKind {
49    Main,
50    /// Sidechain inputs and auxiliary outputs.
51    Aux,
52}
53
54/// A bus as the plugin declares it, before any is activated.
55#[derive(Clone, Debug, Eq, PartialEq)]
56pub struct BusInfo {
57    pub layout: Bus,
58    pub kind: BusKind,
59    /// The plugin's own name for the bus; may be empty.
60    pub name: String,
61    /// The plugin asks for this bus to be active unless the host decides otherwise.
62    pub default_active: bool,
63}
64
65impl BusInfo {
66    pub fn configuration(&self, active: bool) -> BusConfiguration {
67        BusConfiguration {
68            layout: self.layout.clone(),
69            active,
70        }
71    }
72}
73
74#[derive(Clone, Debug, Eq, PartialEq)]
75pub struct ClassInfo {
76    pub id: [u8; 16],
77    pub name: String,
78    pub vendor: String,
79    pub category: String,
80}
81
82#[derive(Clone, Debug, PartialEq)]
83pub struct Parameter {
84    pub id: u32,
85    pub name: String,
86    pub unit: String,
87    pub value: f64,
88    pub default: Option<f64>,
89    pub steps: Option<u32>,
90    pub automatable: bool,
91    pub read_only: bool,
92    pub hidden: bool,
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
96pub enum EditPhase {
97    Begin,
98    Perform,
99    End,
100}
101
102#[derive(Clone, Debug, PartialEq)]
103pub enum Message {
104    /// Sent before Hello only when an OS sandbox was requested and installed.
105    Sandboxed {
106        mechanism: String,
107    },
108    Hello {
109        magic: [u8; 4],
110        major: u16,
111        minor: u16,
112    },
113    Load {
114        path: String,
115        format: PluginFormat,
116        class_id: Option<[u8; 16]>,
117    },
118    Configure {
119        rate: u32,
120        max_frames: u32,
121        in_channels: u16,
122        out_channels: u16,
123    },
124    Editor(EditorAction),
125    EditorStatus(EditorStatus),
126    EditorImage(EditorImage),
127    Prepare(ProcessingSetup),
128    Describe,
129    Features(Capabilities),
130    Activate,
131    SetState {
132        blob: Vec<u8>,
133    },
134    GetState,
135    OpenEditor {
136        scaling: EditorScaling,
137    },
138    OpenEditorIn {
139        parent: ParentWindow,
140        scaling: EditorScaling,
141    },
142    CloseEditor,
143    ListParameters {
144        start: u32,
145        count: u16,
146    },
147    Parameters {
148        total: u32,
149        entries: Vec<Parameter>,
150    },
151    SetParameter {
152        id: u32,
153        value: f64,
154    },
155    RequestError {
156        code: u32,
157        text: String,
158    },
159    Shutdown,
160    Scan {
161        path: String,
162    },
163    Loaded {
164        name: String,
165        vendor: String,
166        latency_frames: u32,
167        inputs: Vec<BusInfo>,
168        outputs: Vec<BusInfo>,
169    },
170    Classes {
171        entries: Vec<ClassInfo>,
172    },
173    LatencyChanged {
174        frames: u32,
175    },
176    State {
177        blob: Vec<u8>,
178    },
179    ParamsChanged,
180    Fault {
181        code: u32,
182        text: String,
183    },
184    /// The reply to a request that returns no data (`Configure`, `Activate`,
185    /// `SetState`, `OpenEditor`). It carries the request's number.
186    Ack,
187    /// The editor window was closed by the user.
188    EditorClosed,
189    EditorResized {
190        width: u32,
191        height: u32,
192    },
193    ParameterEdit {
194        id: u32,
195        phase: EditPhase,
196        value: f64,
197    },
198}
199
200impl Message {
201    pub fn hello() -> Self {
202        Self::Hello {
203            magic: MAGIC,
204            major: PROTOCOL_MAJOR,
205            minor: PROTOCOL_MINOR,
206        }
207    }
208
209    /// The wire kind number, for diagnostics.
210    pub fn kind(&self) -> u16 {
211        match self {
212            Self::Sandboxed { .. } => 29,
213            Self::Hello { .. } => 1,
214            Self::Load { .. } => 2,
215            Self::Configure { .. } => 3,
216            Self::Editor(_) => 26,
217            Self::EditorStatus(_) => 27,
218            Self::EditorImage(_) => 28,
219            Self::Prepare(_) => 23,
220            Self::Describe => 24,
221            Self::Features(_) => 25,
222            Self::Activate => 4,
223            Self::SetState { .. } => 5,
224            Self::GetState => 6,
225            Self::OpenEditor { .. } => 7,
226            Self::OpenEditorIn { .. } => 30,
227            Self::CloseEditor => 18,
228            Self::ListParameters { .. } => 19,
229            Self::Parameters { .. } => 20,
230            Self::SetParameter { .. } => 21,
231            Self::RequestError { .. } => 22,
232            Self::Shutdown => 8,
233            Self::Scan { .. } => 9,
234            Self::Loaded { .. } => 10,
235            Self::Classes { .. } => 11,
236            Self::LatencyChanged { .. } => 12,
237            Self::State { .. } => 13,
238            Self::ParamsChanged => 14,
239            Self::Fault { .. } => 15,
240            Self::Ack => 16,
241            Self::EditorClosed => 17,
242            Self::EditorResized { .. } => 31,
243            Self::ParameterEdit { .. } => 32,
244        }
245    }
246}
247
248#[derive(Clone, Debug, PartialEq)]
249pub struct Frame {
250    pub flags: u16,
251    pub request: u32,
252    pub message: Message,
253}
254
255#[derive(Clone, Copy, Debug, Eq, PartialEq)]
256pub struct UnknownFrame {
257    pub kind: u16,
258    pub flags: u16,
259    pub request: u32,
260    pub body_bytes: usize,
261}
262
263#[derive(Clone, Debug, PartialEq)]
264pub enum DecodedFrame {
265    Known(Frame),
266    Unknown(UnknownFrame),
267}
268
269#[derive(Debug)]
270pub enum CodecError {
271    Io(io::Error),
272    Truncated,
273    InvalidLength,
274    TooLarge { length: usize, limit: usize },
275    StringTooLong,
276    InvalidUtf8,
277    InvalidValue,
278    WrongMagic,
279    WrongMajor { got: u16, expected: u16 },
280    TrailingBytes,
281}
282
283impl fmt::Display for CodecError {
284    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
285        write!(formatter, "{self:?}")
286    }
287}
288
289impl std::error::Error for CodecError {}
290
291impl From<io::Error> for CodecError {
292    fn from(error: io::Error) -> Self {
293        Self::Io(error)
294    }
295}
296
297pub fn write_frame(writer: &mut impl Write, frame: &Frame) -> Result<(), CodecError> {
298    let bytes = encode_frame(frame)?;
299    writer.write_all(&bytes)?;
300    Ok(())
301}
302
303pub fn encode_frame(frame: &Frame) -> Result<Vec<u8>, CodecError> {
304    let mut body = Vec::new();
305    encode_message(&frame.message, &mut body)?;
306    let length = FRAME_META_BYTES
307        .checked_add(body.len())
308        .ok_or(CodecError::InvalidLength)?;
309    let limit = length_limit(frame.message.kind());
310    if length > limit {
311        return Err(CodecError::TooLarge { length, limit });
312    }
313    let mut output = Vec::with_capacity(4 + length);
314    output.extend_from_slice(&(length as u32).to_le_bytes());
315    output.extend_from_slice(&frame.message.kind().to_le_bytes());
316    output.extend_from_slice(&frame.flags.to_le_bytes());
317    output.extend_from_slice(&frame.request.to_le_bytes());
318    output.extend_from_slice(&body);
319    Ok(output)
320}
321
322pub fn decode_frame(bytes: &[u8]) -> Result<DecodedFrame, CodecError> {
323    if bytes.len() < 12 {
324        return Err(CodecError::Truncated);
325    }
326    let length = u32::from_le_bytes(bytes[0..4].try_into().expect("four bytes")) as usize;
327    if length < FRAME_META_BYTES {
328        return Err(CodecError::InvalidLength);
329    }
330    if bytes.len() < 4 + length {
331        return Err(CodecError::Truncated);
332    }
333    if bytes.len() != 4 + length {
334        return Err(CodecError::TrailingBytes);
335    }
336    decode_parts(&bytes[4..12], &bytes[12..])
337}
338
339/// Reads one frame. Its 8-byte metadata is read first, and the declared length
340/// is rejected before allocating the body. `Ok(None)` is a clean EOF before a
341/// new frame; a partial frame is an error.
342pub fn read_frame(reader: &mut impl Read) -> Result<Option<DecodedFrame>, CodecError> {
343    let mut length_bytes = [0_u8; 4];
344    let mut first = [0_u8; 1];
345    match reader.read(&mut first) {
346        Ok(0) => return Ok(None),
347        Ok(_) => length_bytes[0] = first[0],
348        Err(error) => return Err(CodecError::Io(error)),
349    }
350    reader.read_exact(&mut length_bytes[1..]).map_err(map_eof)?;
351    let length = u32::from_le_bytes(length_bytes) as usize;
352    if length < FRAME_META_BYTES {
353        return Err(CodecError::InvalidLength);
354    }
355    let mut meta = [0_u8; FRAME_META_BYTES];
356    reader.read_exact(&mut meta).map_err(map_eof)?;
357    let kind = u16::from_le_bytes(meta[0..2].try_into().expect("two bytes"));
358    let limit = length_limit(kind);
359    if length > limit {
360        return Err(CodecError::TooLarge { length, limit });
361    }
362    let mut body = vec![0; length - FRAME_META_BYTES];
363    reader.read_exact(&mut body).map_err(map_eof)?;
364    decode_parts(&meta, &body).map(Some)
365}
366
367fn map_eof(error: io::Error) -> CodecError {
368    if error.kind() == io::ErrorKind::UnexpectedEof {
369        CodecError::Truncated
370    } else {
371        CodecError::Io(error)
372    }
373}
374
375fn length_limit(kind: u16) -> usize {
376    if kind == 28 {
377        return 16 * 1024 * 1024 + 16;
378    }
379    if matches!(kind, 5 | 13) {
380        MAX_STATE_BYTES
381    } else {
382        MAX_MESSAGE_BYTES
383    }
384}
385
386fn decode_parts(meta: &[u8], body: &[u8]) -> Result<DecodedFrame, CodecError> {
387    let kind = u16::from_le_bytes(meta[0..2].try_into().expect("two bytes"));
388    let flags = u16::from_le_bytes(meta[2..4].try_into().expect("two bytes"));
389    let request = u32::from_le_bytes(meta[4..8].try_into().expect("four bytes"));
390    let length = FRAME_META_BYTES + body.len();
391    let limit = length_limit(kind);
392    if length > limit {
393        return Err(CodecError::TooLarge { length, limit });
394    }
395    let Some(message) = decode_message(kind, body)? else {
396        return Ok(DecodedFrame::Unknown(UnknownFrame {
397            kind,
398            flags,
399            request,
400            body_bytes: body.len(),
401        }));
402    };
403    Ok(DecodedFrame::Known(Frame {
404        flags,
405        request,
406        message,
407    }))
408}
409
410fn encode_message(message: &Message, out: &mut Vec<u8>) -> Result<(), CodecError> {
411    match message {
412        Message::Sandboxed { mechanism } => put_string(out, mechanism)?,
413        Message::Hello {
414            magic,
415            major,
416            minor,
417        } => {
418            out.extend_from_slice(magic);
419            put_u16(out, *major);
420            put_u16(out, *minor);
421        }
422        Message::Load {
423            path,
424            format,
425            class_id,
426        } => {
427            put_string(out, path)?;
428            put_u16(out, *format as u16);
429            out.push(u8::from(class_id.is_some()));
430            if let Some(id) = class_id {
431                out.extend_from_slice(id);
432            }
433        }
434        Message::Configure {
435            rate,
436            max_frames,
437            in_channels,
438            out_channels,
439        } => {
440            put_u32(out, *rate);
441            put_u32(out, *max_frames);
442            put_u16(out, *in_channels);
443            put_u16(out, *out_channels);
444        }
445        Message::Activate
446        | Message::Describe
447        | Message::GetState
448        | Message::CloseEditor
449        | Message::Shutdown
450        | Message::ParamsChanged
451        | Message::Ack
452        | Message::EditorClosed => {}
453        Message::OpenEditor { scaling } => out.push(*scaling as u8),
454        Message::SetState { blob } | Message::State { blob } => out.extend_from_slice(blob),
455        Message::Scan { path } => put_string(out, path)?,
456        Message::Loaded {
457            name,
458            vendor,
459            latency_frames,
460            inputs,
461            outputs,
462        } => {
463            put_string(out, name)?;
464            put_string(out, vendor)?;
465            put_u32(out, *latency_frames);
466            put_bus_infos(out, inputs)?;
467            put_bus_infos(out, outputs)?;
468        }
469        Message::Classes { entries } => {
470            put_u16(
471                out,
472                u16::try_from(entries.len()).map_err(|_| CodecError::InvalidValue)?,
473            );
474            for entry in entries {
475                out.extend_from_slice(&entry.id);
476                put_string(out, &entry.name)?;
477                put_string(out, &entry.vendor)?;
478                put_string(out, &entry.category)?;
479            }
480        }
481        Message::LatencyChanged { frames } => put_u32(out, *frames),
482        Message::EditorResized { width, height } => {
483            if !(1..=8192).contains(width) || !(1..=8192).contains(height) {
484                return Err(CodecError::InvalidValue);
485            }
486            put_u32(out, *width);
487            put_u32(out, *height);
488        }
489        Message::Editor(action) => match action {
490            EditorAction::Show => out.push(0),
491            EditorAction::Hide => out.push(1),
492            EditorAction::Focus => out.push(2),
493            EditorAction::Resize { width, height } => {
494                out.push(3);
495                put_u32(out, *width);
496                put_u32(out, *height);
497            }
498            EditorAction::Scale(scale) => {
499                out.push(4);
500                put_u32(out, scale.to_bits());
501            }
502            EditorAction::Inspect => out.push(5),
503            EditorAction::Capture => out.push(6),
504        },
505        Message::OpenEditorIn { parent, scaling } => {
506            let (tag, handle) = match parent {
507                ParentWindow::Win32 { hwnd } => (0, hwnd),
508                ParentWindow::X11 { window } => (1, window),
509            };
510            out.push(tag);
511            put_u64(out, *handle);
512            out.push(*scaling as u8);
513        }
514        Message::EditorStatus(status) => {
515            out.push(status.platform as u8);
516            out.push(u8::from(status.visible));
517            out.push(u8::from(status.embedded));
518            put_u32(out, status.width);
519            put_u32(out, status.height);
520            put_u32(out, status.scale.to_bits());
521            out.push(u8::from(status.resize_supported));
522            out.push(u8::from(status.scale_supported));
523            out.push(status.scaling as u8);
524        }
525        Message::EditorImage(image) => {
526            if !image.valid() {
527                return Err(CodecError::InvalidValue);
528            }
529            put_u32(out, image.width);
530            put_u32(out, image.height);
531            out.extend_from_slice(&image.rgba);
532        }
533        Message::Prepare(setup) => {
534            if !setup.valid() {
535                return Err(CodecError::InvalidValue);
536            }
537            put_u32(out, setup.sample_rate);
538            put_u32(out, setup.quantum);
539            out.push(setup.sample_format as u8);
540            out.push(u8::from(setup.offline));
541            for buses in [&setup.inputs, &setup.outputs] {
542                out.push(buses.len() as u8);
543                for bus in buses {
544                    put_u16(out, bus.layout.channels);
545                    put_u64(out, bus.layout.arrangement);
546                    out.push(u8::from(bus.active));
547                }
548            }
549        }
550        Message::Features(caps) => {
551            out.push(u8::from(caps.f64));
552            put_u16(out, caps.input_event_buses);
553            put_u16(out, caps.output_event_buses);
554            out.push(u8::from(caps.editor));
555            out.push(u8::from(caps.sysex));
556            out.push(u8::from(caps.sample_accurate_parameters));
557        }
558        Message::ListParameters { start, count } => {
559            if *count == 0 || *count > 32 {
560                return Err(CodecError::InvalidValue);
561            }
562            put_u32(out, *start);
563            put_u16(out, *count);
564        }
565        Message::SetParameter { id, value } => {
566            if !value.is_finite() || !(0.0..=1.0).contains(value) {
567                return Err(CodecError::InvalidValue);
568            }
569            put_u32(out, *id);
570            put_u64(out, value.to_bits());
571        }
572        Message::ParameterEdit { id, phase, value } => {
573            if !value.is_finite() || !(0.0..=1.0).contains(value) {
574                return Err(CodecError::InvalidValue);
575            }
576            put_u32(out, *id);
577            out.push(match phase {
578                EditPhase::Begin => 0,
579                EditPhase::Perform => 1,
580                EditPhase::End => 2,
581            });
582            put_u64(out, value.to_bits());
583        }
584        Message::Parameters { total, entries } => {
585            if entries.len() > 32 || *total > 16384 {
586                return Err(CodecError::InvalidValue);
587            }
588            put_u32(out, *total);
589            put_u16(out, entries.len() as u16);
590            for entry in entries {
591                put_u32(out, entry.id);
592                out.push(
593                    u8::from(entry.automatable)
594                        | (u8::from(entry.read_only) << 1)
595                        | (u8::from(entry.hidden) << 2),
596                );
597                put_string(out, &entry.name)?;
598                put_string(out, &entry.unit)?;
599                put_u64(out, entry.value.to_bits());
600                out.push(u8::from(entry.default.is_some()));
601                if let Some(value) = entry.default {
602                    put_u64(out, value.to_bits());
603                }
604                out.push(u8::from(entry.steps.is_some()));
605                if let Some(steps) = entry.steps {
606                    put_u32(out, steps);
607                }
608            }
609        }
610        Message::RequestError { code, text } | Message::Fault { code, text } => {
611            put_u32(out, *code);
612            put_string(out, text)?;
613        }
614    }
615    Ok(())
616}
617
618fn decode_editor_scaling(value: u8) -> Result<EditorScaling, CodecError> {
619    match value {
620        0 => Ok(EditorScaling::Auto),
621        1 => Ok(EditorScaling::Plugin),
622        2 => Ok(EditorScaling::Stretch),
623        3 => Ok(EditorScaling::Off),
624        _ => Err(CodecError::InvalidValue),
625    }
626}
627
628fn decode_optional_editor_scaling(cursor: &mut Cursor<'_>) -> Result<EditorScaling, CodecError> {
629    if cursor.remaining() == 0 {
630        Ok(EditorScaling::Auto)
631    } else {
632        decode_editor_scaling(cursor.u8()?)
633    }
634}
635
636fn decode_message(kind: u16, body: &[u8]) -> Result<Option<Message>, CodecError> {
637    let mut cursor = Cursor::new(body);
638    let message = match kind {
639        1 => {
640            let magic = cursor.array::<4>()?;
641            let major = cursor.u16()?;
642            let minor = cursor.u16()?;
643            if magic != MAGIC {
644                return Err(CodecError::WrongMagic);
645            }
646            if major != PROTOCOL_MAJOR {
647                return Err(CodecError::WrongMajor {
648                    got: major,
649                    expected: PROTOCOL_MAJOR,
650                });
651            }
652            Message::Hello {
653                magic,
654                major,
655                minor,
656            }
657        }
658        29 => Message::Sandboxed {
659            mechanism: cursor.string()?,
660        },
661        2 => {
662            let path = cursor.string()?;
663            let format = match cursor.u16()? {
664                2 => PluginFormat::Vst2,
665                3 => PluginFormat::Vst3,
666                _ => return Err(CodecError::InvalidValue),
667            };
668            let class_id = match cursor.u8()? {
669                0 => None,
670                1 => Some(cursor.array::<16>()?),
671                _ => return Err(CodecError::InvalidValue),
672            };
673            Message::Load {
674                path,
675                format,
676                class_id,
677            }
678        }
679        3 => Message::Configure {
680            rate: cursor.u32()?,
681            max_frames: cursor.u32()?,
682            in_channels: cursor.u16()?,
683            out_channels: cursor.u16()?,
684        },
685        4 => Message::Activate,
686        5 => Message::SetState {
687            blob: body.to_vec(),
688        },
689        6 => Message::GetState,
690        7 => Message::OpenEditor {
691            scaling: decode_optional_editor_scaling(&mut cursor)?,
692        },
693        8 => Message::Shutdown,
694        9 => Message::Scan {
695            path: cursor.string()?,
696        },
697        10 => Message::Loaded {
698            name: cursor.string()?,
699            vendor: cursor.string()?,
700            latency_frames: cursor.u32()?,
701            inputs: cursor.bus_infos()?,
702            outputs: cursor.bus_infos()?,
703        },
704        11 => {
705            let count = cursor.u16()? as usize;
706            let mut entries = Vec::with_capacity(count);
707            for _ in 0..count {
708                entries.push(ClassInfo {
709                    id: cursor.array::<16>()?,
710                    name: cursor.string()?,
711                    vendor: cursor.string()?,
712                    category: cursor.string()?,
713                });
714            }
715            Message::Classes { entries }
716        }
717        12 => Message::LatencyChanged {
718            frames: cursor.u32()?,
719        },
720        13 => Message::State {
721            blob: body.to_vec(),
722        },
723        14 => Message::ParamsChanged,
724        15 => Message::Fault {
725            code: cursor.u32()?,
726            text: cursor.string()?,
727        },
728        16 => Message::Ack,
729        17 => Message::EditorClosed,
730        31 => {
731            let width = cursor.u32()?;
732            let height = cursor.u32()?;
733            if !(1..=8192).contains(&width) || !(1..=8192).contains(&height) {
734                return Err(CodecError::InvalidValue);
735            }
736            Message::EditorResized { width, height }
737        }
738        18 => Message::CloseEditor,
739        26 => Message::Editor(match cursor.u8()? {
740            0 => EditorAction::Show,
741            1 => EditorAction::Hide,
742            2 => EditorAction::Focus,
743            3 => EditorAction::Resize {
744                width: cursor.u32()?,
745                height: cursor.u32()?,
746            },
747            4 => EditorAction::Scale(f32::from_bits(cursor.u32()?)),
748            5 => EditorAction::Inspect,
749            6 => EditorAction::Capture,
750            _ => return Err(CodecError::InvalidValue),
751        }),
752        30 => {
753            let parent = match cursor.u8()? {
754                0 => ParentWindow::Win32 {
755                    hwnd: cursor.u64()?,
756                },
757                1 => ParentWindow::X11 {
758                    window: cursor.u64()?,
759                },
760                _ => return Err(CodecError::InvalidValue),
761            };
762            Message::OpenEditorIn {
763                parent,
764                scaling: decode_optional_editor_scaling(&mut cursor)?,
765            }
766        }
767        27 => {
768            let platform = match cursor.u8()? {
769                0 => EditorPlatform::Win32,
770                1 => EditorPlatform::Cocoa,
771                2 => EditorPlatform::X11,
772                3 => EditorPlatform::Wayland,
773                _ => return Err(CodecError::InvalidValue),
774            };
775            let visible = cursor.boolean()?;
776            let embedded = cursor.boolean()?;
777            let width = cursor.u32()?;
778            let height = cursor.u32()?;
779            let scale = f32::from_bits(cursor.u32()?);
780            let resize_supported = cursor.boolean()?;
781            let scale_supported = cursor.boolean()?;
782            Message::EditorStatus(EditorStatus {
783                platform,
784                scaling: decode_optional_editor_scaling(&mut cursor)?,
785                visible,
786                embedded,
787                width,
788                height,
789                scale,
790                resize_supported,
791                scale_supported,
792            })
793        }
794        28 => {
795            let width = cursor.u32()?;
796            let height = cursor.u32()?;
797            if width == 0
798                || height == 0
799                || width > 2048
800                || height > 2048
801                || cursor.remaining() != width as usize * height as usize * 4
802            {
803                return Err(CodecError::InvalidValue);
804            }
805            let rgba = cursor.take(cursor.remaining())?.to_vec();
806            Message::EditorImage(EditorImage {
807                width,
808                height,
809                rgba,
810            })
811        }
812        23 => {
813            let sample_rate = cursor.u32()?;
814            let quantum = cursor.u32()?;
815            let sample_format = match cursor.u8()? {
816                0 => SampleFormat::F32,
817                1 => SampleFormat::F64,
818                _ => return Err(CodecError::InvalidValue),
819            };
820            let offline = cursor.boolean()?;
821            let mut lists = Vec::new();
822            for _ in 0..2 {
823                let count = cursor.u8()?;
824                if count > 32 {
825                    return Err(CodecError::InvalidValue);
826                }
827                let mut buses = Vec::with_capacity(count as usize);
828                for _ in 0..count {
829                    buses.push(BusConfiguration {
830                        layout: Bus {
831                            channels: cursor.u16()?,
832                            arrangement: cursor.u64()?,
833                        },
834                        active: cursor.boolean()?,
835                    });
836                }
837                lists.push(buses);
838            }
839            let outputs = lists.pop().expect("two lists");
840            let inputs = lists.pop().expect("two lists");
841            let setup = ProcessingSetup {
842                sample_rate,
843                quantum,
844                sample_format,
845                offline,
846                inputs,
847                outputs,
848            };
849            if !setup.valid() {
850                return Err(CodecError::InvalidValue);
851            }
852            Message::Prepare(setup)
853        }
854        24 => Message::Describe,
855        25 => Message::Features(Capabilities {
856            f64: cursor.boolean()?,
857            input_event_buses: cursor.u16()?,
858            output_event_buses: cursor.u16()?,
859            editor: cursor.boolean()?,
860            sysex: cursor.boolean()?,
861            sample_accurate_parameters: cursor.boolean()?,
862        }),
863        19 => {
864            let start = cursor.u32()?;
865            let count = cursor.u16()?;
866            if count == 0 || count > 32 {
867                return Err(CodecError::InvalidValue);
868            }
869            Message::ListParameters { start, count }
870        }
871        20 => {
872            let total = cursor.u32()?;
873            let count = cursor.u16()?;
874            if total > 16384 || count > 32 {
875                return Err(CodecError::InvalidValue);
876            }
877            let mut entries = Vec::with_capacity(count as usize);
878            for _ in 0..count {
879                let id = cursor.u32()?;
880                let flags = cursor.u8()?;
881                if flags & !7 != 0 {
882                    return Err(CodecError::InvalidValue);
883                }
884                let name = cursor.string()?;
885                let unit = cursor.string()?;
886                let value = cursor.normalized()?;
887                let default = match cursor.u8()? {
888                    0 => None,
889                    1 => Some(cursor.normalized()?),
890                    _ => return Err(CodecError::InvalidValue),
891                };
892                let steps = match cursor.u8()? {
893                    0 => None,
894                    1 => Some(cursor.u32()?),
895                    _ => return Err(CodecError::InvalidValue),
896                };
897                entries.push(Parameter {
898                    automatable: flags & 1 != 0,
899                    read_only: flags & 2 != 0,
900                    hidden: flags & 4 != 0,
901                    id,
902                    name,
903                    unit,
904                    value,
905                    default,
906                    steps,
907                });
908            }
909            Message::Parameters { total, entries }
910        }
911        21 => Message::SetParameter {
912            id: cursor.u32()?,
913            value: cursor.normalized()?,
914        },
915        22 => Message::RequestError {
916            code: cursor.u32()?,
917            text: cursor.string()?,
918        },
919        32 => Message::ParameterEdit {
920            id: cursor.u32()?,
921            phase: match cursor.u8()? {
922                0 => EditPhase::Begin,
923                1 => EditPhase::Perform,
924                2 => EditPhase::End,
925                _ => return Err(CodecError::InvalidValue),
926            },
927            value: cursor.normalized()?,
928        },
929        _ => return Ok(None),
930    };
931    if !matches!(kind, 5 | 13) && cursor.remaining() != 0 {
932        return Err(CodecError::TrailingBytes);
933    }
934    Ok(Some(message))
935}
936
937fn put_u16(out: &mut Vec<u8>, value: u16) {
938    out.extend_from_slice(&value.to_le_bytes());
939}
940fn put_u32(out: &mut Vec<u8>, value: u32) {
941    out.extend_from_slice(&value.to_le_bytes());
942}
943fn put_u64(out: &mut Vec<u8>, value: u64) {
944    out.extend_from_slice(&value.to_le_bytes());
945}
946
947fn put_string(out: &mut Vec<u8>, value: &str) -> Result<(), CodecError> {
948    if value.len() > MAX_STRING_BYTES {
949        return Err(CodecError::StringTooLong);
950    }
951    put_u16(out, value.len() as u16);
952    out.extend_from_slice(value.as_bytes());
953    Ok(())
954}
955
956fn put_bus_infos(out: &mut Vec<u8>, buses: &[BusInfo]) -> Result<(), CodecError> {
957    put_u16(
958        out,
959        u16::try_from(buses.len()).map_err(|_| CodecError::InvalidValue)?,
960    );
961    for bus in buses {
962        put_u16(out, bus.layout.channels);
963        put_u64(out, bus.layout.arrangement);
964        out.push(match bus.kind {
965            BusKind::Main => 0,
966            BusKind::Aux => 1,
967        });
968        out.push(u8::from(bus.default_active));
969        put_string(out, &bus.name)?;
970    }
971    Ok(())
972}
973
974struct Cursor<'a> {
975    bytes: &'a [u8],
976    position: usize,
977}
978
979impl<'a> Cursor<'a> {
980    fn new(bytes: &'a [u8]) -> Self {
981        Self { bytes, position: 0 }
982    }
983    fn remaining(&self) -> usize {
984        self.bytes.len() - self.position
985    }
986    fn take(&mut self, count: usize) -> Result<&'a [u8], CodecError> {
987        let end = self
988            .position
989            .checked_add(count)
990            .ok_or(CodecError::Truncated)?;
991        let value = self
992            .bytes
993            .get(self.position..end)
994            .ok_or(CodecError::Truncated)?;
995        self.position = end;
996        Ok(value)
997    }
998    fn array<const N: usize>(&mut self) -> Result<[u8; N], CodecError> {
999        Ok(self.take(N)?.try_into().expect("exact length"))
1000    }
1001    fn boolean(&mut self) -> Result<bool, CodecError> {
1002        match self.u8()? {
1003            0 => Ok(false),
1004            1 => Ok(true),
1005            _ => Err(CodecError::InvalidValue),
1006        }
1007    }
1008    fn u8(&mut self) -> Result<u8, CodecError> {
1009        Ok(self.take(1)?[0])
1010    }
1011    fn u16(&mut self) -> Result<u16, CodecError> {
1012        Ok(u16::from_le_bytes(self.array()?))
1013    }
1014    fn u32(&mut self) -> Result<u32, CodecError> {
1015        Ok(u32::from_le_bytes(self.array()?))
1016    }
1017    fn u64(&mut self) -> Result<u64, CodecError> {
1018        Ok(u64::from_le_bytes(self.array()?))
1019    }
1020    fn normalized(&mut self) -> Result<f64, CodecError> {
1021        let value = f64::from_bits(self.u64()?);
1022        if value.is_finite() && (0.0..=1.0).contains(&value) {
1023            Ok(value)
1024        } else {
1025            Err(CodecError::InvalidValue)
1026        }
1027    }
1028    fn string(&mut self) -> Result<String, CodecError> {
1029        let length = self.u16()? as usize;
1030        if length > MAX_STRING_BYTES {
1031            return Err(CodecError::StringTooLong);
1032        }
1033        String::from_utf8(self.take(length)?.to_vec()).map_err(|_| CodecError::InvalidUtf8)
1034    }
1035    fn bus_infos(&mut self) -> Result<Vec<BusInfo>, CodecError> {
1036        let count = self.u16()? as usize;
1037        let mut buses = Vec::with_capacity(count);
1038        for _ in 0..count {
1039            let layout = Bus {
1040                channels: self.u16()?,
1041                arrangement: self.u64()?,
1042            };
1043            let kind = match self.u8()? {
1044                0 => BusKind::Main,
1045                1 => BusKind::Aux,
1046                _ => return Err(CodecError::InvalidValue),
1047            };
1048            let flags = self.u8()?;
1049            if flags & !1 != 0 {
1050                return Err(CodecError::InvalidValue);
1051            }
1052            buses.push(BusInfo {
1053                layout,
1054                kind,
1055                name: self.string()?,
1056                default_active: flags & 1 != 0,
1057            });
1058        }
1059        Ok(buses)
1060    }
1061}
1062
1063#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1064pub enum WatchState {
1065    Starting,
1066    Loading,
1067    Configuring,
1068    Running,
1069    Stopping,
1070    /// Stopped on request; it is never restarted.
1071    Stopped,
1072    Backoff,
1073    Disabled,
1074}
1075
1076#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1077pub enum KillReason {
1078    HelloTimeout,
1079    LoadTimeout,
1080    ConfigureTimeout,
1081    HeartbeatTimeout,
1082    RequestTimeout,
1083    StopTimeout,
1084}
1085
1086#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1087pub enum DisableReason {
1088    ThreeFailuresInTenMinutes,
1089}
1090
1091/// What the caller does next. `Kill`, a crash's `EnterFallback` and `Disable`
1092/// all leave the engine on the fallback path until a later `LeaveFallback`.
1093/// `Disable` also ends the process if it is still alive (the third failure can
1094/// be a hang).
1095#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1096pub enum Action {
1097    Kill { reason: KillReason },
1098    Spawn,
1099    Disable { reason: DisableReason },
1100    EnterFallback,
1101    LeaveFallback,
1102    SaveStateNow,
1103    None,
1104}
1105
1106#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1107pub enum Received {
1108    Hello,
1109    Loaded,
1110    Configured,
1111    Response { request: u32 },
1112    ParamsChanged,
1113    EditorClosed,
1114}
1115
1116#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1117pub enum RequestKind {
1118    StateSave,
1119    Editor,
1120}
1121
1122const STARTING_MS: u64 = 5_000;
1123const LOADING_MS: u64 = 60_000;
1124const CONFIGURING_MS: u64 = 10_000;
1125const HEARTBEAT_MS: u64 = 500;
1126/// A longer gap between two heartbeat observations means the host itself (or
1127/// the whole machine) was not running, so it is not counted as silence.
1128const OBSERVER_GAP_MS: u64 = HEARTBEAT_MS / 2;
1129const REQUEST_MS: u64 = 10_000;
1130const STOPPING_MS: u64 = 2_000;
1131const PARAM_QUIET_MS: u64 = 2_000;
1132const FAILURE_WINDOW_MS: u64 = 10 * 60 * 1_000;
1133const BACKOFF_MS: [u64; 3] = [500, 2_000, 8_000];
1134
1135#[derive(Debug)]
1136pub struct HostWatch {
1137    state: WatchState,
1138    entered_at: u64,
1139    heartbeat_value: u64,
1140    heartbeat_at: u64,
1141    observed_at: u64,
1142    pending_requests: Vec<(u32, u64)>,
1143    params_changed_at: Option<u64>,
1144    missed_blocks: u8,
1145    fallback: bool,
1146    failures: VecDeque<u64>,
1147    backoff_attempt: usize,
1148    spawn_at: Option<u64>,
1149}
1150
1151impl HostWatch {
1152    pub fn new(now_ms: u64) -> Self {
1153        Self {
1154            state: WatchState::Starting,
1155            entered_at: now_ms,
1156            heartbeat_value: 0,
1157            heartbeat_at: now_ms,
1158            observed_at: now_ms,
1159            pending_requests: Vec::new(),
1160            params_changed_at: None,
1161            missed_blocks: 0,
1162            fallback: false,
1163            failures: VecDeque::with_capacity(3),
1164            backoff_attempt: 0,
1165            spawn_at: None,
1166        }
1167    }
1168
1169    pub fn state(&self) -> WatchState {
1170        self.state
1171    }
1172
1173    pub fn received(&mut self, now_ms: u64, message: Received) -> Action {
1174        match message {
1175            Received::Hello if self.state == WatchState::Starting => {
1176                self.enter(WatchState::Loading, now_ms)
1177            }
1178            Received::Loaded if self.state == WatchState::Loading => {
1179                self.enter(WatchState::Configuring, now_ms)
1180            }
1181            Received::Configured if self.state == WatchState::Configuring => {
1182                self.enter(WatchState::Running, now_ms);
1183                self.heartbeat_at = now_ms;
1184                self.observed_at = now_ms;
1185                return Action::SaveStateNow;
1186            }
1187            Received::Response { request } => {
1188                self.pending_requests.retain(|(id, _)| *id != request);
1189            }
1190            Received::ParamsChanged => self.params_changed_at = Some(now_ms),
1191            Received::EditorClosed => return Action::SaveStateNow,
1192            _ => {}
1193        }
1194        Action::None
1195    }
1196
1197    /// Silence is counted only across time the host was observing: when the
1198    /// host polls late, the worker may have been frozen together with it
1199    /// (a stalled machine), which is no evidence that the worker stopped.
1200    pub fn heartbeat(&mut self, now_ms: u64, value: u64) -> Action {
1201        let gap = now_ms.saturating_sub(self.observed_at);
1202        if gap > OBSERVER_GAP_MS {
1203            self.heartbeat_at = self.heartbeat_at.saturating_add(gap).min(now_ms);
1204        }
1205        self.observed_at = now_ms;
1206        if value != self.heartbeat_value {
1207            self.heartbeat_value = value;
1208            self.heartbeat_at = now_ms;
1209        }
1210        Action::None
1211    }
1212
1213    pub fn request_sent(&mut self, now_ms: u64, request: u32, _kind: RequestKind) -> Action {
1214        self.pending_requests.retain(|(id, _)| *id != request);
1215        self.pending_requests.push((request, now_ms));
1216        Action::None
1217    }
1218
1219    pub fn block_delivered(&mut self) -> Action {
1220        self.missed_blocks = 0;
1221        if self.fallback {
1222            self.fallback = false;
1223            Action::LeaveFallback
1224        } else {
1225            Action::None
1226        }
1227    }
1228
1229    pub fn block_missed(&mut self) -> Action {
1230        self.missed_blocks = self.missed_blocks.saturating_add(1);
1231        if self.missed_blocks == 3 && !self.fallback {
1232            self.fallback = true;
1233            Action::EnterFallback
1234        } else {
1235            Action::None
1236        }
1237    }
1238
1239    pub fn shutdown(&mut self, now_ms: u64) -> Action {
1240        self.enter(WatchState::Stopping, now_ms);
1241        Action::SaveStateNow
1242    }
1243
1244    pub fn process_exited(&mut self, now_ms: u64, _exit_code: i32) -> Action {
1245        match self.state {
1246            WatchState::Backoff | WatchState::Disabled | WatchState::Stopped => Action::None,
1247            WatchState::Stopping => {
1248                self.enter(WatchState::Stopped, now_ms);
1249                Action::None
1250            }
1251            _ => self.failure(now_ms, None),
1252        }
1253    }
1254
1255    pub fn tick(&mut self, now_ms: u64) -> Action {
1256        if self.state == WatchState::Backoff {
1257            if self.spawn_at.is_some_and(|deadline| now_ms >= deadline) {
1258                self.enter(WatchState::Starting, now_ms);
1259                self.spawn_at = None;
1260                return Action::Spawn;
1261            }
1262            return Action::None;
1263        }
1264        if matches!(self.state, WatchState::Disabled | WatchState::Stopped) {
1265            return Action::None;
1266        }
1267        if self.state == WatchState::Running {
1268            if now_ms.saturating_sub(self.heartbeat_at) >= HEARTBEAT_MS {
1269                return self.failure(now_ms, Some(KillReason::HeartbeatTimeout));
1270            }
1271            if self
1272                .pending_requests
1273                .iter()
1274                .any(|(_, sent)| now_ms.saturating_sub(*sent) >= REQUEST_MS)
1275            {
1276                return self.failure(now_ms, Some(KillReason::RequestTimeout));
1277            }
1278            if self
1279                .params_changed_at
1280                .is_some_and(|changed| now_ms.saturating_sub(changed) >= PARAM_QUIET_MS)
1281            {
1282                self.params_changed_at = None;
1283                return Action::SaveStateNow;
1284            }
1285            return Action::None;
1286        }
1287        let (deadline, reason) = match self.state {
1288            WatchState::Starting => (STARTING_MS, KillReason::HelloTimeout),
1289            WatchState::Loading => (LOADING_MS, KillReason::LoadTimeout),
1290            WatchState::Configuring => (CONFIGURING_MS, KillReason::ConfigureTimeout),
1291            WatchState::Stopping => {
1292                // A host that will not stop is killed; it was leaving anyway,
1293                // so it is not counted as a failure and is not restarted.
1294                if now_ms.saturating_sub(self.entered_at) >= STOPPING_MS {
1295                    self.enter(WatchState::Stopped, now_ms);
1296                    return Action::Kill {
1297                        reason: KillReason::StopTimeout,
1298                    };
1299                }
1300                return Action::None;
1301            }
1302            _ => return Action::None,
1303        };
1304        if now_ms.saturating_sub(self.entered_at) >= deadline {
1305            self.failure(now_ms, Some(reason))
1306        } else {
1307            Action::None
1308        }
1309    }
1310
1311    fn enter(&mut self, state: WatchState, now_ms: u64) {
1312        self.state = state;
1313        self.entered_at = now_ms;
1314    }
1315
1316    fn failure(&mut self, now_ms: u64, kill: Option<KillReason>) -> Action {
1317        while self
1318            .failures
1319            .front()
1320            .is_some_and(|time| now_ms.saturating_sub(*time) > FAILURE_WINDOW_MS)
1321        {
1322            self.failures.pop_front();
1323        }
1324        self.failures.push_back(now_ms);
1325        if self.failures.len() >= 3 {
1326            self.state = WatchState::Disabled;
1327            self.pending_requests.clear();
1328            self.fallback = true;
1329            return Action::Disable {
1330                reason: DisableReason::ThreeFailuresInTenMinutes,
1331            };
1332        }
1333        // The wait grows with each failure and starts again at 0.5 s once no
1334        // other failure is left inside the window.
1335        if self.failures.len() == 1 {
1336            self.backoff_attempt = 0;
1337        }
1338        let delay = BACKOFF_MS[self.backoff_attempt.min(BACKOFF_MS.len() - 1)];
1339        self.backoff_attempt = (self.backoff_attempt + 1).min(BACKOFF_MS.len() - 1);
1340        self.state = WatchState::Backoff;
1341        self.entered_at = now_ms;
1342        self.spawn_at = Some(now_ms.saturating_add(delay));
1343        self.pending_requests.clear();
1344        self.params_changed_at = None;
1345        self.missed_blocks = 0;
1346        self.fallback = true;
1347        if let Some(reason) = kill {
1348            Action::Kill { reason }
1349        } else {
1350            Action::EnterFallback
1351        }
1352    }
1353}
1354
1355/// Two committed state blobs. A failed restore of the newest blob selects the
1356/// previous blob without destroying either copy.
1357#[derive(Clone, Debug, Default, Eq, PartialEq)]
1358pub struct StateHistory {
1359    current: Option<Vec<u8>>,
1360    previous: Option<Vec<u8>>,
1361    restoring_current: bool,
1362}
1363
1364impl StateHistory {
1365    pub fn new() -> Self {
1366        Self::default()
1367    }
1368    pub fn save(&mut self, blob: Vec<u8>) {
1369        if self.current.as_ref() == Some(&blob) {
1370            return;
1371        }
1372        self.previous = self.current.replace(blob);
1373        self.restoring_current = false;
1374    }
1375    pub fn begin_restore(&mut self) -> Option<&[u8]> {
1376        self.restoring_current = self.current.is_some();
1377        self.current.as_deref()
1378    }
1379    pub fn restore_succeeded(&mut self) {
1380        self.restoring_current = false;
1381    }
1382    pub fn crash_after_restore(&mut self) -> Option<&[u8]> {
1383        if self.restoring_current {
1384            self.restoring_current = false;
1385            self.previous.as_deref()
1386        } else {
1387            self.current.as_deref()
1388        }
1389    }
1390    /// The state a respawned worker restores: the newest, unless the worker
1391    /// failed right after restoring the newest, then the one before. Marks the
1392    /// chosen state as being restored, so a failure right after it moves on.
1393    pub fn respawn_state(&mut self) -> Option<Vec<u8>> {
1394        let after_crash = self.crash_after_restore().map(<[u8]>::to_vec);
1395        if after_crash.as_deref() == self.current() {
1396            self.begin_restore().map(<[u8]>::to_vec)
1397        } else {
1398            after_crash
1399        }
1400    }
1401    pub fn current(&self) -> Option<&[u8]> {
1402        self.current.as_deref()
1403    }
1404    pub fn previous(&self) -> Option<&[u8]> {
1405        self.previous.as_deref()
1406    }
1407}
1408
1409pub fn duration_ms(duration: Duration) -> u64 {
1410    u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
1411}
1412
1413#[cfg(test)]
1414mod state_history_tests {
1415    use super::StateHistory;
1416
1417    #[test]
1418    fn respawn_uses_current_then_previous_after_a_failed_restore() {
1419        let mut history = StateHistory::new();
1420        assert_eq!(history.respawn_state(), None);
1421        history.save(vec![1]);
1422        history.save(vec![2]);
1423        assert_eq!(history.respawn_state(), Some(vec![2]));
1424        assert_eq!(history.respawn_state(), Some(vec![1]));
1425        history.save(vec![3]);
1426        assert_eq!(history.respawn_state(), Some(vec![3]));
1427    }
1428}
1429
1430#[cfg(test)]
1431mod editor_scaling_tests {
1432    use super::*;
1433
1434    fn round_trip(message: Message) -> Result<Message, CodecError> {
1435        let encoded = encode_frame(&Frame {
1436            flags: 0,
1437            request: 1,
1438            message,
1439        })?;
1440        let DecodedFrame::Known(decoded) = decode_frame(&encoded)? else {
1441            panic!("known message decoded as unknown");
1442        };
1443        Ok(decoded.message)
1444    }
1445
1446    fn scaling_modes() -> [EditorScaling; 4] {
1447        [
1448            EditorScaling::Auto,
1449            EditorScaling::Plugin,
1450            EditorScaling::Stretch,
1451            EditorScaling::Off,
1452        ]
1453    }
1454
1455    #[test]
1456    fn editor_open_scaling_round_trips_and_accepts_old_bodies() {
1457        for scaling in scaling_modes() {
1458            assert_eq!(
1459                round_trip(Message::OpenEditor { scaling }).unwrap(),
1460                Message::OpenEditor { scaling }
1461            );
1462            for parent in [
1463                ParentWindow::Win32 { hwnd: 42 },
1464                ParentWindow::X11 { window: 43 },
1465            ] {
1466                assert_eq!(
1467                    round_trip(Message::OpenEditorIn { parent, scaling }).unwrap(),
1468                    Message::OpenEditorIn { parent, scaling }
1469                );
1470            }
1471        }
1472
1473        let old_open = [8, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0];
1474        assert!(matches!(
1475            decode_frame(&old_open).unwrap(),
1476            DecodedFrame::Known(Frame {
1477                message: Message::OpenEditor {
1478                    scaling: EditorScaling::Auto
1479                },
1480                ..
1481            })
1482        ));
1483
1484        let mut old_embedded = encode_frame(&Frame {
1485            flags: 0,
1486            request: 1,
1487            message: Message::OpenEditorIn {
1488                parent: ParentWindow::X11 { window: 44 },
1489                scaling: EditorScaling::Auto,
1490            },
1491        })
1492        .unwrap();
1493        old_embedded.pop();
1494        old_embedded[0..4].copy_from_slice(&17_u32.to_le_bytes());
1495        assert!(matches!(
1496            decode_frame(&old_embedded).unwrap(),
1497            DecodedFrame::Known(Frame {
1498                message: Message::OpenEditorIn {
1499                    parent: ParentWindow::X11 { window: 44 },
1500                    scaling: EditorScaling::Auto
1501                },
1502                ..
1503            })
1504        ));
1505    }
1506
1507    #[test]
1508    fn editor_open_rejects_unknown_scaling() {
1509        let mut open = encode_frame(&Frame {
1510            flags: 0,
1511            request: 1,
1512            message: Message::OpenEditor {
1513                scaling: EditorScaling::Auto,
1514            },
1515        })
1516        .unwrap();
1517        *open.last_mut().unwrap() = 4;
1518        assert!(matches!(decode_frame(&open), Err(CodecError::InvalidValue)));
1519
1520        let mut embedded = encode_frame(&Frame {
1521            flags: 0,
1522            request: 1,
1523            message: Message::OpenEditorIn {
1524                parent: ParentWindow::Win32 { hwnd: 42 },
1525                scaling: EditorScaling::Auto,
1526            },
1527        })
1528        .unwrap();
1529        *embedded.last_mut().unwrap() = u8::MAX;
1530        assert!(matches!(
1531            decode_frame(&embedded),
1532            Err(CodecError::InvalidValue)
1533        ));
1534    }
1535
1536    #[test]
1537    fn editor_status_scaling_round_trips_and_accepts_old_body() {
1538        for scaling in scaling_modes() {
1539            let status = EditorStatus {
1540                platform: EditorPlatform::Wayland,
1541                scaling,
1542                visible: true,
1543                embedded: false,
1544                width: 320,
1545                height: 200,
1546                scale: 2.0,
1547                resize_supported: true,
1548                scale_supported: true,
1549            };
1550            assert_eq!(
1551                round_trip(Message::EditorStatus(status.clone())).unwrap(),
1552                Message::EditorStatus(status)
1553            );
1554        }
1555
1556        let mut old = encode_frame(&Frame {
1557            flags: 0,
1558            request: 1,
1559            message: Message::EditorStatus(EditorStatus {
1560                platform: EditorPlatform::X11,
1561                scaling: EditorScaling::Auto,
1562                visible: true,
1563                embedded: false,
1564                width: 640,
1565                height: 480,
1566                scale: 1.0,
1567                resize_supported: true,
1568                scale_supported: false,
1569            }),
1570        })
1571        .unwrap();
1572        old.pop();
1573        old[0..4].copy_from_slice(&25_u32.to_le_bytes());
1574        let DecodedFrame::Known(Frame {
1575            message: Message::EditorStatus(status),
1576            ..
1577        }) = decode_frame(&old).unwrap()
1578        else {
1579            panic!("expected editor status");
1580        };
1581        assert_eq!(status.scaling, EditorScaling::Auto);
1582    }
1583
1584    #[test]
1585    fn editor_status_rejects_unknown_scaling() {
1586        let mut encoded = encode_frame(&Frame {
1587            flags: 0,
1588            request: 1,
1589            message: Message::EditorStatus(EditorStatus {
1590                platform: EditorPlatform::Win32,
1591                scaling: EditorScaling::Auto,
1592                visible: true,
1593                embedded: false,
1594                width: 320,
1595                height: 200,
1596                scale: 1.0,
1597                resize_supported: true,
1598                scale_supported: true,
1599            }),
1600        })
1601        .unwrap();
1602        *encoded.last_mut().unwrap() = 9;
1603        assert!(matches!(
1604            decode_frame(&encoded),
1605            Err(CodecError::InvalidValue)
1606        ));
1607    }
1608}