Data update

This commit is contained in:
Ingy döt Net 2025-08-11 18:05:26 -07:00
parent 4d5544505c
commit 4924dd0264
3073 changed files with 55820 additions and 4408 deletions

View file

@ -0,0 +1,128 @@
import 'dart:io';
class ParseIPAddress {
static final RegExp _ipv4Pat = RegExp(r'^(\d+)\.(\d+)\.(\d+)\.(\d+)(?::(\d+))?$');
static final RegExp _ipv6DoubleColPat = RegExp(r'^\[?([0-9a-f:]*)::([0-9a-f:]*)(?:\]:(\d+))?$');
static late final RegExp _ipv6Pat;
static void _initializeIPv6Pattern() {
String ipv6Pattern = r'^\[?';
for (int i = 1; i <= 7; i++) {
ipv6Pattern += r'([0-9a-f]+):';
}
ipv6Pattern += r'([0-9a-f]+)(?:\]:(\d+))?$';
_ipv6Pat = RegExp(ipv6Pattern);
}
static void main() {
// Initialize the IPv6 pattern
_initializeIPv6Pattern();
List<String> tests = [
"192.168.0.1",
"127.0.0.1",
"256.0.0.1",
"127.0.0.1:80",
"::1",
"[::1]:80",
"[32e::12f]:80",
"2605:2700:0:3::4713:93e3",
"[2605:2700:0:3::4713:93e3]:80",
"2001:db8:85a3:0:0:8a2e:370:7334"
];
print('${'Test Case'.padRight(40)} ${'Hex Address'.padRight(32)} Port');
for (String ip in tests) {
try {
List<String> parsed = _parseIP(ip);
print('${ip.padRight(40)} ${parsed[0].padRight(32)} ${parsed[1]}');
} catch (e) {
print('${ip.padRight(40)} Invalid address: ${e.toString()}');
}
}
}
static List<String> _parseIP(String ip) {
String hex = "";
String port = "";
// IPv4
RegExpMatch? ipv4Match = _ipv4Pat.firstMatch(ip);
if (ipv4Match != null) {
for (int i = 1; i <= 4; i++) {
hex += _toHex4(ipv4Match.group(i)!);
}
if (ipv4Match.group(5) != null) {
port = ipv4Match.group(5)!;
}
return [hex, port];
}
// IPv6, double colon
RegExpMatch? ipv6DoubleColonMatch = _ipv6DoubleColPat.firstMatch(ip);
if (ipv6DoubleColonMatch != null) {
String p1 = ipv6DoubleColonMatch.group(1) ?? "";
if (p1.isEmpty) {
p1 = "0";
}
String p2 = ipv6DoubleColonMatch.group(2) ?? "";
if (p2.isEmpty) {
p2 = "0";
}
ip = p1 + _getZero(8 - _numCount(p1) - _numCount(p2)) + p2;
if (ipv6DoubleColonMatch.group(3) != null) {
ip = "[$ip]:${ipv6DoubleColonMatch.group(3)}";
}
}
// IPv6
RegExpMatch? ipv6Match = _ipv6Pat.firstMatch(ip);
if (ipv6Match != null) {
for (int i = 1; i <= 8; i++) {
String hexPart = _toHex6(ipv6Match.group(i)!);
hex += hexPart.padLeft(4, '0');
}
if (ipv6Match.group(9) != null) {
port = ipv6Match.group(9)!;
}
return [hex, port];
}
throw ArgumentError("ERROR 103: Unknown address: $ip");
}
static int _numCount(String s) {
return s.split(':').length;
}
static String _getZero(int count) {
StringBuffer sb = StringBuffer();
sb.write(":");
while (count > 0) {
sb.write("0:");
count--;
}
return sb.toString();
}
static String _toHex4(String s) {
int val = int.parse(s);
if (val < 0 || val > 255) {
throw ArgumentError("ERROR 101: Invalid value: $s");
}
return val.toRadixString(16).toUpperCase().padLeft(2, '0');
}
static String _toHex6(String s) {
int val = int.parse(s, radix: 16);
if (val < 0 || val > 65536) {
throw ArgumentError("ERROR 102: Invalid hex value: $s");
}
return s;
}
}
void main() {
ParseIPAddress.main();
}

View file

@ -0,0 +1,83 @@
#!/usr/bin/env elixir
defmodule IPParse do
@moduledoc """
IP address parser that handles IPv4 and IPv6 addresses with optional ports
"""
def main(args \\ []) do
addresses = if Enum.empty?(args), do: data(), else: args
for addr <- addresses do
print(addr, parse(addr))
end
System.halt(0)
end
@spec parse(String.t()) ::
{String.t(), binary(), String.t()} |
{String.t(), binary()}
def parse("[" <> addr0) do
case String.split(addr0, "]", parts: 2) do
[addr] ->
{"IPv6", to_hex(:inet.parse_address(String.to_charlist(addr)))}
[addr, ":" <> port] ->
{"IPv6", to_hex(:inet.parse_address(String.to_charlist(addr))), port}
end
end
def parse(addr0) do
case :inet.parse_address(String.to_charlist(addr0)) do
{:error, :einval} ->
[addr, port] = String.split(addr0, ":", parts: 2)
{"IPv4", to_hex(:inet.parse_address(String.to_charlist(addr))), port}
{:ok, v6_addr} ->
{"IPv6", to_hex({:ok, v6_addr})}
end
end
@spec to_hex({:ok, :inet.ip_address()}) :: binary()
def to_hex({:ok, {a, b, c, d}}) do
Base.encode16(<<a::8, b::8, c::8, d::8>>)
end
def to_hex({:ok, {a, b, c, d, e, f, g, h}}) do
Base.encode16(<<a::16, b::16, c::16, d::16, e::16, f::16, g::16, h::16>>)
end
def print(input, {family, hex, port}) do
IO.puts("Input: #{input}")
IO.puts("Family: #{family}")
IO.puts("Hex: #{hex}")
IO.puts("Port: #{port}")
IO.puts("")
end
def print(input, {family, hex}) do
IO.puts("Input: #{input}")
IO.puts("Family: #{family}")
IO.puts("Hex: #{hex}")
IO.puts("")
end
defp data do
[
"127.0.0.1",
"127.0.0.1:80",
"::ffff:127.0.0.1",
"::1",
"[::1]:80",
"1::80",
"2605:2700:0:3::4713:93e3",
"[2605:2700:0:3::4713:93e3]:80"
]
end
end
# Run the main function if this script is executed directly
if __ENV__.file == Path.absname(:escript.script_name()) do
IPParse.main(System.argv())
end

