diff options
author | Richard Walters <rwalters@digitalstirling.com> | 2020-10-13 01:09:18 -0700 |
---|---|---|
committer | Richard Walters <rwalters@digitalstirling.com> | 2020-10-13 01:09:18 -0700 |
commit | dc2a011598f4aa9e9de927333e467e623276d5ec (patch) | |
tree | 4b5c71634af516cdc96c512f28a02370d48c25b3 /src/validate_ipv4_address.rs | |
parent | 4accf8c296ef7a1f6bd10a90b7a06b3b499ccda6 (diff) |
Rust refactoring
* Move Context, Error, and character classes to their own modules.
* Move host/port parsing and IP address validation to their
own modules, and break the code up into different functions
to process their state machines.
Diffstat (limited to 'src/validate_ipv4_address.rs')
-rw-r--r-- | src/validate_ipv4_address.rs | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/src/validate_ipv4_address.rs b/src/validate_ipv4_address.rs new file mode 100644 index 0000000..1b9f7b2 --- /dev/null +++ b/src/validate_ipv4_address.rs @@ -0,0 +1,96 @@ +#![warn(clippy::pedantic)] + +use super::character_classes::{ + DIGIT, +}; +use super::context::Context; +use super::error::Error; + +struct Shared { + num_groups: usize, + octet_buffer: String, +} + +enum State { + NotInOctet(Shared), + ExpectDigitOrDot(Shared), +} + +impl State { + fn finalize(self) -> Result<(), Error> { + match self { + Self::NotInOctet(_) => Err(Error::TruncatedHost), + Self::ExpectDigitOrDot(state) => Self::finalize_expect_digit_or_dot(state), + } + } + + fn finalize_expect_digit_or_dot(state: Shared) -> Result<(), Error> { + let mut state = state; + if !state.octet_buffer.is_empty() { + state.num_groups += 1; + if state.octet_buffer.parse::<u8>().is_err() { + return Err(Error::InvalidDecimalOctet); + } + } + match state.num_groups { + 4 => Ok(()), + n if n < 4 => Err(Error::TooFewAddressParts), + _ => Err(Error::TooManyAddressParts), + } + } + + fn new() -> Self { + Self::NotInOctet(Shared{ + num_groups: 0, + octet_buffer: String::new(), + }) + } + + fn next(self, c: char) -> Result<Self, Error> { + match self { + Self::NotInOctet(state) => Self::next_not_in_octet(state, c), + Self::ExpectDigitOrDot(state) => Self::next_expect_digit_or_dot(state, c), + } + } + + fn next_not_in_octet(state: Shared, c: char) -> Result<Self, Error> { + let mut state = state; + if DIGIT.contains(&c) { + state.octet_buffer.push(c); + Ok(Self::ExpectDigitOrDot(state)) + } else { + Err(Error::IllegalCharacter(Context::Ipv4Address)) + } + } + + fn next_expect_digit_or_dot(state: Shared, c: char)-> Result<Self, Error> { + let mut state = state; + if c == '.' { + state.num_groups += 1; + if state.num_groups > 4 { + return Err(Error::TooManyAddressParts); + } + if state.octet_buffer.parse::<u8>().is_err() { + return Err(Error::InvalidDecimalOctet); + } + state.octet_buffer.clear(); + Ok(Self::NotInOctet(state)) + } else if DIGIT.contains(&c) { + state.octet_buffer.push(c); + Ok(Self::ExpectDigitOrDot(state)) + } else { + Err(Error::IllegalCharacter(Context::Ipv4Address)) + } + } +} + +pub fn validate_ipv4_address<T>(address: T) -> Result<(), Error> + where T: AsRef<str> +{ + address.as_ref() + .chars() + .try_fold(State::new(), |machine, c| { + machine.next(c) + })? + .finalize() +} |