##! Functions for parsing and manipulating IP and MAC addresses.

# Regular expressions for matching IP addresses in strings.

const ipv4_decim = /[0-9]{1}|[0-9]{2}|0[0-9]{2}|1[0-9]{2}|2[0-4][0-9]|25[0-5]/;

const ipv4_addr_regex = ipv4_decim & /\./ & ipv4_decim & /\./ & ipv4_decim & /\./ & ipv4_decim;

const ipv6_hextet = /[0-9A-Fa-f]{1,4}/;

const ipv6_8hex_regex = /([0-9A-Fa-f]{1,4}:){7}/ & ipv6_hextet;

const ipv6_hex4dec_regex = /([0-9A-Fa-f]{1,4}:){6}/ & ipv4_addr_regex;

const ipv6_compressed_lead_hextets0 = /::([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,6})?/;

const ipv6_compressed_lead_hextets1 = /[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0}::([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?/;

const ipv6_compressed_lead_hextets2 = /[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){1}::([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,4})?/;

const ipv6_compressed_lead_hextets3 = /[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){2}::([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,3})?/;

const ipv6_compressed_lead_hextets4 = /[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){3}::([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,2})?/;

const ipv6_compressed_lead_hextets5 = /[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){4}::([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,1})?/;

const ipv6_compressed_lead_hextets6 = /[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){5}::([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,0})?/;

const ipv6_compressed_lead_hextets7 = /[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){6}::/;

const ipv6_compressed_hex_regex = ipv6_compressed_lead_hextets0 |
                                  ipv6_compressed_lead_hextets1 |
                                  ipv6_compressed_lead_hextets2 |
                                  ipv6_compressed_lead_hextets3 |
                                  ipv6_compressed_lead_hextets4 |
                                  ipv6_compressed_lead_hextets5 |
                                  ipv6_compressed_lead_hextets6 |
                                  ipv6_compressed_lead_hextets7;

const ipv6_compressed_hext4dec_lead_hextets0 = /::([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,4})?/ & ipv4_addr_regex;

const ipv6_compressed_hext4dec_lead_hextets1 = /[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0}::([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,3})?/ & ipv4_addr_regex;

const ipv6_compressed_hext4dec_lead_hextets2 = /[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){1}::([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,2})?/ & ipv4_addr_regex;

const ipv6_compressed_hext4dec_lead_hextets3 = /[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){2}::([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,1})?/ & ipv4_addr_regex;

const ipv6_compressed_hext4dec_lead_hextets4 = /[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){3}::([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,0})?/ & ipv4_addr_regex;

const ipv6_compressed_hext4dec_lead_hextets5 = /[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){4}::/ & ipv4_addr_regex;

const ipv6_compressed_hex4dec_regex = ipv6_compressed_hext4dec_lead_hextets0 |
                                      ipv6_compressed_hext4dec_lead_hextets1 |
                                      ipv6_compressed_hext4dec_lead_hextets2 |
                                      ipv6_compressed_hext4dec_lead_hextets3 |
                                      ipv6_compressed_hext4dec_lead_hextets4 |
                                      ipv6_compressed_hext4dec_lead_hextets5;

const ipv6_addr_regex = ipv6_8hex_regex |
                        ipv6_compressed_hex_regex |
                        ipv6_hex4dec_regex |
                        ipv6_compressed_hex4dec_regex;

const ip_addr_regex = ipv4_addr_regex | ipv6_addr_regex;

## Checks if all elements of a string array are a valid octet value.
##
## octets: an array of strings to check for valid octet values.
##
## Returns: T if every element is between 0 and 255, inclusive, else F.
function has_valid_octets(octets: string_vec): bool
	{
	for ( i in octets )
		{
		local num = to_count(octets[i]);
		if ( 255 < num )
			return F;
		}
	return T;
	}

## Extracts all IP (v4 or v6) address strings from a given string.
##
## input: a string that may contain an IP address anywhere within it.
##
## check_wrapping: if true, will only return IP addresses that are wrapped in matching pairs of spaces, square brackets, curly braces, or parens. This can be used to avoid extracting strings that look like IPs from innocuous strings, such as SMTP headers.
##
## Returns: an array containing all valid IP address strings found in *input*.
function extract_ip_addresses(input: string, check_wrapping: bool &default=F): string_vec
	{
	local parts = split_string_all(input, ip_addr_regex);
	local output: string_vec;

	for ( i in parts )
		{
		if ( i % 2 == 1 && is_valid_ip(parts[i]) )
			{
			if ( ! check_wrapping )
				{
				output += parts[i];
				}
			else if ( i > 0 && i < |parts| - 1 )
				{
				local p1 = parts[i-1];
				local p3 = parts[i+1];

				if ( ( |p1| == 0 && |p3| == 0 ) ||
				     ( p1[-1] == "\[" && p3[0] == "\]" ) ||
			             ( p1[-1] == "\(" && p3[0] == "\)" ) ||
			             ( p1[-1] == "\{" && p3[0] == "\}" ) ||
			             ( p1[-1] == " " && p3[0] == " " ) )
					output += parts[i];
				}
			}
		}
	return output;
	}

## Returns the string representation of an IP address suitable for inclusion
## in a URI.  For IPv4, this does no special formatting, but for IPv6, the
## address is included in square brackets.
##
## a: the address to make suitable for URI inclusion.
##
## Returns: the string representation of the address suitable for URI inclusion.
function addr_to_uri(a: addr): string
	{
	if ( is_v4_addr(a) )
		return fmt("%s", a);
	else
		return fmt("[%s]", a);
	}

## Given a string, extracts the hex digits and returns a MAC address in
## the format: 00:a0:32:d7:81:8f. If the string doesn't contain 12 or 16 hex
## digits, an empty string is returned.
##
## a: the string to normalize.
##
## Returns: a normalized MAC address, or an empty string in the case of an error.
function normalize_mac(a: string): string
	{
	local result = to_lower(gsub(a, /[^A-Fa-f0-9]/, ""));
	local octets: string_vec;

	if ( |result| == 12 )
		{
		octets = str_split_indices(result, vector(2, 4, 6, 8, 10));
		return fmt("%s:%s:%s:%s:%s:%s", octets[0], octets[1], octets[2], octets[3], octets[4], octets[5]);
		}

	if ( |result| == 16 )
		{
		octets = str_split_indices(result, vector(2, 4, 6, 8, 10, 12, 14));
		return fmt("%s:%s:%s:%s:%s:%s:%s:%s", octets[0], octets[1], octets[2], octets[3], octets[4], octets[5], octets[6], octets[7]);
		}

	return "";
	}
