meta-google: network-sh: Add ip_to_bytes function

This will be used to compare parts of addresses instead of applying
regexes to them.

Change-Id: Ide7366cab967e31a74cbb4002bad1046432037e3
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/meta-google/recipes-google/networking/network-sh/lib.sh b/meta-google/recipes-google/networking/network-sh/lib.sh
index f37f719..2dc5103 100644
--- a/meta-google/recipes-google/networking/network-sh/lib.sh
+++ b/meta-google/recipes-google/networking/network-sh/lib.sh
@@ -57,6 +57,102 @@
   printf '%02x%02x:%02x%02x:%02x%02x:%02x%02x\n' "${suffix_bytes[@]}"
 }
 
+ip_to_bytes() {
+  local -n bytes_out="$1"
+  local str="$2"
+
+  local bytes=()
+  local oldifs="$IFS"
+  # Heuristic for V4 / V6, validity will be checked as it is parsed
+  if [[ "$str" == *.* ]]; then
+    # Ensure we don't start or end with IFS
+    [ "${str:0:1}" != '.' ] || return 1
+    [ "${str: -1}" != '.' ] || return 1
+
+    local v
+    # Split IPv4 address into octets
+    IFS=.
+    for v in $str; do
+      # IPv4 digits are always decimal numbers
+      if ! [[ "$v" =~ ^[0-9]+$ ]]; then
+        IFS="$oldifs"
+        return 1
+      fi
+      # Each octet is a single byte, make sure the number isn't larger
+      if (( v > 0xff )); then
+        IFS="$oldifs"
+        return 1
+      fi
+      bytes+=($v)
+    done
+    # IPv4 addresses must have all 4 bytes present
+    if (( "${#bytes[@]}" != 4 )); then
+      IFS="$oldifs"
+      return 1
+    fi
+  else
+    # Ensure we bound the padding in an outer byte for
+    # IFS splitting to work correctly
+    [ "${str:0:2}" = '::' ] && str="0$str"
+    [ "${str: -2}" = '::' ] && str="${str}0"
+
+    # Ensure we don't start or end with IFS
+    [ "${str:0:1}" != ':' ] || return 1
+    [ "${str: -1}" != ':' ] || return 1
+
+    # Stores the bytes that come before ::, if it exists
+    local bytesBeforePad=()
+    local v
+    # Split the Address into hextets
+    IFS=:
+    for v in $str; do
+      # Handle ::, which translates to an empty string
+      if [ -z "$v" ]; then
+        # Only allow a single :: sequence in an address
+        if (( "${#bytesBeforePad[@]}" > 0 )); then
+          IFS="$oldifs"
+          return 1
+        fi
+        # Store the already parsed upper bytes separately
+        # This allows us to calculate and insert padding
+        bytesBeforePad=("${bytes[@]}")
+        bytes=()
+        continue
+      fi
+      # IPv6 digits are always hex
+      if ! [[ "$v" =~ ^[[:xdigit:]]+$ ]]; then
+        IFS="$oldifs"
+        return 1
+      fi
+      # Ensure the number is no larger than a hextet
+      v="0x$v"
+      if (( v > 0xffff )); then
+        IFS="$oldifs"
+        return 1
+      fi
+      # Split the hextet into 2 bytes
+      bytes+=($(( v >> 8 )))
+      bytes+=($(( v & 0xff )))
+    done
+    # If we have ::, add padding
+    if (( "${#bytesBeforePad[@]}" > 0 )); then
+      # Fill the middle bytes with padding and store in `bytes`
+      while (( "${#bytes[@]}" + "${#bytesBeforePad[@]}" < 16 )); do
+        bytesBeforePad+=(0)
+      done
+      bytes=("${bytesBeforePad[@]}" "${bytes[@]}")
+    fi
+    # IPv6 addresses must have all 16 bytes present
+    if (( "${#bytes[@]}" != 16 )); then
+      IFS="$oldifs"
+      return 1
+    fi
+  fi
+
+  IFS="$oldifs"
+  bytes_out=("${bytes[@]}")
+}
+
 ipv6_pfx_concat() {
   local pfx="$1"
   local sfx="$2"
diff --git a/meta-google/recipes-google/networking/network-sh/test.sh b/meta-google/recipes-google/networking/network-sh/test.sh
index 57387c4..2361cfe 100755
--- a/meta-google/recipes-google/networking/network-sh/test.sh
+++ b/meta-google/recipes-google/networking/network-sh/test.sh
@@ -21,6 +21,21 @@
 fi
 source lib.sh
 
+expect_array_numeq() {
+  local -n a1="$1"
+  local -n a2="$2"
+
+  if (( "${#a1[@]}" != "${#a2[@]}" )); then
+    echo "  Line ${BASH_LINENO[0]} Array Size ${#a1[@]} != ${#a2[@]}" >&2
+    test_err=1
+  else
+    local i
+    for (( i=0; i < ${#a1[@]}; ++i )); do
+      expect_numeq "${a1[$i]}" "${a2[$i]}"
+    done
+  fi
+}
+
 test_mac_to_bytes() {
   out=()
   expect_err 1 mac_to_bytes out ''
@@ -32,9 +47,7 @@
 
   expect_err 0 mac_to_bytes out 'a2:0:f:de:0:29'
   expected=(0xa2 0 0xf 0xde 0 0x29)
-  for (( i=0; i < ${#expected[@]}; ++i )); do
-    expect_numeq "${out[$i]}" "${expected[$i]}"
-  done
+  expect_array_numeq out expected
 }
 
 test_mac_to_eui_48() {
@@ -47,6 +60,60 @@
   expect_streq "$str" '1334:56ff:fe78:90af'
 }
 
+test_ip4_to_bytes() {
+  out=()
+  expect_err 1 ip_to_bytes out ''
+  expect_err 1 ip_to_bytes out '10.0.0.'
+  expect_err 1 ip_to_bytes out '.0.1.1'
+  expect_err 1 ip_to_bytes out '10.0.0'
+  expect_err 1 ip_to_bytes out '10.0..0'
+  expect_err 1 ip_to_bytes out '.10.0.0.0'
+  expect_err 1 ip_to_bytes out '10.0.0.0.'
+  expect_err 1 ip_to_bytes out '10.0.0.256'
+  expect_err 1 ip_to_bytes out '10.0.0.0.256'
+  expect_err 1 ip_to_bytes out '10.0.0.0.1'
+
+  expect_err 0 ip_to_bytes out '10.0.0.1'
+  expected=(10 0 0 1)
+  expect_array_numeq out expected
+}
+
+test_ip6_to_bytes() {
+  out=()
+  expect_err 1 ip_to_bytes out ''
+  expect_err 1 ip_to_bytes out ':::'
+  expect_err 1 ip_to_bytes out '::z'
+  expect_err 1 ip_to_bytes out '1::1::1'
+  expect_err 1 ip_to_bytes out '1:1:1'
+  expect_err 1 ip_to_bytes out ':1::1'
+  expect_err 1 ip_to_bytes out '1::1:'
+
+  expect_err 0 ip_to_bytes out '::'
+  expected=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
+  expect_array_numeq out expected
+  out=()
+
+  expect_err 0 ip_to_bytes out '::1'
+  expected=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1)
+  expect_array_numeq out expected
+  out=()
+
+  expect_err 0 ip_to_bytes out 'fd00::'
+  expected=(0xfd 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
+  expect_array_numeq out expected
+  out=()
+
+  expect_err 0 ip_to_bytes out 'fd00:ffee::ddff:22'
+  expected=(0xfd 0 0xff 0xee 0 0 0 0 0 0 0 0 0xdd 0xff 0 0x22)
+  expect_array_numeq out expected
+  out=()
+
+  expect_err 0 ip_to_bytes out '1:2:3:4:5:6:7:8'
+  expected=(0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8)
+  expect_array_numeq out expected
+  out=()
+}
+
 test_ipv6_pfx_concat() {
   # Invalid inputs
   expect_err 1 ipv6_pfx_concat 'fd/64' '1234:5678:90af'