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