diff --git a/Cargo.lock b/Cargo.lock index b4e20e5..3e1e4be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,21 @@ name = "adler32" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ansi_term" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "atty" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bitflags" version = "1.0.1" @@ -18,6 +33,20 @@ name = "byteorder" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "clap" +version = "2.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "coco" version = "0.1.1" @@ -165,10 +194,11 @@ dependencies = [ ] [[package]] -name = "pixelpwn" +name = "pixelpwnr" version = "0.1.0" dependencies = [ "bufstream 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -212,6 +242,19 @@ dependencies = [ "rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "redox_syscall" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "scoped_threadpool" version = "0.1.8" @@ -222,11 +265,66 @@ name = "scopeguard" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "strsim" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [metadata] "checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45" +"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455" +"checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859" "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" "checksum bufstream 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f382711e76b9de6c744cc00d0497baba02fb00a787f088c879f01d09468e32" "checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" +"checksum clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "110d43e343eb29f4f51c1db31beb879d546db27998577e5715270a54bcf41d3f" "checksum coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c06169f5beb7e31c7c67ebf5540b8b472d23e3eade3b2ec7d1f5b504a85f91bd" "checksum color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a475fc4af42d83d28adf72968d9bcfaf035a1a9381642d8e85d8a04957767b0d" "checksum deflate 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4dddda59aaab719767ab11d3efd9a714e95b610c4445d4435765021e9d52dfb1" @@ -250,5 +348,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "9e7944d95d25ace8f377da3ac7068ce517e4c646754c43a1b1849177bbf72e59" "checksum rayon 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b614fe08b6665cb9a231d07ac1364b0ef3cb3698f1239ee0c4c3a88a524f54c8" "checksum rayon-core 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e64b609139d83da75902f88fd6c01820046840a18471e4dfcd5ac7c0f46bea53" +"checksum redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "07b8f011e3254d5a9b318fde596d409a0001c9ae4c6e7907520c2eaa4d988c99" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum scoped_threadpool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "4ea459fe3ceff01e09534847c49860891d3ff1c12b4eb7731b67f2778fb60190" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" +"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" +"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" +"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" +"checksum winapi 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "890b38836c01d72fdb636d15c9cfc52ec7fd783b330abc93cd1686f4308dfccc" +"checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc" +"checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668" diff --git a/src/main.rs b/src/main.rs index 7bbbc39..724b16f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,8 @@ use std::io::Error; use std::io::prelude::*; use std::net::TcpStream; use std::path::Path; +use std::sync::mpsc; +use std::sync::mpsc::{Sender, Receiver}; use std::thread; use std::thread::JoinHandle; use std::time::Duration; @@ -192,6 +194,7 @@ struct PixCanvas { host: String, painter_count: usize, painters: Vec>, + painter_handles: Vec, image: DynamicImage, size: (u32, u32), offset: (u32, u32), @@ -214,6 +217,7 @@ impl PixCanvas { host: host.to_string(), painter_count, painters: Vec::with_capacity(painter_count), + painter_handles: Vec::with_capacity(painter_count), image, size, offset, @@ -259,12 +263,15 @@ impl PixCanvas { area.x, area.y, area.w, - area.h + area.h, ); // Redefine the offset to make it usable in the thread let offset = (self.offset.0, self.offset.1); + // Create a channel to push new images + let (tx, rx): (Sender, Receiver) = mpsc::channel(); + // Create the painter thread let thread = thread::spawn(move || { // Create a new stream @@ -279,12 +286,69 @@ impl PixCanvas { // Do some work loop { - painter.work().expect("Painter failed to perform work"); + // Work + painter.work() + .expect("Painter failed to perform work"); + + // Update the image to paint + if let Ok(image) = rx.try_recv() { + painter.set_image(image); + } } }); - // Add the painter thread to the list - self.painters.push(thread); + // Create a new painter handle, pust it to the list + self.painter_handles.push( + PainterHandle::new( + thread, + area, + tx, + ) + ); + } + + // Update the image that is being rendered for all painters. + pub fn update_image(&self, image: DynamicImage) { + // Update the image for each specific painter handle + for handle in self.painter_handles { + handle.update_image(&image); + } + } +} + + + +/// A handle for a painter thread. +struct PainterHandle { + thread: JoinHandle, + area: Rect, + image_sender: Sender, +} + +impl PainterHandle { + /// Create a new handle from the given properties. + pub fn new(thread: JoinHandle, area: Rect, image_sender: Sender) -> PainterHandle { + PainterHandle { + thread, + area, + image_sender, + } + } + + /// Push an image update. + pub fn update_image(&self, full_image: &mut DynamicImage) { + // Crop the image to the area + let image = full_image.crop( + self.area.x, + self.area.y, + self.area.w, + self.area.h, + ); + + // Push a new image to the thread + // TODO: return this result + self.image_sender.send(image) + .expect("Failed to send image update to painter"); } } @@ -342,6 +406,11 @@ impl Painter { // Everything seems to be ok Ok(()) } + + /// Update the image that should be painted + pub fn set_image(&mut self, image: DynamicImage) { + self.image = image; + } } @@ -436,6 +505,7 @@ impl Color { /// Rectangle struct. +#[derive(Copy, Clone)] pub struct Rect { // TODO: Make these properties private pub x: u32, diff --git a/src/main.rs~ b/src/main.rs~ new file mode 100644 index 0000000..76ca63f --- /dev/null +++ b/src/main.rs~ @@ -0,0 +1,526 @@ +extern crate bufstream; +extern crate clap; +extern crate image; + +use std::io::Error; +use std::io::prelude::*; +use std::net::TcpStream; +use std::path::Path; +use std::sync::mpsc; +use std::sync::mpsc::{Sender, Receiver}; +use std::thread; +use std::thread::JoinHandle; +use std::time::Duration; + +use bufstream::BufStream; +use clap::{Arg, ArgMatches, App}; +use image::{DynamicImage, FilterType, Pixel}; + + + +// The default thread count +const DEFAULT_THREAD_COUNT: usize = 4; + +// The default image width and height +const DEFAULT_IMAGE_WIDTH: u32 = 1920; +const DEFAULT_IMAGE_HEIGHT: u32 = 1080; + +// The default size of the command output read buffer +// const CMD_READ_BUFFER_SIZE: usize = 16; + + + +/// Main application entrypoint. +fn main() { + // Parse CLI arguments + let matches = parse_args(); + + // Get the host + let host = matches + .value_of("HOST") + .expect("Please specify a host"); + + // Get the count + let count = matches + .value_of("count") + .unwrap_or(&format!("{}", DEFAULT_THREAD_COUNT)) + .parse::() + .expect("Invalid count specified"); + + // Get the image path + let image_path = matches + .value_of("image") + .expect("Please specify an image path"); + + // Get the width and height + let width = matches + .value_of("width") + .unwrap_or(&format!("{}", DEFAULT_IMAGE_WIDTH)) + .parse::() + .expect("Invalid image width"); + let height = matches + .value_of("height") + .unwrap_or(&format!("{}", DEFAULT_IMAGE_HEIGHT)) + .parse::() + .expect("Invalid image height"); + + // Get the offset + let offset_x = matches + .value_of("x") + .unwrap_or("0") + .parse::() + .expect("Invalid X offset"); + let offset_y = matches + .value_of("y") + .unwrap_or("0") + .parse::() + .expect("Invalid Y offset"); + + // Start + start( + host, + image_path, + count, + (width, height), + (offset_x, offset_y) + ); +} + +/// Parse CLI arguments, return the matches. +fn parse_args<'a>() -> ArgMatches<'a> { + // Handle/parse arguments + App::new("pixelpwnr") + .version("0.1") + .author("Tim Visee ") + .about("Pwns pixelflut") + .arg(Arg::with_name("HOST") + .help("The host to pwn \"host:port\"") + .required(true) + .index(1)) + .arg(Arg::with_name("count") + .short("c") + .long("count") + .value_name("COUNT") + .help("Number of simultanious threads (def: 4)") + .takes_value(true)) + .arg(Arg::with_name("image") + .short("i") + .long("image") + .value_name("PATH") + .help("Path of the image to print") + .required(true) + .takes_value(true)) + .arg(Arg::with_name("width") + .short("w") + .long("width") + .value_name("PIXELS") + .help("Drawing width in pixels (def: 1920)") + .takes_value(true)) + .arg(Arg::with_name("height") + .short("h") + .long("height") + .value_name("PIXELS") + .help("Drawing height in pixels (def: 1080)") + .takes_value(true)) + .arg(Arg::with_name("x") + .short("x") + .long("x") + .value_name("PIXELS") + .help("Drawing X offset in pixels (def: 0)") + .takes_value(true)) + .arg(Arg::with_name("y") + .short("y") + .long("y") + .value_name("PIXELS") + .help("Drawing Y offset in pixels (def: 0)") + .takes_value(true)) + .get_matches() +} + +/// Start the client. +fn start( + host: &str, + image_path: &str, + count: usize, + size: (u32, u32), + offset: (u32, u32) +) { + // Start + println!("Starting..."); + + // Create a new pixelflut canvas + PixCanvas::new(host, image_path, count, size, offset); + + // Sleep this thread + thread::sleep(Duration::new(99999999, 0)); +} + +/// Create a stream to talk to the pixelflut server. +/// +/// The stream is returned as result. +fn create_stream(host: String) -> Result { + TcpStream::connect(host) +} + +/// Load the image at the given path, and size it correctly +fn load_image(path: &str, size: &(u32, u32)) -> DynamicImage { + // Create a path instance + let path = Path::new(&path); + + // Check whether the path exists + if !path.is_file() { + panic!("The given path does not exist or is not a file"); + } + + // Load the image + println!("Loading image..."); + let image = image::open(&path).unwrap(); + + // Start processing the image for the screen + println!("Processing image..."); + + // Resize the image to fit the screen + image.resize_exact( + size.0, + size.1, + FilterType::Gaussian, + ) +} + + + +/// A pixflut instance +struct PixCanvas { + host: String, + painter_count: usize, + painters: Vec>, + painter_handles: Vec, + image: DynamicImage, + size: (u32, u32), + offset: (u32, u32), +} + +impl PixCanvas { + /// Create a new pixelflut canvas. + pub fn new( + host: &str, + image_path: &str, + painter_count: usize, + size: (u32, u32), + offset: (u32, u32), + ) -> PixCanvas { + // Load the image + let image = load_image(image_path, &size); + + // Initialize the object + let mut canvas = PixCanvas { + host: host.to_string(), + painter_count, + painters: Vec::with_capacity(painter_count), + painter_handles: Vec::with_capacity(painter_count), + image, + size, + offset, + }; + + // Show a status message + println!("Starting painter threads..."); + + // Spawn some painters + canvas.spawn_painters(); + + // Return the canvas + canvas + } + + /// Spawn the painters for this canvas + fn spawn_painters(&mut self) { + // Spawn some painters + for i in 0..self.painter_count { + // Determine the slice width + let width = self.size.0 / (self.painter_count as u32); + + // Define the area to paint per thread + let painter_area = Rect::from( + (i as u32) * width, + 0, + width, + self.size.1, + ); + + // Spawn the painter + self.spawn_painter(painter_area); + } + } + + /// Spawn a single painter in a thread. + fn spawn_painter(&mut self, area: Rect) { + // Get the host that will be used + let host = self.host.to_string(); + + // Get the part of the image to draw by this painter + let image = self.image.crop( + area.x, + area.y, + area.w, + area.h, + ); + + // Redefine the offset to make it usable in the thread + let offset = (self.offset.0, self.offset.1); + + // Create a channel to push new images + let (tx, rx): (Sender, Receiver) = mpsc::channel(); + + // Create the painter thread + let thread = thread::spawn(move || { + // Create a new stream + let stream = create_stream(host) + .expect("failed to open stream to pixelflut"); + + // Create a new client + let client = PixClient::new(stream); + + // Create a painter + let mut painter = Painter::new(client, area, offset, image); + + // Do some work + loop { + // Work + painter.work() + .expect("Painter failed to perform work"); + + // Update the image to paint + if let Ok(image) = rx.try_recv() { + painter.set_image(image); + } + } + }); + + // Create a new painter handle, pust it to the list + self.painter_handles.push( + PainterHandle::new( + thread, + area, + tx, + ) + ); + } + + // Update the image that is being rendered for all painters. + pub fn update_image(&self, image: DynamicImage) { + // Update the image for each specific painter handle + for handle in self.painter_handles { + handle.update_image(&image); + } + } +} + + + +/// A handle for a painter thread. +struct PainterHandle { + thread: JoinHandle, + area: Rect, + image_sender: Sender, +} + +impl PainterHandle { + /// Create a new handle from the given properties. + pub fn new(thread: JoinHandle, area: Rect, image_sender: Sender) -> PainterHandle { + PainterHandle { + thread, + area, + image_sender, + } + } + + /// Push an image update. + pub fn update_image(&self, &mut full_image: DynamicImage) { + // Crop the image to the area + let image = full_image.crop( + self.area.x, + self.area.y, + self.area.w, + self.area.h, + ); + + // Push a new image to the thread + // TODO: return this result + self.image_sender.send(image) + .expect("Failed to send image update to painter"); + } +} + + + +struct Painter { + client: PixClient, + area: Rect, + offset: (u32, u32), + image: DynamicImage, +} + +impl Painter { + /// Create a new painter. + pub fn new(client: PixClient, area: Rect, offset: (u32, u32), image: DynamicImage) -> Painter { + Painter { + client, + area, + offset, + image, + } + } + + /// Perform work. + /// Paint the whole defined area. + pub fn work(&mut self) -> Result<(), Error> { + // Get an RGB image + let image = self.image.to_rgb(); + + // Loop through all the pixels, and set their color + for x in 0..self.area.w { + for y in 0..self.area.h { + // Get the pixel at this location + let pixel = image.get_pixel(x, y); + + // Get the channels + let channels = pixel.channels(); + + // Define the color + let color = Color::from( + channels[0], + channels[1], + channels[2], + ); + + // Set the pixel + self.client.write_pixel( + x + self.area.x + self.offset.0, + y + self.area.y + self.offset.1, + &color, + )?; + } + } + + // Everything seems to be ok + Ok(()) + } + + /// Update the image that should be painted + pub fn set_image(&mut self, image: DynamicImage) { + self.image = image; + } +} + + + +/// A pixelflut client. +/// This client uses a stream to talk to a pixelflut panel. +/// It allows to write pixels to the panel, and read some status. +struct PixClient { + stream: BufStream, +} + +impl PixClient { + /// Create a new client instance. + pub fn new(stream: TcpStream) -> PixClient { + PixClient { + stream: BufStream::new(stream), + } + } + + /// Write a pixel to the given stream. + fn write_pixel(&mut self, x: u32, y: u32, color: &Color) -> Result<(), Error> { + // Write the command to set a pixel + self.write_command( + format!("PX {} {} {}", x, y, color.as_hex()), + ) + } + + // /// Read the size of the screen. + // fn read_screen_size(&mut self) { + // // Read the screen size + // let size = self + // .write_read_command("SIZE".into()) + // .expect("Failed to read screen size"); + // + // // TODO: Remove this after debugging + // println!("Read size: {}", size); + // } + + /// Write the given command to the given stream. + fn write_command(&mut self, cmd: String) -> Result<(), Error> { + // Write the pixels and a new line + self.stream.write(cmd.as_bytes())?; + self.stream.write("\n".as_bytes())?; + + // Everything seems to be ok + Ok(()) + } + + // /// Write the given command to the given stream, and read the output. + // fn write_read_command(&mut self, cmd: String) -> Result { + // // Write the command + // self.write_command(cmd); + // + // // Read the output + // let mut buffer = String::with_capacity(CMD_READ_BUFFER_SIZE); + // println!("Reading line..."); + // self.stream.read_line(&mut buffer)?; + // println!("Done reading"); + // + // // Return the read string + // Ok(buffer) + // } +} + + + +/// Color structure. +#[derive(Copy, Clone)] +struct Color { + r: u8, + g: u8, + b: u8, +} + +impl Color { + /// Create a new color instance + pub fn from(r: u8, g: u8, b: u8) -> Color { + Color { + r, + g, + b, + } + } + + /// Get a hexadecimal representation of the color, + /// such as `FFFFFF` for white and `FF0000` for red. + pub fn as_hex(&self) -> String { + format!("{:02X}{:02X}{:02X}", self.r, self.g, self.b) + } +} + + + +/// Rectangle struct. +#[derive(Copy, Clone)] +pub struct Rect { + // TODO: Make these properties private + pub x: u32, + pub y: u32, + pub w: u32, + pub h: u32, +} + +impl Rect { + pub fn from(x: u32, y: u32, w: u32, h: u32) -> Rect { + Rect { + x, + y, + w, + h, + } + } +}