Add a new IpPrefix class and use it in RouteInfo.

This change uses IpPrefix only in the public API and continues
to use LinkAddress for everything else. It does not change the
callers to use the new APIs, with the exception of changing
all current uses of getDestination to getDestinationLinkAddress
to make room for the new getDestination method that returns an
IpPrefix.

Based on Sreeram's earlier change:
https://googleplex-android-review.git.corp.google.com/#/c/477874/
but a bit simplified and with a bit more documentation.

Bug: 15142362
Bug: 13885501
Change-Id: Ib4cd96b22cbff4ea31bb26a7853989f50da8de4e
diff --git a/api/current.txt b/api/current.txt
index f204f99..2582474 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16160,6 +16160,14 @@
     field public int serverAddress;
   }
 
+  public class IpPrefix implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.net.InetAddress getAddress();
+    method public int getPrefixLength();
+    method public byte[] getRawAddress();
+    method public void writeToParcel(android.os.Parcel, int);
+  }
+
   public class LinkAddress implements android.os.Parcelable {
     method public int describeContents();
     method public java.net.InetAddress getAddress();
@@ -16171,8 +16179,6 @@
   }
 
   public class LinkProperties implements android.os.Parcelable {
-    ctor public LinkProperties();
-    ctor public LinkProperties(android.net.LinkProperties);
     method public int describeContents();
     method public java.util.List<java.net.InetAddress> getDnsServers();
     method public java.lang.String getDomains();
@@ -16380,12 +16386,8 @@
   }
 
   public class RouteInfo implements android.os.Parcelable {
-    ctor public RouteInfo(android.net.LinkAddress, java.net.InetAddress, java.lang.String);
-    ctor public RouteInfo(android.net.LinkAddress, java.net.InetAddress);
-    ctor public RouteInfo(java.net.InetAddress);
-    ctor public RouteInfo(android.net.LinkAddress);
     method public int describeContents();
-    method public android.net.LinkAddress getDestination();
+    method public android.net.IpPrefix getDestination();
     method public java.net.InetAddress getGateway();
     method public java.lang.String getInterface();
     method public boolean isDefaultRoute();
diff --git a/core/java/android/net/IpPrefix.aidl b/core/java/android/net/IpPrefix.aidl
new file mode 100644
index 0000000..9e552c7
--- /dev/null
+++ b/core/java/android/net/IpPrefix.aidl
@@ -0,0 +1,20 @@
+/**
+ *
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable IpPrefix;
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
new file mode 100644
index 0000000..dfe0384
--- /dev/null
+++ b/core/java/android/net/IpPrefix.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/**
+ * This class represents an IP prefix, i.e., a contiguous block of IP addresses aligned on a
+ * power of two boundary (also known as an "IP subnet"). A prefix is specified by two pieces of
+ * information:
+ *
+ * <ul>
+ * <li>A starting IP address (IPv4 or IPv6). This is the first IP address of the prefix.
+ * <li>A prefix length. This specifies the length of the prefix by specifing the number of bits
+ *     in the IP address, starting from the most significant bit in network byte order, that
+ *     are constant for all addresses in the prefix.
+ * </ul>
+ *
+ * For example, the prefix <code>192.0.2.0/24</code> covers the 256 IPv4 addresses from
+ * <code>192.0.2.0</code> to <code>192.0.2.255</code>, inclusive, and the prefix
+ * <code>2001:db8:1:2</code>  covers the 2^64 IPv6 addresses from <code>2001:db8:1:2::</code> to
+ * <code>2001:db8:1:2:ffff:ffff:ffff:ffff</code>, inclusive.
+ *
+ * Objects of this class are immutable.
+ */
+public class IpPrefix implements Parcelable {
+    private final byte[] address;  // network byte order
+    private final int prefixLength;
+
+    /**
+     * Constructs a new {@code IpPrefix} from a byte array containing an IPv4 or IPv6 address in
+     * network byte order and a prefix length.
+     *
+     * @param address the IP address. Must be non-null and exactly 4 or 16 bytes long.
+     * @param prefixLength the prefix length. Must be &gt;= 0 and &lt;= (32 or 128) (IPv4 or IPv6).
+     *
+     * @hide
+     */
+    public IpPrefix(byte[] address, int prefixLength) {
+        if (address.length != 4 && address.length != 16) {
+            throw new IllegalArgumentException(
+                    "IpPrefix has " + address.length + " bytes which is neither 4 nor 16");
+        }
+        if (prefixLength < 0 || prefixLength > (address.length * 8)) {
+            throw new IllegalArgumentException("IpPrefix with " + address.length +
+                    " bytes has invalid prefix length " + prefixLength);
+        }
+        this.address = address.clone();
+        this.prefixLength = prefixLength;
+        // TODO: Validate that the non-prefix bits are zero
+    }
+
+    /**
+     * @hide
+     */
+    public IpPrefix(InetAddress address, int prefixLength) {
+        this(address.getAddress(), prefixLength);
+    }
+
+    /**
+     * Compares this {@code IpPrefix} object against the specified object in {@code obj}. Two
+     * objects are equal if they have the same startAddress and prefixLength.
+     *
+     * @param obj the object to be tested for equality.
+     * @return {@code true} if both objects are equal, {@code false} otherwise.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof IpPrefix)) {
+            return false;
+        }
+        IpPrefix that = (IpPrefix) obj;
+        return Arrays.equals(this.address, that.address) && this.prefixLength == that.prefixLength;
+    }
+
+    /**
+     * Gets the hashcode of the represented IP prefix.
+     *
+     * @return the appropriate hashcode value.
+     */
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(address) + 11 * prefixLength;
+    }
+
+    /**
+     * Returns a copy of the first IP address in the prefix. Modifying the returned object does not
+     * change this object's contents.
+     *
+     * @return the address in the form of a byte array.
+     */
+    public InetAddress getAddress() {
+        try {
+            return InetAddress.getByAddress(address);
+        } catch (UnknownHostException e) {
+            // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte
+            // array is the wrong length, but we check that in the constructor.
+            return null;
+        }
+    }
+
+    /**
+     * Returns a copy of the IP address bytes in network order (the highest order byte is the zeroth
+     * element). Modifying the returned array does not change this object's contents.
+     *
+     * @return the address in the form of a byte array.
+     */
+    public byte[] getRawAddress() {
+        return address.clone();
+    }
+
+    /**
+     * Returns the prefix length of this {@code IpAddress}.
+     *
+     * @return the prefix length.
+     */
+    public int getPrefixLength() {
+        return prefixLength;
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByteArray(address);
+        dest.writeInt(prefixLength);
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public static final Creator<IpPrefix> CREATOR =
+            new Creator<IpPrefix>() {
+                public IpPrefix createFromParcel(Parcel in) {
+                    byte[] address = in.createByteArray();
+                    int prefixLength = in.readInt();
+                    return new IpPrefix(address, prefixLength);
+                }
+
+                public IpPrefix[] newArray(int size) {
+                    return new IpPrefix[size];
+                }
+            };
+}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index cff9025..bb05936 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -77,9 +77,15 @@
         }
     }
 
