Functional Weave
Code in Python

net.ipv6@1.0.0

impl/rust/parse_ipv6.rs

2,786 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::net_ipv4_parse_ipv4::ipv4_value;

/// The eight 16-bit groups of an IPv6 address written as RFC 4291 allows.
///
/// Hex groups of one to four digits in either case, at most one "::" standing
/// for one or more zero groups, and optionally a dotted IPv4 address as the
/// last 32 bits. A zone id ("%eth0") or a prefix ("/64") is not part of an
/// address and is refused, as is anything around it (spaces, newlines).
///
/// # Panics
/// Panics when the text is not an IPv6 address.
pub fn parse_ipv6(text: &str) -> Vec<i64> {
    match ipv6_groups(text) {
        Some(groups) => groups,
        None => panic!("\"{}\" is not an IPv6 address", text),
    }
}

// Exported for is_ipv6 and for capabilities that parse IPv6 text without
// wanting a panic; not part of the group's contract.

/// The eight groups, or None when the text is not an IPv6 address.
pub fn ipv6_groups(text: &str) -> Option<Vec<i64>> {
    if text.is_empty() || !text.bytes().all(|b| b.is_ascii_hexdigit() || b == b':' || b == b'.') {
        return None;
    }
    let at = match text.find("::") {
        None => {
            let groups = pieces(&text.split(':').collect::<Vec<_>>(), true)?;
            return if groups.len() == 8 { Some(groups) } else { None };
        }
        Some(at) => at,
    };
    let before = &text[..at];
    let after = &text[at + 2..];
    if after.contains("::") {
        return None;
    }
    let split = |s: &str| -> Vec<String> {
        if s.is_empty() {
            Vec::new()
        } else {
            s.split(':').map(str::to_string).collect()
        }
    };
    let head = pieces(&split(before), false)?;
    let tail = pieces(&split(after), true)?;
    // "::" stands for at least one group, so at most seven are written.
    if head.len() + tail.len() > 7 {
        return None;
    }
    let mut groups = head.clone();
    groups.extend(std::iter::repeat(0).take(8 - head.len() - tail.len()));
    groups.extend(tail);
    Some(groups)
}

fn pieces<S: AsRef<str>>(parts: &[S], may_end_with_ipv4: bool) -> Option<Vec<i64>> {
    let mut groups = Vec::new();
    for (i, part) in parts.iter().enumerate() {
        let part = part.as_ref();
        if part.contains('.') {
            if !may_end_with_ipv4 || i != parts.len() - 1 {
                return None;
            }
            let v4 = ipv4_value(part)?;
            groups.push(v4 / 65536);
            groups.push(v4 % 65536);
            continue;
        }
        if part.is_empty() || part.len() > 4 {
            return None;
        }
        groups.push(i64::from_str_radix(part, 16).ok()?);
    }
    Some(groups)
}

pub fn fune_vector(args: &[Value]) -> Value {
    Value::Arr(parse_ipv6(args[0].as_str()).into_iter().map(Value::Int).collect())
}