LinkProperties.java revision 3f05bf4d7838cb719e78f9d93b22d7ce777392c5
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    }
386
387    /**
388     * Adds a {@link RouteInfo} to this {@code LinkProperties}, if not present. If the
389     * {@link RouteInfo} had an interface name set and that differs from the interface set for this
390     * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown.  The proper
391     * course is to add either un-named or properly named {@link RouteInfo}.
392     *
393     * @param route A {@link RouteInfo} to add to this object.
394     * @return {@code false} if the route was already present, {@code true} if it was added.
395     *
396     * @hide
397     */
398    public boolean addRoute(RouteInfo route) {
399        if (route != null) {
400            String routeIface = route.getInterface();
401            if (routeIface != null && !routeIface.equals(mIfaceName)) {
402                throw new IllegalArgumentException(
403                   "Route added with non-matching interface: " + routeIface +
404                   " vs. " + mIfaceName);
405            }
406            route = routeWithInterface(route);
407            if (!mRoutes.contains(route)) {
408                mRoutes.add(route);
409                return true;
410            }
411        }
412        return false;
413    }
414
415    /**
416     * Removes a {@link RouteInfo} from this {@code LinkProperties}, if present. The route must
417     * specify an interface and the interface must match the interface of this
418     * {@code LinkProperties}, or it will not be removed.
419     *
420     * @return {@code true} if the route was removed, {@code false} if it was not present.
421     *
422     * @hide
423     */
424    public boolean removeRoute(RouteInfo route) {
425        return route != null &&
426                Objects.equals(mIfaceName, route.getInterface()) &&
427                mRoutes.remove(route);
428    }
429
430    /**
431     * Returns all the {@link RouteInfo} set on this link.
432     *
433     * @return An unmodifiable {@link List} of {@link RouteInfo} for this link.
434     */
435    public List<RouteInfo> getRoutes() {
436        return Collections.unmodifiableList(mRoutes);
437    }
438
439    /**
440     * Returns all the routes on this link and all the links stacked above it.
441     * @hide
442     */
443    public List<RouteInfo> getAllRoutes() {
444        List<RouteInfo> routes = new ArrayList();
445        routes.addAll(mRoutes);
446        for (LinkProperties stacked: mStackedLinks.values()) {
447            routes.addAll(stacked.getAllRoutes());
448        }
449        return routes;
450    }
451
452    /**
453     * Sets the recommended {@link ProxyInfo} to use on this link, or {@code null} for none.
454     * Note that Http Proxies are only a hint - the system recommends their use, but it does
455     * not enforce it and applications may ignore them.
456     *
457     * @param proxy A {@link ProxyInfo} defining the Http Proxy to use on this link.
458     * @hide
459     */
460    public void setHttpProxy(ProxyInfo proxy) {
461        mHttpProxy = proxy;
462    }
463
464    /**
465     * Gets the recommended {@link ProxyInfo} (or {@code null}) set on this link.
466     *
467     * @return The {@link ProxyInfo} set on this link
468     */
469    public ProxyInfo getHttpProxy() {
470        return mHttpProxy;
471    }
472
473    /**
474     * Adds a stacked link.
475     *
476     * If there is already a stacked link with the same interfacename as link,
477     * that link is replaced with link. Otherwise, link is added to the list
478     * of stacked links. If link is null, nothing changes.
479     *
480     * @param link The link to add.
481     * @return true if the link was stacked, false otherwise.
482     * @hide
483     */
484    public boolean addStackedLink(LinkProperties link) {
485        if (link != null && link.getInterfaceName() != null) {
486            mStackedLinks.put(link.getInterfaceName(), link);
487            return true;
488        }
489        return false;
490    }
491
492    /**
493     * Removes a stacked link.
494     *
495     * If there a stacked link with the same interfacename as link, it is
496     * removed. Otherwise, nothing changes.
497     *
498     * @param link The link to remove.
499     * @return true if the link was removed, false otherwise.
500     * @hide
501     */
502    public boolean removeStackedLink(LinkProperties link) {
503        if (link != null && link.getInterfaceName() != null) {
504            LinkProperties removed = mStackedLinks.remove(link.getInterfaceName());
505            return removed != null;
506        }
507        return false;
508    }
509
510    /**
511     * Returns all the links stacked on top of this link.
512     * @hide
513     */
514    public @NonNull List<LinkProperties> getStackedLinks() {
515        if (mStackedLinks.isEmpty()) {
516            return Collections.EMPTY_LIST;
517        }
518        List<LinkProperties> stacked = new ArrayList<LinkProperties>();
519        for (LinkProperties link : mStackedLinks.values()) {
520            stacked.add(new LinkProperties(link));
521        }
522        return Collections.unmodifiableList(stacked);
523    }
524
525    /**
526     * Clears this object to its initial state.
527     * @hide
528     */
529    public void clear() {
530        mIfaceName = null;
531        mLinkAddresses.clear();
532        mDnses.clear();
533        mDomains = null;
534        mRoutes.clear();
535        mHttpProxy = null;
536        mStackedLinks.clear();
537        mMtu = 0;
538        mTcpBufferSizes = null;
539    }
540
541    /**
542     * Implement the Parcelable interface
543     */
544    public int describeContents() {
545        return 0;
546    }
547
548    @Override
549    public String toString() {
550        String ifaceName = (mIfaceName == null ? "" : "InterfaceName: " + mIfaceName + " ");
551
552        String linkAddresses = "LinkAddresses: [";
553        for (LinkAddress addr : mLinkAddresses) linkAddresses += addr.toString() + ",";
554        linkAddresses += "] ";
555
556        String dns = "DnsAddresses: [";
557        for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
558        dns += "] ";
559
560        String domainName = "Domains: " + mDomains;
561
562        String mtu = " MTU: " + mMtu;
563
564        String tcpBuffSizes = "";
565        if (mTcpBufferSizes != null) {
566            tcpBuffSizes = " TcpBufferSizes: " + mTcpBufferSizes;
567        }
568
569        String routes = " Routes: [";
570        for (RouteInfo route : mRoutes) routes += route.toString() + ",";
571        routes += "] ";
572        String proxy = (mHttpProxy == null ? "" : " HttpProxy: " + mHttpProxy.toString() + " ");
573
574        String stacked = "";
575        if (mStackedLinks.values().size() > 0) {
576            stacked += " Stacked: [";
577            for (LinkProperties link: mStackedLinks.values()) {
578                stacked += " [" + link.toString() + " ],";
579            }
580            stacked += "] ";
581        }
582        return "{" + ifaceName + linkAddresses + routes + dns + domainName + mtu
583            + tcpBuffSizes + proxy + stacked + "}";
584    }
585
586    /**
587     * Returns true if this link has an IPv4 address.
588     *
589     * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
590     * @hide
591     */
592    public boolean hasIPv4Address() {
593        for (LinkAddress address : mLinkAddresses) {
594          if (address.getAddress() instanceof Inet4Address) {
595            return true;
596          }
597        }
598        return false;
599    }
600
601    /**
602     * Returns true if this link has a global preferred IPv6 address.
603     *
604     * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
605     * @hide
606     */
607    public boolean hasGlobalIPv6Address() {
608        for (LinkAddress address : mLinkAddresses) {
609          if (address.getAddress() instanceof Inet6Address && address.isGlobalPreferred()) {
610            return true;
611          }
612        }
613        return false;
614    }
615
616    /**
617     * Returns true if this link has an IPv4 default route.
618     *
619     * @return {@code true} if there is an IPv4 default route, {@code false} otherwise.
620     * @hide
621     */
622    public boolean hasIPv4DefaultRoute() {
623        for (RouteInfo r : mRoutes) {
624          if (r.isIPv4Default()) {
625            return true;
626          }
627        }
628        return false;
629    }
630
631    /**
632     * Returns true if this link has an IPv6 default route.
633     *
634     * @return {@code true} if there is an IPv6 default route, {@code false} otherwise.
635     * @hide
636     */
637    public boolean hasIPv6DefaultRoute() {
638        for (RouteInfo r : mRoutes) {
639          if (r.isIPv6Default()) {
640            return true;
641          }
642        }
643        return false;
644    }
645
646    /**
647     * Returns true if this link has an IPv4 DNS server.
648     *
649     * @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise.
650     * @hide
651     */
652    public boolean hasIPv4DnsServer() {
653        for (InetAddress ia : mDnses) {
654          if (ia instanceof Inet4Address) {
655            return true;
656          }
657        }
658        return false;
659    }
660
661    /**
662     * Returns true if this link has an IPv6 DNS server.
663     *
664     * @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise.
665     * @hide
666     */
667    public boolean hasIPv6DnsServer() {
668        for (InetAddress ia : mDnses) {
669          if (ia instanceof Inet6Address) {
670            return true;
671          }
672        }
673        return false;
674    }
675
676    /**
677     * Returns true if this link is provisioned for global connectivity. For IPv6, this requires an
678     * IP address, default route, and DNS server. For IPv4, this requires only an IPv4 address,
679     * because WifiStateMachine accepts static configurations that only specify an address but not
680     * DNS servers or a default route.
681     *
682     * @return {@code true} if the link is provisioned, {@code false} otherwise.
683     * @hide
684     */
685    public boolean isProvisioned() {
686        return (hasIPv4Address() ||
687                (hasGlobalIPv6Address() && hasIPv6DefaultRoute() && hasIPv6DnsServer()));
688    }
689
690    /**
691     * Compares this {@code LinkProperties} interface name against the target
692     *
693     * @param target LinkProperties to compare.
694     * @return {@code true} if both are identical, {@code false} otherwise.
695     * @hide
696     */
697    public boolean isIdenticalInterfaceName(LinkProperties target) {
698        return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
699    }
700
701    /**
702     * Compares this {@code LinkProperties} interface addresses against the target
703     *
704     * @param target LinkProperties to compare.
705     * @return {@code true} if both are identical, {@code false} otherwise.
706     * @hide
707     */
708    public boolean isIdenticalAddresses(LinkProperties target) {
709        Collection<InetAddress> targetAddresses = target.getAddresses();
710        Collection<InetAddress> sourceAddresses = getAddresses();
711        return (sourceAddresses.size() == targetAddresses.size()) ?
712                    sourceAddresses.containsAll(targetAddresses) : false;
713    }
714
715    /**
716     * Compares this {@code LinkProperties} DNS addresses against the target
717     *
718     * @param target LinkProperties to compare.
719     * @return {@code true} if both are identical, {@code false} otherwise.
720     * @hide
721     */
722    public boolean isIdenticalDnses(LinkProperties target) {
723        Collection<InetAddress> targetDnses = target.getDnsServers();
724        String targetDomains = target.getDomains();
725        if (mDomains == null) {
726            if (targetDomains != null) return false;
727        } else {
728            if (mDomains.equals(targetDomains) == false) return false;
729        }
730        return (mDnses.size() == targetDnses.size()) ?
731                    mDnses.containsAll(targetDnses) : false;
732    }
733
734    /**
735     * Compares this {@code LinkProperties} Routes against the target
736     *
737     * @param target LinkProperties to compare.
738     * @return {@code true} if both are identical, {@code false} otherwise.
739     * @hide
740     */
741    public boolean isIdenticalRoutes(LinkProperties target) {
742        Collection<RouteInfo> targetRoutes = target.getRoutes();
743        return (mRoutes.size() == targetRoutes.size()) ?
744                    mRoutes.containsAll(targetRoutes) : false;
745    }
746
747    /**
748     * Compares this {@code LinkProperties} HttpProxy against the target
749     *
750     * @param target LinkProperties to compare.
751     * @return {@code true} if both are identical, {@code false} otherwise.
752     * @hide
753     */
754    public boolean isIdenticalHttpProxy(LinkProperties target) {
755        return getHttpProxy() == null ? target.getHttpProxy() == null :
756                    getHttpProxy().equals(target.getHttpProxy());
757    }
758
759    /**
760     * Compares this {@code LinkProperties} stacked links against the target
761     *
762     * @param target LinkProperties to compare.
763     * @return {@code true} if both are identical, {@code false} otherwise.
764     * @hide
765     */
766    public boolean isIdenticalStackedLinks(LinkProperties target) {
767        if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) {
768            return false;
769        }
770        for (LinkProperties stacked : mStackedLinks.values()) {
771            // Hashtable values can never be null.
772            String iface = stacked.getInterfaceName();
773            if (!stacked.equals(target.mStackedLinks.get(iface))) {
774                return false;
775            }
776        }
777        return true;
778    }
779
780    /**
781     * Compares this {@code LinkProperties} MTU against the target
782     *
783     * @param target LinkProperties to compare.
784     * @return {@code true} if both are identical, {@code false} otherwise.
785     * @hide
786     */
787    public boolean isIdenticalMtu(LinkProperties target) {
788        return getMtu() == target.getMtu();
789    }
790
791    /**
792     * Compares this {@code LinkProperties} Tcp buffer sizes against the target.
793     *
794     * @param target LinkProperties to compare.
795     * @return {@code true} if both are identical, {@code false} otherwise.
796     * @hide
797     */
798    public boolean isIdenticalTcpBufferSizes(LinkProperties target) {
799        return Objects.equals(mTcpBufferSizes, target.mTcpBufferSizes);
800    }
801
802    @Override
803    /**
804     * Compares this {@code LinkProperties} instance against the target
805     * LinkProperties in {@code obj}. Two LinkPropertieses are equal if
806     * all their fields are equal in values.
807     *
808     * For collection fields, such as mDnses, containsAll() is used to check
809     * if two collections contains the same elements, independent of order.
810     * There are two thoughts regarding containsAll()
811     * 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal.
812     * 2. Worst case performance is O(n^2).
813     *
814     * @param obj the object to be tested for equality.
815     * @return {@code true} if both objects are equal, {@code false} otherwise.
816     */
817    public boolean equals(Object obj) {
818        if (this == obj) return true;
819
820        if (!(obj instanceof LinkProperties)) return false;
821
822        LinkProperties target = (LinkProperties) obj;
823        /**
824         * This method does not check that stacked interfaces are equal, because
825         * stacked interfaces are not so much a property of the link as a
826         * description of connections between links.
827         */
828        return isIdenticalInterfaceName(target) &&
829                isIdenticalAddresses(target) &&
830                isIdenticalDnses(target) &&
831                isIdenticalRoutes(target) &&
832                isIdenticalHttpProxy(target) &&
833                isIdenticalStackedLinks(target) &&
834                isIdenticalMtu(target) &&
835                isIdenticalTcpBufferSizes(target);
836    }
837
838    /**
839     * Compares the addresses in this LinkProperties with another
840     * LinkProperties, examining only addresses on the base link.
841     *
842     * @param target a LinkProperties with the new list of addresses
843     * @return the differences between the addresses.
844     * @hide
845     */
846    public CompareResult<LinkAddress> compareAddresses(LinkProperties target) {
847        /*
848         * Duplicate the LinkAddresses into removed, we will be removing
849         * address which are common between mLinkAddresses and target
850         * leaving the addresses that are different. And address which
851         * are in target but not in mLinkAddresses are placed in the
852         * addedAddresses.
853         */
854        CompareResult<LinkAddress> result = new CompareResult<LinkAddress>();
855        result.removed = new ArrayList<LinkAddress>(mLinkAddresses);
856        result.added.clear();
857        if (target != null) {
858            for (LinkAddress newAddress : target.getLinkAddresses()) {
859                if (! result.removed.remove(newAddress)) {
860                    result.added.add(newAddress);
861                }
862            }
863        }
864        return result;
865    }
866
867    /**
868     * Compares the DNS addresses in this LinkProperties with another
869     * LinkProperties, examining only DNS addresses on the base link.
870     *
871     * @param target a LinkProperties with the new list of dns addresses
872     * @return the differences between the DNS addresses.
873     * @hide
874     */
875    public CompareResult<InetAddress> compareDnses(LinkProperties target) {
876        /*
877         * Duplicate the InetAddresses into removed, we will be removing
878         * dns address which are common between mDnses and target
879         * leaving the addresses that are different. And dns address which
880         * are in target but not in mDnses are placed in the
881         * addedAddresses.
882         */
883        CompareResult<InetAddress> result = new CompareResult<InetAddress>();
884
885        result.removed = new ArrayList<InetAddress>(mDnses);
886        result.added.clear();
887        if (target != null) {
888            for (InetAddress newAddress : target.getDnsServers()) {
889                if (! result.removed.remove(newAddress)) {
890                    result.added.add(newAddress);
891                }
892            }
893        }
894        return result;
895    }
896
897    /**
898     * Compares all routes in this LinkProperties with another LinkProperties,
899     * examining both the the base link and all stacked links.
900     *
901     * @param target a LinkProperties with the new list of routes
902     * @return the differences between the routes.
903     * @hide
904     */
905    public CompareResult<RouteInfo> compareAllRoutes(LinkProperties target) {
906        /*
907         * Duplicate the RouteInfos into removed, we will be removing
908         * routes which are common between mRoutes and target
909         * leaving the routes that are different. And route address which
910         * are in target but not in mRoutes are placed in added.
911         */
912        CompareResult<RouteInfo> result = new CompareResult<RouteInfo>();
913
914        result.removed = getAllRoutes();
915        result.added.clear();
916        if (target != null) {
917            for (RouteInfo r : target.getAllRoutes()) {
918                if (! result.removed.remove(r)) {
919                    result.added.add(r);
920                }
921            }
922        }
923        return result;
924    }
925
926    /**
927     * Compares all interface names in this LinkProperties with another
928     * LinkProperties, examining both the the base link and all stacked links.
929     *
930     * @param target a LinkProperties with the new list of interface names
931     * @return the differences between the interface names.
932     * @hide
933     */
934    public CompareResult<String> compareAllInterfaceNames(LinkProperties target) {
935        /*
936         * Duplicate the interface names into removed, we will be removing
937         * interface names which are common between this and target
938         * leaving the interface names that are different. And interface names which
939         * are in target but not in this are placed in added.
940         */
941        CompareResult<String> result = new CompareResult<String>();
942
943        result.removed = getAllInterfaceNames();
944        result.added.clear();
945        if (target != null) {
946            for (String r : target.getAllInterfaceNames()) {
947                if (! result.removed.remove(r)) {
948                    result.added.add(r);
949                }
950            }
951        }
952        return result;
953    }
954
955
956    @Override
957    /**
958     * generate hashcode based on significant fields
959     * Equal objects must produce the same hash code, while unequal objects
960     * may have the same hash codes.
961     */
962    public int hashCode() {
963        return ((null == mIfaceName) ? 0 : mIfaceName.hashCode()
964                + mLinkAddresses.size() * 31
965                + mDnses.size() * 37
966                + ((null == mDomains) ? 0 : mDomains.hashCode())
967                + mRoutes.size() * 41
968                + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode())
969                + mStackedLinks.hashCode() * 47)
970                + mMtu * 51
971                + ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode());
972    }
973
974    /**
975     * Implement the Parcelable interface.
976     */
977    public void writeToParcel(Parcel dest, int flags) {
978        dest.writeString(getInterfaceName());
979        dest.writeInt(mLinkAddresses.size());
980        for(LinkAddress linkAddress : mLinkAddresses) {
981            dest.writeParcelable(linkAddress, flags);
982        }
983
984        dest.writeInt(mDnses.size());
985        for(InetAddress d : mDnses) {
986            dest.writeByteArray(d.getAddress());
987        }
988        dest.writeString(mDomains);
989        dest.writeInt(mMtu);
990        dest.writeString(mTcpBufferSizes);
991        dest.writeInt(mRoutes.size());
992        for(RouteInfo route : mRoutes) {
993            dest.writeParcelable(route, flags);
994        }
995
996        if (mHttpProxy != null) {
997            dest.writeByte((byte)1);
998            dest.writeParcelable(mHttpProxy, flags);
999        } else {
1000            dest.writeByte((byte)0);
1001        }
1002        ArrayList<LinkProperties> stackedLinks = new ArrayList(mStackedLinks.values());
1003        dest.writeList(stackedLinks);
1004    }
1005
1006    /**
1007     * Implement the Parcelable interface.
1008     */
1009    public static final Creator<LinkProperties> CREATOR =
1010        new Creator<LinkProperties>() {
1011            public LinkProperties createFromParcel(Parcel in) {
1012                LinkProperties netProp = new LinkProperties();
1013
1014                String iface = in.readString();
1015                if (iface != null) {
1016                    netProp.setInterfaceName(iface);
1017                }
1018                int addressCount = in.readInt();
1019                for (int i=0; i<addressCount; i++) {
1020                    netProp.addLinkAddress((LinkAddress)in.readParcelable(null));
1021                }
1022                addressCount = in.readInt();
1023                for (int i=0; i<addressCount; i++) {
1024                    try {
1025                        netProp.addDnsServer(InetAddress.getByAddress(in.createByteArray()));
1026                    } catch (UnknownHostException e) { }
1027                }
1028                netProp.setDomains(in.readString());
1029                netProp.setMtu(in.readInt());
1030                netProp.setTcpBufferSizes(in.readString());
1031                addressCount = in.readInt();
1032                for (int i=0; i<addressCount; i++) {
1033                    netProp.addRoute((RouteInfo)in.readParcelable(null));
1034                }
1035                if (in.readByte() == 1) {
1036                    netProp.setHttpProxy((ProxyInfo)in.readParcelable(null));
1037                }
1038                ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>();
1039                in.readList(stackedLinks, LinkProperties.class.getClassLoader());
1040                for (LinkProperties stackedLink: stackedLinks) {
1041                    netProp.addStackedLink(stackedLink);
1042                }
1043                return netProp;
1044            }
1045
1046            public LinkProperties[] newArray(int size) {
1047                return new LinkProperties[size];
1048            }
1049        };
1050
1051        /**
1052         * Check the valid MTU range based on IPv4 or IPv6.
1053         * @hide
1054         */
1055        public static boolean isValidMtu(int mtu, boolean ipv6) {
1056            if (ipv6) {
1057                if ((mtu >= MIN_MTU_V6 && mtu <= MAX_MTU)) return true;
1058            } else {
1059                if ((mtu >= MIN_MTU && mtu <= MAX_MTU)) return true;
1060            }
1061            return false;
1062        }
1063}
1064