1 /*
2  * Copyright (C) 2019 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 
17 package android.net.util;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.net.LinkAddress;
22 import android.net.LinkProperties;
23 import android.net.RouteInfo;
24 import android.text.TextUtils;
25 
26 import java.net.InetAddress;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Objects;
32 import java.util.function.Function;
33 
34 /**
35  * Collection of link properties utilities.
36  * @hide
37  */
38 public final class LinkPropertiesUtils {
39 
40     /**
41      * @param <T> The type of data to compare.
42      */
43     public static class CompareResult<T> {
44         public final List<T> removed = new ArrayList<>();
45         public final List<T> added = new ArrayList<>();
46 
CompareResult()47         public CompareResult() {}
48 
CompareResult(@ullable Collection<T> oldItems, @Nullable Collection<T> newItems)49         public CompareResult(@Nullable Collection<T> oldItems, @Nullable Collection<T> newItems) {
50             if (oldItems != null) {
51                 removed.addAll(oldItems);
52             }
53             if (newItems != null) {
54                 for (T newItem : newItems) {
55                     if (!removed.remove(newItem)) {
56                         added.add(newItem);
57                     }
58                 }
59             }
60         }
61 
62         @Override
toString()63         public String toString() {
64             return "removed=[" + TextUtils.join(",", removed)
65                     + "] added=[" + TextUtils.join(",", added)
66                     + "]";
67         }
68     }
69 
70     /**
71      * Generic class to compare two lists of items of type {@code T} whose properties can change.
72      * The items to be compared must provide a way to calculate a corresponding key of type
73      * {@code K} such that if (and only if) an old and a new item have the same key, then the new
74      * item is an update of the old item. Both the old list and the new list may not contain more
75      * than one item with the same key, and may not contain any null items.
76      *
77      * @param <K> A class that represents the key of the items to be compared.
78      * @param <T> The class that represents the object to be compared.
79      */
80     public static class CompareOrUpdateResult<K, T> {
81         public final List<T> added = new ArrayList<>();
82         public final List<T> removed = new ArrayList<>();
83         public final List<T> updated = new ArrayList<>();
84 
85         /**
86          * Compares two lists of items.
87          * @param oldItems the old list of items.
88          * @param newItems the new list of items.
89          * @param keyCalculator a {@link Function} that calculates an item's key.
90          */
CompareOrUpdateResult(Collection<T> oldItems, Collection<T> newItems, Function<T, K> keyCalculator)91         public CompareOrUpdateResult(Collection<T> oldItems, Collection<T> newItems,
92                 Function<T, K> keyCalculator) {
93             HashMap<K, T> updateTracker = new HashMap<>();
94 
95             if (oldItems != null) {
96                 for (T oldItem : oldItems) {
97                     updateTracker.put(keyCalculator.apply(oldItem), oldItem);
98                 }
99             }
100 
101             if (newItems != null) {
102                 for (T newItem : newItems) {
103                     T oldItem = updateTracker.remove(keyCalculator.apply(newItem));
104                     if (oldItem != null) {
105                         if (!oldItem.equals(newItem)) {
106                             // Update of existing item.
107                             updated.add(newItem);
108                         }
109                     } else {
110                         // New item.
111                         added.add(newItem);
112                     }
113                 }
114             }
115 
116             removed.addAll(updateTracker.values());
117         }
118 
119         @Override
toString()120         public String toString() {
121             return "removed=[" + TextUtils.join(",", removed)
122                     + "] added=[" + TextUtils.join(",", added)
123                     + "] updated=[" + TextUtils.join(",", updated)
124                     + "]";
125         }
126     }
127 
128     /**
129      * Compares the addresses in {@code left} LinkProperties with {@code right}
130      * LinkProperties, examining only addresses on the base link.
131      *
132      * @param left A LinkProperties with the old list of addresses.
133      * @param right A LinkProperties with the new list of addresses.
134      * @return the differences between the addresses.
135      */
compareAddresses( @ullable LinkProperties left, @Nullable LinkProperties right)136     public static @NonNull CompareResult<LinkAddress> compareAddresses(
137             @Nullable LinkProperties left, @Nullable LinkProperties right) {
138         /*
139          * Duplicate the LinkAddresses into removed, we will be removing
140          * address which are common between mLinkAddresses and target
141          * leaving the addresses that are different. And address which
142          * are in target but not in mLinkAddresses are placed in the
143          * addedAddresses.
144          */
145         return new CompareResult<>(left != null ? left.getLinkAddresses() : null,
146                 right != null ? right.getLinkAddresses() : null);
147     }
148 
149    /**
150      * Compares {@code left} {@code LinkProperties} interface addresses against the {@code right}.
151      *
152      * @param left A LinkProperties.
153      * @param right LinkProperties to be compared with {@code left}.
154      * @return {@code true} if both are identical, {@code false} otherwise.
155      */
isIdenticalAddresses(@onNull LinkProperties left, @NonNull LinkProperties right)156     public static boolean isIdenticalAddresses(@NonNull LinkProperties left,
157             @NonNull LinkProperties right) {
158         final Collection<InetAddress> leftAddresses = left.getAddresses();
159         final Collection<InetAddress> rightAddresses = right.getAddresses();
160         return (leftAddresses.size() == rightAddresses.size())
161                     ? leftAddresses.containsAll(rightAddresses) : false;
162     }
163 
164     /**
165      * Compares {@code left} {@code LinkProperties} DNS addresses against the {@code right}.
166      *
167      * @param left A LinkProperties.
168      * @param right A LinkProperties to be compared with {@code left}.
169      * @return {@code true} if both are identical, {@code false} otherwise.
170      */
isIdenticalDnses(@onNull LinkProperties left, @NonNull LinkProperties right)171     public static boolean isIdenticalDnses(@NonNull LinkProperties left,
172             @NonNull LinkProperties right) {
173         final Collection<InetAddress> leftDnses = left.getDnsServers();
174         final Collection<InetAddress> rightDnses = right.getDnsServers();
175 
176         final String leftDomains = left.getDomains();
177         final String rightDomains = right.getDomains();
178         if (leftDomains == null) {
179             if (rightDomains != null) return false;
180         } else {
181             if (!leftDomains.equals(rightDomains)) return false;
182         }
183         return (leftDnses.size() == rightDnses.size())
184                 ? leftDnses.containsAll(rightDnses) : false;
185     }
186 
187     /**
188      * Compares {@code left} {@code LinkProperties} HttpProxy against the {@code right}.
189      *
190      * @param left A LinkProperties.
191      * @param right A LinkProperties to be compared with {@code left}.
192      * @return {@code true} if both are identical, {@code false} otherwise.
193      */
isIdenticalHttpProxy(@onNull LinkProperties left, @NonNull LinkProperties right)194     public static boolean isIdenticalHttpProxy(@NonNull LinkProperties left,
195             @NonNull LinkProperties right) {
196         return Objects.equals(left.getHttpProxy(), right.getHttpProxy());
197     }
198 
199     /**
200      * Compares {@code left} {@code LinkProperties} interface name against the {@code right}.
201      *
202      * @param left A LinkProperties.
203      * @param right A LinkProperties to be compared with {@code left}.
204      * @return {@code true} if both are identical, {@code false} otherwise.
205      */
isIdenticalInterfaceName(@onNull LinkProperties left, @NonNull LinkProperties right)206     public static boolean isIdenticalInterfaceName(@NonNull LinkProperties left,
207             @NonNull LinkProperties right) {
208         return TextUtils.equals(left.getInterfaceName(), right.getInterfaceName());
209     }
210 
211     /**
212      * Compares {@code left} {@code LinkProperties} Routes against the {@code right}.
213      *
214      * @param left A LinkProperties.
215      * @param right A LinkProperties to be compared with {@code left}.
216      * @return {@code true} if both are identical, {@code false} otherwise.
217      */
isIdenticalRoutes(@onNull LinkProperties left, @NonNull LinkProperties right)218     public static boolean isIdenticalRoutes(@NonNull LinkProperties left,
219             @NonNull LinkProperties right) {
220         final Collection<RouteInfo> leftRoutes = left.getRoutes();
221         final Collection<RouteInfo> rightRoutes = right.getRoutes();
222         return (leftRoutes.size() == rightRoutes.size())
223                 ? leftRoutes.containsAll(rightRoutes) : false;
224     }
225 }
226