View file

@ -0,0 +1,320 @@
program parse_ip_address
implicit none
! Parameters
integer, parameter :: MAX_TESTS = 10
integer, parameter :: MAX_STR_LEN = 100
integer, parameter :: MAX_HEX_LEN = 32
! Variables
character(len=MAX_STR_LEN), dimension(MAX_TESTS) :: test_cases
character(len=MAX_HEX_LEN) :: hex_address
character(len=10) :: port_str
integer :: i, status
! Initialize test cases
test_cases(1) = '192.168.0.1'
test_cases(2) = '127.0.0.1'
test_cases(3) = '256.0.0.1'
test_cases(4) = '127.0.0.1:80'
test_cases(5) = '::1'
test_cases(6) = '[::1]:80'
test_cases(7) = '[32e::12f]:80'
test_cases(8) = '2605:2700:0:3::4713:93e3'
test_cases(9) = '[2605:2700:0:3::4713:93e3]:80'
test_cases(10) = '2001:db8:85a3:0:0:8a2e:370:7334'
! Print header
write(*,'(A40,1X,A32,3X,A)') 'Test Case', 'Hex Address', 'Port'
write(*,'(A40,1X,A32,3X,A)') repeat('-',40), repeat('-',32), repeat('-',4)
! Process each test case
do i = 1, MAX_TESTS
call parse_ip(trim(test_cases(i)), hex_address, port_str, status)
if (status == 0) then
write(*,'(A40,1X,A32,3X,A)') trim(test_cases(i)), trim(hex_address), trim(port_str)
else
write(*,'(A40,1X,A)') trim(test_cases(i)), 'Invalid address'
end if
end do
end program parse_ip_address
! Main parsing subroutine
subroutine parse_ip(ip_str, hex_address, port_str, status)
implicit none
character(len=*), intent(in) :: ip_str
character(len=*), intent(out) :: hex_address, port_str
integer, intent(out) :: status
! Try IPv4 first
call parse_ipv4(ip_str, hex_address, port_str, status)
if (status == 0) return
! Try IPv6
call parse_ipv6(ip_str, hex_address, port_str, status)
if (status == 0) return
! If neither worked, return error
status = -1
end subroutine parse_ip
! Parse IPv4 address
subroutine parse_ipv4(ip_str, hex_address, port_str, status)
implicit none
character(len=*), intent(in) :: ip_str
character(len=*), intent(out) :: hex_address, port_str
integer, intent(out) :: status
character(len=100) :: work_str
integer :: dot_pos(3), colon_pos
integer :: octets(4)
integer :: i, start_pos, end_pos, port_val
character(len=2) :: hex_part
work_str = trim(ip_str)
hex_address = ''
port_str = ''
status = 0
! Find colon for port (if present)
colon_pos = index(work_str, ':', .true.) ! Find last colon
if (colon_pos > 0) then
! Extract port
read(work_str(colon_pos+1:), *, iostat=status) port_val
if (status /= 0) then
status = -1
return
end if
write(port_str, '(I0)') port_val
work_str = work_str(1:colon_pos-1)
end if
! Find dots
dot_pos(1) = index(work_str, '.')
if (dot_pos(1) == 0) then
status = -1
return
end if
dot_pos(2) = index(work_str(dot_pos(1)+1:), '.') + dot_pos(1)
if (dot_pos(2) == dot_pos(1)) then
status = -1
return
end if
dot_pos(3) = index(work_str(dot_pos(2)+1:), '.') + dot_pos(2)
if (dot_pos(3) == dot_pos(2)) then
status = -1
return
end if
! Parse octets
read(work_str(1:dot_pos(1)-1), *, iostat=status) octets(1)
if (status /= 0 .or. octets(1) < 0 .or. octets(1) > 255) then
status = -1
return
end if
read(work_str(dot_pos(1)+1:dot_pos(2)-1), *, iostat=status) octets(2)
if (status /= 0 .or. octets(2) < 0 .or. octets(2) > 255) then
status = -1
return
end if
read(work_str(dot_pos(2)+1:dot_pos(3)-1), *, iostat=status) octets(3)
if (status /= 0 .or. octets(3) < 0 .or. octets(3) > 255) then
status = -1
return
end if
read(work_str(dot_pos(3)+1:), *, iostat=status) octets(4)
if (status /= 0 .or. octets(4) < 0 .or. octets(4) > 255) then
status = -1
return
end if
! Convert to hex
do i = 1, 4
call int_to_hex2(octets(i), hex_part)
hex_address = trim(hex_address) // hex_part
end do
status = 0
end subroutine parse_ipv4
! Parse IPv6 address (simplified version)
subroutine parse_ipv6(ip_str, hex_address, port_str, status)
implicit none
character(len=*), intent(in) :: ip_str
character(len=*), intent(out) :: hex_address, port_str
integer, intent(out) :: status
character(len=100) :: work_str, expanded_str
integer :: bracket_start, bracket_end, colon_pos
integer :: port_val
work_str = trim(ip_str)
hex_address = ''
port_str = ''
status = 0
! Handle bracketed IPv6 with port
bracket_start = index(work_str, '[')
bracket_end = index(work_str, ']')
if (bracket_start > 0 .and. bracket_end > bracket_start) then
! Extract IPv6 part
work_str = work_str(bracket_start+1:bracket_end-1)
! Check for port after bracket
colon_pos = index(ip_str(bracket_end+1:), ':')
if (colon_pos > 0) then
read(ip_str(bracket_end+colon_pos+1:), *, iostat=status) port_val
if (status /= 0) then
status = -1
return
end if
write(port_str, '(I0)') port_val
end if
end if
! Expand double colon if present
call expand_ipv6(work_str, expanded_str, status)
if (status /= 0) return
! Convert expanded IPv6 to hex
call ipv6_to_hex(expanded_str, hex_address, status)
end subroutine parse_ipv6
! Expand IPv6 double colon notation
subroutine expand_ipv6(ipv6_str, expanded_str, status)
implicit none
character(len=*), intent(in) :: ipv6_str
character(len=*), intent(out) :: expanded_str
integer, intent(out) :: status
character(len=100) :: work_str
integer :: double_colon_pos, colon_count, zeros_needed
integer :: i, pos
work_str = trim(ipv6_str)
expanded_str = ''
status = 0
! Find double colon
double_colon_pos = index(work_str, '::')
if (double_colon_pos == 0) then
! No double colon, just copy
expanded_str = work_str
return
end if
! Count existing colons (excluding the double colon)
colon_count = 0
do i = 1, len_trim(work_str)
if (work_str(i:i) == ':') colon_count = colon_count + 1
end do
colon_count = colon_count - 2 ! Subtract the double colon
! Calculate zeros needed
zeros_needed = 8 - colon_count - 1
if (double_colon_pos == 1) zeros_needed = zeros_needed + 1
if (double_colon_pos == len_trim(work_str)-1) zeros_needed = zeros_needed + 1
! Build expanded string
if (double_colon_pos == 1) then
expanded_str = '0'
do i = 1, zeros_needed - 1
expanded_str = trim(expanded_str) // ':0'
end do
if (len_trim(work_str) > 2) then
expanded_str = trim(expanded_str) // work_str(3:)
end if
else if (double_colon_pos == len_trim(work_str)-1) then
expanded_str = work_str(1:double_colon_pos-1)
do i = 1, zeros_needed
expanded_str = trim(expanded_str) // ':0'
end do
else
expanded_str = work_str(1:double_colon_pos-1)
do i = 1, zeros_needed
expanded_str = trim(expanded_str) // ':0'
end do
expanded_str = trim(expanded_str) // work_str(double_colon_pos+2:)
end if
end subroutine expand_ipv6
! Convert expanded IPv6 to hex
subroutine ipv6_to_hex(ipv6_str, hex_address, status)
implicit none
character(len=*), intent(in) :: ipv6_str
character(len=*), intent(out) :: hex_address
integer, intent(out) :: status
character(len=100) :: work_str
character(len=10) :: segment
character(len=4) :: hex_segment
integer :: colon_pos, start_pos, i
work_str = trim(ipv6_str) // ':' ! Add trailing colon for easier parsing
hex_address = ''
start_pos = 1
status = 0
do i = 1, 8
colon_pos = index(work_str(start_pos:), ':')
if (colon_pos == 0) then
status = -1
return
end if
colon_pos = colon_pos + start_pos - 1
segment = work_str(start_pos:colon_pos-1)
if (len_trim(segment) == 0) segment = '0'
! Pad to 4 hex digits
write(hex_segment, '(A4)') segment
call pad_hex(hex_segment)
hex_address = trim(hex_address) // hex_segment
start_pos = colon_pos + 1
end do
end subroutine ipv6_to_hex
! Convert integer to 2-digit hex
subroutine int_to_hex2(val, hex_str)
implicit none
integer, intent(in) :: val
character(len=2), intent(out) :: hex_str
character(len=16), parameter :: hex_digits = '0123456789abcdef'
hex_str(1:1) = hex_digits(val/16 + 1:val/16 + 1)
hex_str(2:2) = hex_digits(mod(val,16) + 1:mod(val,16) + 1)
end subroutine int_to_hex2
! Pad hex string with leading zeros
subroutine pad_hex(hex_str)
implicit none
character(len=4), intent(inout) :: hex_str
integer :: i, j
character(len=4) :: temp_str
! Remove leading spaces and pad with zeros
temp_str = adjustl(hex_str)
hex_str = '0000'
j = 4 - len_trim(temp_str) + 1
if (j <= 4) then
hex_str(j:4) = trim(temp_str)
end if
end subroutine pad_hex

