use crate::security::sanitize_text;
use std::io::{self, BufRead, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
enum ReadLineOutcome {
Line,
Eof,
}
#[derive(Debug, Clone)]
pub struct StdioConfig {
pub max_message_size: usize,
pub auto_flush: bool,
pub stderr_logging: bool,
pub buffer_size: usize,
}
impl Default for StdioConfig {
fn default() -> Self {
Self {
max_message_size: 10 * 1024 * 1024, auto_flush: true,
stderr_logging: true,
buffer_size: 4096,
}
}
}
pub struct StdioTransport {
read_buffer: String,
config: StdioConfig,
shutdown: Arc<AtomicBool>,
}
impl Default for StdioTransport {
fn default() -> Self {
Self::new()
}
}
impl StdioTransport {
pub fn new() -> Self {
Self::with_config(StdioConfig::default())
}
pub fn with_config(config: StdioConfig) -> Self {
Self {
read_buffer: String::with_capacity(config.buffer_size),
config,
shutdown: Arc::new(AtomicBool::new(false)),
}
}
pub fn with_auto_flush(mut self, auto_flush: bool) -> Self {
self.config.auto_flush = auto_flush;
self
}
pub fn shutdown_handle(&self) -> Arc<AtomicBool> {
Arc::clone(&self.shutdown)
}
pub fn shutdown(&self) {
self.shutdown.store(true, Ordering::SeqCst);
}
pub fn is_shutdown(&self) -> bool {
self.shutdown.load(Ordering::SeqCst)
}
pub fn read_message<R: BufRead>(&mut self, reader: &mut R) -> io::Result<Option<String>> {
loop {
if self.is_shutdown() {
return Ok(None);
}
self.read_buffer.clear();
match self.read_line_bounded(reader)? {
ReadLineOutcome::Eof => {
self.log_stderr("EOF received on stdin, initiating shutdown");
self.shutdown();
return Ok(None);
}
ReadLineOutcome::Line => {}
}
let message = self.read_buffer.trim_end().to_string();
if message.is_empty() {
continue;
}
return Ok(Some(message));
}
}
fn read_line_bounded<R: BufRead>(&mut self, reader: &mut R) -> io::Result<ReadLineOutcome> {
loop {
let available = reader.fill_buf()?;
if available.is_empty() {
if self.read_buffer.is_empty() {
return Ok(ReadLineOutcome::Eof);
}
return Ok(ReadLineOutcome::Line);
}
let take = available
.iter()
.position(|byte| *byte == b'\n')
.map(|index| index + 1)
.unwrap_or(available.len());
let next_len = self.read_buffer.len().saturating_add(take);
if next_len > self.config.max_message_size {
let remaining = self
.config
.max_message_size
.saturating_sub(self.read_buffer.len());
let allowed = remaining.min(available.len());
let consume_len = (allowed + 1).min(available.len());
if allowed > 0 {
let text = std::str::from_utf8(&available[..allowed]).map_err(|_| {
io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8 in stdin message")
})?;
self.read_buffer.push_str(text);
}
reader.consume(consume_len);
self.log_error("stdin message exceeded maximum size");
self.shutdown();
self.read_buffer.clear();
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Message exceeds maximum size",
));
}
let text = std::str::from_utf8(&available[..take]).map_err(|_| {
io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8 in stdin message")
})?;
self.read_buffer.push_str(text);
reader.consume(take);
if self.read_buffer.ends_with('\n') {
return Ok(ReadLineOutcome::Line);
}
}
}
pub fn write_message<W: Write>(&self, writer: &mut W, message: &str) -> io::Result<()> {
if self.is_shutdown() {
return Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"transport is shut down",
));
}
if message.len() > self.config.max_message_size {
self.log_error("stdout message exceeded maximum size");
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Message exceeds maximum size",
));
}
writeln!(writer, "{}", message)?;
if self.config.auto_flush {
writer.flush()?;
}
Ok(())
}
pub fn read_all_messages<R: BufRead>(&mut self, reader: &mut R) -> io::Result<Vec<String>> {
let mut messages = Vec::new();
while !self.is_shutdown() {
match self.read_message(reader)? {
Some(msg) => messages.push(msg),
None => break,
}
}
Ok(messages)
}
pub fn log_stderr(&self, message: &str) {
if self.config.stderr_logging {
let _ = writeln!(io::stderr(), "{}", Self::format_log_line("DCP", message));
}
}
pub fn log_error(&self, message: &str) {
if self.config.stderr_logging {
let _ = writeln!(
io::stderr(),
"{}",
Self::format_log_line("DCP ERROR", message)
);
}
}
pub fn log_debug(&self, message: &str) {
if self.config.stderr_logging {
let _ = writeln!(
io::stderr(),
"{}",
Self::format_log_line("DCP DEBUG", message)
);
}
}
pub fn format_log_line(prefix: &str, message: &str) -> String {
format!("[{}] {}", prefix, sanitize_text(message))
}
}
pub struct MessageFramer {
buffer: Vec<u8>,
max_size: usize,
}
impl Default for MessageFramer {
fn default() -> Self {
Self::new()
}
}
impl MessageFramer {
pub fn new() -> Self {
Self {
buffer: Vec::with_capacity(4096),
max_size: 10 * 1024 * 1024, }
}
pub fn with_max_size(mut self, max_size: usize) -> Self {
self.max_size = max_size;
self
}
pub fn feed(&mut self, data: &[u8]) -> io::Result<Vec<String>> {
let mut messages = Vec::new();
for &byte in data {
if byte == b'\n' {
if !self.buffer.is_empty() {
let message = String::from_utf8(std::mem::take(&mut self.buffer))
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let trimmed = message.trim().to_string();
if !trimmed.is_empty() {
messages.push(trimmed);
}
}
} else {
if self.buffer.len() >= self.max_size {
self.buffer.clear();
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Message exceeds maximum size",
));
}
self.buffer.push(byte);
}
}
Ok(messages)
}
pub fn has_partial(&self) -> bool {
!self.buffer.is_empty()
}
pub fn buffer_size(&self) -> usize {
self.buffer.len()
}
pub fn clear(&mut self) {
self.buffer.clear();
}
}
pub fn frame_message(message: &str) -> String {
format!("{}\n", message)
}
pub fn unframe_message(data: &str) -> &str {
data.trim_end_matches('\n').trim_end_matches('\r')
}
#[cfg(feature = "async-stdio")]
pub mod async_transport {
use super::*;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::sync::mpsc;
pub struct AsyncStdioTransport {
config: StdioConfig,
shutdown: Arc<AtomicBool>,
}
impl AsyncStdioTransport {
pub fn new(config: StdioConfig) -> Self {
Self {
config,
shutdown: Arc::new(AtomicBool::new(false)),
}
}
pub fn shutdown_handle(&self) -> Arc<AtomicBool> {
Arc::clone(&self.shutdown)
}
pub async fn run(self) -> io::Result<(mpsc::Receiver<String>, mpsc::Sender<String>)> {
let (in_tx, in_rx) = mpsc::channel(100);
let (out_tx, mut out_rx) = mpsc::channel::<String>(100);
let shutdown = Arc::clone(&self.shutdown);
let config = self.config.clone();
tokio::spawn(async move {
let stdin = tokio::io::stdin();
let mut reader = BufReader::new(stdin);
let mut line = String::new();
loop {
if shutdown.load(Ordering::SeqCst) {
break;
}
line.clear();
match reader.read_line(&mut line).await {
Ok(0) => {
if config.stderr_logging {
eprintln!("[DCP] EOF on stdin");
}
break;
}
Ok(_) => {
let msg = line.trim().to_string();
if !msg.is_empty() {
if in_tx.send(msg).await.is_err() {
break;
}
}
}
Err(e) => {
if config.stderr_logging {
eprintln!("[DCP ERROR] stdin read error: {}", e);
}
break;
}
}
}
});
tokio::spawn(async move {
let mut stdout = tokio::io::stdout();
while let Some(msg) = out_rx.recv().await {
let framed = format!("{}\n", msg);
if let Err(e) = stdout.write_all(framed.as_bytes()).await {
eprintln!("[DCP ERROR] stdout write error: {}", e);
break;
}
if let Err(e) = stdout.flush().await {
eprintln!("[DCP ERROR] stdout flush error: {}", e);
break;
}
}
});
Ok((in_rx, out_tx))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_stdio_transport_read_message() {
let mut transport = StdioTransport::new();
let input = r#"{"jsonrpc":"2.0","method":"test","id":1}
"#;
let mut reader = Cursor::new(input);
let message = transport.read_message(&mut reader).unwrap();
assert_eq!(
message,
Some(r#"{"jsonrpc":"2.0","method":"test","id":1}"#.to_string())
);
}
#[test]
fn test_stdio_transport_read_eof() {
let mut transport = StdioTransport::new();
let mut reader = Cursor::new("");
let message = transport.read_message(&mut reader).unwrap();
assert_eq!(message, None);
assert!(transport.is_shutdown()); }
#[test]
fn test_stdio_transport_write_message() {
let transport = StdioTransport::new();
let mut output = Vec::new();
transport
.write_message(&mut output, r#"{"jsonrpc":"2.0","result":{},"id":1}"#)
.unwrap();
assert_eq!(
String::from_utf8(output).unwrap(),
"{\"jsonrpc\":\"2.0\",\"result\":{},\"id\":1}\n"
);
}
#[test]
fn test_stdio_transport_read_multiple() {
let mut transport = StdioTransport::new();
let input = r#"{"id":1}
{"id":2}
{"id":3}
"#;
let mut reader = Cursor::new(input);
let messages = transport.read_all_messages(&mut reader).unwrap();
assert_eq!(messages.len(), 3);
assert_eq!(messages[0], r#"{"id":1}"#);
assert_eq!(messages[1], r#"{"id":2}"#);
assert_eq!(messages[2], r#"{"id":3}"#);
}
#[test]
fn test_stdio_transport_shutdown() {
let transport = StdioTransport::new();
assert!(!transport.is_shutdown());
transport.shutdown();
assert!(transport.is_shutdown());
}
#[test]
fn test_stdio_transport_shutdown_handle() {
let transport = StdioTransport::new();
let handle = transport.shutdown_handle();
assert!(!handle.load(Ordering::SeqCst));
transport.shutdown();
assert!(handle.load(Ordering::SeqCst));
}
#[test]
fn test_stdio_config() {
let config = StdioConfig {
max_message_size: 1024,
auto_flush: false,
stderr_logging: false,
buffer_size: 2048,
};
let transport = StdioTransport::with_config(config);
assert!(!transport.config.auto_flush);
assert!(!transport.config.stderr_logging);
}
#[test]
fn test_message_framer_single() {
let mut framer = MessageFramer::new();
let messages = framer.feed(b"{\"test\":1}\n").unwrap();
assert_eq!(messages.len(), 1);
assert_eq!(messages[0], "{\"test\":1}");
assert!(!framer.has_partial());
}
#[test]
fn test_message_framer_multiple() {
let mut framer = MessageFramer::new();
let messages = framer.feed(b"{\"a\":1}\n{\"b\":2}\n").unwrap();
assert_eq!(messages.len(), 2);
assert_eq!(messages[0], "{\"a\":1}");
assert_eq!(messages[1], "{\"b\":2}");
}
#[test]
fn test_message_framer_partial() {
let mut framer = MessageFramer::new();
let messages1 = framer.feed(b"{\"partial\":").unwrap();
assert!(messages1.is_empty());
assert!(framer.has_partial());
let messages2 = framer.feed(b"true}\n").unwrap();
assert_eq!(messages2.len(), 1);
assert_eq!(messages2[0], "{\"partial\":true}");
assert!(!framer.has_partial());
}
#[test]
fn test_message_framer_max_size() {
let mut framer = MessageFramer::new().with_max_size(10);
let result = framer.feed(b"this is way too long");
assert!(result.is_err());
}
#[test]
fn test_frame_unframe() {
let original = r#"{"jsonrpc":"2.0"}"#;
let framed = frame_message(original);
let unframed = unframe_message(&framed);
assert_eq!(unframed, original);
}
}