meta-google: network-sh: Convert to IP bytes

Now that we can convert to and from IP bytes, we should use them
everywhere to ensure values are parsed correctly.

Change-Id: I995091d1eff670db6678b4a2f4a64113e93308f7
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-from-ra.sh b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-from-ra.sh
index 9a5586b..18341fe 100644
--- a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-from-ra.sh
+++ b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-from-ra.sh
@@ -27,7 +27,7 @@
   local pfx
   for pfx in "${!gbmc_br_from_ra_pfxs[@]}"; do
     local cidr
-    if ! cidr="$(ipv6_pfx_to_cidr "$pfx")"; then
+    if ! cidr="$(ip_pfx_to_cidr "$pfx")"; then
       unset 'gbmc_br_from_ra_pfxs[$pfx]'
       continue
     fi
@@ -38,8 +38,12 @@
         continue
       fi
       local addr
-      addr="$(ipv6_pfx_concat "$pfx" "$sfx")"
+      if ! addr="$(ip_pfx_concat "$pfx" "$sfx")"; then
+        unset 'gbmc_br_from_ra_pfxs[$pfx]'
+        continue
+      fi
     else
+      unset 'gbmc_br_from_ra_pfxs[$pfx]'
       continue
     fi
     local valid="${gbmc_br_from_ra_pfxs["$pfx"]}"
diff --git a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-nft.sh b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-nft.sh
index 185d78b..19b8f64 100644
--- a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-nft.sh
+++ b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-nft.sh
@@ -56,10 +56,14 @@
       echo "gBMC Bridge NFT Invalid IP: $ip" >&2
       return 1
     fi
-    if (( ip_bytes[9] != 0xfd )); then
+    if (( ip_bytes[8] != 0xfd )); then
       return 0
     fi
-    pfx="$(printf '%02x%02x:%02x%02x:%02x%02x:%02x%02x:fd00::/72' "${ip_bytes[@]}")"
+    local i
+    for (( i=9; i<16; i++ )); do
+      ip_bytes[$i]=0
+    done
+    pfx="$(ip_bytes_to_str ip_bytes)/72"
     if [ "$action" = "add" -a "$pfx" != "$gbmc_br_nft_pfx" ]; then
       gbmc_br_nft_pfx="$pfx"
       gbmc_br_nft_update
diff --git a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-ula.sh b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-ula.sh
index ac273a3..6989710 100644
--- a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-ula.sh
+++ b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-ula.sh
@@ -27,10 +27,11 @@
   local addr=
   contents='[Network]'$'\n'
   if [ -n "$gbmc_br_ula_mac" ]; then
-    local eui64
-    eui64="$(mac_to_eui64 "$mac")" || return
-    addr="fdb5:0481:10ce:0:$eui64/64"
-    contents+="Address=$addr"$'\n'
+    local sfx
+    if sfx="$(mac_to_eui64 "$gbmc_br_ula_mac")" &&
+       addr="$(ip_pfx_concat "fdb5:0481:10ce::/64" "$sfx")"; then
+      contents+="Address=$addr"$'\n'
+    fi
   fi
 
   local netfile
