commit a91e7fb7403f3cc9aab512feec571c9b458d59fb Author: Ondřej Slabý Date: Sat Jan 6 14:49:46 2024 +0100 Initial code commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..92ae7c2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,25 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "sus" +version = "0.1.0" +dependencies = [ + "getopts", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f7353cc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "sus" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +getopts = "0.2.21" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8c15c5f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 IIM + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1240436 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,111 @@ +use std::{env, fs::File, path::{PathBuf}, net::{UdpSocket, IpAddr, Ipv4Addr, SocketAddr}, io::{Seek, Read, ErrorKind}, thread, time::Duration}; + +use getopts::Options; + +fn main() -> std::io::Result<()> { + let mut options = Options::new(); + options.optopt("i", "file", "Video file to parse", "FILE"); + options.optopt( + "s", + "chunkspersecond", + "Rate at which the chunk bursts will be sent", + "NUMBER", + ); + options.optopt( + "c", + "chunksize", + "How large a file chunk should be processed each frame", + "NUMBER", + ); + options.optopt( + "f", + "frame", + "Split each chunk into this many frames", + "NUMBER", + ); + options.optflag("r", "repeat", "Continue from start of the file after reaching the end"); + options.optopt("t", "target", "Target address for sending", "IP:PORT"); + + let args: Vec = env::args().collect(); + let program = args[0].clone(); + + let matches = match options.parse(args) { + Ok(m) => m, + Err(e) => { + println!("Error parsing options: {e}"); + print_usage(program, options); + return Ok(()); + } + }; + + if !matches.opt_present("t") || !matches.opt_present("i") { + print_usage(program, options); + return Ok(()); + } + + let path = PathBuf::from(matches.opt_str("i").unwrap()); + let mut file = File::open(path)?; + + + let chunks_per_second: u32 = matches.opt_get("s").unwrap_or_default().unwrap_or(24); + let chunk_size: usize = matches.opt_get("c").unwrap_or_default().unwrap_or(530 * 10); + let frame_count: usize = matches.opt_get("f").unwrap_or_default().unwrap_or(10); + let frame_size: usize = chunk_size / frame_count; + let chunk_interval: f64 = 1.0 / chunks_per_second as f64; + let mut repeat = matches.opt_present("f"); + + let mut buffer: Vec = Vec::new(); + buffer.resize(chunk_size, 0); + + let socket = UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)).unwrap(); + let target = matches.opt_str("t").unwrap(); + + let mut count = 0; + let mut frameid = 0; + + while repeat { + file.rewind()?; + println!("Sending {chunk_size}b large chunks in {frame_size}b parts at rate {chunks_per_second}. Repeat {count}."); + loop { + frameid += 1; + // println!("Sending chunk {frameid} (repeat {count}) per {frame_size} b parts."); + match file.read_exact(buffer.as_mut_slice()) { + Ok(b) => b, + Err(e) => { + if e.kind() == ErrorKind::UnexpectedEof { + println!("Reached end of file."); + break; + } + else { + println!("File read error: {e}"); + repeat = false; + break; + } + } + }; + for fr in 0..frame_count { + let frame = &buffer[fr * frame_size .. (fr + 1) * frame_size]; + + // for i in 0..frame_size { + // print!("{:02X} ", frame[i]); + // if i % 24 == 0 { + // print!("\n"); + // } + // } + // print!("\n"); + + socket.send_to(frame, target.clone()).unwrap(); + } + // println!("Sleeping for {chunk_interval}"); + thread::sleep(Duration::from_secs_f64(chunk_interval)); + } + count += 1; + } + + Ok(()) +} + +fn print_usage(program: String, options: Options) { + let brief = format!("Usage: {} -i FILE -t IP:PORT [options]\n\tSimple UDP Sender - takes file contents as sepcified by the arguments and sends them as UDP packets.", program); + print!("{}", options.usage(&brief)); +}