View file

@ -0,0 +1,123 @@
// IP Address Parser in JavaScript
class ParseIPAddress {
constructor() {
this.IPV4_PAT = /^(\d+)\.(\d+)\.(\d+)\.(\d+)(?::(\d+))?$/;
this.IPV6_DOUBL_COL_PAT = /^\[?([0-9a-f:]*)::([0-9a-f:]*)(?:\]:(\d+))?$/;
// Build IPv6 pattern dynamically
let ipv6Pattern = "^\\[?";
for (let i = 1; i <= 7; i++) {
ipv6Pattern += "([0-9a-f]+):";
}
ipv6Pattern += "([0-9a-f]+)(?:\\]:(\\d+))?$";
this.IPV6_PAT = new RegExp(ipv6Pattern);
}
static main() {
const tests = [
"192.168.0.1",
"127.0.0.1",
"256.0.0.1",
"127.0.0.1:80",
"::1",
"[::1]:80",
"[32e::12f]:80",
"2605:2700:0:3::4713:93e3",
"[2605:2700:0:3::4713:93e3]:80",
"2001:db8:85a3:0:0:8a2e:370:7334"
];
const parser = new ParseIPAddress();
console.log(`${"Test Case".padEnd(40)} ${"Hex Address".padEnd(32)} Port`);
for (const ip of tests) {
try {
const parsed = parser.parseIP(ip);
console.log(`${ip.padEnd(40)} ${parsed[0].padEnd(32)} ${parsed[1]}`);
} catch (e) {
console.log(`${ip.padEnd(40)} Invalid address: ${e.message}`);
}
}
}
parseIP(ip) {
let hex = "";
let port = "";
// IPv4
const ipv4Match = ip.match(this.IPV4_PAT);
if (ipv4Match) {
for (let i = 1; i <= 4; i++) {
hex += this.toHex4(ipv4Match[i]);
}
if (ipv4Match[5] !== undefined) {
port = ipv4Match[5];
}
return [hex, port];
}
// IPv6, double colon
const ipv6DoubleColonMatch = ip.match(this.IPV6_DOUBL_COL_PAT);
if (ipv6DoubleColonMatch) {
let p1 = ipv6DoubleColonMatch[1];
if (p1 === "") {
p1 = "0";
}
let p2 = ipv6DoubleColonMatch[2];
if (p2 === "") {
p2 = "0";
}
ip = p1 + this.getZero(8 - this.numCount(p1) - this.numCount(p2)) + p2;
if (ipv6DoubleColonMatch[3] !== undefined) {
ip = "[" + ip + "]:" + ipv6DoubleColonMatch[3];
}
}
// IPv6
const ipv6Match = ip.match(this.IPV6_PAT);
if (ipv6Match) {
for (let i = 1; i <= 8; i++) {
hex += this.toHex6(ipv6Match[i]).padStart(4, "0");
}
if (ipv6Match[9] !== undefined) {
port = ipv6Match[9];
}
return [hex, port];
}
throw new Error("ERROR 103: Unknown address: " + ip);
}
numCount(s) {
return s.split(":").length;
}
getZero(count) {
let result = ":";
while (count > 0) {
result += "0:";
count--;
}
return result;
}
toHex4(s) {
const val = parseInt(s, 10);
if (val < 0 || val > 255) {
throw new Error("ERROR 101: Invalid value : " + s);
}
return val.toString(16).padStart(2, "0");
}
toHex6(s) {
const val = parseInt(s, 16);
if (val < 0 || val > 65536) {
throw new Error("ERROR 102: Invalid hex value : " + s);
}
return s;
}
}
// Run the main function
ParseIPAddress.main();

