mirror of
https://github.com/storytold/soundcraft.git
synced 2026-10-14 14:26:46 -04:00
control server: cap request lines at about 86 MiB, not 256 MiB
256 MiB per line times 16 connections let a peer make the app buffer about 4 GiB. 16 MiB would break a legitimate request: mix.insert_state carries a plugin's state as base64 inline, and the engine accepts states of up to b64::MAX_DECODED_BYTES (64 MiB), about 85.3 MiB of base64. Nothing else sent to the app is that large (screenshots are replies, which this doesn't bound). So the cap is that base64 length plus 1 MiB for the JSON around it, derived from the same constant so the two stay in step; a compile-time check keeps it under 96 MiB, about 1.5 GiB across all 16 connections. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
4fd015f836
commit
74b3278a19
1 file changed
+14
-3
@@ -13,9 +13,13 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::mpsc::{Receiver, Sender, channel};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Longest accepted request line (bytes, without the newline). Generous, so requests that carry
|
||||
/// large payloads (base64 data) still fit.
|
||||
const MAX_LINE: usize = 256 * 1024 * 1024;
|
||||
/// Longest accepted request line (bytes, without the newline). The largest payload a request
|
||||
/// carries is a plugin state for `mix.insert_state`: base64 of at most `b64::MAX_DECODED_BYTES`
|
||||
/// (64 MiB, so about 85 MiB of text). Room for that and 1 MiB of JSON around it; replies
|
||||
/// (screenshots) aren't bounded by this.
|
||||
const MAX_LINE: usize = soundcraft_engine::model::b64::MAX_DECODED_BYTES.div_ceil(3) * 4 + (1 << 20);
|
||||
// With MAX_CONNECTIONS, at most 1.5 GiB of request lines are buffered at once.
|
||||
const _: () = assert!(MAX_LINE <= 96 << 20);
|
||||
/// Connections served at once; further ones get an error line and are closed.
|
||||
const MAX_CONNECTIONS: usize = 16;
|
||||
|
||||
@@ -160,6 +164,13 @@ mod tests {
|
||||
assert_eq!(input.len(), 100 - 11, "the rest of the line is left unread");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_largest_plugin_state_fits_in_a_line() {
|
||||
let state = soundcraft_engine::model::b64::MAX_DECODED_BYTES.div_ceil(3) * 4;
|
||||
let request = json!({"id": 1, "method": "engine.execute", "params": {"command": "mix.insert_state", "params": {"track": "Audio 1", "slot": 0, "state": ""}}});
|
||||
assert!(state + request.to_string().len() <= MAX_LINE);
|
||||
}
|
||||
|
||||
/// The control server on a loopback port, with a stand-in UI that echoes each method.
|
||||
fn serve_echo() -> std::net::SocketAddr {
|
||||
let listener = TcpListener::bind(("127.0.0.1", 0)).unwrap();
|
||||
|
||||
Reference in new issue
Block a user