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