From 6a9bc09be37c202a6933b80bd64963e8127f1ae1 Mon Sep 17 00:00:00 2001 From: Myrddin Dundragon Date: Fri, 9 May 2025 11:14:03 -0400 Subject: [PATCH] Just pushing everything in for backup. --- Cargo.lock | 261 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + docs/problem_statement.md | 64 ++++++++++ src/.main.rs.swp | Bin 0 -> 20480 bytes src/commands.rs | 18 +++ src/main.rs | 182 ++++++++++++++++++++++++-- src/project.rs | 33 ----- src/satellite.rs | 3 + 8 files changed, 521 insertions(+), 41 deletions(-) create mode 100644 Cargo.lock create mode 100644 docs/problem_statement.md create mode 100644 src/.main.rs.swp create mode 100644 src/commands.rs delete mode 100644 src/project.rs create mode 100644 src/satellite.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ef703cd --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,261 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "satellite" +version = "0.0.0" +dependencies = [ + "tokio", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index 59d1d41..a7f3b38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ license-file = "LICENSE.md" [dependencies] +tokio = { version = "1.44.1", features = ["macros", "rt-multi-thread", "io-std", "io-util", "time", "signal", "sync"] } diff --git a/docs/problem_statement.md b/docs/problem_statement.md new file mode 100644 index 0000000..ce70786 --- /dev/null +++ b/docs/problem_statement.md @@ -0,0 +1,64 @@ +# Problem Statement +You are a member of the flight software team at Umbra and are responsible +for writing code that manages the satellite’s propulsion system. + +Firing the propulsion system involves waiting for a certain period of time before ignition. + +The following is an example usage of this system: + +* At absolute time t = 0, send a command to the computer to fire the propulsion in 15 seconds +* At absolute time t = 2, send a command to the computer to fire the propulsion in 30 seconds +* At absolute time t = 32, the computer begins firing the propulsion + +The flight computer should accept a command with a relative time of when to fire +the propulsion - once that time has elapsed, the program should print out “firing now!”. +If another command is received before the propulsion is fired then the most recently +received relative time should overwrite any existing commands. + +More formally, if a command _A_ is waiting to fire and another command _B_ is received +before A has fired, then B should replace _A_ as the pending command and _A_ should never fire. + +If a time of -1 is given, any outstanding commands to fire the propulsion are cancelled. + +Note that the flight computer should be able to fire the thruster multiple times in a +single execution of the program. + +You may use exactly one of the following interfaces for getting data into and out of your program: +* Standard input/standard output +* TCP + +You can use whichever is more convenient - we note that some languages make asynchronous +IO with standard input/standard output cumbersome and have thus included TCP. + +If you do choose to use TCP, please have your server listen on port `8124`. + +A sample TCP client, `propulsion_tcp_client.py`, is provided that plumbs standard input +to TCP writes and likewise plumbs TCP reads to standard output. + +Commands should be delineated by newlines. + +A sample _complete_ execution of your program using standard input and output is shown below + +``` +./your_program +15 +30 +firing now! +``` + +# Submitting your code +You may complete this assignment in any programming language that you wish. +We're most familiar with Rust and Python at Umbra, but we always enjoy learning +new languages and seeing solutions with different tools! + +Your final submission for this exercise should be a zip file including: +* The source code of your program. +* A brief README with a description of your approach and instructions on how to + build and run the program. +* Any libraries or frameworks needed to run your program, or instructions for installing them. + +Please ensure that your code is clear and legible before submitting. + +While we don't expect you to write production-quality code for this exercise, +readability goes a long way in helping us review your submission. + diff --git a/src/.main.rs.swp b/src/.main.rs.swp new file mode 100644 index 0000000000000000000000000000000000000000..0332a3f32cd74bffdd17eab055da560e6bca9ba4 GIT binary patch literal 20480 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 = Result; + + + +/// Reads input from the user until the termination signal is +/// received. It will send command messages every time it reads valid input. +fn read_user_input(command_sender: mpsc::Sender, + term_sender: watch::Sender) + -> IoResult<()> { - println!("{} v{}", project::get_name(), project::get_version()); + let mut running: bool = true; + let mut buffer = String::new(); + + // Loop and read the user input. + while running + { + buffer.clear(); + std::io::stdin().read_line(&mut buffer)?; + std::io::stdout().flush().unwrap(); + + let input = buffer.trim(); + + match input + { + // A termination method wasn't specified in the document, so + // this will terminate when either stop, exit, or quit is entered. + "exit" | "stop" | "quit" => + { + println!("Exiting application."); + running = false; + term_sender.send_replace(running); + } + + _ => + { + // Check to see if we were given a number. + match input.parse::() + { + Ok(seconds) => + { + // Here we are just handling the different possible numbers + // passed in. -1 cancels commands, less than -1 is ignored + // with a message, and greated than -1 is turned into a + // propulsion command. + if seconds == -1 + { + match command_sender.blocking_send(Command::Cancel) + { + Ok(_) => + { + println!("Cancelling any outstanding commands.") + } + Err(e) => println!("Failed to send command: {}", e) + } + } + else if seconds < -1 + { + println!("All propulsion delay times are given in \ + seconds within the range of [0, {}]", + u32::MAX); + println!("A value of -1 will cancel any current \ + propulsion commands."); + } + else + { + let larger_seconds: u64 = seconds as u64; + let delay_duration: Duration = Duration::from_secs(larger_seconds); + match command_sender.try_send(Command::Propulsion { delay: delay_duration }) + { + Ok(_) => + { + println!("Firing the engines in {} seconds.", + seconds) + } + Err(e) => println!("Failed to send command: {}", e) + } + } + } + + Err(e) => + { + println!("Unable to parse the given input into seconds."); + println!("Please specify only seconds until the time to \ + fire the engines."); + } + } + } + } + } + + Ok(()) } - -/// The usual starting point of your project. -fn main() +async fn process_commands(mut command_receiver: mpsc::Receiver, + mut term_receiver: watch::Receiver) + ->IoResult<()> { - print_version(); + let mut stdout = tokio::io::stdout(); + + let mut running: bool = true; + while running + { + tokio::select! { + Some(command) = command_receiver.recv() => + { + match command + { + Command::Cancel => + { + stdout.write_all(b"Received: Cancel\n").await?; + stdout.flush().await?; + } + + Command::Propulsion { delay } => + { + let mut buffer: Vec = Vec::new(); + writeln!(&mut buffer, "Received: {:?} delay", delay); + stdout.write_all(&buffer).await?; + stdout.flush().await?; + } + + _ => {} + } + } + + _ = term_receiver.changed() => { + stdout.write_all(b"Communication task received shutdown message.").await?; + stdout.flush().await?; + running = *term_receiver.borrow_and_update(); + } + } + } + + Ok(()) +} + +/// Program entry point. +#[tokio::main] +async fn main() -> IoResult<()> +{ + // The channel that will be used to send satellite commands between tasks. + let (command_sender, mut command_receiver) = mpsc::channel::(100); + + // The channel that will be used to signal termination of the program. + // True means the program is running. False means it has been terminated. + // + // This could be done as a satellite command since the program is driven by + // user interaction and it all happens from the input_task, but this is a + // program signal so I prefer to keep it seperate from the command messages. + // It also allows for moving termination control to another task, say from + // Ctrl+C, if the program was changed to use TCP or something else. + let (term_sender, mut term_receiver) = watch::channel(true); + + // Spawn a task to handle reading input from the user. We use a thread here + // because tokio recommends it for the blocking read calls for interactive + // user input. + let input_task = tokio::task::spawn_blocking(move || { + read_user_input(command_sender, term_sender) + }); + + // Spawn a task to handle simulating the satellite. + let sim_task = tokio::spawn(async move { + process_commands(command_receiver, term_receiver).await + }); + + let (sim_result, input_result) = tokio::join!(sim_task, input_task); + sim_result?; + input_result??; + println!("Shutdown complete."); + Ok(()) } diff --git a/src/project.rs b/src/project.rs deleted file mode 100644 index a977e1c..0000000 --- a/src/project.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! This is where the Projects build information can be retreived from. - - - -/// The environment variable defined by Cargo for the name. -const NAME: Option<&str> = option_env!("CARGO_PKG_NAME"); - -/// The environment variable defined by Cargo for the version. -const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); - -/// The string to display if a value is not defined during compile time. -const NOT_DEFINED: &'static str = "UNDEFINED"; - - - -/// Returns the name of the program as defined by the CARGO_PKG_NAME. This is -/// set at compile time and comes from the Cargo.toml file. -/// -/// If a value is not found, then it will return the not defined value. -pub fn get_name() -> &'static str -{ - NAME.unwrap_or(NOT_DEFINED) -} - - -/// Returns the name of the program as defined by the CARGO_PKG_VERSION. This is -/// set at compile time and comes from the Cargo.toml file. -/// -/// If a value is not found, then it will return the not defined value. -pub fn get_version() -> &'static str -{ - VERSION.unwrap_or(NOT_DEFINED) -} diff --git a/src/satellite.rs b/src/satellite.rs new file mode 100644 index 0000000..dcc5021 --- /dev/null +++ b/src/satellite.rs @@ -0,0 +1,3 @@ +pub struct Satellite +{ +}