feat(ssh): Implement SCP protocol handling with ChannelReadWrite (Phase 8 complete)
This commit is contained in:
@@ -780,42 +780,49 @@ impl ChannelManager {
|
|||||||
channel.scp_input_buffer.len()
|
channel.scp_input_buffer.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Process SCP packets (line-based protocol)
|
// ⭐⭐⭐⭐⭐ Phase 8: Use ChannelReadWrite wrapper
|
||||||
// SCP uses newline-terminated commands: C0644, D0755, E, T
|
use crate::ssh_server::scp_handler::ChannelReadWrite;
|
||||||
// Reference: OpenSSH scp.c
|
|
||||||
|
// Create wrapper with accumulated input
|
||||||
// Find complete lines in buffer
|
let mut channel_rw = ChannelReadWrite::new(channel.scp_input_buffer.clone());
|
||||||
let mut responses: Vec<Vec<u8>> = Vec::new();
|
|
||||||
while let Some(newline_pos) = channel.scp_input_buffer.iter().position(|&b| b == b'\n') {
|
// Process SCP protocol
|
||||||
let line = channel.scp_input_buffer[..newline_pos].to_vec();
|
// Reference: OpenSSH scp.c: sink() (destination mode)
|
||||||
channel.scp_input_buffer = channel.scp_input_buffer[newline_pos + 1..].to_vec();
|
match scp_handler.handle_scp(&mut channel_rw) {
|
||||||
|
Ok(_) => {
|
||||||
info!("SCP command: {}", String::from_utf8_lossy(&line));
|
info!("SCP protocol handled successfully");
|
||||||
|
|
||||||
// Process SCP command
|
// Get output from wrapper
|
||||||
// TODO: Full implementation requires ScpHandler.handle_scp() with ReadWrite trait
|
let output = channel_rw.drain_output();
|
||||||
// Current implementation: basic ACK (0 byte)
|
info!("SCP produced {} bytes output", output.len());
|
||||||
responses.push(vec![0]); // SCP ACK
|
|
||||||
}
|
// Update input buffer (remove consumed data)
|
||||||
|
if channel_rw.has_remaining_input() {
|
||||||
// Check for window adjust
|
warn!("SCP handler did not consume all input");
|
||||||
if let Some(window_adjust_packet) =
|
}
|
||||||
channel_check_window(recipient_channel, &mut self.channels)
|
channel.scp_input_buffer.clear();
|
||||||
{
|
|
||||||
return Ok(Some(window_adjust_packet));
|
// Check for window adjust
|
||||||
}
|
if let Some(window_adjust_packet) =
|
||||||
|
channel_check_window(recipient_channel, &mut self.channels)
|
||||||
// Send SCP responses
|
{
|
||||||
if !responses.is_empty() {
|
// Send window adjust before SCP response
|
||||||
// All responses except last go to pending_packets
|
self.pending_packets.push_back(window_adjust_packet);
|
||||||
for i in 0..responses.len().saturating_sub(1) {
|
}
|
||||||
let pending = self.build_channel_data(recipient_channel, &responses[i])?;
|
|
||||||
self.pending_packets.push_back(pending);
|
// Send SCP response if available
|
||||||
|
if !output.is_empty() {
|
||||||
|
return Ok(Some(self.build_channel_data(recipient_channel, &output)?));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
// Last response is returned
|
warn!("SCP protocol error: {}", e);
|
||||||
if let Some(last_response) = responses.into_iter().last() {
|
|
||||||
return Ok(Some(self.build_channel_data(recipient_channel, &last_response)?));
|
// Send error response (SCP protocol sends errors as text)
|
||||||
|
let error_msg = format!("{}\n", e);
|
||||||
|
channel.scp_input_buffer.clear();
|
||||||
|
|
||||||
|
return Ok(Some(self.build_channel_data(recipient_channel, error_msg.as_bytes())?));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -397,6 +397,65 @@ impl ScpHandler {
|
|||||||
pub trait ReadWrite: Read + Write {}
|
pub trait ReadWrite: Read + Write {}
|
||||||
impl<T: Read + Write> ReadWrite for T {}
|
impl<T: Read + Write> ReadWrite for T {}
|
||||||
|
|
||||||
|
/// ⭐⭐⭐⭐⭐ Phase 8: Channel wrapper for SCP protocol
|
||||||
|
/// 实现 Read + Write traits,用于 ScpHandler 和 SSH channel 之间传递数据
|
||||||
|
pub struct ChannelReadWrite {
|
||||||
|
input_buffer: Vec<u8>,
|
||||||
|
output_buffer: Vec<u8>,
|
||||||
|
input_pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChannelReadWrite {
|
||||||
|
pub fn new(input_buffer: Vec<u8>) -> Self {
|
||||||
|
Self {
|
||||||
|
input_buffer,
|
||||||
|
output_buffer: Vec::new(),
|
||||||
|
input_pos: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn feed_input(&mut self, data: &[u8]) {
|
||||||
|
self.input_buffer.extend_from_slice(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drain_output(&mut self) -> Vec<u8> {
|
||||||
|
let output = self.output_buffer.clone();
|
||||||
|
self.output_buffer.clear();
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_remaining_input(&self) -> bool {
|
||||||
|
self.input_pos < self.input_buffer.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for ChannelReadWrite {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
let remaining = self.input_buffer.len() - self.input_pos;
|
||||||
|
let to_read = std::cmp::min(buf.len(), remaining);
|
||||||
|
|
||||||
|
if to_read == 0 {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[..to_read].copy_from_slice(&self.input_buffer[self.input_pos..self.input_pos + to_read]);
|
||||||
|
self.input_pos += to_read;
|
||||||
|
|
||||||
|
Ok(to_read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for ChannelReadWrite {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
self.output_buffer.extend_from_slice(buf);
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
Reference in New Issue
Block a user