diff --git a/meta-google/recipes-google/networking/network-sh/lib.sh b/meta-google/recipes-google/networking/network-sh/lib.sh
index 04e1480..b5d9382 100644
--- a/meta-google/recipes-google/networking/network-sh/lib.sh
+++ b/meta-google/recipes-google/networking/network-sh/lib.sh
@@ -33,11 +33,11 @@
 }
 
 mac_to_eui48() {
-  local mac_bytes=()
+  local mac_bytes=(0 0 0 0 0 0 0 0 0 0)
   mac_to_bytes mac_bytes "$1" || return
 
   # Return the EUI-64 bytes in the IPv6 format
-  printf '%02x%02x:%02x%02x:%02x%02x\n' "${mac_bytes[@]}"
+  ip_bytes_to_str mac_bytes
 }
 
 mac_to_eui64() {
@@ -47,6 +47,7 @@
   # Using EUI-64 conversion rules, create the suffix bytes from MAC bytes
   # Invert bit-0 of the first byte, and insert 0xfffe in the middle.
   local suffix_bytes=(
+    0 0 0 0 0 0 0 0
     $((mac_bytes[0] ^ 1))
     ${mac_bytes[@]:1:2}
     $((0xff)) $((0xfe))
@@ -54,7 +55,7 @@
   )
 
   # Return the EUI-64 bytes in the IPv6 format
-  printf '%02x%02x:%02x%02x:%02x%02x:%02x%02x\n' "${suffix_bytes[@]}"
+  ip_bytes_to_str suffix_bytes
 }
 
 ip_to_bytes() {
@@ -207,49 +208,86 @@
   fi
 }
 
