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