+    /**
+     * @hide
+     */
     public LinkProperties() {
     }
 
+    /**
+     * @hide
+     */
     public LinkProperties(LinkProperties source) {
         if (source != null) {
             mIfaceName = source.getInterfaceName();
@@ -267,7 +273,7 @@
     }
 
     /**
-     * Returns all the {@link LinkAddress} for DNS servers on this link.
+     * Returns all the {@link InetAddress} for DNS servers on this link.
      *
      * @return An umodifiable {@link List} of {@link InetAddress} for DNS servers on
      *         this link.
@@ -477,12 +483,12 @@
 
         String domainName = "Domains: " + mDomains;
 
-        String mtu = "MTU: " + mMtu;
+        String mtu = " MTU: " + mMtu;
 
         String routes = " Routes: [";
         for (RouteInfo route : mRoutes) routes += route.toString() + ",";
         routes += "] ";
-        String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
+        String proxy = (mHttpProxy == null ? "" : " HttpProxy: " + mHttpProxy.toString() + " ");
 
         String stacked = "";
         if (mStackedLinks.values().size() > 0) {
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index ad8e4f7..a65523b 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -35,10 +35,10 @@
  *
  * A route contains three pieces of information:
  * <ul>
- * <li>a destination {@link LinkAddress} for directly-connected subnets.  If this is
- *     {@code null} it indicates a default route of the address family (IPv4 or IPv6)
+ * <li>a destination {@link IpPrefix} specifying the network destinations covered by this route.
+ *     If this is {@code null} it indicates a default route of the address family (IPv4 or IPv6)
  *     implied by the gateway IP address.
- * <li>a gateway {@link InetAddress} for default routes.  If this is {@code null} it
+ * <li>a gateway {@link InetAddress} indicating the next hop to use.  If this is {@code null} it
  *     indicates a directly-connected route.
  * <li>an interface (which may be unspecified).
  * </ul>
@@ -49,6 +49,7 @@
 public class RouteInfo implements Parcelable {
     /**
      * The IP destination address for this route.
+     * TODO: Make this an IpPrefix.
      */
     private final LinkAddress mDestination;
 
@@ -80,6 +81,19 @@
      * @param destination the destination prefix
      * @param gateway the IP address to route packets through
      * @param iface the interface name to send packets on
+     *
+     * TODO: Convert to use IpPrefix.
+     *
+     * @hide
+     */
+    public RouteInfo(IpPrefix destination, InetAddress gateway, String iface) {
+        this(destination == null ? null :
+                new LinkAddress(destination.getAddress(), destination.getPrefixLength()),
+                gateway, iface);
+    }
+
+    /**
+     * @hide
      */
     public RouteInfo(LinkAddress destination, InetAddress gateway, String iface) {
         if (destination == null) {
@@ -128,8 +142,17 @@
      * <p>
      * Destination and gateway may not both be null.
      *
-     * @param destination the destination address and prefix in a {@link LinkAddress}
+     * @param destination the destination address and prefix in an {@link IpPrefix}
      * @param gateway the {@link InetAddress} to route packets through
+     *
+     * @hide
+     */
+    public RouteInfo(IpPrefix destination, InetAddress gateway) {
+        this(destination, gateway, null);
+    }
+
+    /**
+     * @hide
      */
     public RouteInfo(LinkAddress destination, InetAddress gateway) {
         this(destination, gateway, null);
@@ -139,16 +162,27 @@
      * Constructs a default {@code RouteInfo} object.
      *
      * @param gateway the {@link InetAddress} to route packets through
+     *
+     * @hide
      */
     public RouteInfo(InetAddress gateway) {
-        this(null, gateway, null);
+        this((LinkAddress) null, gateway, null);
     }
 
     /**
      * Constructs a {@code RouteInfo} object representing a direct connected subnet.
      *
-     * @param destination the {@link LinkAddress} describing the address and prefix
+     * @param destination the {@link IpPrefix} describing the address and prefix
      *                    length of the subnet.
+     *
+     * @hide
+     */
+    public RouteInfo(IpPrefix destination) {
+        this(destination, null, null);
+    }
+
+    /**
+     * @hide
      */
     public RouteInfo(LinkAddress destination) {
         this(destination, null, null);
@@ -194,11 +228,19 @@
     }
 
     /**
-     * Retrieves the destination address and prefix length in the form of a {@link LinkAddress}.
+     * Retrieves the destination address and prefix length in the form of an {@link IpPrefix}.
      *
-     * @return {@link LinkAddress} specifying the destination.  This is never {@code null}.
+     * @return {@link IpPrefix} specifying the destination.  This is never {@code null}.
      */
-    public LinkAddress getDestination() {
+    public IpPrefix getDestination() {
+        return new IpPrefix(mDestination.getAddress(), mDestination.getPrefixLength());
+    }
+
+    /**
+     * TODO: Convert callers to use IpPrefix and then remove.
+     * @hide
+     */
+    public LinkAddress getDestinationLinkAddress() {
         return mDestination;
     }
 
@@ -233,7 +275,8 @@
     /**
      * Indicates if this route is a host route (ie, matches only a single host address).
      *
-     * @return {@code true} if the destination has a prefix length of 32/128 for v4/v6.
+     * @return {@code true} if the destination has a prefix length of 32 or 128 for IPv4 or IPv6,
+     * respectively.
      * @hide
      */
     public boolean isHostRoute() {
@@ -295,13 +338,22 @@
         return bestRoute;
     }
 
+    /**
+     * Returns a human-readable description of this object.
+     */
     public String toString() {
         String val = "";
         if (mDestination != null) val = mDestination.toString();
-        if (mGateway != null) val += " -> " + mGateway.getHostAddress();
+        val += " ->";
+        if (mGateway != null) val += " " + mGateway.getHostAddress();
+        if (mInterface != null) val += " " + mInterface;
         return val;
     }
 
+    /**
+     * Compares this RouteInfo object against the specified object and indicates if they are equal.
+     * @return {@code true} if the objects are equal, {@code false} otherwise.
+     */
     public boolean equals(Object obj) {
         if (this == obj) return true;
 
@@ -314,6 +366,9 @@
                 Objects.equals(mInterface, target.getInterface());
     }
 
+    /**
+     *  Returns a hashcode for this <code>RouteInfo</code> object.
+     */
     public int hashCode() {
         return (mDestination == null ? 0 : mDestination.hashCode() * 41)
                 + (mGateway == null ? 0 :mGateway.hashCode() * 47)
diff --git a/core/tests/coretests/src/android/net/RouteInfoTest.java b/core/tests/coretests/src/android/net/RouteInfoTest.java
index 55d6592..01283a6 100644
--- a/core/tests/coretests/src/android/net/RouteInfoTest.java
+++ b/core/tests/coretests/src/android/net/RouteInfoTest.java
@@ -43,17 +43,17 @@
 
         // Invalid input.
         try {
-            r = new RouteInfo(null, null, "rmnet0");
+            r = new RouteInfo((LinkAddress) null, null, "rmnet0");
             fail("Expected RuntimeException:  destination and gateway null");
         } catch(RuntimeException e) {}
 
         // Null destination is default route.
-        r = new RouteInfo(null, Address("2001:db8::1"), null);
+        r = new RouteInfo((LinkAddress) null, Address("2001:db8::1"), null);
         assertEquals(Prefix("::/0"), r.getDestination());
         assertEquals(Address("2001:db8::1"), r.getGateway());
         assertNull(r.getInterface());
 
-        r = new RouteInfo(null, Address("192.0.2.1"), "wlan0");
+        r = new RouteInfo((LinkAddress) null, Address("192.0.2.1"), "wlan0");
         assertEquals(Prefix("0.0.0.0/0"), r.getDestination());
         assertEquals(Address("192.0.2.1"), r.getGateway());
         assertEquals("wlan0", r.getInterface());
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4c594e1..25e7822 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1871,7 +1871,7 @@
                             mNetd.addRoute(netId, r);
                         }
                         if (exempt) {
-                            LinkAddress dest = r.getDestination();
+                            LinkAddress dest = r.getDestinationLinkAddress();
                             if (!mExemptAddresses.contains(dest)) {
                                 mNetd.setHostExemption(dest);
                                 mExemptAddresses.add(dest);
@@ -1904,7 +1904,7 @@
                             } else {
                                 mNetd.removeRoute(netId, r);
                             }
-                            LinkAddress dest = r.getDestination();
+                            LinkAddress dest = r.getDestinationLinkAddress();
                             if (mExemptAddresses.contains(dest)) {
                                 mNetd.clearHostExemption(dest);
                                 mExemptAddresses.remove(dest);
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index eefe8da..3b748bd 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -882,7 +882,7 @@
         final Command cmd = new Command("network", "route", action, netId);
 
         // create triplet: interface dest-ip-addr/prefixlength gateway-ip-addr
-        final LinkAddress la = route.getDestination();
+        final LinkAddress la = route.getDestinationLinkAddress();
         cmd.appendArg(route.getInterface());
         cmd.appendArg(la.getAddress().getHostAddress() + "/" + la.getNetworkPrefixLength());
         if (route.hasGateway()) {
@@ -1697,7 +1697,7 @@
     public void setMarkedForwardingRoute(String iface, RouteInfo route) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         try {
-            LinkAddress dest = route.getDestination();
+            LinkAddress dest = route.getDestinationLinkAddress();
             mConnector.execute("interface", "fwmark", "route", "add", iface,
                     dest.getAddress().getHostAddress(), dest.getNetworkPrefixLength());
         } catch (NativeDaemonConnectorException e) {
@@ -1709,7 +1709,7 @@
     public void clearMarkedForwardingRoute(String iface, RouteInfo route) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         try {
-            LinkAddress dest = route.getDestination();
+            LinkAddress dest = route.getDestinationLinkAddress();
             mConnector.execute("interface", "fwmark", "route", "remove", iface,
                     dest.getAddress().getHostAddress(), dest.getNetworkPrefixLength());
         } catch (NativeDaemonConnectorException e) {
@@ -1998,7 +1998,7 @@
         final Command cmd = new Command("network", "route", "legacy", uid, action, netId);
 
         // create triplet: interface dest-ip-addr/prefixlength gateway-ip-addr
-        final LinkAddress la = routeInfo.getDestination();
+        final LinkAddress la = routeInfo.getDestinationLinkAddress();
         cmd.appendArg(routeInfo.getInterface());
         cmd.appendArg(la.getAddress().getHostAddress() + "/" + la.getNetworkPrefixLength());
         if (routeInfo.hasGateway()) {
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java
index bb0d5fe..35bc6ec 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/services/core/java/com/android/server/net/IpConfigStore.java
@@ -85,7 +85,7 @@
                     }
                     for (RouteInfo route : linkProperties.getRoutes()) {
                         out.writeUTF(GATEWAY_KEY);
-                        LinkAddress dest = route.getDestination();
+                        LinkAddress dest = route.getDestinationLinkAddress();
                         if (dest != null) {
                             out.writeInt(1);
                             out.writeUTF(dest.getAddress().getHostAddress());