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