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.net.ProxyProperties;
20import android.os.Parcelable;
21import android.os.Parcel;
22import android.text.TextUtils;
23
24import java.net.InetAddress;
25import java.net.Inet4Address;
26import java.net.Inet6Address;
27
28import java.net.UnknownHostException;
29import java.util.ArrayList;
30import java.util.Collection;
31import java.util.Collections;
32import java.util.Hashtable;
33
34/**
35 * Describes the properties of a network link.
36 *
37 * A link represents a connection to a network.
38 * It may have multiple addresses and multiple gateways,
39 * multiple dns servers but only one http proxy.
40 *
41 * Because it's a single network, the dns's
42 * are interchangeable and don't need associating with
43 * particular addresses.  The gateways similarly don't
44 * need associating with particular addresses.
45 *
46 * A dual stack interface works fine in this model:
47 * each address has it's own prefix length to describe
48 * the local network.  The dns servers all return
49 * both v4 addresses and v6 addresses regardless of the
50 * address family of the server itself (rfc4213) and we
51 * don't care which is used.  The gateways will be
52 * selected based on the destination address and the
53 * source address has no relavence.
54 *
55 * Links can also be stacked on top of each other.
56 * This can be used, for example, to represent a tunnel
57 * interface that runs on top of a physical interface.
58 *
59 * @hide
60 */
61public class LinkProperties implements Parcelable {
62    // The interface described by the network link.
63    private String mIfaceName;
64    private Collection<LinkAddress> mLinkAddresses = new ArrayList<LinkAddress>();
65    private Collection<InetAddress> mDnses = new ArrayList<InetAddress>();
66    private String mDomains;
67    private Collection<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
68    private ProxyProperties mHttpProxy;
69    private int mMtu;
70
71    // Stores the properties of links that are "stacked" above this link.
72    // Indexed by interface name to allow modification and to prevent duplicates being added.
73    private Hashtable<String, LinkProperties> mStackedLinks =
74        new Hashtable<String, LinkProperties>();
75
76    public static class CompareResult<T> {
77        public Collection<T> removed = new ArrayList<T>();
78        public Collection<T> added = new ArrayList<T>();
79
80        @Override
81        public String toString() {
82            String retVal = "removed=[";
83            for (T addr : removed) retVal += addr.toString() + ",";
84            retVal += "] added=[";
85            for (T addr : added) retVal += addr.toString() + ",";
86            retVal += "]";
87            return retVal;
88        }
89    }
90
91    public LinkProperties() {
92        clear();
93    }
94
95    // copy constructor instead of clone
96    public LinkProperties(LinkProperties source) {
97        if (source != null) {
98            mIfaceName = source.getInterfaceName();
99            for (LinkAddress l : source.getLinkAddresses()) mLinkAddresses.add(l);
100            for (InetAddress i : source.getDnses()) mDnses.add(i);
101            mDomains = source.getDomains();
102            for (RouteInfo r : source.getRoutes()) mRoutes.add(r);
103            mHttpProxy = (source.getHttpProxy() == null)  ?
104                    null : new ProxyProperties(source.getHttpProxy());
105            for (LinkProperties l: source.mStackedLinks.values()) {
106                addStackedLink(l);
107            }
108            setMtu(source.getMtu());
109        }
110    }
111
112    public void setInterfaceName(String iface) {
113        mIfaceName = iface;
114        ArrayList<RouteInfo> newRoutes = new ArrayList<RouteInfo>(mRoutes.size());
115        for (RouteInfo route : mRoutes) {
116            newRoutes.add(routeWithInterface(route));
117        }
118        mRoutes = newRoutes;
119    }
120
121    public String getInterfaceName() {
122        return mIfaceName;
123    }
124
125    public Collection<String> getAllInterfaceNames() {
126        Collection interfaceNames = new ArrayList<String>(mStackedLinks.size() + 1);
127        if (mIfaceName != null) interfaceNames.add(new String(mIfaceName));
128        for (LinkProperties stacked: mStackedLinks.values()) {
129            interfaceNames.addAll(stacked.getAllInterfaceNames());
130        }
131        return interfaceNames;
132    }
133
134    /**
135     * Returns all the addresses on this link.
136     */
137    public Collection<InetAddress> getAddresses() {
138        Collection<InetAddress> addresses = new ArrayList<InetAddress>();
139        for (LinkAddress linkAddress : mLinkAddresses) {
140            addresses.add(linkAddress.getAddress());
141        }
142        return Collections.unmodifiableCollection(addresses);
143    }
144
145    /**
146     * Returns all the addresses on this link and all the links stacked above it.
147     */
148    public Collection<InetAddress> getAllAddresses() {
149        Collection<InetAddress> addresses = new ArrayList<InetAddress>();
150        for (LinkAddress linkAddress : mLinkAddresses) {
151            addresses.add(linkAddress.getAddress());
152        }
153        for (LinkProperties stacked: mStackedLinks.values()) {
154            addresses.addAll(stacked.getAllAddresses());
155        }
156        return addresses;
157    }
158
159    /**
160     * Adds a link address if it does not exist, or update it if it does.
161     * @param address The {@code LinkAddress} to add.
162     * @return true if the address was added, false if it already existed.
163     */
164    public boolean addLinkAddress(LinkAddress address) {
165        // TODO: when the LinkAddress has other attributes beyond the
166        // address and the prefix length, update them here.
167        if (address != null && !mLinkAddresses.contains(address)) {
168            mLinkAddresses.add(address);
169            return true;
170        }
171        return false;
172    }
173
174    /**
175     * Removes a link address.
176     * @param address The {@code LinkAddress} to remove.
177     * @return true if the address was removed, false if it did not exist.
178     */
179    public boolean removeLinkAddress(LinkAddress toRemove) {
180        return mLinkAddresses.remove(toRemove);
181    }
182
183    /**
184     * Returns all the addresses on this link.
185     */
186    public Collection<LinkAddress> getLinkAddresses() {
187        return Collections.unmodifiableCollection(mLinkAddresses);
188    }
189
190    /**
191     * Returns all the addresses on this link and all the links stacked above it.
192     */
193    public Collection<LinkAddress> getAllLinkAddresses() {
194        Collection<LinkAddress> addresses = new ArrayList<LinkAddress>();
195        addresses.addAll(mLinkAddresses);
196        for (LinkProperties stacked: mStackedLinks.values()) {
197            addresses.addAll(stacked.getAllLinkAddresses());
198        }
199        return addresses;
200    }
201
202    /**
203     * Replaces the LinkAddresses on this link with the given collection of addresses.
204     */
205    public void setLinkAddresses(Collection<LinkAddress> addresses) {
206        mLinkAddresses.clear();
207        for (LinkAddress address: addresses) {
208            addLinkAddress(address);
209        }
210    }
211
212    public void addDns(InetAddress dns) {
213        if (dns != null) mDnses.add(dns);
214    }
215
216    public Collection<InetAddress> getDnses() {
217        return Collections.unmodifiableCollection(mDnses);
218    }
219
220    public String getDomains() {
221        return mDomains;
222    }
223
224    public void setDomains(String domains) {
225        mDomains = domains;
226    }
227
228    public void setMtu(int mtu) {
229        mMtu = mtu;
230    }
231
232    public int getMtu() {
233        return mMtu;
234    }
235
236    private RouteInfo routeWithInterface(RouteInfo route) {
237        return new RouteInfo(
238            route.getDestination(),
239            route.getGateway(),
240            mIfaceName);
241    }
242
243    public void addRoute(RouteInfo route) {
244        if (route != null) {
245            String routeIface = route.getInterface();
246            if (routeIface != null && !routeIface.equals(mIfaceName)) {
247                throw new IllegalArgumentException(
248                   "Route added with non-matching interface: " + routeIface +
249                   " vs. " + mIfaceName);
250            }
251            mRoutes.add(routeWithInterface(route));
252        }
253    }
254
255    /**
256     * Returns all the routes on this link.
257     */
258    public Collection<RouteInfo> getRoutes() {
259        return Collections.unmodifiableCollection(mRoutes);
260    }
261
262    /**
263     * Returns all the routes on this link and all the links stacked above it.
264     */
265    public Collection<RouteInfo> getAllRoutes() {
266        Collection<RouteInfo> routes = new ArrayList();
267        routes.addAll(mRoutes);
268        for (LinkProperties stacked: mStackedLinks.values()) {
269            routes.addAll(stacked.getAllRoutes());
270        }
271        return routes;
272    }
273
274    public void setHttpProxy(ProxyProperties proxy) {
275        mHttpProxy = proxy;
276    }
277    public ProxyProperties getHttpProxy() {
278        return mHttpProxy;
279    }
280
281    /**
282     * Adds a stacked link.
283     *
284     * If there is already a stacked link with the same interfacename as link,
285     * that link is replaced with link. Otherwise, link is added to the list
286     * of stacked links. If link is null, nothing changes.
287     *
288     * @param link The link to add.
289     * @return true if the link was stacked, false otherwise.
290     */
291    public boolean addStackedLink(LinkProperties link) {
292        if (link != null && link.getInterfaceName() != null) {
293            mStackedLinks.put(link.getInterfaceName(), link);
294            return true;
295        }
296        return false;
297    }
298
299    /**
300     * Removes a stacked link.
301     *
302     * If there a stacked link with the same interfacename as link, it is
303     * removed. Otherwise, nothing changes.
304     *
305     * @param link The link to remove.
306     * @return true if the link was removed, false otherwise.
307     */
308    public boolean removeStackedLink(LinkProperties link) {
309        if (link != null && link.getInterfaceName() != null) {
310            LinkProperties removed = mStackedLinks.remove(link.getInterfaceName());
311            return removed != null;
312        }
313        return false;
314    }
315
316    /**
317     * Returns all the links stacked on top of this link.
318     */
319    public Collection<LinkProperties> getStackedLinks() {
320        Collection<LinkProperties> stacked = new ArrayList<LinkProperties>();
321        for (LinkProperties link : mStackedLinks.values()) {
322          stacked.add(new LinkProperties(link));
323        }
324        return Collections.unmodifiableCollection(stacked);
325    }
326
327    public void clear() {
328        mIfaceName = null;
329        mLinkAddresses.clear();
330        mDnses.clear();
331        mDomains = null;
332        mRoutes.clear();
333        mHttpProxy = null;
334        mStackedLinks.clear();
335        mMtu = 0;
336    }
337
338    /**
339     * Implement the Parcelable interface
340     * @hide
341     */
342    public int describeContents() {
343        return 0;
344    }
345
346    @Override
347    public String toString() {
348        String ifaceName = (mIfaceName == null ? "" : "InterfaceName: " + mIfaceName + " ");
349
350        String linkAddresses = "LinkAddresses: [";
351        for (LinkAddress addr : mLinkAddresses) linkAddresses += addr.toString() + ",";
352        linkAddresses += "] ";
353
354        String dns = "DnsAddresses: [";
355        for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
356        dns += "] ";
357
358        String domainName = "Domains: " + mDomains;
359
360        String mtu = "MTU: " + mMtu;
361
362        String routes = " Routes: [";
363        for (RouteInfo route : mRoutes) routes += route.toString() + ",";
364        routes += "] ";
365        String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
366
367        String stacked = "";
368        if (mStackedLinks.values().size() > 0) {
369            stacked += " Stacked: [";
370            for (LinkProperties link: mStackedLinks.values()) {
371                stacked += " [" + link.toString() + " ],";
372            }
373            stacked += "] ";
374        }
375        return "{" + ifaceName + linkAddresses + routes + dns + domainName + mtu
376            + proxy + stacked + "}";
377    }
378
379    /**
380     * Returns true if this link has an IPv4 address.
381     *
382     * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
383     */
384    public boolean hasIPv4Address() {
385        for (LinkAddress address : mLinkAddresses) {
386          if (address.getAddress() instanceof Inet4Address) {
387            return true;
388          }
389        }
390        return false;
391    }
392
393    /**
394     * Returns true if this link has an IPv6 address.
395     *
396     * @return {@code true} if there is an IPv6 address, {@code false} otherwise.
397     */
398    public boolean hasIPv6Address() {
399        for (LinkAddress address : mLinkAddresses) {
400          if (address.getAddress() instanceof Inet6Address) {
401            return true;
402          }
403        }
404        return false;
405    }
406
407    /**
408     * Compares this {@code LinkProperties} interface name against the target
409     *
410     * @param target LinkProperties to compare.
411     * @return {@code true} if both are identical, {@code false} otherwise.
412     */
413    public boolean isIdenticalInterfaceName(LinkProperties target) {
414        return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
415    }
416
417    /**
418     * Compares this {@code LinkProperties} interface addresses against the target
419     *
420     * @param target LinkProperties to compare.
421     * @return {@code true} if both are identical, {@code false} otherwise.
422     */
423    public boolean isIdenticalAddresses(LinkProperties target) {
424        Collection<InetAddress> targetAddresses = target.getAddresses();
425        Collection<InetAddress> sourceAddresses = getAddresses();
426        return (sourceAddresses.size() == targetAddresses.size()) ?
427                    sourceAddresses.containsAll(targetAddresses) : false;
428    }
429
430    /**
431     * Compares this {@code LinkProperties} DNS addresses against the target
432     *
433     * @param target LinkProperties to compare.
434     * @return {@code true} if both are identical, {@code false} otherwise.
435     */
436    public boolean isIdenticalDnses(LinkProperties target) {
437        Collection<InetAddress> targetDnses = target.getDnses();
438        String targetDomains = target.getDomains();
439        if (mDomains == null) {
440            if (targetDomains != null) return false;
441        } else {
442            if (mDomains.equals(targetDomains) == false) return false;
443        }
444        return (mDnses.size() == targetDnses.size()) ?
445                    mDnses.containsAll(targetDnses) : false;
446    }
447
448    /**
449     * Compares this {@code LinkProperties} Routes against the target
450     *
451     * @param target LinkProperties to compare.
452     * @return {@code true} if both are identical, {@code false} otherwise.
453     */
454    public boolean isIdenticalRoutes(LinkProperties target) {
455        Collection<RouteInfo> targetRoutes = target.getRoutes();
456        return (mRoutes.size() == targetRoutes.size()) ?
457                    mRoutes.containsAll(targetRoutes) : false;
458    }
459
460    /**
461     * Compares this {@code LinkProperties} HttpProxy against the target
462     *
463     * @param target LinkProperties to compare.
464     * @return {@code true} if both are identical, {@code false} otherwise.
465     */
466    public boolean isIdenticalHttpProxy(LinkProperties target) {
467        return getHttpProxy() == null ? target.getHttpProxy() == null :
468                    getHttpProxy().equals(target.getHttpProxy());
469    }
470
471    /**
472     * Compares this {@code LinkProperties} stacked links against the target
473     *
474     * @param target LinkProperties to compare.
475     * @return {@code true} if both are identical, {@code false} otherwise.
476     */
477    public boolean isIdenticalStackedLinks(LinkProperties target) {
478        if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) {
479            return false;
480        }
481        for (LinkProperties stacked : mStackedLinks.values()) {
482            // Hashtable values can never be null.
483            String iface = stacked.getInterfaceName();
484            if (!stacked.equals(target.mStackedLinks.get(iface))) {
485                return false;
486            }
487        }
488        return true;
489    }
490
491    /**
492     * Compares this {@code LinkProperties} MTU against the target
493     *
494     * @param target LinkProperties to compare.
495     * @return {@code true} if both are identical, {@code false} otherwise.
496     */
497    public boolean isIdenticalMtu(LinkProperties target) {
498        return getMtu() == target.getMtu();
499    }
500
501    @Override
502    /**
503     * Compares this {@code LinkProperties} instance against the target
504     * LinkProperties in {@code obj}. Two LinkPropertieses are equal if
505     * all their fields are equal in values.
506     *
507     * For collection fields, such as mDnses, containsAll() is used to check
508     * if two collections contains the same elements, independent of order.
509     * There are two thoughts regarding containsAll()
510     * 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal.
511     * 2. Worst case performance is O(n^2).
512     *
513     * This method does not check that stacked interfaces are equal, because
514     * stacked interfaces are not so much a property of the link as a
515     * description of connections between links.
516     *
517     * @param obj the object to be tested for equality.
518     * @return {@code true} if both objects are equal, {@code false} otherwise.
519     */
520    public boolean equals(Object obj) {
521        if (this == obj) return true;
522
523        if (!(obj instanceof LinkProperties)) return false;
524
525        LinkProperties target = (LinkProperties) obj;
526
527        return isIdenticalInterfaceName(target) &&
528                isIdenticalAddresses(target) &&
529                isIdenticalDnses(target) &&
530                isIdenticalRoutes(target) &&
531                isIdenticalHttpProxy(target) &&
532                isIdenticalStackedLinks(target) &&
533                isIdenticalMtu(target);
534    }
535
536    /**
537     * Compares the addresses in this LinkProperties with another
538     * LinkProperties, examining only addresses on the base link.
539     *
540     * @param target a LinkProperties with the new list of addresses
541     * @return the differences between the addresses.
542     */
543    public CompareResult<LinkAddress> compareAddresses(LinkProperties target) {
544        /*
545         * Duplicate the LinkAddresses into removed, we will be removing
546         * address which are common between mLinkAddresses and target
547         * leaving the addresses that are different. And address which
548         * are in target but not in mLinkAddresses are placed in the
549         * addedAddresses.
550         */
551        CompareResult<LinkAddress> result = new CompareResult<LinkAddress>();
552        result.removed = new ArrayList<LinkAddress>(mLinkAddresses);
553        result.added.clear();
554        if (target != null) {
555            for (LinkAddress newAddress : target.getLinkAddresses()) {
556                if (! result.removed.remove(newAddress)) {
557                    result.added.add(newAddress);
558                }
559            }
560        }
561        return result;
562    }
563
564    /**
565     * Compares the DNS addresses in this LinkProperties with another
566     * LinkProperties, examining only DNS addresses on the base link.
567     *
568     * @param target a LinkProperties with the new list of dns addresses
569     * @return the differences between the DNS addresses.
570     */
571    public CompareResult<InetAddress> compareDnses(LinkProperties target) {
572        /*
573         * Duplicate the InetAddresses into removed, we will be removing
574         * dns address which are common between mDnses and target
575         * leaving the addresses that are different. And dns address which
576         * are in target but not in mDnses are placed in the
577         * addedAddresses.
578         */
579        CompareResult<InetAddress> result = new CompareResult<InetAddress>();
580
581        result.removed = new ArrayList<InetAddress>(mDnses);
582        result.added.clear();
583        if (target != null) {
584            for (InetAddress newAddress : target.getDnses()) {
585                if (! result.removed.remove(newAddress)) {
586                    result.added.add(newAddress);
587                }
588            }
589        }
590        return result;
591    }
592
593    /**
594     * Compares all routes in this LinkProperties with another LinkProperties,
595     * examining both the the base link and all stacked links.
596     *
597     * @param target a LinkProperties with the new list of routes
598     * @return the differences between the routes.
599     */
600    public CompareResult<RouteInfo> compareAllRoutes(LinkProperties target) {
601        /*
602         * Duplicate the RouteInfos into removed, we will be removing
603         * routes which are common between mRoutes and target
604         * leaving the routes that are different. And route address which
605         * are in target but not in mRoutes are placed in added.
606         */
607        CompareResult<RouteInfo> result = new CompareResult<RouteInfo>();
608
609        result.removed = getAllRoutes();
610        result.added.clear();
611        if (target != null) {
612            for (RouteInfo r : target.getAllRoutes()) {
613                if (! result.removed.remove(r)) {
614                    result.added.add(r);
615                }
616            }
617        }
618        return result;
619    }
620
621
622    @Override
623    /**
624     * generate hashcode based on significant fields
625     * Equal objects must produce the same hash code, while unequal objects
626     * may have the same hash codes.
627     */
628    public int hashCode() {
629        return ((null == mIfaceName) ? 0 : mIfaceName.hashCode()
630                + mLinkAddresses.size() * 31
631                + mDnses.size() * 37
632                + ((null == mDomains) ? 0 : mDomains.hashCode())
633                + mRoutes.size() * 41
634                + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode())
635                + mStackedLinks.hashCode() * 47)
636                + mMtu * 51;
637    }
638
639    /**
640     * Implement the Parcelable interface.
641     */
642    public void writeToParcel(Parcel dest, int flags) {
643        dest.writeString(getInterfaceName());
644        dest.writeInt(mLinkAddresses.size());
645        for(LinkAddress linkAddress : mLinkAddresses) {
646            dest.writeParcelable(linkAddress, flags);
647        }
648
649        dest.writeInt(mDnses.size());
650        for(InetAddress d : mDnses) {
651            dest.writeByteArray(d.getAddress());
652        }
653        dest.writeString(mDomains);
654        dest.writeInt(mMtu);
655        dest.writeInt(mRoutes.size());
656        for(RouteInfo route : mRoutes) {
657            dest.writeParcelable(route, flags);
658        }
659
660        if (mHttpProxy != null) {
661            dest.writeByte((byte)1);
662            dest.writeParcelable(mHttpProxy, flags);
663        } else {
664            dest.writeByte((byte)0);
665        }
666        ArrayList<LinkProperties> stackedLinks = new ArrayList(mStackedLinks.values());
667        dest.writeList(stackedLinks);
668    }
669
670    /**
671     * Implement the Parcelable interface.
672     */
673    public static final Creator<LinkProperties> CREATOR =
674        new Creator<LinkProperties>() {
675            public LinkProperties createFromParcel(Parcel in) {
676                LinkProperties netProp = new LinkProperties();
677
678                String iface = in.readString();
679                if (iface != null) {
680                    netProp.setInterfaceName(iface);
681                }
682                int addressCount = in.readInt();
683                for (int i=0; i<addressCount; i++) {
684                    netProp.addLinkAddress((LinkAddress)in.readParcelable(null));
685                }
686                addressCount = in.readInt();
687                for (int i=0; i<addressCount; i++) {
688                    try {
689                        netProp.addDns(InetAddress.getByAddress(in.createByteArray()));
690                    } catch (UnknownHostException e) { }
691                }
692                netProp.setDomains(in.readString());
693                netProp.setMtu(in.readInt());
694                addressCount = in.readInt();
695                for (int i=0; i<addressCount; i++) {
696                    netProp.addRoute((RouteInfo)in.readParcelable(null));
697                }
698                if (in.readByte() == 1) {
699                    netProp.setHttpProxy((ProxyProperties)in.readParcelable(null));
700                }
701                ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>();
702                in.readList(stackedLinks, LinkProperties.class.getClassLoader());
703                for (LinkProperties stackedLink: stackedLinks) {
704                    netProp.addStackedLink(stackedLink);
705                }
706                return netProp;
707            }
708
709            public LinkProperties[] newArray(int size) {
710                return new LinkProperties[size];
711            }
712        };
713}
714