LinkProperties.java revision eb2c2c790c4b86c9c09245e0b87a38972713434a
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.net;
18
19import android.annotation.NonNull;
20import android.net.ProxyInfo;
21import android.os.Parcelable;
22import android.os.Parcel;
23import android.text.TextUtils;
24
25import java.net.InetAddress;
26import java.net.Inet4Address;
27import java.net.Inet6Address;
28import java.net.UnknownHostException;
29import java.util.ArrayList;
30import java.util.Collection;
31import java.util.Collections;
32import java.util.Hashtable;
33import java.util.List;
34import java.util.Objects;
35
36/**
37 * Describes the properties of a network link.
38 *
39 * A link represents a connection to a network.
40 * It may have multiple addresses and multiple gateways,
41 * multiple dns servers but only one http proxy and one
42 * network interface.
43 *
44 * Note that this is just a holder of data.  Modifying it
45 * does not affect live networks.
46 *
47 */
48public final class LinkProperties implements Parcelable {
49    // The interface described by the network link.
50    private String mIfaceName;
51    private ArrayList<LinkAddress> mLinkAddresses = new ArrayList<LinkAddress>();
52    private ArrayList<InetAddress> mDnses = new ArrayList<InetAddress>();
53    private String mDomains;
54    private ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
55    private ProxyInfo mHttpProxy;
56    private int mMtu;
57
58    // Stores the properties of links that are "stacked" above this link.
59    // Indexed by interface name to allow modification and to prevent duplicates being added.
60    private Hashtable<String, LinkProperties> mStackedLinks =
61        new Hashtable<String, LinkProperties>();
62
63    /**
64     * @hide
65     */
66    public static class CompareResult<T> {
67        public List<T> removed = new ArrayList<T>();
68        public List<T> added = new ArrayList<T>();
69
70        @Override
71        public String toString() {
72            String retVal = "removed=[";
73            for (T addr : removed) retVal += addr.toString() + ",";
74            retVal += "] added=[";
75            for (T addr : added) retVal += addr.toString() + ",";
76            retVal += "]";
77            return retVal;
78        }
79    }
80
81    /**
82     * @hide
83     */
84    public LinkProperties() {
85    }
86
87    /**
88     * @hide
89     */
90    public LinkProperties(LinkProperties source) {
91        if (source != null) {
92            mIfaceName = source.getInterfaceName();
93            for (LinkAddress l : source.getLinkAddresses()) mLinkAddresses.add(l);
94            for (InetAddress i : source.getDnsServers()) mDnses.add(i);
95            mDomains = source.getDomains();
96            for (RouteInfo r : source.getRoutes()) mRoutes.add(r);
97            mHttpProxy = (source.getHttpProxy() == null)  ?
98                    null : new ProxyInfo(source.getHttpProxy());
99            for (LinkProperties l: source.mStackedLinks.values()) {
100                addStackedLink(l);
101            }
102            setMtu(source.getMtu());
103        }
104    }
105
106    /**
107     * Sets the interface name for this link.  All {@link RouteInfo} already set for this
108     * will have their interface changed to match this new value.
109     *
110     * @param iface The name of the network interface used for this link.
111     * @hide
112     */
113    public void setInterfaceName(String iface) {
114        mIfaceName = iface;
115        ArrayList<RouteInfo> newRoutes = new ArrayList<RouteInfo>(mRoutes.size());
116        for (RouteInfo route : mRoutes) {
117            newRoutes.add(routeWithInterface(route));
118        }
119        mRoutes = newRoutes;
120    }
121
122    /**
123     * Gets the interface name for this link.  May be {@code null} if not set.
124     *
125     * @return The interface name set for this link or {@code null}.
126     */
127    public String getInterfaceName() {
128        return mIfaceName;
129    }
130
131    /**
132     * @hide
133     */
134    public List<String> getAllInterfaceNames() {
135        List<String> interfaceNames = new ArrayList<String>(mStackedLinks.size() + 1);
136        if (mIfaceName != null) interfaceNames.add(new String(mIfaceName));
137        for (LinkProperties stacked: mStackedLinks.values()) {
138            interfaceNames.addAll(stacked.getAllInterfaceNames());
139        }
140        return interfaceNames;
141    }
142
143    /**
144     * Returns all the addresses on this link.  We often think of a link having a single address,
145     * however, particularly with Ipv6 several addresses are typical.  Note that the
146     * {@code LinkProperties} actually contains {@link LinkAddress} objects which also include
147     * prefix lengths for each address.  This is a simplified utility alternative to
148     * {@link LinkProperties#getLinkAddresses}.
149     *
150     * @return An umodifiable {@link List} of {@link InetAddress} for this link.
151     * @hide
152     */
153    public List<InetAddress> getAddresses() {
154        List<InetAddress> addresses = new ArrayList<InetAddress>();
155        for (LinkAddress linkAddress : mLinkAddresses) {
156            addresses.add(linkAddress.getAddress());
157        }
158        return Collections.unmodifiableList(addresses);
159    }
160
161    /**
162     * Returns all the addresses on this link and all the links stacked above it.
163     * @hide
164     */
165    public List<InetAddress> getAllAddresses() {
166        List<InetAddress> addresses = new ArrayList<InetAddress>();
167        for (LinkAddress linkAddress : mLinkAddresses) {
168            addresses.add(linkAddress.getAddress());
169        }
170        for (LinkProperties stacked: mStackedLinks.values()) {
171            addresses.addAll(stacked.getAllAddresses());
172        }
173        return addresses;
174    }
175
176    private int findLinkAddressIndex(LinkAddress address) {
177        for (int i = 0; i < mLinkAddresses.size(); i++) {
178            if (mLinkAddresses.get(i).isSameAddressAs(address)) {
179                return i;
180            }
181        }
182        return -1;
183    }
184
185    /**
186     * Adds a {@link LinkAddress} to this {@code LinkProperties} if a {@link LinkAddress} of the
187     * same address/prefix does not already exist.  If it does exist it is replaced.
188     * @param address The {@code LinkAddress} to add.
189     * @return true if {@code address} was added or updated, false otherwise.
190     * @hide
191     */
192    public boolean addLinkAddress(LinkAddress address) {
193        if (address == null) {
194            return false;
195        }
196        int i = findLinkAddressIndex(address);
197        if (i < 0) {
198            // Address was not present. Add it.
199            mLinkAddresses.add(address);
200            return true;
201        } else if (mLinkAddresses.get(i).equals(address)) {
202            // Address was present and has same properties. Do nothing.
203            return false;
204        } else {
205            // Address was present and has different properties. Update it.
206            mLinkAddresses.set(i, address);
207            return true;
208        }
209    }
210
211    /**
212     * Removes a {@link LinkAddress} from this {@code LinkProperties}.  Specifically, matches
213     * and {@link LinkAddress} with the same address and prefix.
214     *
215     * @param toRemove A {@link LinkAddress} specifying the address to remove.
216     * @return true if the address was removed, false if it did not exist.
217     * @hide
218     */
219    public boolean removeLinkAddress(LinkAddress toRemove) {
220        int i = findLinkAddressIndex(toRemove);
221        if (i >= 0) {
222            mLinkAddresses.remove(i);
223            return true;
224        }
225        return false;
226    }
227
228    /**
229     * Returns all the {@link LinkAddress} on this link.  Typically a link will have
230     * one IPv4 address and one or more IPv6 addresses.
231     *
232     * @return An unmodifiable {@link List} of {@link LinkAddress} for this link.
233     */
234    public List<LinkAddress> getLinkAddresses() {
235        return Collections.unmodifiableList(mLinkAddresses);
236    }
237
238    /**
239     * Returns all the addresses on this link and all the links stacked above it.
240     * @hide
241     */
242    public List<LinkAddress> getAllLinkAddresses() {
243        List<LinkAddress> addresses = new ArrayList<LinkAddress>();
244        addresses.addAll(mLinkAddresses);
245        for (LinkProperties stacked: mStackedLinks.values()) {
246            addresses.addAll(stacked.getAllLinkAddresses());
247        }
248        return addresses;
249    }
250
251    /**
252     * Replaces the {@link LinkAddress} in this {@code LinkProperties} with
253     * the given {@link Collection} of {@link LinkAddress}.
254     *
255     * @param addresses The {@link Collection} of {@link LinkAddress} to set in this
256     *                  object.
257     * @hide
258     */
259    public void setLinkAddresses(Collection<LinkAddress> addresses) {
260        mLinkAddresses.clear();
261        for (LinkAddress address: addresses) {
262            addLinkAddress(address);
263        }
264    }
265
266    /**
267     * Adds the given {@link InetAddress} to the list of DNS servers, if not present.
268     *
269     * @param dnsServer The {@link InetAddress} to add to the list of DNS servers.
270     * @return true if the DNS server was added, false if it was already present.
271     * @hide
272     */
273    public boolean addDnsServer(InetAddress dnsServer) {
274        if (dnsServer != null && !mDnses.contains(dnsServer)) {
275            mDnses.add(dnsServer);
276            return true;
277        }
278        return false;
279    }
280
281    /**
282     * Replaces the DNS servers in this {@code LinkProperties} with
283     * the given {@link Collection} of {@link InetAddress} objects.
284     *
285     * @param addresses The {@link Collection} of DNS servers to set in this object.
286     * @hide
287     */
288    public void setDnsServers(Collection<InetAddress> dnsServers) {
289        mDnses.clear();
290        for (InetAddress dnsServer: dnsServers) {
291            addDnsServer(dnsServer);
292        }
293    }
294
295    /**
296     * Returns all the {@link InetAddress} for DNS servers on this link.
297     *
298     * @return An umodifiable {@link List} of {@link InetAddress} for DNS servers on
299     *         this link.
300     */
301    public List<InetAddress> getDnsServers() {
302        return Collections.unmodifiableList(mDnses);
303    }
304
305    /**
306     * Sets the DNS domain search path used on this link.
307     *
308     * @param domains A {@link String} listing in priority order the comma separated
309     *                domains to search when resolving host names on this link.
310     * @hide
311     */
312    public void setDomains(String domains) {
313        mDomains = domains;
314    }
315
316    /**
317     * Get the DNS domains search path set for this link.
318     *
319     * @return A {@link String} containing the comma separated domains to search when resolving
320     *         host names on this link.
321     */
322    public String getDomains() {
323        return mDomains;
324    }
325
326    /**
327     * Sets the Maximum Transmission Unit size to use on this link.  This should not be used
328     * unless the system default (1500) is incorrect.  Values less than 68 or greater than
329     * 10000 will be ignored.
330     *
331     * @param mtu The MTU to use for this link.
332     * @hide
333     */
334    public void setMtu(int mtu) {
335        mMtu = mtu;
336    }
337
338    /**
339     * Gets any non-default MTU size set for this link.  Note that if the default is being used
340     * this will return 0.
341     *
342     * @return The mtu value set for this link.
343     * @hide
344     */
345    public int getMtu() {
346        return mMtu;
347    }
348
349    private RouteInfo routeWithInterface(RouteInfo route) {
350        return new RouteInfo(
351            route.getDestination(),
352            route.getGateway(),
353            mIfaceName);
354    }
355
356    /**
357     * Adds a {@link RouteInfo} to this {@code LinkProperties}, if not present. If the
358     * {@link RouteInfo} had an interface name set and that differs from the interface set for this
359     * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown.  The proper
360     * course is to add either un-named or properly named {@link RouteInfo}.
361     *
362     * @param route A {@link RouteInfo} to add to this object.
363     * @return {@code false} if the route was already present, {@code true} if it was added.
364     *
365     * @hide
366     */
367    public boolean addRoute(RouteInfo route) {
368        if (route != null) {
369            String routeIface = route.getInterface();
370            if (routeIface != null && !routeIface.equals(mIfaceName)) {
371                throw new IllegalArgumentException(
372                   "Route added with non-matching interface: " + routeIface +
373                   " vs. " + mIfaceName);
374            }
375            route = routeWithInterface(route);
376            if (!mRoutes.contains(route)) {
377                mRoutes.add(route);
378                return true;
379            }
380        }
381        return false;
382    }
383
384    /**
385     * Removes a {@link RouteInfo} from this {@code LinkProperties}, if present. The route must
386     * specify an interface and the interface must match the interface of this
387     * {@code LinkProperties}, or it will not be removed.
388     *
389     * @return {@code true} if the route was removed, {@code false} if it was not present.
390     *
391     * @hide
392     */
393    public boolean removeRoute(RouteInfo route) {
394        return route != null &&
395                Objects.equals(mIfaceName, route.getInterface()) &&
396                mRoutes.remove(route);
397    }
398
399    /**
400     * Returns all the {@link RouteInfo} set on this link.
401     *
402     * @return An unmodifiable {@link List} of {@link RouteInfo} for this link.
403     */
404    public List<RouteInfo> getRoutes() {
405        return Collections.unmodifiableList(mRoutes);
406    }
407
408    /**
409     * Returns all the routes on this link and all the links stacked above it.
410     * @hide
411     */
412    public List<RouteInfo> getAllRoutes() {
413        List<RouteInfo> routes = new ArrayList();
414        routes.addAll(mRoutes);
415        for (LinkProperties stacked: mStackedLinks.values()) {
416            routes.addAll(stacked.getAllRoutes());
417        }
418        return routes;
419    }
420
421    /**
422     * Sets the recommended {@link ProxyInfo} to use on this link, or {@code null} for none.
423     * Note that Http Proxies are only a hint - the system recommends their use, but it does
424     * not enforce it and applications may ignore them.
425     *
426     * @param proxy A {@link ProxyInfo} defining the Http Proxy to use on this link.
427     * @hide
428     */
429    public void setHttpProxy(ProxyInfo proxy) {
430        mHttpProxy = proxy;
431    }
432
433    /**
434     * Gets the recommended {@link ProxyInfo} (or {@code null}) set on this link.
435     *
436     * @return The {@link ProxyInfo} set on this link
437     */
438    public ProxyInfo getHttpProxy() {
439        return mHttpProxy;
440    }
441
442    /**
443     * Adds a stacked link.
444     *
445     * If there is already a stacked link with the same interfacename as link,
446     * that link is replaced with link. Otherwise, link is added to the list
447     * of stacked links. If link is null, nothing changes.
448     *
449     * @param link The link to add.
450     * @return true if the link was stacked, false otherwise.
451     * @hide
452     */
453    public boolean addStackedLink(LinkProperties link) {
454        if (link != null && link.getInterfaceName() != null) {
455            mStackedLinks.put(link.getInterfaceName(), link);
456            return true;
457        }
458        return false;
459    }
460
461    /**
462     * Removes a stacked link.
463     *
464     * If there a stacked link with the same interfacename as link, it is
465     * removed. Otherwise, nothing changes.
466     *
467     * @param link The link to remove.
468     * @return true if the link was removed, false otherwise.
469     * @hide
470     */
471    public boolean removeStackedLink(LinkProperties link) {
472        if (link != null && link.getInterfaceName() != null) {
473            LinkProperties removed = mStackedLinks.remove(link.getInterfaceName());
474            return removed != null;
475        }
476        return false;
477    }
478
479    /**
480     * Returns all the links stacked on top of this link.
481     * @hide
482     */
483    public @NonNull List<LinkProperties> getStackedLinks() {
484        if (mStackedLinks.isEmpty()) {
485            return Collections.EMPTY_LIST;
486        }
487        List<LinkProperties> stacked = new ArrayList<LinkProperties>();
488        for (LinkProperties link : mStackedLinks.values()) {
489            stacked.add(new LinkProperties(link));
490        }
491        return Collections.unmodifiableList(stacked);
492    }
493
494    /**
495     * Clears this object to its initial state.
496     * @hide
497     */
498    public void clear() {
499        mIfaceName = null;
500        mLinkAddresses.clear();
501        mDnses.clear();
502        mDomains = null;
503        mRoutes.clear();
504        mHttpProxy = null;
505        mStackedLinks.clear();
506        mMtu = 0;
507    }
508
509    /**
510     * Implement the Parcelable interface
511     */
512    public int describeContents() {
513        return 0;
514    }
515
516    @Override
517    public String toString() {
518        String ifaceName = (mIfaceName == null ? "" : "InterfaceName: " + mIfaceName + " ");
519
520        String linkAddresses = "LinkAddresses: [";
521        for (LinkAddress addr : mLinkAddresses) linkAddresses += addr.toString() + ",";
522        linkAddresses += "] ";
523
524        String dns = "DnsAddresses: [";
525        for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
526        dns += "] ";
527
528        String domainName = "Domains: " + mDomains;
529
530        String mtu = " MTU: " + mMtu;
531
532        String routes = " Routes: [";
533        for (RouteInfo route : mRoutes) routes += route.toString() + ",";
534        routes += "] ";
535        String proxy = (mHttpProxy == null ? "" : " HttpProxy: " + mHttpProxy.toString() + " ");
536
537        String stacked = "";
538        if (mStackedLinks.values().size() > 0) {
539            stacked += " Stacked: [";
540            for (LinkProperties link: mStackedLinks.values()) {
541                stacked += " [" + link.toString() + " ],";
542            }
543            stacked += "] ";
544        }
545        return "{" + ifaceName + linkAddresses + routes + dns + domainName + mtu
546            + proxy + stacked + "}";
547    }
548
549    /**
550     * Returns true if this link has an IPv4 address.
551     *
552     * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
553     * @hide
554     */
555    public boolean hasIPv4Address() {
556        for (LinkAddress address : mLinkAddresses) {
557          if (address.getAddress() instanceof Inet4Address) {
558            return true;
559          }
560        }
561        return false;
562    }
563
564    /**
565     * Returns true if this link has a global preferred IPv6 address.
566     *
567     * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
568     * @hide
569     */
570    public boolean hasGlobalIPv6Address() {
571        for (LinkAddress address : mLinkAddresses) {
572          if (address.getAddress() instanceof Inet6Address && address.isGlobalPreferred()) {
573            return true;
574          }
575        }
576        return false;
577    }
578
579    /**
580     * Returns true if this link has an IPv4 default route.
581     *
582     * @return {@code true} if there is an IPv4 default route, {@code false} otherwise.
583     * @hide
584     */
585    public boolean hasIPv4DefaultRoute() {
586        for (RouteInfo r : mRoutes) {
587          if (r.isIPv4Default()) {
588            return true;
589          }
590        }
591        return false;
592    }
593
594    /**
595     * Returns true if this link has an IPv6 default route.
596     *
597     * @return {@code true} if there is an IPv6 default route, {@code false} otherwise.
598     * @hide
599     */
600    public boolean hasIPv6DefaultRoute() {
601        for (RouteInfo r : mRoutes) {
602          if (r.isIPv6Default()) {
603            return true;
604          }
605        }
606        return false;
607    }
608
609    /**
610     * Returns true if this link has an IPv4 DNS server.
611     *
612     * @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise.
613     * @hide
614     */
615    public boolean hasIPv4DnsServer() {
616        for (InetAddress ia : mDnses) {
617          if (ia instanceof Inet4Address) {
618            return true;
619          }
620        }
621        return false;
622    }
623
624    /**
625     * Returns true if this link has an IPv6 DNS server.
626     *
627     * @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise.
628     * @hide
629     */
630    public boolean hasIPv6DnsServer() {
631        for (InetAddress ia : mDnses) {
632          if (ia instanceof Inet6Address) {
633            return true;
634          }
635        }
636        return false;
637    }
638
639    /**
640     * Returns true if this link is provisioned for global connectivity. For IPv6, this requires an
641     * IP address, default route, and DNS server. For IPv4, this requires only an IPv4 address,
642     * because WifiStateMachine accepts static configurations that only specify an address but not
643     * DNS servers or a default route.
644     *
645     * @return {@code true} if the link is provisioned, {@code false} otherwise.
646     * @hide
647     */
648    public boolean isProvisioned() {
649        return (hasIPv4Address() ||
650                (hasGlobalIPv6Address() && hasIPv6DefaultRoute() && hasIPv6DnsServer()));
651    }
652
653    /**
654     * Compares this {@code LinkProperties} interface name against the target
655     *
656     * @param target LinkProperties to compare.
657     * @return {@code true} if both are identical, {@code false} otherwise.
658     * @hide
659     */
660    public boolean isIdenticalInterfaceName(LinkProperties target) {
661        return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
662    }
663
664    /**
665     * Compares this {@code LinkProperties} interface addresses against the target
666     *
667     * @param target LinkProperties to compare.
668     * @return {@code true} if both are identical, {@code false} otherwise.
669     * @hide
670     */
671    public boolean isIdenticalAddresses(LinkProperties target) {
672        Collection<InetAddress> targetAddresses = target.getAddresses();
673        Collection<InetAddress> sourceAddresses = getAddresses();
674        return (sourceAddresses.size() == targetAddresses.size()) ?
675                    sourceAddresses.containsAll(targetAddresses) : false;
676    }
677
678    /**
679     * Compares this {@code LinkProperties} DNS addresses against the target
680     *
681     * @param target LinkProperties to compare.
682     * @return {@code true} if both are identical, {@code false} otherwise.
683     * @hide
684     */
685    public boolean isIdenticalDnses(LinkProperties target) {
686        Collection<InetAddress> targetDnses = target.getDnsServers();
687        String targetDomains = target.getDomains();
688        if (mDomains == null) {
689            if (targetDomains != null) return false;
690        } else {
691            if (mDomains.equals(targetDomains) == false) return false;
692        }
693        return (mDnses.size() == targetDnses.size()) ?
694                    mDnses.containsAll(targetDnses) : false;
695    }
696
697    /**
698     * Compares this {@code LinkProperties} Routes against the target
699     *
700     * @param target LinkProperties to compare.
701     * @return {@code true} if both are identical, {@code false} otherwise.
702     * @hide
703     */
704    public boolean isIdenticalRoutes(LinkProperties target) {
705        Collection<RouteInfo> targetRoutes = target.getRoutes();
706        return (mRoutes.size() == targetRoutes.size()) ?
707                    mRoutes.containsAll(targetRoutes) : false;
708    }
709
710    /**
711     * Compares this {@code LinkProperties} HttpProxy against the target
712     *
713     * @param target LinkProperties to compare.
714     * @return {@code true} if both are identical, {@code false} otherwise.
715     * @hide
716     */
717    public boolean isIdenticalHttpProxy(LinkProperties target) {
718        return getHttpProxy() == null ? target.getHttpProxy() == null :
719                    getHttpProxy().equals(target.getHttpProxy());
720    }
721
722    /**
723     * Compares this {@code LinkProperties} stacked links against the target
724     *
725     * @param target LinkProperties to compare.
726     * @return {@code true} if both are identical, {@code false} otherwise.
727     * @hide
728     */
729    public boolean isIdenticalStackedLinks(LinkProperties target) {
730        if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) {
731            return false;
732        }
733        for (LinkProperties stacked : mStackedLinks.values()) {
734            // Hashtable values can never be null.
735            String iface = stacked.getInterfaceName();
736            if (!stacked.equals(target.mStackedLinks.get(iface))) {
737                return false;
738            }
739        }
740        return true;
741    }
742
743    /**
744     * Compares this {@code LinkProperties} MTU against the target
745     *
746     * @param target LinkProperties to compare.
747     * @return {@code true} if both are identical, {@code false} otherwise.
748     * @hide
749     */
750    public boolean isIdenticalMtu(LinkProperties target) {
751        return getMtu() == target.getMtu();
752    }
753
754    @Override
755    /**
756     * Compares this {@code LinkProperties} instance against the target
757     * LinkProperties in {@code obj}. Two LinkPropertieses are equal if
758     * all their fields are equal in values.
759     *
760     * For collection fields, such as mDnses, containsAll() is used to check
761     * if two collections contains the same elements, independent of order.
762     * There are two thoughts regarding containsAll()
763     * 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal.
764     * 2. Worst case performance is O(n^2).
765     *
766     * @param obj the object to be tested for equality.
767     * @return {@code true} if both objects are equal, {@code false} otherwise.
768     */
769    public boolean equals(Object obj) {
770        if (this == obj) return true;
771
772        if (!(obj instanceof LinkProperties)) return false;
773
774        LinkProperties target = (LinkProperties) obj;
775        /**
776         * This method does not check that stacked interfaces are equal, because
777         * stacked interfaces are not so much a property of the link as a
778         * description of connections between links.
779         */
780        return isIdenticalInterfaceName(target) &&
781                isIdenticalAddresses(target) &&
782                isIdenticalDnses(target) &&
783                isIdenticalRoutes(target) &&
784                isIdenticalHttpProxy(target) &&
785                isIdenticalStackedLinks(target) &&
786                isIdenticalMtu(target);
787    }
788
789    /**
790     * Compares the addresses in this LinkProperties with another
791     * LinkProperties, examining only addresses on the base link.
792     *
793     * @param target a LinkProperties with the new list of addresses
794     * @return the differences between the addresses.
795     * @hide
796     */
797    public CompareResult<LinkAddress> compareAddresses(LinkProperties target) {
798        /*
799         * Duplicate the LinkAddresses into removed, we will be removing
800         * address which are common between mLinkAddresses and target
801         * leaving the addresses that are different. And address which
802         * are in target but not in mLinkAddresses are placed in the
803         * addedAddresses.
804         */
805        CompareResult<LinkAddress> result = new CompareResult<LinkAddress>();
806        result.removed = new ArrayList<LinkAddress>(mLinkAddresses);
807        result.added.clear();
808        if (target != null) {
809            for (LinkAddress newAddress : target.getLinkAddresses()) {
810                if (! result.removed.remove(newAddress)) {
811                    result.added.add(newAddress);
812                }
813            }
814        }
815        return result;
816    }
817
818    /**
819     * Compares the DNS addresses in this LinkProperties with another
820     * LinkProperties, examining only DNS addresses on the base link.
821     *
822     * @param target a LinkProperties with the new list of dns addresses
823     * @return the differences between the DNS addresses.
824     * @hide
825     */
826    public CompareResult<InetAddress> compareDnses(LinkProperties target) {
827        /*
828         * Duplicate the InetAddresses into removed, we will be removing
829         * dns address which are common between mDnses and target
830         * leaving the addresses that are different. And dns address which
831         * are in target but not in mDnses are placed in the
832         * addedAddresses.
833         */
834        CompareResult<InetAddress> result = new CompareResult<InetAddress>();
835
836        result.removed = new ArrayList<InetAddress>(mDnses);
837        result.added.clear();
838        if (target != null) {
839            for (InetAddress newAddress : target.getDnsServers()) {
840                if (! result.removed.remove(newAddress)) {
841                    result.added.add(newAddress);
842                }
843            }
844        }
845        return result;
846    }
847
848    /**
849     * Compares all routes in this LinkProperties with another LinkProperties,
850     * examining both the the base link and all stacked links.
851     *
852     * @param target a LinkProperties with the new list of routes
853     * @return the differences between the routes.
854     * @hide
855     */
856    public CompareResult<RouteInfo> compareAllRoutes(LinkProperties target) {
857        /*
858         * Duplicate the RouteInfos into removed, we will be removing
859         * routes which are common between mRoutes and target
860         * leaving the routes that are different. And route address which
861         * are in target but not in mRoutes are placed in added.
862         */
863        CompareResult<RouteInfo> result = new CompareResult<RouteInfo>();
864
865        result.removed = getAllRoutes();
866        result.added.clear();
867        if (target != null) {
868            for (RouteInfo r : target.getAllRoutes()) {
869                if (! result.removed.remove(r)) {
870                    result.added.add(r);
871                }
872            }
873        }
874        return result;
875    }
876
877    /**
878     * Compares all interface names in this LinkProperties with another
879     * LinkProperties, examining both the the base link and all stacked links.
880     *
881     * @param target a LinkProperties with the new list of interface names
882     * @return the differences between the interface names.
883     * @hide
884     */
885    public CompareResult<String> compareAllInterfaceNames(LinkProperties target) {
886        /*
887         * Duplicate the interface names into removed, we will be removing
888         * interface names which are common between this and target
889         * leaving the interface names that are different. And interface names which
890         * are in target but not in this are placed in added.
891         */
892        CompareResult<String> result = new CompareResult<String>();
893
894        result.removed = getAllInterfaceNames();
895        result.added.clear();
896        if (target != null) {
897            for (String r : target.getAllInterfaceNames()) {
898                if (! result.removed.remove(r)) {
899                    result.added.add(r);
900                }
901            }
902        }
903        return result;
904    }
905
906
907    @Override
908    /**
909     * generate hashcode based on significant fields
910     * Equal objects must produce the same hash code, while unequal objects
911     * may have the same hash codes.
912     */
913    public int hashCode() {
914        return ((null == mIfaceName) ? 0 : mIfaceName.hashCode()
915                + mLinkAddresses.size() * 31
916                + mDnses.size() * 37
917                + ((null == mDomains) ? 0 : mDomains.hashCode())
918                + mRoutes.size() * 41
919                + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode())
920                + mStackedLinks.hashCode() * 47)
921                + mMtu * 51;
922    }
923
924    /**
925     * Implement the Parcelable interface.
926     */
927    public void writeToParcel(Parcel dest, int flags) {
928        dest.writeString(getInterfaceName());
929        dest.writeInt(mLinkAddresses.size());
930        for(LinkAddress linkAddress : mLinkAddresses) {
931            dest.writeParcelable(linkAddress, flags);
932        }
933
934        dest.writeInt(mDnses.size());
935        for(InetAddress d : mDnses) {
936            dest.writeByteArray(d.getAddress());
937        }
938        dest.writeString(mDomains);
939        dest.writeInt(mMtu);
940        dest.writeInt(mRoutes.size());
941        for(RouteInfo route : mRoutes) {
942            dest.writeParcelable(route, flags);
943        }
944
945        if (mHttpProxy != null) {
946            dest.writeByte((byte)1);
947            dest.writeParcelable(mHttpProxy, flags);
948        } else {
949            dest.writeByte((byte)0);
950        }
951        ArrayList<LinkProperties> stackedLinks = new ArrayList(mStackedLinks.values());
952        dest.writeList(stackedLinks);
953    }
954
955    /**
956     * Implement the Parcelable interface.
957     */
958    public static final Creator<LinkProperties> CREATOR =
959        new Creator<LinkProperties>() {
960            public LinkProperties createFromParcel(Parcel in) {
961                LinkProperties netProp = new LinkProperties();
962
963                String iface = in.readString();
964                if (iface != null) {
965                    netProp.setInterfaceName(iface);
966                }
967                int addressCount = in.readInt();
968                for (int i=0; i<addressCount; i++) {
969                    netProp.addLinkAddress((LinkAddress)in.readParcelable(null));
970                }
971                addressCount = in.readInt();
972                for (int i=0; i<addressCount; i++) {
973                    try {
974                        netProp.addDnsServer(InetAddress.getByAddress(in.createByteArray()));
975                    } catch (UnknownHostException e) { }
976                }
977                netProp.setDomains(in.readString());
978                netProp.setMtu(in.readInt());
979                addressCount = in.readInt();
980                for (int i=0; i<addressCount; i++) {
981                    netProp.addRoute((RouteInfo)in.readParcelable(null));
982                }
983                if (in.readByte() == 1) {
984                    netProp.setHttpProxy((ProxyInfo)in.readParcelable(null));
985                }
986                ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>();
987                in.readList(stackedLinks, LinkProperties.class.getClassLoader());
988                for (LinkProperties stackedLink: stackedLinks) {
989                    netProp.addStackedLink(stackedLink);
990                }
991                return netProp;
992            }
993
994            public LinkProperties[] newArray(int size) {
995                return new LinkProperties[size];
996            }
997        };
998}
999