aboutsummaryrefslogtreecommitdiff
path: root/src/validate_ipv4_address.rs
diff options
context:
space:
mode:
authorRichard Walters <rwalters@digitalstirling.com>2020-10-13 01:09:18 -0700
committerRichard Walters <rwalters@digitalstirling.com>2020-10-13 01:09:18 -0700
commitdc2a011598f4aa9e9de927333e467e623276d5ec (patch)
tree4b5c71634af516cdc96c512f28a02370d48c25b3 /src/validate_ipv4_address.rs
parent4accf8c296ef7a1f6bd10a90b7a06b3b499ccda6 (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.rs96
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()
+}