Skip to main content

vst_audio_protocol/
lib.rs

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