View file

@ -0,0 +1,100 @@
testdata = {"127.0.0.1", "127.0.0.1:80", "::1", "[::1]:80",
"2605:2700:0:3::4713:93e3", "[2605:2700:0:3::4713:93e3]:80",
"::ffff:192.168.173.22", "[::ffff:192.168.173.22]:80",
"1::", "[1::]:80", "::", "[::]:80"};
maybev4[ip_] := StringCount[ip, "."] > 0 && StringCount[ip, ":"] < 2
maybev6[ip_] := StringCount[ip, ":"] > 1
parseip[ip_] := Module[{mat, port, ipaddr, iphex, addressspace},
(* Try to match IPv6 with brackets and port *)
mat = StringCases[ip,
RegularExpression["^\\[([:.\\da-fA-F]+)\\]:(\\d+)$"] -> {"$1", "$2"}];
If[Length[mat] > 0,
{ipaddr, port} = First[mat],
(* Try to match IPv4 with port *)
mat = StringCases[ip,
RegularExpression["^([\\d.]+)[:/](\\d+)$"] -> {"$1", "$2"}];
If[Length[mat] > 0,
{ipaddr, port} = First[mat],
(* No port found *)
{ipaddr, port} = {ip, "none"}
]
];
If[maybev4[ipaddr],
Print["Processing ip v4 ", ipaddr];
(* Convert IPv4 to hex *)
Module[{octets, ipint},
octets = ToExpression /@ StringSplit[ipaddr, "."];
ipint = Total[MapIndexed[#1*256^(4 - First[#2]) &, octets]];
iphex = IntegerString[ipint, 16];
];
addressspace = "IPv4",
If[maybev6[ipaddr],
Print["Processing ip v6 ", ipaddr];
(* Convert IPv6 to hex *)
Module[{cleanip, parts, expandedparts, fullhex},
cleanip = StringReplace[ipaddr, {"[" -> "", "]" -> ""}];
(* Handle embedded IPv4 addresses *)
If[StringContainsQ[cleanip, "."],
Module[{parts, ipv4part, ipv6parts, ipv4octets, ipv4hex},
parts = StringSplit[cleanip, ":"];
(* Find the part with dots (IPv4) *)
ipv4part = SelectFirst[parts, StringContainsQ[#, "."] &];
ipv6parts = DeleteCases[parts, ipv4part];
If[ipv4part =!= Missing["NotFound"],
ipv4octets = ToExpression /@ StringSplit[ipv4part, "."];
(* Convert IPv4 to two 16-bit hex values *)
ipv4hex = {
IntegerString[ipv4octets[[1]]*256 + ipv4octets[[2]], 16],
IntegerString[ipv4octets[[3]]*256 + ipv4octets[[4]], 16]
};
cleanip = StringJoin[Riffle[Join[ipv6parts, ipv4hex], ":"]];
]
]
];
(* Handle :: expansion *)
If[StringContainsQ[cleanip, "::"],
parts = StringSplit[cleanip, "::"];
If[Length[parts] == 2,
Module[{left, right, leftparts, rightparts, missing},
left = If[parts[[1]] == "", {}, StringSplit[parts[[1]], ":"]];
right = If[parts[[2]] == "", {}, StringSplit[parts[[2]], ":"]];
missing = 8 - Length[left] - Length[right];
expandedparts = Join[left, Table["0", missing], right];
],
expandedparts = StringSplit[cleanip, ":"]
],
expandedparts = StringSplit[cleanip, ":"]
];
(* Pad each part to 4 hex digits and join *)
expandedparts = StringPadLeft[#, 4, "0"] & /@ expandedparts;
fullhex = StringJoin[expandedparts];
(* Remove leading zeros for final output *)
iphex = IntegerString[FromDigits[fullhex, 16], 16];
];
addressspace = "IPv6",
(* Neither IPv4 nor IPv6 *)
Throw["Bad IP address argument " <> ipaddr]
]
];
{iphex, addressspace, port}
]
(* Test the function *)
Do[
{hx, add, por} = parseip[ip];
Print["For input ", ip, ", IP in hex is ", hx, ", address space ", add, ", port ", por, "."],
{ip, testdata}
]

View file

@ -0,0 +1,192 @@
open Printf
(* Custom exception for invalid addresses *)
exception Invalid_address of string
(* Helper functions for string parsing *)
let is_digit c = c >= '0' && c <= '9'
let is_hex_digit c = is_digit c || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
let split_string s delimiter =
let len = String.length s in
let rec aux acc start i =
if i >= len then
if start < len then (String.sub s start (len - start)) :: acc
else acc
else if s.[i] = delimiter then
let part = String.sub s start (i - start) in
aux (part :: acc) (i + 1) (i + 1)
else
aux acc start (i + 1)
in
List.rev (aux [] 0 0)
let string_contains s substr =
let s_len = String.length s in
let substr_len = String.length substr in
let rec aux i =
if i > s_len - substr_len then false
else if String.sub s i substr_len = substr then true
else aux (i + 1)
in
if substr_len = 0 then true else aux 0
(* Check if string contains only digits *)
let is_numeric s =
let len = String.length s in
len > 0 &&
let rec aux i =
if i >= len then true
else if is_digit s.[i] then aux (i + 1)
else false
in
aux 0
(* Check if string contains only hex digits *)
let is_hex s =
let len = String.length s in
len > 0 &&
let rec aux i =
if i >= len then true
else if is_hex_digit (Char.lowercase_ascii s.[i]) then aux (i + 1)
else false
in
aux 0
(* Convert decimal string to 2-digit hex for IPv4 *)
let to_hex4 s =
if not (is_numeric s) then
raise (Invalid_address ("ERROR 101: Invalid value : " ^ s));
let val_int = int_of_string s in
if val_int < 0 || val_int > 255 then
raise (Invalid_address ("ERROR 101: Invalid value : " ^ s))
else
Printf.sprintf "%02x" val_int
(* Validate and return hex string for IPv6 *)
let to_hex6 s =
if not (is_hex s) then
raise (Invalid_address ("ERROR 102: Invalid hex value : " ^ s));
let val_int = int_of_string ("0x" ^ s) in
if val_int < 0 || val_int > 65535 then
raise (Invalid_address ("ERROR 102: Invalid hex value : " ^ s))
else
s
(* Parse IPv4 address *)
let parse_ipv4 ip =
let parts = split_string ip '.' in
match parts with
| [p1; p2; p3; p4_port] ->
(* Check if last part has port *)
if string_contains p4_port ":" then
let port_parts = split_string p4_port ':' in
(match port_parts with
| [p4; port] when is_numeric p4 && is_numeric port ->
let hex = (to_hex4 p1) ^ (to_hex4 p2) ^ (to_hex4 p3) ^ (to_hex4 p4) in
(hex, port)
| _ -> raise (Invalid_address ("ERROR 103: Unknown address: " ^ ip)))
else if is_numeric p4_port then
let hex = (to_hex4 p1) ^ (to_hex4 p2) ^ (to_hex4 p3) ^ (to_hex4 p4_port) in
(hex, "")
else
raise (Invalid_address ("ERROR 103: Unknown address: " ^ ip))
| _ -> raise (Invalid_address ("ERROR 103: Unknown address: " ^ ip))
(* Parse IPv6 address *)
let parse_ipv6 ip =
let (clean_ip, port) =
if String.length ip > 0 && ip.[0] = '[' then
(* Bracketed IPv6 with possible port *)
try
let bracket_end = String.index ip ']' in
let addr_part = String.sub ip 1 (bracket_end - 1) in
if bracket_end + 1 < String.length ip && ip.[bracket_end + 1] = ':' then
let port_part = String.sub ip (bracket_end + 2) (String.length ip - bracket_end - 2) in
(addr_part, port_part)
else
(addr_part, "")
with Not_found ->
raise (Invalid_address ("ERROR 103: Unknown address: " ^ ip))
else
(ip, "") in
(* Handle double colon expansion *)
let expanded_ip =
if string_contains clean_ip "::" then
(* Split on :: to get before and after parts *)
let double_colon_pos =
let rec find_pos i =
if i >= String.length clean_ip - 1 then -1
else if clean_ip.[i] = ':' && clean_ip.[i+1] = ':' then i
else find_pos (i + 1)
in
find_pos 0 in
if double_colon_pos = -1 then
raise (Invalid_address ("ERROR 103: Unknown address: " ^ ip))
else
let before_part =
if double_colon_pos = 0 then ""
else String.sub clean_ip 0 double_colon_pos in
let after_part =
let start = double_colon_pos + 2 in
if start >= String.length clean_ip then ""
else String.sub clean_ip start (String.length clean_ip - start) in
let before_groups = if before_part = "" then [] else split_string before_part ':' in
let after_groups = if after_part = "" then [] else split_string after_part ':' in
let total_existing = List.length before_groups + List.length after_groups in
let zeros_needed = 8 - total_existing in
let zero_groups = List.init zeros_needed (fun _ -> "0") in
String.concat ":" (before_groups @ zero_groups @ after_groups)
else
clean_ip in
(* Parse the expanded IPv6 *)
let hex_parts = split_string expanded_ip ':' in
if List.length hex_parts = 8 then
let hex = String.concat "" (List.map (fun part ->
Printf.sprintf "%04s" (to_hex6 part) |>
String.map (function ' ' -> '0' | c -> c)) hex_parts) in
(hex, port)
else
raise (Invalid_address ("ERROR 103: Unknown address: " ^ ip))
(* Main parsing function *)
let parse_ip ip =
(* Check if it looks like IPv4 (contains dots but no colons except maybe one for port) *)
if string_contains ip "." &&
(not (string_contains ip ":") ||
(let colon_count = List.length (split_string ip ':') - 1 in colon_count = 1)) then
parse_ipv4 ip
else if string_contains ip ":" then
parse_ipv6 ip
else
raise (Invalid_address ("ERROR 103: Unknown address: " ^ ip))
(* Test cases *)
let tests = [|
"192.168.0.1";
"127.0.0.1";
"256.0.0.1";
"127.0.0.1:80";
"::1";
"[::1]:80";
"[32e::12f]:80";
"2605:2700:0:3::4713:93e3";
"[2605:2700:0:3::4713:93e3]:80";
"2001:db8:85a3:0:0:8a2e:370:7334"
|]
(* Main function *)
let () =
printf "%-40s %-32s %s\n" "Test Case" "Hex Address" "Port";
Array.iter (fun ip ->
try
let (hex_addr, port) = parse_ip ip in
printf "%-40s %-32s %s\n" ip hex_addr port
with Invalid_address msg ->
printf "%-40s Invalid address: %s\n" ip msg
) tests

View file

@ -0,0 +1,127 @@
# IP Address Parser in R
library(stringr)
# Helper function to convert decimal to 2-digit hex (for IPv4)
to_hex4 <- function(s) {
val <- as.integer(s)
if (is.na(val) || val < 0 || val > 255) {
stop(paste("ERROR 101: Invalid value:", s))
}
sprintf("%02x", val)
}
# Helper function to validate and return hex value (for IPv6)
to_hex6 <- function(s) {
val <- strtoi(s, base = 16)
if (is.na(val) || val < 0 || val > 65535) { # Fixed: should be 65535, not 65536
stop(paste("ERROR 102: Invalid hex value:", s))
}
return(s)
}
# Helper function to count number of parts separated by colons
num_count <- function(s) {
if (s == "") return(0)
length(strsplit(s, ":")[[1]])
}
# Helper function to generate zero padding for IPv6 double colon expansion
get_zero <- function(count) {
if (count <= 0) return("")
paste0(":", paste(rep("0", count), collapse = ":"), ":")
}
# Main parsing function
parse_ip <- function(ip) {
hex <- ""
port <- ""
# IPv4 pattern: xxx.xxx.xxx.xxx with optional :port
ipv4_pattern <- "^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)(?::(\\d+))?$"
ipv4_match <- str_match(ip, ipv4_pattern)
if (!is.na(ipv4_match[1])) {
# Extract IPv4 components
for (i in 2:5) {
hex <- paste0(hex, to_hex4(ipv4_match[i]))
}
if (!is.na(ipv4_match[6])) {
port <- ipv4_match[6]
}
return(c(hex, port))
}
# IPv6 with double colon pattern
ipv6_double_colon_pattern <- "^\\[?([0-9a-f:]*)::([0-9a-f:]*)(?:\\]:(\\d+))?$"
ipv6_dc_match <- str_match(tolower(ip), ipv6_double_colon_pattern)
if (!is.na(ipv6_dc_match[1])) {
p1 <- ipv6_dc_match[2]
if (p1 == "") p1 <- "0"
p2 <- ipv6_dc_match[3]
if (p2 == "") p2 <- "0"
# Expand the double colon
zero_count <- 8 - num_count(p1) - num_count(p2)
expanded_ip <- paste0(p1, get_zero(zero_count), p2)
# Add port back if it exists
if (!is.na(ipv6_dc_match[4])) {
expanded_ip <- paste0("[", expanded_ip, "]:", ipv6_dc_match[4])
}
ip <- expanded_ip
}
# IPv6 full pattern (8 groups of hex digits)
ipv6_pattern <- "^\\[?([0-9a-f]+):([0-9a-f]+):([0-9a-f]+):([0-9a-f]+):([0-9a-f]+):([0-9a-f]+):([0-9a-f]+):([0-9a-f]+)(?:\\]:(\\d+))?$"
ipv6_match <- str_match(tolower(ip), ipv6_pattern)
if (!is.na(ipv6_match[1])) {
# Extract IPv6 components
for (i in 2:9) {
hex_part <- to_hex6(ipv6_match[i])
# Pad to 4 characters
hex <- paste0(hex, sprintf("%04s", hex_part))
}
hex <- str_replace_all(hex, " ", "0")
if (!is.na(ipv6_match[10])) {
port <- ipv6_match[10]
}
return(c(hex, port))
}
stop(paste("ERROR 103: Unknown address:", ip))
}
# Main execution
main <- function() {
tests <- c(
"192.168.0.1",
"127.0.0.1",
"256.0.0.1",
"127.0.0.1:80",
"::1",
"[::1]:80",
"[32e::12f]:80",
"2605:2700:0:3::4713:93e3",
"[2605:2700:0:3::4713:93e3]:80",
"2001:db8:85a3:0:0:8a2e:370:7334"
)
cat(sprintf("%-40s %-32s %s\n", "Test Case", "Hex Address", "Port"))
for (ip in tests) {
tryCatch({
parsed <- parse_ip(ip)
cat(sprintf("%-40s %-32s %s\n", ip, parsed[1], parsed[2]))
}, error = function(e) {
cat(sprintf("%-40s Invalid address: %s\n", ip, e$message))
})
}
}
# Run the main function
main()

View file

@ -0,0 +1,354 @@
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<u16>,
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<char> {
self.input.chars().nth(self.cursor)
}
fn peek_char(&self, offset: usize) -> Option<char> {
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<usize> {
self.input[self.cursor..].find(ch).map(|pos| self.cursor + pos)
}
fn parse_decimal(&mut self) -> Result<u32, &'static str> {
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<u32, &'static str> {
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<u16, &'static str> {
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<ParseResult, &'static str> {
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<u16>, 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");
}

View file

@ -0,0 +1,187 @@
import Foundation
struct IPParseResult {
let hexAddress: String
let port: String
}
enum IPParseError: Error {
case invalidValue(String)
case invalidHexValue(String)
case unknownAddress(String)
var localizedDescription: String {
switch self {
case .invalidValue(let value):
return "ERROR 101: Invalid value: \(value)"
case .invalidHexValue(let value):
return "ERROR 102: Invalid hex value: \(value)"
case .unknownAddress(let address):
return "ERROR 103: Unknown address: \(address)"
}
}
}
class ParseIPAddress {
// IPv4 pattern: matches IP with optional port
private static let ipv4Pattern = #"^(\d+)\.(\d+)\.(\d+)\.(\d+)(?::(\d+))?$"#
// IPv6 double colon pattern: matches compressed IPv6 with optional port
private static let ipv6DoubleColonPattern = #"^\[?([0-9a-f:]*)::([0-9a-f:]*)(?:\]:(\d+))?$"#
// IPv6 full pattern: dynamically built for full IPv6 addresses
private static let ipv6Pattern: String = {
var pattern = #"^\[?"#
for i in 1...7 {
pattern += #"([0-9a-f]+):"#
}
pattern += #"([0-9a-f]+)(?:\]:(\d+))?$"#
return pattern
}()
static func main() {
let tests = [
"192.168.0.1",
"127.0.0.1",
"256.0.0.1",
"127.0.0.1:80",
"::1",
"[::1]:80",
"[32e::12f]:80",
"2605:2700:0:3::4713:93e3",
"[2605:2700:0:3::4713:93e3]:80",
"2001:db8:85a3:0:0:8a2e:370:7334"
]
// Print header with proper padding
let header = "Test Case".padding(toLength: 40, withPad: " ", startingAt: 0) +
"Hex Address".padding(toLength: 32, withPad: " ", startingAt: 0) +
" Port"
print(header)
for ip in tests {
do {
let result = try parseIP(ip)
let output = ip.padding(toLength: 40, withPad: " ", startingAt: 0) +
result.hexAddress.padding(toLength: 32, withPad: " ", startingAt: 0) +
" " + result.port
print(output)
} catch {
let output = ip.padding(toLength: 40, withPad: " ", startingAt: 0) +
"Invalid address: " + error.localizedDescription
print(output)
}
}
}
private static func parseIP(_ ip: String) throws -> IPParseResult {
var hex = ""
var port = ""
// Try IPv4 first
if let ipv4Regex = try? NSRegularExpression(pattern: ipv4Pattern, options: [.caseInsensitive]) {
let range = NSRange(location: 0, length: ip.utf16.count)
if let match = ipv4Regex.firstMatch(in: ip, options: [], range: range) {
// Extract the 4 octets
for i in 1...4 {
let groupRange = match.range(at: i)
let octet = String(ip[Range(groupRange, in: ip)!])
hex += try toHex4(octet)
}
// Check for port
let portRange = match.range(at: 5)
if portRange.location != NSNotFound {
port = String(ip[Range(portRange, in: ip)!])
}
return IPParseResult(hexAddress: hex, port: port)
}
}
var modifiedIP = ip
// Try IPv6 with double colon
if let ipv6DoubleColonRegex = try? NSRegularExpression(pattern: ipv6DoubleColonPattern, options: [.caseInsensitive]) {
let range = NSRange(location: 0, length: ip.utf16.count)
if let match = ipv6DoubleColonRegex.firstMatch(in: ip, options: [], range: range) {
let p1Range = match.range(at: 1)
let p2Range = match.range(at: 2)
let portRange = match.range(at: 3)
var p1 = p1Range.location != NSNotFound ? String(ip[Range(p1Range, in: ip)!]) : ""
var p2 = p2Range.location != NSNotFound ? String(ip[Range(p2Range, in: ip)!]) : ""
if p1.isEmpty {
p1 = "0"
}
if p2.isEmpty {
p2 = "0"
}
let zeroCount = 8 - numCount(p1) - numCount(p2)
modifiedIP = p1 + getZero(zeroCount) + p2
if portRange.location != NSNotFound {
let portStr = String(ip[Range(portRange, in: ip)!])
modifiedIP = "[\(modifiedIP)]:\(portStr)"
}
}
}
// Try full IPv6
if let ipv6Regex = try? NSRegularExpression(pattern: ipv6Pattern, options: [.caseInsensitive]) {
let range = NSRange(location: 0, length: modifiedIP.utf16.count)
if let match = ipv6Regex.firstMatch(in: modifiedIP, options: [], range: range) {
// Extract the 8 groups
for i in 1...8 {
let groupRange = match.range(at: i)
let group = String(modifiedIP[Range(groupRange, in: modifiedIP)!])
let hexGroup = try toHex6(group)
let paddedHex = hexGroup.padding(toLength: 4, withPad: "0", startingAt: 0)
hex += paddedHex
}
// Check for port
let portRange = match.range(at: 9)
if portRange.location != NSNotFound {
port = String(modifiedIP[Range(portRange, in: modifiedIP)!])
}
return IPParseResult(hexAddress: hex, port: port)
}
}
throw IPParseError.unknownAddress(ip)
}
private static func numCount(_ s: String) -> Int {
return s.components(separatedBy: ":").count
}
private static func getZero(_ count: Int) -> String {
var result = ":"
for _ in 0..<count {
result += "0:"
}
return result
}
private static func toHex4(_ s: String) throws -> String {
guard let val = Int(s), val >= 0 && val <= 255 else {
throw IPParseError.invalidValue(s)
}
return String(format: "%02x", val)
}
private static func toHex6(_ s: String) throws -> String {
guard let val = Int(s, radix: 16), val >= 0 && val <= 65535 else {
throw IPParseError.invalidHexValue(s)
}
return s
}
}
// Run the program
ParseIPAddress.main()

View file

@ -0,0 +1,238 @@
const std = @import("std");
const print = std.debug.print;
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const ParseError = error{
InvalidAddress,
InvalidValue,
InvalidHexValue,
OutOfMemory,
};
const ParseResult = struct {
hex_address: []const u8,
port: []const u8,
fn deinit(self: ParseResult, allocator: Allocator) void {
allocator.free(self.hex_address);
allocator.free(self.port);
}
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const tests = [_][]const u8{
"192.168.0.1",
"127.0.0.1",
"256.0.0.1",
"127.0.0.1:80",
"::1",
"[::1]:80",
"[32e::12f]:80",
"2605:2700:0:3::4713:93e3",
"[2605:2700:0:3::4713:93e3]:80",
"2001:db8:85a3:0:0:8a2e:370:7334"
};
print("{s:<40} {s:<32} {s}\n", .{ "Test Case", "Hex Address", "Port" });
for (tests) |ip| {
const result = parseIP(allocator, ip) catch |err| {
const error_msg = switch (err) {
ParseError.InvalidAddress => "Unknown address",
ParseError.InvalidValue => "Invalid value",
ParseError.InvalidHexValue => "Invalid hex value",
else => "Parse error",
};
print("{s:<40} Invalid address: {s}\n", .{ ip, error_msg });
continue;
};
defer result.deinit(allocator);
print("{s:<40} {s:<32} {s}\n", .{ ip, result.hex_address, result.port });
}
}
fn parseIP(allocator: Allocator, ip: []const u8) !ParseResult {
// Try IPv4 first
if (parseIPv4(allocator, ip)) |result| {
return result;
} else |_| {}
// Try IPv6 with double colon
if (parseIPv6DoubleColon(allocator, ip)) |result| {
return result;
} else |_| {}
// Try regular IPv6
if (parseIPv6(allocator, ip)) |result| {
return result;
} else |_| {}
return ParseError.InvalidAddress;
}
fn parseIPv4(allocator: Allocator, ip: []const u8) !ParseResult {
var parts = std.mem.splitSequence(u8, ip, ".");
var octets: [4]u8 = undefined;
var port_str: []const u8 = "";
var i: usize = 0;
while (parts.next()) |part| {
if (i >= 4) return ParseError.InvalidAddress;
// Check if this part contains a port (last octet with colon)
if (i == 3) {
if (std.mem.indexOf(u8, part, ":")) |colon_pos| {
const octet_str = part[0..colon_pos];
port_str = part[colon_pos + 1..];
const octet = std.fmt.parseInt(u8, octet_str, 10) catch return ParseError.InvalidValue;
octets[i] = octet;
} else {
const octet = std.fmt.parseInt(u8, part, 10) catch return ParseError.InvalidValue;
octets[i] = octet;
}
} else {
const octet = std.fmt.parseInt(u8, part, 10) catch return ParseError.InvalidValue;
octets[i] = octet;
}
i += 1;
}
if (i != 4) return ParseError.InvalidAddress;
// Convert to hex
var hex = ArrayList(u8).init(allocator);
defer hex.deinit();
for (octets) |octet| {
try hex.writer().print("{x:0>2}", .{octet});
}
const port_copy = try allocator.dupe(u8, port_str);
return ParseResult{
.hex_address = try hex.toOwnedSlice(),
.port = port_copy,
};
}
fn parseIPv6DoubleColon(allocator: Allocator, ip: []const u8) !ParseResult {
var working_ip = ip;
var port_str: []const u8 = "";
// Handle brackets and port
if (std.mem.startsWith(u8, working_ip, "[")) {
if (std.mem.lastIndexOf(u8, working_ip, "]:")) |bracket_pos| {
port_str = working_ip[bracket_pos + 2..];
working_ip = working_ip[1..bracket_pos];
} else if (std.mem.endsWith(u8, working_ip, "]")) {
working_ip = working_ip[1..working_ip.len - 1];
}
}
// Check if it contains ::
const double_colon_pos = std.mem.indexOf(u8, working_ip, "::") orelse return ParseError.InvalidAddress;
const p1 = working_ip[0..double_colon_pos];
const p2 = working_ip[double_colon_pos + 2..];
const p1_count = if (p1.len == 0) 0 else countColons(p1) + 1;
const p2_count = if (p2.len == 0) 0 else countColons(p2) + 1;
const zeros_needed = 8 - p1_count - p2_count;
// Reconstruct the full IPv6 address
var full_ip = ArrayList(u8).init(allocator);
defer full_ip.deinit();
if (p1.len > 0) {
try full_ip.appendSlice(p1);
try full_ip.append(':');
}
for (0..zeros_needed) |_| {
try full_ip.appendSlice("0:");
}
if (p2.len > 0) {
try full_ip.appendSlice(p2);
} else {
// Remove trailing colon if p2 is empty
if (full_ip.items.len > 0 and full_ip.items[full_ip.items.len - 1] == ':') {
_ = full_ip.pop();
}
}
const reconstructed = try full_ip.toOwnedSlice();
defer allocator.free(reconstructed);
return parseIPv6WithPort(allocator, reconstructed, port_str);
}
fn parseIPv6(allocator: Allocator, ip: []const u8) !ParseResult {
return parseIPv6WithPort(allocator, ip, "");
}
fn parseIPv6WithPort(allocator: Allocator, ip: []const u8, port_override: []const u8) !ParseResult {
var working_ip = ip;
var port_str = port_override;
// Handle brackets and port if not overridden
if (port_override.len == 0) {
if (std.mem.startsWith(u8, working_ip, "[")) {
if (std.mem.lastIndexOf(u8, working_ip, "]:")) |bracket_pos| {
port_str = working_ip[bracket_pos + 2..];
working_ip = working_ip[1..bracket_pos];
} else if (std.mem.endsWith(u8, working_ip, "]")) {
working_ip = working_ip[1..working_ip.len - 1];
}
}
}
var parts = std.mem.splitSequence(u8, working_ip, ":");
var groups: [8][]const u8 = undefined;
var i: usize = 0;
while (parts.next()) |part| {
if (i >= 8) return ParseError.InvalidAddress;
groups[i] = part;
i += 1;
}
if (i != 8) return ParseError.InvalidAddress;
// Convert to hex
var hex = ArrayList(u8).init(allocator);
defer hex.deinit();
for (groups) |group| {
if (group.len == 0) {
try hex.appendSlice("0000");
} else {
// Validate hex and pad to 4 digits
const val = std.fmt.parseInt(u16, group, 16) catch return ParseError.InvalidHexValue;
try hex.writer().print("{x:0>4}", .{val});
}
}
const port_copy = try allocator.dupe(u8, port_str);
return ParseResult{
.hex_address = try hex.toOwnedSlice(),
.port = port_copy,
};
}
fn countColons(s: []const u8) usize {
var count: usize = 0;
for (s) |c| {
if (c == ':') count += 1;
}
return count;
}