-ipv6_pfx_concat() {
+ip_pfx_concat() {
   local pfx="$1"
   local sfx="$2"
 
-  # Validate the prefix
-  if ! [[ "$pfx" =~ ^(([0-9a-fA-F]{1,4}:)+):/([0-9]+)$ ]]; then
-    echo "Invalid IPv6 prefix: $pfx" >&2
+  # Parse the prefix
+  if ! [[ "$pfx" =~ ^([0-9a-fA-F:.]+)/([0-9]+)$ ]]; then
+    echo "Invalid IP prefix: $pfx" >&2
     return 1
   fi
   local addr="${BASH_REMATCH[1]}"
-  local cidr="${BASH_REMATCH[3]}"
+  local cidr="${BASH_REMATCH[2]}"
+
   # Ensure prefix doesn't have too many bytes
-  local nos="${addr//:/}"
-  if (( ${#addr} - ${#nos} > (cidr+7)/16 )); then
-    echo "Too many prefix bytes: $pfx" >&2
+  local pfx_bytes=()
+  if ! ip_to_bytes pfx_bytes "$addr"; then
+    echo "Invalid IP prefix: $pfx" >&2
     return 1
   fi
+  if (( ${#pfx_bytes[@]}*8 < cidr )); then
+    echo "Prefix CIDR too large" >&2
+    return 1
+  fi
+  # CIDR values might partially divide a byte so we need to mask out
+  # only the part of the byte we want to check for emptiness
+  if (( (pfx_bytes[cidr/8] & ~(~0 << (8-cidr%8))) != 0 )); then
+    echo "Invalid byte $((cidr/8)): $pfx" >&2
+    return 1
+  fi
+  local i
+  # Check the rest of the whole bytes to make sure they are empty
+  for (( i=cidr/8+1; i<${#pfx_bytes[@]}; i++ )); do
+    if (( pfx_bytes[$i] != 0 )); then
+      echo "Byte $i not 0: $pfx" >&2
+      return 1
+    fi
+  done
 
   # Validate the suffix
-  if ! [[ "$sfx" =~ ^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4})*$ ]]; then
+  local sfx_bytes=()
+  if ! ip_to_bytes sfx_bytes "$sfx"; then
     echo "Invalid IPv6 suffix: $sfx" >&2
     return 1
   fi
-  # Ensure suffix doesn't have too many bytes
-  local nos="${sfx//:/}"
-  if (( ${#sfx} - ${#nos} >= (128-cidr)/16 )); then
-    echo "Too many suffix bytes: $sfx" >&2
+  if (( "${#sfx_bytes[@]}" != "${#pfx_bytes[@]}" )); then
+    echo "Suffix not the same family as prefix: $pfx $sfx" >&2
     return 1
   fi
-
-  local comb="$addr:$sfx"
-  local nos="${comb//:/}"
-  if (( ${#comb} - ${#nos} == 8 )); then
-    comb="$addr$sfx"
+  # Check potential partially divided bytes for emptiness in the upper part
+  # based on the division specified in CIDR.
+  if (( (sfx_bytes[cidr/8] & (~0 << (8-cidr%8))) != 0 )); then
+    echo "Invalid byte $((cidr/8)): $sfx" >&2
+    return 1
   fi
-  echo "$comb/$cidr"
+  local i
+  # Check the bytes before the CIDR for emptiness to ensure they don't overlap
+  for (( i=0; i<cidr/8; i++ )); do
+    if (( sfx_bytes[$i] != 0 )); then
+      echo "Byte $i not 0: $sfx" >&2
+      return 1
+    fi
+  done
+
+  out_bytes=()
+  for (( i=0; i<${#pfx_bytes[@]}; i++ )); do
+    out_bytes+=($(( pfx_bytes[$i] | sfx_bytes[$i] )))
+  done
+  echo "$(ip_bytes_to_str out_bytes)/$cidr"
 }
 
-ipv6_pfx_to_cidr() {
-  [[ "$1" =~ ^[0-9a-fA-F:]+/([0-9]+)$ ]] || return
+ip_pfx_to_cidr() {
+  [[ "$1" =~ ^[0-9a-fA-F:.]+/([0-9]+)$ ]] || return
   echo "${BASH_REMATCH[1]}"
 }
 
+normalize_ip() {
+  local ip_bytes=()
+  ip_to_bytes ip_bytes "$1" || return
+  ip_bytes_to_str ip_bytes
+}
+
 network_init=1
 return 0 2>/dev/null
 echo "network is a library, not executed directly" >&2
diff --git a/meta-google/recipes-google/networking/network-sh/test.sh b/meta-google/recipes-google/networking/network-sh/test.sh
index 33cab86..2803c09 100755
--- a/meta-google/recipes-google/networking/network-sh/test.sh
+++ b/meta-google/recipes-google/networking/network-sh/test.sh
@@ -50,14 +50,14 @@
   expect_array_numeq out expected
 }
 
-test_mac_to_eui_48() {
+test_mac_to_eui48() {
   str="$(mac_to_eui48 '12:34:56:78:90:af')" || fail
-  expect_streq "$str" '1234:5678:90af'
+  expect_streq "$str" '::1234:5678:90af'
 }
 
-test_eui_64() {
+test_mac_to_eui64() {
   str="$(mac_to_eui64 '12:34:56:78:90:af')" || fail
-  expect_streq "$str" '1334:56ff:fe78:90af'
+  expect_streq "$str" '::1334:56ff:fe78:90af'
 }
 
 test_ip4_to_bytes() {
@@ -144,37 +144,57 @@
   expect_streq "$str" '1:1:dddd:1:1:1:1:1'
 }
 
-test_ipv6_pfx_concat() {
+test_ip_pfx_concat() {
   # Invalid inputs
-  expect_err 1 ipv6_pfx_concat 'fd/64' '1234:5678:90af'
-  expect_err 1 ipv6_pfx_concat 'fd01::' '1234:5678:90af'
-  expect_err 1 ipv6_pfx_concat 'fd01:' '1234:5678:90af'
-  expect_err 1 ipv6_pfx_concat 'fd01::/a0' '1234:5678:90af'
-  expect_err 1 ipv6_pfx_concat 'fd01::/64' ':1234:5678:90af'
-  expect_err 1 ipv6_pfx_concat 'fd01::/64' '::'
+  expect_err 1 ip_pfx_concat 'fd/64' '::1234:5678:90af'
+  expect_err 1 ip_pfx_concat 'fd01::' '::1234:5678:90af'
+  expect_err 1 ip_pfx_concat 'fd01:' '::1234:5678:90af'
+  expect_err 1 ip_pfx_concat 'fd01::/a0' '::1234:5678:90af'
+  expect_err 1 ip_pfx_concat 'fd01::/64' ':1234:5678:90af'
+  expect_err 1 ip_pfx_concat 'fd01::/64' ''
+  expect_err 1 ip_pfx_concat 'fd01::/129' '::1'
 
   # Too many address bits
-  expect_err 1 ipv6_pfx_concat 'fd01:1:1:1:1::/64' '1234:5678:90af'
-  expect_err 1 ipv6_pfx_concat 'fd01::/64' '1:0:1234:5678:90af'
-  expect_err 1 ipv6_pfx_concat 'fd01::/65' '1:1234:5678:90af'
-  expect_err 1 ipv6_pfx_concat 'fd01::/72' '1:1234:5678:90af'
+  expect_err 1 ip_pfx_concat 'fd01:1:1:1:1::/64' '::1234:5678:90af'
+  expect_err 1 ip_pfx_concat 'fd01::/64' '::1:0:1234:5678:90af'
+  expect_err 1 ip_pfx_concat 'fd01::/79' '::3:1234:5678:90af'
+  expect_err 1 ip_pfx_concat 'fd01::/15' '::3:1234:5678:90af'
+  expect_err 1 ip_pfx_concat '10.0.0.1/31' '0.0.0.0'
 
-  str="$(ipv6_pfx_concat 'fd01::/64' '1')" || fail
+  str="$(ip_pfx_concat '::1/128' '::0')" || fail
+  expect_streq "$str" '::1/128'
+  str="$(ip_pfx_concat 'fd01::/64' '::1')" || fail
   expect_streq "$str" 'fd01::1/64'
-  str="$(ipv6_pfx_concat 'fd01::/72' '1234:5678:90af')" || fail
+  str="$(ip_pfx_concat 'fd01::/127' '::1')" || fail
+  expect_streq "$str" 'fd01::1/127'
+  str="$(ip_pfx_concat 'fd02::/15' '::1')" || fail
+  expect_streq "$str" 'fd02::1/15'
+  str="$(ip_pfx_concat 'fd01::/72' '::1234:5678:90af')" || fail
   expect_streq "$str" 'fd01::1234:5678:90af/72'
-  str="$(ipv6_pfx_concat 'fd01:eeee:aaaa:cccc::/64' 'a:1234:5678:90af')" || fail
+  str="$(ip_pfx_concat 'fd01:eeee:aaaa:cccc::/64' '::a:1234:5678:90af')" || fail
   expect_streq "$str" 'fd01:eeee:aaaa:cccc:a:1234:5678:90af/64'
+  str="$(ip_pfx_concat 'fd01::fd00:0:0:0/80' '::1')" || fail
+  expect_streq "$str" 'fd01::fd00:0:0:1/80'
+
+  str="$(ip_pfx_concat '10.0.0.0/24' '0.0.0.1')" || fail
+  expect_streq "$str" '10.0.0.1/24'
 }
 
-test_ipv6_pfx_to_cidr() {
-  expect_err 1 ipv6_pfx_to_cidr 'z/64'
-  expect_err 1 ipv6_pfx_to_cidr '64'
+test_ip_pfx_to_cidr() {
+  expect_err 1 ip_pfx_to_cidr 'z/64'
+  expect_err 1 ip_pfx_to_cidr '64'
 
-  cidr="$(ipv6_pfx_to_cidr 'fd01::/64')" || fail
+  cidr="$(ip_pfx_to_cidr 'fd01::/64')" || fail
   expect_numeq "$cidr" 64
-  cidr="$(ipv6_pfx_to_cidr 'fd01:eeee:aaaa:cccc:a:1234:5678:90af/128')" || fail
+  cidr="$(ip_pfx_to_cidr 'fd01:eeee:aaaa:cccc:a:1234:5678:90af/128')" || fail
   expect_numeq "$cidr" 128
+  cidr="$(ip_pfx_to_cidr '10.0.0.1/24')" || fail
+  expect_numeq "$cidr" 24
+}
+
+test_normalize_ip() {
+  ip="$(normalize_ip 'fd01:1::0:0:1')" || fail
+  expect_streq "$ip" 'fd01:1::1'
 }
 
 return 0 2>/dev/null