From 1a008a060ec15bac0b74fe8acf129f4b0436e306 Mon Sep 17 00:00:00 2001 From: Nicholas Omer Chiasson Date: Tue, 4 Jul 2023 15:06:11 -0400 Subject: [PATCH] Some house keeping --- .github/FUNDING.yml | 12 ++ .github/workflows/rust.yml | 150 +++++++++++++++++++++++ .gitignore | 16 ++- Cargo.toml | 19 ++- LICENSE | 21 ++++ README.md | 12 ++ src/cidr.rs | 38 +++--- src/fcidr.rs | 241 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 244 +------------------------------------ 9 files changed, 488 insertions(+), 265 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/rust.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/fcidr.rs diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..daf64af --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +# github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +# patreon: # Replace with a single Patreon username +open_collective: nicholaschiasson +ko_fi: nicholaschiasson +# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: nicholaschiasson +issuehunt: nicholaschiasson +# otechie: # Replace with a single Otechie username +# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..94f28a3 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,150 @@ +name: Rust + +on: + push: + branches: + - 'main' + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + validate: + strategy: + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Checkout + uses: actions/checkout@v3.5.2 + - name: Check + run: cargo check --verbose + - name: Format + run: cargo fmt --check --verbose + - name: Lint + run: rustup component add clippy && cargo clippy --verbose + - name: Test + run: cargo test --verbose + + tag: + if: github.event_name == 'push' || (github.base_ref == 'main' && github.event.pull_request.merged == true) + runs-on: ubuntu-latest + needs: [validate] + outputs: + version: ${{ steps.stamp.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@v3.5.2 + - name: Check semver bump + id: check-semver + run: | + if [[ "${{ github.event.head_commit.message }}" =~ ^Merge\ pull\ request\ #[0-9]+\ from\ [^/]+/patch/.+$ ]] + then + echo "semver=patch" >> $GITHUB_OUTPUT + elif [[ "${{ github.event.head_commit.message }}" =~ ^Merge\ pull\ request\ #[0-9]+\ from\ [^/]+/major/.+$ ]] + then + echo "semver=major" >> $GITHUB_OUTPUT + else + echo "semver=minor" >> $GITHUB_OUTPUT + fi + - name: Bump major version and push tag + id: bump-major + if: ${{ steps.check-semver.outputs.semver == 'major' }} + uses: anothrNick/github-tag-action@1.65.0 + env: + DEFAULT_BUMP: major + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Bump minor version and push tag + id: bump-minor + if: ${{ steps.check-semver.outputs.semver == 'minor' }} + uses: anothrNick/github-tag-action@1.65.0 + env: + DEFAULT_BUMP: minor + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Bump patch version and push tag + id: bump-patch + if: ${{ steps.check-semver.outputs.semver == 'patch' }} + uses: anothrNick/github-tag-action@1.65.0 + env: + DEFAULT_BUMP: patch + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Stamp version + id: stamp + run: | + if [[ "${{ steps.check-semver.outputs.semver }}" == patch ]] + then + VERSION=${{ steps.bump-patch.outputs.new_tag }} + elif [[ "${{ steps.check-semver.outputs.semver }}" == major ]] + then + VERSION=${{ steps.bump-major.outputs.new_tag }} + else + VERSION=${{ steps.bump-minor.outputs.new_tag }} + fi + echo "version=${VERSION}" >> $GITHUB_OUTPUT + sed -i "s/version = \"0.0.0\"/version = \"${VERSION}\"/" Cargo.toml + - name: Upload Build Artifact + uses: actions/upload-artifact@v3.1.2 + with: + name: 'Cargo.toml' + path: 'Cargo.toml' + + build: + if: github.event_name == 'push' || (github.base_ref == 'main' && github.event.pull_request.merged == true) + strategy: + matrix: + platform: [macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + needs: [tag] + steps: + - name: Checkout + uses: actions/checkout@v3.5.2 + - name: Download Build Artifacts + uses: actions/download-artifact@v3.0.2 + with: + name: 'Cargo.toml' + - name: Build + shell: bash + run: | + RAW_BINARY_NAME=fcidr + BINARY_NAME=${RAW_BINARY_NAME} + if [[ ${{ startsWith(matrix.platform, 'windows') }} == true ]] + then + BINARY_NAME=${BINARY_NAME}.exe + fi + cargo build --release --verbose + cp target/release/${BINARY_NAME} ./ + tar czf ${RAW_BINARY_NAME}-${{ runner.os }}-${{ runner.arch }}.tar.gz ${BINARY_NAME} + - name: Upload Build Artifact + uses: actions/upload-artifact@v3.1.2 + with: + path: '*.tar.gz' + + publish: + if: github.event_name == 'push' || (github.base_ref == 'main' && github.event.pull_request.merged == true) + runs-on: ubuntu-latest + needs: [tag] + steps: + - name: Checkout + uses: actions/checkout@v3.5.2 + - name: Download Build Artifacts + uses: actions/download-artifact@v3.0.2 + with: + name: 'Cargo.toml' + - name: Publish to crates.io + run: | + cargo login ${{ secrets.CRATES_IO_API_TOKEN }} + cargo publish --allow-dirty --verbose + + release: + if: github.event_name == 'push' || (github.base_ref == 'main' && github.event.pull_request.merged == true) + runs-on: ubuntu-latest + needs: [tag, build] + steps: + - name: Download Build Artifacts + uses: actions/download-artifact@v3.0.2 + - name: Release + uses: softprops/action-gh-release@v0.1.15 + with: + files: 'artifact/*.tar.gz' + tag_name: ${{ needs.tag.outputs.version }} diff --git a/.gitignore b/.gitignore index 4fffb2f..6985cf1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,14 @@ -/target -/Cargo.lock +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/Cargo.toml b/Cargo.toml index c398e99..06a4d71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,23 @@ [package] name = "fcidr" -version = "0.1.0" +version = "0.0.0" +authors = ["Nicholas Omer Chiasson "] edition = "2021" +license = "MIT" +description = """ +Fragmented Classless Inter-Domain Routing (FCIDR) -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +A library exposing a data structure to represent a set of CIDR ranges and +easily manipulate its entries using set-like operations. +""" +readme = "README.md" +homepage = "https://github.com/nicholaschiasson/fcidr" +repository = "https://github.com/nicholaschiasson/fcidr" +keywords = ["network", "ip", "ipv4", "cidr"] +categories = ["data-structures", "network-programming"] + +[badges] +github = { repository = "nicholaschiasson/fcidr" } +maintenance = { status = "passively-maintained" } [dependencies] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bee018c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Nicholas Omer Chiasson + +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/README.md b/README.md new file mode 100644 index 0000000..afdc29b --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# fcidr + +[![crates.io](https://img.shields.io/crates/v/fcidr)](https://crates.io/crates/fcidr) + +Fragmented Classless Inter-Domain Routing (FCIDR) + +A library exposing a data structure to represent a set of CIDR ranges and +easily manipulate its entries using set-like operations. + +This data structure can be applied, for example, in configuring firewalls that +*implicitly deny* (AWS Security Groups) using a rule set that explicitly +expresses rules for both allow and deny. diff --git a/src/cidr.rs b/src/cidr.rs index 28cd9d5..df3b551 100644 --- a/src/cidr.rs +++ b/src/cidr.rs @@ -12,18 +12,18 @@ pub struct Cidr { #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub enum Error { - CidrBoundsError(String), - InvalidNetworkError(String), - PrefixRangeError(String), - ParseError(String), - TypeCastError(String), - ImpossibleError(String), + CidrNotInRange(String), + InvalidNetwork(String), + InvalidPrefix(String), + Parse(String), + TypeCast(String), + Impossible(String), } impl Cidr { pub fn new(network: Ipv4Addr, prefix: u8) -> Result { if prefix as u32 > u32::BITS { - return Err(Error::PrefixRangeError(format!( + return Err(Error::InvalidPrefix(format!( "network prefix '{prefix}' must be 32 or less" ))); } @@ -37,7 +37,7 @@ impl Cidr { (if i > 0 { o } else { o << offset >> offset }) != 0 }) { - return Err(Error::InvalidNetworkError(format!( + return Err(Error::InvalidNetwork(format!( "network address '{network}' must be clear after the first {prefix} bits" ))); } @@ -67,12 +67,12 @@ impl Cidr { pub fn last(&self) -> Ipv4Addr { let mut last = self.network.octets(); let first_octet: usize = (self.prefix() / 8).into(); - for i in first_octet..last.len() { - if i > first_octet { - last[i] = u8::MAX + for (i, o) in last.iter_mut().skip(first_octet).enumerate() { + if i > 0 { + *o = u8::MAX } else { let offset = self.prefix % 8; - last[i] |= u8::MAX << offset >> offset; + *o |= u8::MAX << offset >> offset; } } Ipv4Addr::from(last) @@ -82,9 +82,9 @@ impl Cidr { where T: Copy + Debug + TryInto, { - let cidr: Cidr = net.try_into().map_err(|_| { - Error::TypeCastError(format!("could not cast value '{:?}' to cidr", net)) - })?; + let cidr: Cidr = net + .try_into() + .map_err(|_| Error::TypeCast(format!("could not cast value '{:?}' to cidr", net)))?; Ok(cidr.first() >= self.first() && cidr.last() <= self.last()) } @@ -128,15 +128,13 @@ impl FromStr for Cidr { Self::new( network .parse::() - .map_err(|e| Error::ParseError(e.to_string()))?, + .map_err(|e| Error::Parse(e.to_string()))?, prefix .parse::() - .map_err(|e| Error::ParseError(e.to_string()))?, + .map_err(|e| Error::Parse(e.to_string()))?, ) } else { - Err(Error::ParseError( - "missing network prefix delimiter".to_owned(), - )) + Err(Error::Parse("missing network prefix delimiter".to_owned())) } } } diff --git a/src/fcidr.rs b/src/fcidr.rs new file mode 100644 index 0000000..2662510 --- /dev/null +++ b/src/fcidr.rs @@ -0,0 +1,241 @@ +use std::{cell::RefCell, fmt::Display, rc::Rc}; + +use crate::{cidr, Cidr}; + +#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] +enum FcidrInclusion { + #[default] + Excluded, + Included, + Subnets([Option>>; 2]), +} + +enum SetInclusionAction { + Exclude, + Include, +} + +#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] +pub struct Fcidr { + cidr: Cidr, + inclusion: FcidrInclusion, +} + +impl Fcidr { + pub fn new(cidr: Cidr) -> Result { + let mut fcidr = Self::default(); + fcidr.include(cidr)?; + Ok(fcidr) + } + + fn new_subnet(child: Cidr) -> Self { + Self { + cidr: child, + ..Default::default() + } + } + + fn set_cidr_inclusion( + &mut self, + cidr: Cidr, + inclusion: &SetInclusionAction, + ) -> Result<(), cidr::Error> { + let (fcidr_inclusion, inverse_fcidr_inclusion, inverse_inclusion, inclusion_str) = + match inclusion { + SetInclusionAction::Exclude => ( + FcidrInclusion::Excluded, + FcidrInclusion::Included, + SetInclusionAction::Include, + "exclude", + ), + SetInclusionAction::Include => ( + FcidrInclusion::Included, + FcidrInclusion::Excluded, + SetInclusionAction::Exclude, + "include", + ), + }; + + if self.cidr == cidr { + self.inclusion = fcidr_inclusion; + return Ok(()); + } + + if !self.cidr.contains(cidr)? { + return Err(cidr::Error::CidrNotInRange(format!( + "cidr '{}' cannot {inclusion_str} '{}' which it does not contain", + self.cidr, cidr + ))); + } + + if self.inclusion == inverse_fcidr_inclusion { + for cidr in self.cidr.split()? { + self.set_cidr_inclusion(cidr, &inverse_inclusion)?; + } + } + + if !matches!(self.inclusion, FcidrInclusion::Subnets(_)) { + self.inclusion = FcidrInclusion::Subnets([None, None]); + } + + let prefix = self.cidr.prefix() + 1; + + if prefix as u32 > u32::BITS { + return Err(cidr::Error::InvalidPrefix(format!( + "network prefix '{}' must be 32 or less", + cidr.prefix() + ))); + } + + let index = ((u32::from(cidr.network()) >> (u32::BITS - prefix as u32)) & 1) as usize; + + let subnet = match (index & 1, &mut self.inclusion) { + (0, FcidrInclusion::Subnets([Some(subnet), _])) + | (1, FcidrInclusion::Subnets([_, Some(subnet)])) => subnet.clone(), + (_, FcidrInclusion::Subnets(subnets)) => { + let subnet = Rc::new(RefCell::new(Fcidr::new_subnet(self.cidr.split()?[index]))); + subnets[index] = Some(subnet.clone()); + subnet + } + (_, inclusion) => { + return Err(cidr::Error::Impossible(format!( + "inclusion state is '{inclusion:?}'" + ))) + } + }; + + let res = (*subnet).borrow_mut().set_cidr_inclusion(cidr, inclusion); + res + } + + pub fn exclude(&mut self, cidr: Cidr) -> Result<(), cidr::Error> { + self.set_cidr_inclusion(cidr, &SetInclusionAction::Exclude) + } + + pub fn include(&mut self, cidr: Cidr) -> Result<(), cidr::Error> { + self.set_cidr_inclusion(cidr, &SetInclusionAction::Include) + } + + pub fn iter(&self) -> FcidrIntoIterator { + match &self.inclusion { + FcidrInclusion::Excluded => FcidrIntoIterator { + next: None, + remaining: Vec::new(), + }, + FcidrInclusion::Included => FcidrIntoIterator { + next: Some(self.cidr), + remaining: Vec::new(), + }, + FcidrInclusion::Subnets(subnets) => FcidrIntoIterator { + next: None, + remaining: subnets + .iter() + .rev() + .flatten() + .map(|s| s.to_owned()) + .collect(), + }, + } + } +} + +impl Display for Fcidr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for cidr in self.into_iter() { + writeln!(f, "{cidr}")?; + } + Ok(()) + } +} + +impl IntoIterator for Fcidr { + type Item = Cidr; + + type IntoIter = FcidrIntoIterator; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl IntoIterator for &Fcidr { + type Item = Cidr; + + type IntoIter = FcidrIntoIterator; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl TryFrom for Fcidr { + type Error = cidr::Error; + + fn try_from(value: Cidr) -> Result { + Self::new(value) + } +} + +pub struct FcidrIntoIterator { + // TODO: Get rid of this when datastructure uses actual tree node for root + next: Option, + remaining: Vec>>, +} + +impl Iterator for FcidrIntoIterator { + type Item = Cidr; + + fn next(&mut self) -> Option { + // TODO: Get rid of this when datastructure uses actual tree node for root + if let Some(next) = self.next { + self.next = None; + return Some(next); + } + + while let Some(fcidr) = self.remaining.pop() { + let fcidr = (*fcidr).borrow(); + match &fcidr.inclusion { + FcidrInclusion::Excluded => continue, + FcidrInclusion::Included => return Some(fcidr.cidr), + FcidrInclusion::Subnets(subnets) => { + for subnet in subnets.iter().rev().flatten().map(|s| s.to_owned()) { + self.remaining.push(subnet); + } + } + } + } + + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let mut fcidr = Fcidr::default(); + fcidr.include("10.0.0.0/24".parse().unwrap()).unwrap(); + fcidr.include("10.0.128.0/25".parse().unwrap()).unwrap(); + fcidr.include("11.0.0.0/8".parse().unwrap()).unwrap(); + fcidr.exclude("10.0.0.64/32".parse().unwrap()).unwrap(); + fcidr.include("0.0.0.0/0".parse().unwrap()).unwrap(); + fcidr.exclude("128.0.0.0/32".parse().unwrap()).unwrap(); + fcidr + .exclude("255.255.255.255/32".parse().unwrap()) + .unwrap(); + // fcidr.include("0.0.0.0/0".parse().unwrap()).unwrap(); + println!("{fcidr}"); + // println!("{:?}", fcidr.iter().collect::>()); + println!("{fcidr:?}"); + // fcidr.exclude("10.0.0.1/32".parse().unwrap()); + // for i in 0..=32 { + // println!("{} {}", i / 8, i % 8); + // } + // let o = 127_u8; + // println!("{}", o == o >> 1 << 1); + // println!("{}", "127.0.343.0".parse::().unwrap()); + // println!("{}", "127.0.343.0".parse::().unwrap()); + } +} diff --git a/src/lib.rs b/src/lib.rs index d0c3378..e9dbd5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,243 +1,5 @@ mod cidr; +mod fcidr; -use std::{cell::RefCell, fmt::Display, rc::Rc}; - -pub use cidr::Cidr; - -#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] -pub enum FcidrInclusion { - #[default] - Excluded, - Included, - Subnets([Option>>; 2]), -} - -enum SetInclusionAction { - Exclude, - Include, -} - -#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] -pub struct Fcidr { - cidr: Cidr, - inclusion: FcidrInclusion, -} - -impl Fcidr { - pub fn new(cidr: Cidr) -> Result { - let mut fcidr = Self::default(); - fcidr.include(cidr)?; - Ok(fcidr) - } - - fn new_subnet(child: Cidr) -> Self { - Self { - cidr: child, - ..Default::default() - } - } - - fn set_cidr_inclusion( - &mut self, - cidr: Cidr, - inclusion: &SetInclusionAction, - ) -> Result<(), cidr::Error> { - let (fcidr_inclusion, inverse_fcidr_inclusion, inverse_inclusion, inclusion_str) = - match inclusion { - SetInclusionAction::Exclude => ( - FcidrInclusion::Excluded, - FcidrInclusion::Included, - SetInclusionAction::Include, - "exclude", - ), - SetInclusionAction::Include => ( - FcidrInclusion::Included, - FcidrInclusion::Excluded, - SetInclusionAction::Exclude, - "include", - ), - }; - - if self.cidr == cidr { - self.inclusion = fcidr_inclusion; - return Ok(()); - } - - if !self.cidr.contains(cidr)? { - return Err(cidr::Error::CidrBoundsError(format!( - "cidr '{}' cannot {inclusion_str} '{}' which it does not contain", - self.cidr, cidr - ))); - } - - if self.inclusion == inverse_fcidr_inclusion { - for cidr in self.cidr.split()? { - self.set_cidr_inclusion(cidr, &inverse_inclusion)?; - } - } - - if !matches!(self.inclusion, FcidrInclusion::Subnets(_)) { - self.inclusion = FcidrInclusion::Subnets([None, None]); - } - - let prefix = self.cidr.prefix() + 1; - - if prefix as u32 > u32::BITS { - return Err(cidr::Error::PrefixRangeError(format!( - "network prefix '{}' must be 32 or less", - cidr.prefix() - ))); - } - - let index = ((u32::from(cidr.network()) >> (u32::BITS - prefix as u32)) & 1) as usize; - - let subnet = match (index & 1, &mut self.inclusion) { - (0, FcidrInclusion::Subnets([Some(subnet), _])) - | (1, FcidrInclusion::Subnets([_, Some(subnet)])) => subnet.clone(), - (_, FcidrInclusion::Subnets(subnets)) => { - let subnet = Rc::new(RefCell::new(Fcidr::new_subnet(self.cidr.split()?[index]))); - subnets[index] = Some(subnet.clone()); - subnet - } - (_, inclusion) => { - return Err(cidr::Error::ImpossibleError(format!( - "inclusion state is '{inclusion:?}'" - ))) - } - }; - - let res = (*subnet).borrow_mut().set_cidr_inclusion(cidr, inclusion); - res - } - - pub fn exclude(&mut self, cidr: Cidr) -> Result<(), cidr::Error> { - self.set_cidr_inclusion(cidr, &SetInclusionAction::Exclude) - } - - pub fn include(&mut self, cidr: Cidr) -> Result<(), cidr::Error> { - self.set_cidr_inclusion(cidr, &SetInclusionAction::Include) - } - - pub fn iter(&self) -> FcidrIntoIterator { - match &self.inclusion { - FcidrInclusion::Excluded => FcidrIntoIterator { - next: None, - remaining: Vec::new(), - }, - FcidrInclusion::Included => FcidrIntoIterator { - next: Some(self.cidr), - remaining: Vec::new(), - }, - FcidrInclusion::Subnets(subnets) => FcidrIntoIterator { - next: None, - remaining: subnets - .iter() - .rev() - .flatten() - .map(|s| s.to_owned()) - .collect(), - }, - } - } -} - -impl Display for Fcidr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for cidr in self.into_iter() { - writeln!(f, "{cidr}")?; - } - Ok(()) - } -} - -impl IntoIterator for Fcidr { - type Item = Cidr; - - type IntoIter = FcidrIntoIterator; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl IntoIterator for &Fcidr { - type Item = Cidr; - - type IntoIter = FcidrIntoIterator; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl TryFrom for Fcidr { - type Error = cidr::Error; - - fn try_from(value: Cidr) -> Result { - Self::new(value) - } -} - -pub struct FcidrIntoIterator { - // TODO: Get rid of this when datastructure uses actual tree node for root - next: Option, - remaining: Vec>>, -} - -impl Iterator for FcidrIntoIterator { - type Item = Cidr; - - fn next(&mut self) -> Option { - // TODO: Get rid of this when datastructure uses actual tree node for root - if let Some(next) = self.next { - self.next = None; - return Some(next); - } - - while let Some(fcidr) = self.remaining.pop() { - let fcidr = (*fcidr).borrow(); - match &fcidr.inclusion { - FcidrInclusion::Excluded => continue, - FcidrInclusion::Included => return Some(fcidr.cidr), - FcidrInclusion::Subnets(subnets) => { - for subnet in subnets.iter().rev().flatten().map(|s| s.to_owned()) { - self.remaining.push(subnet); - } - } - } - } - - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let mut fcidr = Fcidr::default(); - fcidr.include("10.0.0.0/24".parse().unwrap()).unwrap(); - fcidr.include("10.0.128.0/25".parse().unwrap()).unwrap(); - fcidr.include("11.0.0.0/8".parse().unwrap()).unwrap(); - fcidr.exclude("10.0.0.64/32".parse().unwrap()).unwrap(); - fcidr.include("0.0.0.0/0".parse().unwrap()).unwrap(); - fcidr.exclude("128.0.0.0/32".parse().unwrap()).unwrap(); - fcidr - .exclude("255.255.255.255/32".parse().unwrap()) - .unwrap(); - // fcidr.include("0.0.0.0/0".parse().unwrap()).unwrap(); - println!("{fcidr}"); - // println!("{:?}", fcidr.iter().collect::>()); - println!("{fcidr:?}"); - // fcidr.exclude("10.0.0.1/32".parse().unwrap()); - // for i in 0..=32 { - // println!("{} {}", i / 8, i % 8); - // } - // let o = 127_u8; - // println!("{}", o == o >> 1 << 1); - // println!("{}", "127.0.343.0".parse::().unwrap()); - // println!("{}", "127.0.343.0".parse::().unwrap()); - } -} +pub use crate::cidr::Cidr; +pub use crate::fcidr::Fcidr;