use std::net::{Ipv4Addr, Ipv6Addr}; #[derive(Debug, Clone, Copy)] pub enum IpAddr { V4([u8; 4]), V6([u8; 16]), } #[derive(Debug)] pub struct ParseResult { pub addr: IpAddr, pub port: Option, pub consumed: usize, } pub struct Parser<'a> { input: &'a str, cursor: usize, } impl<'a> Parser<'a> { fn new(input: &'a str) -> Self { Self { input, cursor: 0 } } fn current_char(&self) -> Option { self.input.chars().nth(self.cursor) } fn peek_char(&self, offset: usize) -> Option { self.input.chars().nth(self.cursor + offset) } fn advance(&mut self) { if self.cursor < self.input.len() { self.cursor += 1; } } fn find_char(&self, ch: char) -> Option { self.input[self.cursor..].find(ch).map(|pos| self.cursor + pos) } fn parse_decimal(&mut self) -> Result { let start = self.cursor; let mut val = 0u32; while let Some(ch) = self.current_char() { if ch.is_ascii_digit() { val = val.checked_mul(10).ok_or("decimal overflow")?; val = val.checked_add((ch as u32) - ('0' as u32)).ok_or("decimal overflow")?; self.advance(); } else { break; } } if self.cursor == start { Err("no digits found") } else { Ok(val) } } fn parse_hex(&mut self) -> Result { let start = self.cursor; let mut val = 0u32; while let Some(ch) = self.current_char() { let digit = match ch { '0'..='9' => (ch as u32) - ('0' as u32), 'a'..='f' => (ch as u32) - ('a' as u32) + 10, 'A'..='F' => (ch as u32) - ('A' as u32) + 10, _ => break, }; val = val.checked_shl(4).ok_or("hex overflow")?; val = val.checked_add(digit).ok_or("hex overflow")?; self.advance(); } if self.cursor == start { Err("no hex digits found") } else { Ok(val) } } fn parse_ipv4(&mut self) -> Result<[u8; 4], &'static str> { let mut addr = [0u8; 4]; for i in 0..4 { let val = self.parse_decimal()?; if val > 255 { return Err("IPv4 octet out of range"); } addr[i] = val as u8; if i < 3 { if self.current_char() != Some('.') { return Err("expected '.' in IPv4 address"); } self.advance(); } } Ok(addr) } fn parse_ipv6(&mut self) -> Result<[u8; 16], &'static str> { let mut addr = [0u8; 16]; let mut groups = Vec::new(); let mut compression_pos = None; let mut has_ipv4_suffix = false; // Handle brackets let has_brackets = self.current_char() == Some('['); if has_brackets { self.advance(); } // Parse groups loop { let start_cursor = self.cursor; // Check for empty group (compression) if self.current_char() == Some(':') { if compression_pos.is_some() { // Check if this is the end of the address if groups.len() == 0 || (groups.len() == 1 && compression_pos == Some(0)) { break; } return Err("multiple :: compressions not allowed"); } compression_pos = Some(groups.len()); self.advance(); // Handle leading :: if groups.is_empty() && self.current_char() == Some(':') { self.advance(); } continue; } // Try to parse hex match self.parse_hex() { Ok(val) => { if val > 0xFFFF { return Err("IPv6 group out of range"); } // Check for IPv4 suffix if self.current_char() == Some('.') { // Rewind and parse as IPv4 self.cursor = start_cursor; let ipv4_addr = self.parse_ipv4()?; // Validate IPv4-mapped IPv6 prefix if groups.len() != 6 || groups[0..5] != [0, 0, 0, 0, 0] || groups[5] != 0xFFFF { return Err("IPv4 suffix only allowed in ::ffff: mapping"); } // Add IPv4 bytes as two 16-bit groups groups.push(((ipv4_addr[0] as u16) << 8) | (ipv4_addr[1] as u16)); groups.push(((ipv4_addr[2] as u16) << 8) | (ipv4_addr[3] as u16)); has_ipv4_suffix = true; break; } groups.push(val as u16); // Check for continuation if self.current_char() == Some(':') { self.advance(); } else { break; } } Err(_) => { if groups.is_empty() { return Err("invalid IPv6 format"); } break; } } } // Handle compression if let Some(pos) = compression_pos { let groups_after = groups.len() - pos; let zeros_needed = 8 - groups.len(); if zeros_needed > 0 { let mut new_groups = Vec::new(); new_groups.extend_from_slice(&groups[..pos]); new_groups.resize(new_groups.len() + zeros_needed, 0); new_groups.extend_from_slice(&groups[pos..]); groups = new_groups; } } if groups.len() != 8 { return Err("IPv6 address must have 8 groups"); } // Convert to bytes for (i, &group) in groups.iter().enumerate() { addr[i * 2] = (group >> 8) as u8; addr[i * 2 + 1] = (group & 0xFF) as u8; } // Validate IPv4-mapped address if has_ipv4_suffix { let ipv4_mapped_prefix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF]; if addr[..12] != ipv4_mapped_prefix { return Err("invalid IPv4-mapped IPv6 address"); } } // Handle closing bracket if has_brackets { if self.current_char() != Some(']') { return Err("expected closing bracket"); } self.advance(); } Ok(addr) } fn parse_port(&mut self) -> Result { if self.current_char() != Some(':') { return Err("expected ':' before port"); } self.advance(); let port = self.parse_decimal()?; if port > 65535 { return Err("port out of range"); } Ok(port as u16) } } pub fn parse_ipv4_or_ipv6(input: &str) -> Result { let mut parser = Parser::new(input); // Determine if this looks like IPv6 let colon_pos = parser.find_char(':'); let dot_pos = parser.find_char('.'); let bracket_pos = parser.find_char('['); let is_ipv6 = bracket_pos.is_some() || dot_pos.is_none() || (colon_pos.is_some() && (dot_pos.is_none() || colon_pos < dot_pos)); let addr = if is_ipv6 { IpAddr::V6(parser.parse_ipv6()?) } else { IpAddr::V4(parser.parse_ipv4()?) }; let port = if parser.current_char() == Some(':') { Some(parser.parse_port()?) } else { None }; Ok(ParseResult { addr, port, consumed: parser.cursor, }) } // Helper function for compatibility pub fn parse_ipv4_or_ipv6_simple(input: &str) -> Result<(IpAddr, Option, bool), &'static str> { let result = parse_ipv4_or_ipv6(input)?; let is_ipv6 = matches!(result.addr, IpAddr::V6(_)); Ok((result.addr, result.port, is_ipv6)) } fn dump_bytes(bytes: &[u8]) { for &byte in bytes { print!("{:02x}", byte); } } fn test_case(input: &str) { println!("Test case '{}'", input); match parse_ipv4_or_ipv6(input) { Ok(result) => { print!("addr: "); match result.addr { IpAddr::V4(addr) => dump_bytes(&addr), IpAddr::V6(addr) => dump_bytes(&addr), } println!(); if let Some(port) = result.port { println!("port: {}", port); } else { println!("port absent"); } } Err(e) => { println!("parse failed: {}", e); } } println!(); } fn main() { // The "localhost" IPv4 address test_case("127.0.0.1"); // The "localhost" IPv4 address, with a specified port (80) test_case("127.0.0.1:80"); // The "localhost" IPv6 address test_case("::1"); // The "localhost" IPv6 address, with a specified port (80) test_case("[::1]:80"); // Rosetta Code's primary server's public IPv6 address test_case("2605:2700:0:3::4713:93e3"); // Rosetta Code's primary server's public IPv6 address, with a specified port (80) test_case("[2605:2700:0:3::4713:93e3]:80"); // IPv4 space test_case("::ffff:192.168.173.22"); // IPv4 space with port test_case("[::ffff:192.168.173.22]:80"); // Trailing compression test_case("1::"); // Trailing compression with port test_case("[1::]:80"); // 'any' address compression test_case("::"); // 'any' address compression with port test_case("[::]:80"); }