commit ed9402b947a59094e4f3657d83107ff74ec6013c Author: Maciej Pędzich Date: Fri Dec 27 19:56:27 2024 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a0038a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f47e0a9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,54 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "podsieci-rust" +version = "0.1.0" +dependencies = [ + "regex", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..dab1710 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "podsieci-rust" +version = "0.1.0" +edition = "2021" + +[dependencies] +regex = "1.11.1" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..34d36fa --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,6 @@ +max_width = 80 +format_code_in_doc_comments = true +imports_granularity = "Crate" +imports_layout = "Vertical" +use_small_heuristics = "Max" +wrap_comments = true \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9bc18eb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,139 @@ +use regex::Regex; +use std::cmp::Ordering; +use std::collections::BTreeSet; +use std::env::args; +use std::fmt::{Display, Formatter}; +use std::net::Ipv4Addr; +use std::process::exit; +use std::str::FromStr; + +fn main() { + println!("(c) 2024 Maciej Pędzich. Released under the GNU General Public License v3.0"); + println!(); + + let arguments = args().collect::>(); + + if arguments.len() != 4 { + eprintln!("Please enter 3 arguments:"); + eprintln!(); + eprintln!("1. Main subnet CIDR, eg. 12.34.56.78/9"); + eprintln!( + "2. Comma-separated list of subnets with their names and minimum number of hosts, \ + eg. \"(A,12), (B,34), (C,56)\"" + ); + eprintln!( + "3. A-Z to order subnets with the same sizes alphabetically, \ + or Z-A to use reverse alphabetical order" + ); + exit(1); + } + + let input_cidr = arguments[1].split_once("/").unwrap(); + let input_ip = Ipv4Addr::from_str(input_cidr.0).unwrap(); + let input_num_subnet_bits = input_cidr.1.parse::().unwrap(); + let input_num_host_bits = Ipv4Addr::BITS - input_num_subnet_bits; + + let input_subnet_mask = Ipv4Addr::from( + ((1 << input_num_subnet_bits) - 1) << input_num_host_bits, + ); + let input_broadcast_mask = Ipv4Addr::from((1 << input_num_host_bits) - 1); + let input_subnet_base_ip = input_ip & input_subnet_mask; + let input_subnet_broadcast_ip = input_subnet_base_ip | input_broadcast_mask; + + println!("Input subnet's base IP: {}", input_subnet_base_ip); + println!("Input subnet's broadcast IP: {}", input_subnet_broadcast_ip); + println!(); + + let subnet_pattern = Regex::new(r"\((\w),(\d+)\)").unwrap(); + let ordered_subnets = subnet_pattern + .captures_iter(&arguments[2]) + .map(|capture| { + let [name, min_num_hosts] = capture.extract::<2>().1; + let min_num_ips = min_num_hosts.parse::().unwrap() + 2; + + Subnet::new(name, min_num_ips) + }) + .collect::>(); + + let mut current_subnet_base_ip = input_subnet_base_ip; + + for subnet in ordered_subnets { + print!("{}, ", subnet); + print!("Base IP: {}, ", current_subnet_base_ip); + + let num_subnet_bits = (subnet.size - 1).leading_zeros(); + let num_host_bits = Ipv4Addr::BITS - num_subnet_bits; + let subnet_mask = + Ipv4Addr::from(((1 << num_subnet_bits) - 1) << num_host_bits); + + print!("Subnet mask: {}/{}, ", subnet_mask, num_subnet_bits); + + current_subnet_base_ip = + Ipv4Addr::from(current_subnet_base_ip.to_bits() + subnet.size); + + println!( + "Broadcast IP: {}", + Ipv4Addr::from(current_subnet_base_ip.to_bits() - 1) + ); + } + + let num_input_subnet_ips = 1 << input_num_host_bits; + let total_num_subnet_ips = + current_subnet_base_ip.to_bits() - input_subnet_base_ip.to_bits(); + + println!(); + println!("Number of available IPs: {}", num_input_subnet_ips); + println!("Number of IPs used by all subnets: {}", total_num_subnet_ips); + + if total_num_subnet_ips > num_input_subnet_ips { + eprintln!("ERROR: Not enough available IPs"); + exit(1); + } +} + +struct Subnet { + name: String, + size: u32, +} + +impl Subnet { + fn new(name: &str, min_num_ips: u32) -> Self { + let num_host_bits = Ipv4Addr::BITS - (min_num_ips - 1).leading_zeros(); + + Subnet { name: name.to_string(), size: 1 << num_host_bits } + } +} + +impl PartialEq for Subnet { + fn eq(&self, other: &Self) -> bool { + self.name == other.name && self.size == other.size + } +} + +impl Eq for Subnet {} + +impl Ord for Subnet { + fn cmp(&self, other: &Self) -> Ordering { + other.size.cmp(&self.size).then_with(|| { + let name_order = args().nth(3).unwrap(); + + if name_order == "A-Z" { + self.name.cmp(&other.name) + } else { + other.name.cmp(&self.name) + } + }) + } +} + +impl PartialOrd for Subnet { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Display for Subnet { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!(f, "{}) Size: {}", self.name, self.size) + } +}