use chrono::{DateTime, Utc};
use nix::libc;
use std::io::{self, Write};
use std::os::unix::net::UnixStream;
use uuid::Uuid;
#[allow(dead_code)]
#[derive(Debug)]
pub struct ClientInfo {
pub id: String, pub stream: UnixStream,
pub rows: u16,
pub cols: u16,
pub connected_at: DateTime<Utc>,
#[allow(dead_code)]
pub remote_addr: Option<String>, #[allow(dead_code)]
pub user_agent: Option<String>, pub pending_output: Vec<u8>, }
impl ClientInfo {
#[allow(dead_code)]
pub fn new(stream: UnixStream) -> Self {
let (rows, cols) = get_terminal_size().unwrap_or((24, 80));
Self {
id: Uuid::new_v4().to_string()[..8].to_string(),
stream,
rows,
cols,
connected_at: Utc::now(),
remote_addr: None,
user_agent: None,
pending_output: Vec::new(),
}
}
pub fn flush_pending(&mut self) -> io::Result<()> {
while !self.pending_output.is_empty() {
match self.stream.write(&self.pending_output) {
Ok(0) => {
return Err(io::Error::new(
io::ErrorKind::WriteZero,
"socket closed while flushing pending output",
));
}
Ok(n) => {
self.pending_output.drain(..n);
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
break;
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
Ok(())
}
pub fn send_data(&mut self, data: &[u8]) -> io::Result<()> {
if data.is_empty() {
return Ok(());
}
if !self.pending_output.is_empty() {
self.pending_output.extend_from_slice(data);
return self.flush_pending();
}
let mut offset = 0;
while offset < data.len() {
match self.stream.write(&data[offset..]) {
Ok(0) => {
return Err(io::Error::new(
io::ErrorKind::WriteZero,
"socket closed while sending",
));
}
Ok(n) => {
offset += n;
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
self.pending_output.extend_from_slice(&data[offset..]);
break;
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
Ok(())
}
#[allow(dead_code)]
pub fn display_info(&self) -> String {
let duration = Utc::now().signed_duration_since(self.connected_at);
let hours = duration.num_hours();
let minutes = (duration.num_minutes() % 60) as u32;
let seconds = (duration.num_seconds() % 60) as u32;
let duration_str = if hours > 0 {
format!("{}h{}m", hours, minutes)
} else if minutes > 0 {
format!("{}m{}s", minutes, seconds)
} else {
format!("{}s", seconds)
};
format!(
"Client {} | Size: {}x{} | Connected: {} | Duration: {}",
self.id,
self.cols,
self.rows,
self.connected_at.format("%H:%M:%S"),
duration_str
)
}
#[allow(dead_code)]
pub fn update_size(&mut self, rows: u16, cols: u16) {
self.rows = rows;
self.cols = cols;
}
}
#[allow(dead_code)]
pub fn get_terminal_size() -> Result<(u16, u16), std::io::Error> {
unsafe {
let mut size: libc::winsize = std::mem::zeroed();
if libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, &mut size) == -1 {
return Err(std::io::Error::last_os_error());
}
Ok((size.ws_row, size.ws_col))
}
}
#[cfg(test)]
mod tests {
use super::ClientInfo;
use std::io::{Read, Result};
use std::os::unix::net::UnixStream;
#[test]
fn send_data_buffers_when_socket_would_block() -> Result<()> {
let (writer, mut reader) = UnixStream::pair()?;
writer.set_nonblocking(true)?;
let mut client = ClientInfo::new(writer);
let chunk = vec![0u8; 64 * 1024];
let mut total_expected = 0usize;
for _ in 0..128 {
client.send_data(&chunk)?;
total_expected += chunk.len();
if !client.pending_output.is_empty() {
break;
}
}
assert!(
!client.pending_output.is_empty(),
"socket never blocked during test"
);
let mut buf = [0u8; 32 * 1024];
let mut received = 0usize;
while received < total_expected {
match reader.read(&mut buf) {
Ok(0) => break,
Ok(n) => {
received += n;
client.flush_pending()?;
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
client.flush_pending()?;
}
Err(e) => return Err(e),
}
}
assert_eq!(received, total_expected);
assert!(client.pending_output.is_empty());
Ok(())
}
}