aboutsummaryrefslogtreecommitdiff
path: root/src/validate_ipv6_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_ipv6_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_ipv6_address.rs')
-rw-r--r--src/validate_ipv6_address.rs212
1 files changed, 212 insertions, 0 deletions
diff --git a/src/validate_ipv6_address.rs b/src/validate_ipv6_address.rs
new file mode 100644
index 0000000..eb3900d
--- /dev/null
+++ b/src/validate_ipv6_address.rs
@@ -0,0 +1,212 @@
+#![warn(clippy::pedantic)]
+
+use super::character_classes::{
+ DIGIT,
+ HEXDIG,
+};
+use super::context::Context;
+use super::error::Error;
+use super::validate_ipv4_address::validate_ipv4_address;
+
+enum MachineExitStatus<'a> {
+ Error(Error),
+ Ipv4Trailer(Shared<'a>),
+}
+
+impl<'a> From<Error> for MachineExitStatus<'a> {
+ fn from(error: Error) -> Self {
+ MachineExitStatus::Error(error)
+ }
+}
+
+struct Shared<'a> {
+ address: &'a str,
+ num_groups: usize,
+ num_digits: usize,
+ double_colon_encountered: bool,
+ potential_ipv4_address_start: usize,
+}
+
+enum State<'a> {
+ NoGroupsYet(Shared<'a>),
+ ColonButNoGroupsYet(Shared<'a>),
+ AfterDoubleColon(Shared<'a>),
+ InGroupNotIpv4(Shared<'a>),
+ InGroupCouldBeIpv4(Shared<'a>),
+ InGroupIpv4(Shared<'a>),
+ ColonAfterGroup(Shared<'a>),
+}
+
+impl<'a> State<'a> {
+ fn finalize(mut self) -> Result<(), Error> {
+ match &mut self {
+ Self::InGroupNotIpv4(state)
+ | Self::InGroupCouldBeIpv4(state) => {
+ // count trailing group
+ state.num_groups += 1;
+ },
+ Self::InGroupIpv4(state) => {
+ validate_ipv4_address(&state.address[state.potential_ipv4_address_start..])?;
+ state.num_groups += 2;
+ },
+ _ => {},
+ };
+ match self {
+ Self::ColonButNoGroupsYet(_)
+ | Self::ColonAfterGroup(_) => Err(Error::TruncatedHost),
+
+ Self::AfterDoubleColon(state)
+ | Self::InGroupNotIpv4(state)
+ | Self::InGroupCouldBeIpv4(state)
+ | Self::InGroupIpv4(state)
+ | Self::NoGroupsYet(state) => {
+ match (state.double_colon_encountered, state.num_groups) {
+ (true, n) if n <= 7 => Ok(()),
+ (false, 8) => Ok(()),
+ (false, n) if n < 8 => Err(Error::TooFewAddressParts),
+ (_, _) => Err(Error::TooManyAddressParts),
+ }
+ }
+ }
+ }
+
+ fn new(address: &'a str) -> Self {
+ Self::NoGroupsYet(Shared{
+ address,
+ num_groups: 0,
+ num_digits: 0,
+ double_colon_encountered: false,
+ potential_ipv4_address_start: 0,
+ })
+ }
+
+ fn next(self, i: usize, c: char) -> Result<Self, MachineExitStatus<'a>> {
+ match self {
+ Self::NoGroupsYet(state) => Self::next_no_groups_yet(state, i, c),
+ Self::ColonButNoGroupsYet(state) => Self::next_colon_but_no_groups_yet(state, c),
+ Self::AfterDoubleColon(state) => Self::next_after_double_colon(state, i, c),
+ Self::InGroupNotIpv4(state) => Self::next_in_group_not_ipv4(state, c),
+ Self::InGroupCouldBeIpv4(state) => Self::next_in_group_could_be_ipv4(state, c),
+ Self::InGroupIpv4(state) => Ok(Self::InGroupIpv4(state)),
+ Self::ColonAfterGroup(state) => Self::next_colon_after_group(state, i, c),
+ }
+ }
+
+ fn next_no_groups_yet(state: Shared<'a>, i: usize, c: char) -> Result<Self, MachineExitStatus> {
+ let mut state = state;
+ if c == ':' {
+ Ok(Self::ColonButNoGroupsYet(state))
+ } else if DIGIT.contains(&c) {
+ state.potential_ipv4_address_start = i;
+ state.num_digits = 1;
+ Ok(Self::InGroupCouldBeIpv4(state))
+ } else if HEXDIG.contains(&c) {
+ state.num_digits = 1;
+ Ok(Self::InGroupNotIpv4(state))
+ } else {
+ Err(Error::IllegalCharacter(Context::Ipv6Address).into())
+ }
+ }
+
+ fn next_colon_but_no_groups_yet(state: Shared<'a>, c: char) -> Result<Self, MachineExitStatus> {
+ let mut state = state;
+ if c == ':' {
+ state.double_colon_encountered = true;
+ Ok(Self::AfterDoubleColon(state))
+ } else {
+ Err(Error::IllegalCharacter(Context::Ipv6Address).into())
+ }
+ }
+
+ fn next_after_double_colon(state: Shared<'a>, i: usize, c: char) -> Result<Self, MachineExitStatus> {
+ let mut state = state;
+ state.num_digits += 1;
+ if state.num_digits > 4 {
+ Err(Error::TooManyDigits.into())
+ } else if DIGIT.contains(&c) {
+ state.potential_ipv4_address_start = i;
+ Ok(Self::InGroupCouldBeIpv4(state))
+ } else if HEXDIG.contains(&c) {
+ Ok(Self::InGroupNotIpv4(state))
+ } else {
+ Err(Error::IllegalCharacter(Context::Ipv6Address).into())
+ }
+ }
+
+ fn next_in_group_not_ipv4(state: Shared<'a>, c: char) -> Result<Self, MachineExitStatus> {
+ let mut state = state;
+ if c == ':' {
+ state.num_digits = 0;
+ state.num_groups += 1;
+ Ok(Self::ColonAfterGroup(state))
+ } else if HEXDIG.contains(&c) {
+ state.num_digits += 1;
+ if state.num_digits > 4 {
+ Err(Error::TooManyDigits.into())
+ } else {
+ Ok(Self::InGroupNotIpv4(state))
+ }
+ } else {
+ Err(Error::IllegalCharacter(Context::Ipv6Address).into())
+ }
+ }
+
+ fn next_in_group_could_be_ipv4(state: Shared<'a>, c: char) -> Result<Self, MachineExitStatus> {
+ let mut state = state;
+ if c == ':' {
+ state.num_digits = 0;
+ state.num_groups += 1;
+ Ok(Self::ColonAfterGroup(state))
+ } else if c == '.' {
+ Err(MachineExitStatus::Ipv4Trailer(state))
+ } else {
+ state.num_digits += 1;
+ if state.num_digits > 4 {
+ Err(Error::TooManyDigits.into())
+ } else if DIGIT.contains(&c) {
+ Ok(Self::InGroupCouldBeIpv4(state))
+ } else if HEXDIG.contains(&c) {
+ Ok(Self::InGroupNotIpv4(state))
+ } else {
+ Err(Error::IllegalCharacter(Context::Ipv6Address).into())
+ }
+ }
+ }
+
+ fn next_colon_after_group(state: Shared<'a>, i: usize, c: char) -> Result<Self, MachineExitStatus> {
+ let mut state = state;
+ if c == ':' {
+ if state.double_colon_encountered {
+ Err(Error::TooManyDoubleColons.into())
+ } else {
+ state.double_colon_encountered = true;
+ Ok(Self::AfterDoubleColon(state))
+ }
+ } else if DIGIT.contains(&c) {
+ state.potential_ipv4_address_start = i;
+ state.num_digits += 1;
+ Ok(Self::InGroupCouldBeIpv4(state))
+ } else if HEXDIG.contains(&c) {
+ state.num_digits += 1;
+ Ok(Self::InGroupNotIpv4(state))
+ } else {
+ Err(Error::IllegalCharacter(Context::Ipv6Address).into())
+ }
+ }
+}
+
+pub fn validate_ipv6_address<T>(address: T) -> Result<(), Error>
+ where T: AsRef<str>
+{
+ let address = address.as_ref();
+ address
+ .char_indices()
+ .try_fold(State::new(address), |machine, (i, c)| {
+ machine.next(i, c)
+ })
+ .or_else(|machine_exit_status| match machine_exit_status {
+ MachineExitStatus::Ipv4Trailer(state) => Ok(State::InGroupIpv4(state)),
+ MachineExitStatus::Error(error) => Err(error)
+ })?
+ .finalize()
+}