1#![forbid(unsafe_code)]
2
3use 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;
29pub const MAX_STRING_BYTES: usize = 4096;
32const FRAME_META_BYTES: usize = 8;
33pub const MAX_STATE_BODY_BYTES: usize = MAX_STATE_BYTES - FRAME_META_BYTES;
35pub 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
89pub enum BusKind {
90 Main,
91 Aux,
93}
94
95#[derive(Clone, Debug, Eq, PartialEq)]
97pub struct BusInfo {
98 pub layout: Bus,
99 pub kind: BusKind,
100 pub name: String,
102 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 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 Ack,
228 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 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
383pub 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,
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#[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;
1206const 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#[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 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 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 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 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#[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 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}