1 /*
2  * Copyright (C) 2018 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.ipmemorystore;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 
22 import com.android.internal.annotations.VisibleForTesting;
23 
24 import java.net.Inet4Address;
25 import java.net.InetAddress;
26 import java.net.UnknownHostException;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Objects;
31 import java.util.StringJoiner;
32 
33 /**
34  * A POD object to represent attributes of a single L2 network entry.
35  * @hide
36  */
37 public class NetworkAttributes {
38     private static final boolean DBG = true;
39 
40     // Weight cutoff for grouping. To group, a similarity score is computed with the following
41     // algorithm : if both fields are non-null and equals() then add their assigned weight, else if
42     // both are null then add a portion of their assigned weight (see NULL_MATCH_WEIGHT),
43     // otherwise add nothing.
44     // As a guideline, this should be something like 60~75% of the total weights in this class. The
45     // design states "in essence a reader should imagine that if two important columns don't match,
46     // or one important and several unimportant columns don't match then the two records are
47     // considered a different group".
48     private static final float TOTAL_WEIGHT_CUTOFF = 520.0f;
49     // The portion of the weight that is earned when scoring group-sameness by having both columns
50     // being null. This is because some networks rightfully don't have some attributes (e.g. a
51     // V6-only network won't have an assigned V4 address) and both being null should count for
52     // something, but attributes may also be null just because data is unavailable.
53     private static final float NULL_MATCH_WEIGHT = 0.25f;
54 
55     // The v4 address that was assigned to this device the last time it joined this network.
56     // This typically comes from DHCP but could be something else like static configuration.
57     // This does not apply to IPv6.
58     // TODO : add a list of v6 prefixes for the v6 case.
59     @Nullable
60     public final Inet4Address assignedV4Address;
61     private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f;
62 
63     // The lease expiry timestamp of v4 address allocated from DHCP server, in milliseconds.
64     @Nullable
65     public final Long assignedV4AddressExpiry;
66     // lease expiry doesn't imply any correlation between "the same lease expiry value" and "the
67     // same L3 network".
68     private static final float WEIGHT_ASSIGNEDV4ADDREXPIRY = 0.0f;
69 
70     // Optionally supplied by the client to signify belonging to a notion of a group owned by
71     // the client. For example, this could be a hash of the SSID on WiFi.
72     @Nullable
73     public final String cluster;
74     private static final float WEIGHT_CLUSTER = 300.0f;
75 
76     // The list of DNS server addresses.
77     @Nullable
78     public final List<InetAddress> dnsAddresses;
79     private static final float WEIGHT_DNSADDRESSES = 200.0f;
80 
81     // The mtu on this network.
82     @Nullable
83     public final Integer mtu;
84     private static final float WEIGHT_MTU = 50.0f;
85 
86     // The sum of all weights in this class. Tests ensure that this stays equal to the total of
87     // all weights.
88     /** @hide */
89     @VisibleForTesting
90     public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR
91             + WEIGHT_ASSIGNEDV4ADDREXPIRY
92             + WEIGHT_CLUSTER
93             + WEIGHT_DNSADDRESSES
94             + WEIGHT_MTU;
95 
96     /** @hide */
97     @VisibleForTesting
NetworkAttributes( @ullable final Inet4Address assignedV4Address, @Nullable final Long assignedV4AddressExpiry, @Nullable final String cluster, @Nullable final List<InetAddress> dnsAddresses, @Nullable final Integer mtu)98     public NetworkAttributes(
99             @Nullable final Inet4Address assignedV4Address,
100             @Nullable final Long assignedV4AddressExpiry,
101             @Nullable final String cluster,
102             @Nullable final List<InetAddress> dnsAddresses,
103             @Nullable final Integer mtu) {
104         if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
105         if (assignedV4AddressExpiry != null && assignedV4AddressExpiry <= 0) {
106             throw new IllegalArgumentException("lease expiry can't be negative or zero");
107         }
108         this.assignedV4Address = assignedV4Address;
109         this.assignedV4AddressExpiry = assignedV4AddressExpiry;
110         this.cluster = cluster;
111         this.dnsAddresses = null == dnsAddresses ? null :
112                 Collections.unmodifiableList(new ArrayList<>(dnsAddresses));
113         this.mtu = mtu;
114     }
115 
116     @VisibleForTesting
NetworkAttributes(@onNull final NetworkAttributesParcelable parcelable)117     public NetworkAttributes(@NonNull final NetworkAttributesParcelable parcelable) {
118         // The call to the other constructor must be the first statement of this constructor,
119         // so everything has to be inline
120         this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address),
121                 parcelable.assignedV4AddressExpiry > 0
122                         ? parcelable.assignedV4AddressExpiry : null,
123                 parcelable.cluster,
124                 blobArrayToInetAddressList(parcelable.dnsAddresses),
125                 parcelable.mtu >= 0 ? parcelable.mtu : null);
126     }
127 
128     @Nullable
getByAddressOrNull(@ullable final byte[] address)129     private static InetAddress getByAddressOrNull(@Nullable final byte[] address) {
130         if (null == address) return null;
131         try {
132             return InetAddress.getByAddress(address);
133         } catch (UnknownHostException e) {
134             return null;
135         }
136     }
137 
138     @Nullable
blobArrayToInetAddressList(@ullable final Blob[] blobs)139     private static List<InetAddress> blobArrayToInetAddressList(@Nullable final Blob[] blobs) {
140         if (null == blobs) return null;
141         final ArrayList<InetAddress> list = new ArrayList<>(blobs.length);
142         for (final Blob b : blobs) {
143             final InetAddress addr = getByAddressOrNull(b.data);
144             if (null != addr) list.add(addr);
145         }
146         return list;
147     }
148 
149     @Nullable
inetAddressListToBlobArray(@ullable final List<InetAddress> addresses)150     private static Blob[] inetAddressListToBlobArray(@Nullable final List<InetAddress> addresses) {
151         if (null == addresses) return null;
152         final ArrayList<Blob> blobs = new ArrayList<>();
153         for (int i = 0; i < addresses.size(); ++i) {
154             final InetAddress addr = addresses.get(i);
155             if (null == addr) continue;
156             final Blob b = new Blob();
157             b.data = addr.getAddress();
158             blobs.add(b);
159         }
160         return blobs.toArray(new Blob[0]);
161     }
162 
163     /** Converts this NetworkAttributes to a parcelable object */
164     @NonNull
toParcelable()165     public NetworkAttributesParcelable toParcelable() {
166         final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable();
167         parcelable.assignedV4Address =
168                 (null == assignedV4Address) ? null : assignedV4Address.getAddress();
169         parcelable.assignedV4AddressExpiry =
170                 (null == assignedV4AddressExpiry) ? 0 : assignedV4AddressExpiry;
171         parcelable.cluster = cluster;
172         parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses);
173         parcelable.mtu = (null == mtu) ? -1 : mtu;
174         return parcelable;
175     }
176 
samenessContribution(final float weight, @Nullable final Object o1, @Nullable final Object o2)177     private float samenessContribution(final float weight,
178             @Nullable final Object o1, @Nullable final Object o2) {
179         if (null == o1) {
180             return (null == o2) ? weight * NULL_MATCH_WEIGHT : 0f;
181         }
182         return Objects.equals(o1, o2) ? weight : 0f;
183     }
184 
185     /** @hide */
getNetworkGroupSamenessConfidence(@onNull final NetworkAttributes o)186     public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) {
187         final float samenessScore =
188                 samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address)
189                 + samenessContribution(WEIGHT_ASSIGNEDV4ADDREXPIRY, assignedV4AddressExpiry,
190                       o.assignedV4AddressExpiry)
191                 + samenessContribution(WEIGHT_CLUSTER, cluster, o.cluster)
192                 + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses)
193                 + samenessContribution(WEIGHT_MTU, mtu, o.mtu);
194         // The minimum is 0, the max is TOTAL_WEIGHT and should be represented by 1.0, and
195         // TOTAL_WEIGHT_CUTOFF should represent 0.5, but there is no requirement that
196         // TOTAL_WEIGHT_CUTOFF would be half of TOTAL_WEIGHT (indeed, it should not be).
197         // So scale scores under the cutoff between 0 and 0.5, and the scores over the cutoff
198         // between 0.5 and 1.0.
199         if (samenessScore < TOTAL_WEIGHT_CUTOFF) {
200             return samenessScore / (TOTAL_WEIGHT_CUTOFF * 2);
201         } else {
202             return (samenessScore - TOTAL_WEIGHT_CUTOFF) / (TOTAL_WEIGHT - TOTAL_WEIGHT_CUTOFF) / 2
203                     + 0.5f;
204         }
205     }
206 
207     /** @hide */
208     public static class Builder {
209         @Nullable
210         private Inet4Address mAssignedAddress;
211         @Nullable
212         private Long mAssignedAddressExpiry;
213         @Nullable
214         private String mCluster;
215         @Nullable
216         private List<InetAddress> mDnsAddresses;
217         @Nullable
218         private Integer mMtu;
219 
220         /**
221          * Constructs a new Builder.
222          */
Builder()223         public Builder() {}
224 
225         /**
226          * Constructs a Builder from the passed NetworkAttributes.
227          */
Builder(@onNull final NetworkAttributes attributes)228         public Builder(@NonNull final NetworkAttributes attributes) {
229             mAssignedAddress = attributes.assignedV4Address;
230             mAssignedAddressExpiry = attributes.assignedV4AddressExpiry;
231             mCluster = attributes.cluster;
232             mDnsAddresses = new ArrayList<>(attributes.dnsAddresses);
233             mMtu = attributes.mtu;
234         }
235 
236         /**
237          * Set the assigned address.
238          * @param assignedV4Address The assigned address.
239          * @return This builder.
240          */
setAssignedV4Address(@ullable final Inet4Address assignedV4Address)241         public Builder setAssignedV4Address(@Nullable final Inet4Address assignedV4Address) {
242             mAssignedAddress = assignedV4Address;
243             return this;
244         }
245 
246         /**
247          * Set the lease expiry timestamp of assigned v4 address. Long.MAX_VALUE is used
248          * to represent "infinite lease".
249          *
250          * @param assignedV4AddressExpiry The lease expiry timestamp of assigned v4 address.
251          * @return This builder.
252          */
setAssignedV4AddressExpiry( @ullable final Long assignedV4AddressExpiry)253         public Builder setAssignedV4AddressExpiry(
254                 @Nullable final Long assignedV4AddressExpiry) {
255             if (null != assignedV4AddressExpiry && assignedV4AddressExpiry <= 0) {
256                 throw new IllegalArgumentException("lease expiry can't be negative or zero");
257             }
258             mAssignedAddressExpiry = assignedV4AddressExpiry;
259             return this;
260         }
261 
262         /**
263          * Set the cluster.
264          * @param cluster The cluster.
265          * @return This builder.
266          */
setCluster(@ullable final String cluster)267         public Builder setCluster(@Nullable final String cluster) {
268             mCluster = cluster;
269             return this;
270         }
271 
272         /**
273          * Set the DNS addresses.
274          * @param dnsAddresses The DNS addresses.
275          * @return This builder.
276          */
setDnsAddresses(@ullable final List<InetAddress> dnsAddresses)277         public Builder setDnsAddresses(@Nullable final List<InetAddress> dnsAddresses) {
278             if (DBG && null != dnsAddresses) {
279                 // Parceling code crashes if one of the addresses is null, therefore validate
280                 // them when running in debug.
281                 for (final InetAddress address : dnsAddresses) {
282                     if (null == address) throw new IllegalArgumentException("Null DNS address");
283                 }
284             }
285             this.mDnsAddresses = dnsAddresses;
286             return this;
287         }
288 
289         /**
290          * Set the MTU.
291          * @param mtu The MTU.
292          * @return This builder.
293          */
setMtu(@ullable final Integer mtu)294         public Builder setMtu(@Nullable final Integer mtu) {
295             if (null != mtu && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
296             mMtu = mtu;
297             return this;
298         }
299 
300         /**
301          * Return the built NetworkAttributes object.
302          * @return The built NetworkAttributes object.
303          */
build()304         public NetworkAttributes build() {
305             return new NetworkAttributes(mAssignedAddress, mAssignedAddressExpiry,
306                     mCluster, mDnsAddresses, mMtu);
307         }
308     }
309 
310     /** @hide */
isEmpty()311     public boolean isEmpty() {
312         return (null == assignedV4Address) && (null == assignedV4AddressExpiry)
313                 && (null == cluster) && (null == dnsAddresses) && (null == mtu);
314     }
315 
316     @Override
equals(@ullable final Object o)317     public boolean equals(@Nullable final Object o) {
318         if (!(o instanceof NetworkAttributes)) return false;
319         final NetworkAttributes other = (NetworkAttributes) o;
320         return Objects.equals(assignedV4Address, other.assignedV4Address)
321                 && Objects.equals(assignedV4AddressExpiry, other.assignedV4AddressExpiry)
322                 && Objects.equals(cluster, other.cluster)
323                 && Objects.equals(dnsAddresses, other.dnsAddresses)
324                 && Objects.equals(mtu, other.mtu);
325     }
326 
327     @Override
hashCode()328     public int hashCode() {
329         return Objects.hash(assignedV4Address, assignedV4AddressExpiry,
330                 cluster, dnsAddresses, mtu);
331     }
332 
333     /** Pretty print */
334     @Override
toString()335     public String toString() {
336         final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}");
337         final ArrayList<String> nullFields = new ArrayList<>();
338 
339         if (null != assignedV4Address) {
340             resultJoiner.add("assignedV4Addr :");
341             resultJoiner.add(assignedV4Address.toString());
342         } else {
343             nullFields.add("assignedV4Addr");
344         }
345 
346         if (null != assignedV4AddressExpiry) {
347             resultJoiner.add("assignedV4AddressExpiry :");
348             resultJoiner.add(assignedV4AddressExpiry.toString());
349         } else {
350             nullFields.add("assignedV4AddressExpiry");
351         }
352 
353         if (null != cluster) {
354             resultJoiner.add("cluster :");
355             resultJoiner.add(cluster);
356         } else {
357             nullFields.add("cluster");
358         }
359 
360         if (null != dnsAddresses) {
361             resultJoiner.add("dnsAddr : [");
362             for (final InetAddress addr : dnsAddresses) {
363                 resultJoiner.add(addr.getHostAddress());
364             }
365             resultJoiner.add("]");
366         } else {
367             nullFields.add("dnsAddr");
368         }
369 
370         if (null != mtu) {
371             resultJoiner.add("mtu :");
372             resultJoiner.add(mtu.toString());
373         } else {
374             nullFields.add("mtu");
375         }
376 
377         if (!nullFields.isEmpty()) {
378             resultJoiner.add("; Null fields : [");
379             for (final String field : nullFields) {
380                 resultJoiner.add(field);
381             }
382             resultJoiner.add("]");
383         }
384 
385         return resultJoiner.toString();
386     }
387 }
388