Skip to main content

control_seeds/
control_seeds.rs

1//! Writes one control-corpus seed per protocol message kind with the real
2//! encoder, or verifies a corpus directory. tools/fuzz_tests.py runs it.
3#![forbid(unsafe_code)]
4use std::{env, fs, path::Path};
5use vst_audio_protocol::{
6    Capabilities, DecodedFrame, EditPhase, EditorAction, EditorImage, EditorPlatform,
7    EditorScaling, EditorStatus, Frame, Message, ParentWindow, PluginFormat, ProcessingSetup,
8    SampleFormat, decode_frame, encode_frame,
9};
10
11fn messages() -> Vec<(&'static str, Message)> {
12    vec![
13        ("hello", Message::hello()),
14        (
15            "load",
16            Message::Load {
17                path: "plugin.vst3".into(),
18                format: PluginFormat::Vst3,
19                class_id: None,
20            },
21        ),
22        (
23            "configure",
24            Message::Configure {
25                rate: 48_000,
26                max_frames: 512,
27                in_channels: 2,
28                out_channels: 2,
29            },
30        ),
31        ("activate", Message::Activate),
32        (
33            "set-state",
34            Message::SetState {
35                blob: b"state".to_vec(),
36            },
37        ),
38        ("get-state", Message::GetState),
39        (
40            "open-editor",
41            Message::OpenEditor {
42                scaling: EditorScaling::Auto,
43            },
44        ),
45        ("shutdown", Message::Shutdown),
46        (
47            "scan",
48            Message::Scan {
49                path: "plugin.vst3".into(),
50            },
51        ),
52        (
53            "loaded",
54            Message::Loaded {
55                name: "plugin".into(),
56                vendor: "vendor".into(),
57                latency_frames: 0,
58                inputs: vec![],
59                outputs: vec![],
60            },
61        ),
62        ("classes", Message::Classes { entries: vec![] }),
63        ("latency-changed", Message::LatencyChanged { frames: 64 }),
64        (
65            "state",
66            Message::State {
67                blob: b"state".to_vec(),
68            },
69        ),
70        ("params-changed", Message::ParamsChanged),
71        (
72            "fault",
73            Message::Fault {
74                code: 1,
75                text: "fault".into(),
76            },
77        ),
78        ("ack", Message::Ack),
79        ("editor-closed", Message::EditorClosed),
80        ("close-editor", Message::CloseEditor),
81        (
82            "list-parameters",
83            Message::ListParameters { start: 0, count: 1 },
84        ),
85        (
86            "parameters",
87            Message::Parameters {
88                total: 0,
89                entries: vec![],
90            },
91        ),
92        ("set-parameter", Message::SetParameter { id: 1, value: 0.5 }),
93        (
94            "request-error",
95            Message::RequestError {
96                code: 1,
97                text: "error".into(),
98            },
99        ),
100        (
101            "prepare",
102            Message::Prepare(ProcessingSetup {
103                sample_rate: 48_000,
104                quantum: 512,
105                sample_format: SampleFormat::F32,
106                offline: false,
107                inputs: vec![],
108                outputs: vec![],
109            }),
110        ),
111        ("describe", Message::Describe),
112        ("features", Message::Features(Capabilities::default())),
113        ("editor", Message::Editor(EditorAction::Show)),
114        (
115            "editor-status",
116            Message::EditorStatus(EditorStatus {
117                platform: EditorPlatform::X11,
118                scaling: EditorScaling::Auto,
119                visible: true,
120                embedded: false,
121                width: 640,
122                height: 480,
123                scale: 1.0,
124                resize_supported: true,
125                scale_supported: true,
126            }),
127        ),
128        (
129            "editor-image",
130            Message::EditorImage(EditorImage {
131                width: 1,
132                height: 1,
133                rgba: vec![32, 64, 128, 255],
134            }),
135        ),
136        (
137            "sandboxed",
138            Message::Sandboxed {
139                mechanism: "test".into(),
140            },
141        ),
142        (
143            "open-editor-in",
144            Message::OpenEditorIn {
145                parent: ParentWindow::X11 { window: 1 },
146                scaling: EditorScaling::Auto,
147            },
148        ),
149        (
150            "editor-resized",
151            Message::EditorResized {
152                width: 640,
153                height: 480,
154            },
155        ),
156        (
157            "parameter-edit",
158            Message::ParameterEdit {
159                id: 1,
160                phase: EditPhase::Perform,
161                value: 0.5,
162            },
163        ),
164    ]
165}
166
167fn verify(directory: &Path) {
168    let mut kinds = Vec::new();
169    for entry in fs::read_dir(directory)
170        .expect("read corpus")
171        .map(Result::unwrap)
172    {
173        let path = entry.path();
174        if path.extension().and_then(|value| value.to_str()) != Some("bin") {
175            continue;
176        }
177        let bytes = fs::read(&path).expect("read seed");
178        let DecodedFrame::Known(frame) = decode_frame(&bytes).expect("seed must decode") else {
179            panic!("{} decoded as Unknown", path.display());
180        };
181        assert_eq!(
182            encode_frame(&frame).expect("re-encode seed"),
183            bytes,
184            "{}",
185            path.display()
186        );
187        kinds.push(frame.message.kind());
188    }
189    kinds.sort_unstable();
190    assert_eq!(kinds, (1_u16..=32).collect::<Vec<_>>());
191    println!(
192        "verified {} Known control seeds (kinds 1..=32)",
193        kinds.len()
194    );
195}
196
197fn main() {
198    let mut args = env::args_os().skip(1);
199    let mode = args.next().expect("mode");
200    let directory = args.next().expect("directory");
201    assert!(args.next().is_none(), "unexpected argument");
202    let directory = Path::new(&directory);
203    if mode == "generate" {
204        fs::create_dir_all(directory).expect("create output");
205        let messages = messages();
206        assert_eq!(messages.len(), 32);
207        for (expected_kind, (name, message)) in (1_u16..=32).zip(messages) {
208            assert_eq!(message.kind(), expected_kind, "{name}");
209            let bytes = encode_frame(&Frame {
210                flags: 0,
211                request: u32::from(expected_kind),
212                message,
213            })
214            .expect("encode seed");
215            assert!(
216                matches!(decode_frame(&bytes), Ok(DecodedFrame::Known(_))),
217                "{name}"
218            );
219            fs::write(
220                directory.join(format!("known-{expected_kind:02}-{name}.bin")),
221                bytes,
222            )
223            .expect("write seed");
224        }
225    } else if mode != "verify" {
226        panic!("mode must be generate or verify");
227    }
228    verify(directory);
229}