//! Parse an IP address or a CIDR specification.
/*
 * Copyright (c) 2021  Peter Pentchev <roam@ringlet.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

use std::error;
use std::net;
use std::str::FromStr;

use expect_exit::ExpectedResult;

use crate::defs;

quick_error! {
    #[derive(Debug)]
    enum ParseError {
        CidrBadStart {
            display("CIDR base address didn't start at subnet boundary")
        }
        CidrPrefixTooLarge {
            display("CIDR offsets are between 0 and 32")
        }
        StartBeforeEnd {
            display("The start address must be smaller than the end address")
        }
    }
}

/// Parse an IPv4 address into an unsigned 32-bit number.
pub fn parse_addr(saddr: &str) -> Result<u32, Box<dyn error::Error>> {
    let start_addr = net::Ipv4Addr::from_str(saddr)?;
    let octets = start_addr.octets();
    Ok(((octets[0] as u32) << 24)
        | ((octets[1] as u32) << 16)
        | ((octets[2] as u32) << 8)
        | (octets[3] as u32))
}

/// Parse an address/prefixlen specification into an address range.
pub fn parse_cidr(spec: &str) -> Result<defs::AddrRange, Box<dyn error::Error>> {
    let (saddr, sprefix_len) = spec
        .split_once('/')
        .expect_result_("No CIDR prefix length specified")?;
    let start = parse_addr(saddr)?;

    let prefix_len: u32 = sprefix_len.parse()?;
    match prefix_len {
        0 => match start == 0 {
            true => Ok(defs::AddrRange {
                start,
                end: 0xFFFFFFFF,
            }),
            false => Err(Box::new(ParseError::CidrBadStart)),
        },
        value if value < 32 => {
            let offset = 1 << (32 - value);
            match (start & (offset - 1)) == 0 {
                true => Ok(defs::AddrRange {
                    start,
                    end: start + (offset - 1),
                }),
                false => Err(Box::new(ParseError::CidrBadStart)),
            }
        }
        32 => Ok(defs::AddrRange { start, end: start }),
        _ => Err(Box::new(ParseError::CidrPrefixTooLarge)),
    }
}

/// Parse two IPv4 addresses into a range.
pub fn parse_range(first: &str, second: &str) -> Result<defs::AddrRange, Box<dyn error::Error>> {
    let start = parse_addr(first)?;
    let end = parse_addr(second)?;
    match start <= end {
        true => Ok(defs::AddrRange { start, end }),
        false => Err(Box::new(ParseError::StartBeforeEnd)),
    }
}
