blob: 5397b57a456895740aa216303a23e96622092d71 [file] [log] [blame]
Chalard Jeanf89d7be2018-12-07 23:09:02 +09001/*
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
17package android.net.ipmemorystore;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21
22import com.android.internal.annotations.VisibleForTesting;
23
24import java.net.Inet4Address;
25import java.net.InetAddress;
26import java.net.UnknownHostException;
27import java.util.ArrayList;
28import java.util.Collections;
29import java.util.List;
30import java.util.Objects;
Chalard Jean8fe503f2018-12-14 00:59:53 +090031import java.util.StringJoiner;
Chalard Jeanf89d7be2018-12-07 23:09:02 +090032
33/**
34 * A POD object to represent attributes of a single L2 network entry.
35 * @hide
36 */
37public class NetworkAttributes {
38 private static final boolean DBG = true;
39
Chalard Jeana39756a2019-01-16 18:18:44 +090040 // 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
Chalard Jeanf89d7be2018-12-07 23:09:02 +090055 // 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;
Chalard Jeana39756a2019-01-16 18:18:44 +090061 private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f;
Chalard Jeanf89d7be2018-12-07 23:09:02 +090062
63 // Optionally supplied by the client if it has an opinion on L3 network. For example, this
64 // could be a hash of the SSID + security type on WiFi.
65 @Nullable
66 public final String groupHint;
Chalard Jeana39756a2019-01-16 18:18:44 +090067 private static final float WEIGHT_GROUPHINT = 300.0f;
Chalard Jeanf89d7be2018-12-07 23:09:02 +090068
69 // The list of DNS server addresses.
70 @Nullable
71 public final List<InetAddress> dnsAddresses;
Chalard Jeana39756a2019-01-16 18:18:44 +090072 private static final float WEIGHT_DNSADDRESSES = 200.0f;
Chalard Jeanf89d7be2018-12-07 23:09:02 +090073
74 // The mtu on this network.
75 @Nullable
76 public final Integer mtu;
Chalard Jeana39756a2019-01-16 18:18:44 +090077 private static final float WEIGHT_MTU = 50.0f;
Chalard Jeanf89d7be2018-12-07 23:09:02 +090078
Chalard Jeana39756a2019-01-16 18:18:44 +090079 // The sum of all weights in this class. Tests ensure that this stays equal to the total of
80 // all weights.
81 /** @hide */
82 @VisibleForTesting
83 public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR
84 + WEIGHT_GROUPHINT
85 + WEIGHT_DNSADDRESSES
86 + WEIGHT_MTU;
87
88 /** @hide */
89 @VisibleForTesting
90 public NetworkAttributes(
Chalard Jeanf89d7be2018-12-07 23:09:02 +090091 @Nullable final Inet4Address assignedV4Address,
92 @Nullable final String groupHint,
93 @Nullable final List<InetAddress> dnsAddresses,
94 @Nullable final Integer mtu) {
95 if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
96 this.assignedV4Address = assignedV4Address;
97 this.groupHint = groupHint;
98 this.dnsAddresses = null == dnsAddresses ? null :
99 Collections.unmodifiableList(new ArrayList<>(dnsAddresses));
100 this.mtu = mtu;
101 }
102
103 @VisibleForTesting
104 public NetworkAttributes(@NonNull final NetworkAttributesParcelable parcelable) {
105 // The call to the other constructor must be the first statement of this constructor,
106 // so everything has to be inline
107 this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address),
108 parcelable.groupHint,
109 blobArrayToInetAddressList(parcelable.dnsAddresses),
110 parcelable.mtu >= 0 ? parcelable.mtu : null);
111 }
112
113 @Nullable
114 private static InetAddress getByAddressOrNull(@Nullable final byte[] address) {
115 try {
116 return InetAddress.getByAddress(address);
117 } catch (UnknownHostException e) {
118 return null;
119 }
120 }
121
122 @Nullable
123 private static List<InetAddress> blobArrayToInetAddressList(@Nullable final Blob[] blobs) {
124 if (null == blobs) return null;
125 final ArrayList<InetAddress> list = new ArrayList<>(blobs.length);
126 for (final Blob b : blobs) {
127 final InetAddress addr = getByAddressOrNull(b.data);
128 if (null != addr) list.add(addr);
129 }
130 return list;
131 }
132
133 @Nullable
134 private static Blob[] inetAddressListToBlobArray(@Nullable final List<InetAddress> addresses) {
135 if (null == addresses) return null;
136 final ArrayList<Blob> blobs = new ArrayList<>();
137 for (int i = 0; i < addresses.size(); ++i) {
138 final InetAddress addr = addresses.get(i);
139 if (null == addr) continue;
140 final Blob b = new Blob();
141 b.data = addr.getAddress();
142 blobs.add(b);
143 }
144 return blobs.toArray(new Blob[0]);
145 }
146
147 /** Converts this NetworkAttributes to a parcelable object */
148 @NonNull
149 public NetworkAttributesParcelable toParcelable() {
150 final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable();
151 parcelable.assignedV4Address =
152 (null == assignedV4Address) ? null : assignedV4Address.getAddress();
153 parcelable.groupHint = groupHint;
154 parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses);
155 parcelable.mtu = (null == mtu) ? -1 : mtu;
156 return parcelable;
157 }
158
Chalard Jeana39756a2019-01-16 18:18:44 +0900159 private float samenessContribution(final float weight,
160 @Nullable final Object o1, @Nullable final Object o2) {
161 if (null == o1) {
162 return (null == o2) ? weight * NULL_MATCH_WEIGHT : 0f;
163 }
164 return Objects.equals(o1, o2) ? weight : 0f;
165 }
166
167 /** @hide */
168 public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) {
169 final float samenessScore =
170 samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address)
171 + samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint)
172 + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses)
173 + samenessContribution(WEIGHT_MTU, mtu, o.mtu);
174 // The minimum is 0, the max is TOTAL_WEIGHT and should be represented by 1.0, and
175 // TOTAL_WEIGHT_CUTOFF should represent 0.5, but there is no requirement that
176 // TOTAL_WEIGHT_CUTOFF would be half of TOTAL_WEIGHT (indeed, it should not be).
177 // So scale scores under the cutoff between 0 and 0.5, and the scores over the cutoff
178 // between 0.5 and 1.0.
179 if (samenessScore < TOTAL_WEIGHT_CUTOFF) {
180 return samenessScore / (TOTAL_WEIGHT_CUTOFF * 2);
181 } else {
182 return (samenessScore - TOTAL_WEIGHT_CUTOFF) / (TOTAL_WEIGHT - TOTAL_WEIGHT_CUTOFF) / 2
183 + 0.5f;
184 }
185 }
186
Chalard Jeanf89d7be2018-12-07 23:09:02 +0900187 /** @hide */
188 public static class Builder {
189 @Nullable
190 private Inet4Address mAssignedAddress;
191 @Nullable
192 private String mGroupHint;
193 @Nullable
194 private List<InetAddress> mDnsAddresses;
195 @Nullable
196 private Integer mMtu;
197
198 /**
199 * Set the assigned address.
200 * @param assignedV4Address The assigned address.
201 * @return This builder.
202 */
203 public Builder setAssignedV4Address(@Nullable final Inet4Address assignedV4Address) {
204 mAssignedAddress = assignedV4Address;
205 return this;
206 }
207
208 /**
209 * Set the group hint.
210 * @param groupHint The group hint.
211 * @return This builder.
212 */
213 public Builder setGroupHint(@Nullable final String groupHint) {
214 mGroupHint = groupHint;
215 return this;
216 }
217
218 /**
219 * Set the DNS addresses.
220 * @param dnsAddresses The DNS addresses.
221 * @return This builder.
222 */
223 public Builder setDnsAddresses(@Nullable final List<InetAddress> dnsAddresses) {
224 if (DBG && null != dnsAddresses) {
225 // Parceling code crashes if one of the addresses is null, therefore validate
226 // them when running in debug.
227 for (final InetAddress address : dnsAddresses) {
228 if (null == address) throw new IllegalArgumentException("Null DNS address");
229 }
230 }
231 this.mDnsAddresses = dnsAddresses;
232 return this;
233 }
234
235 /**
236 * Set the MTU.
237 * @param mtu The MTU.
238 * @return This builder.
239 */
240 public Builder setMtu(@Nullable final Integer mtu) {
241 if (null != mtu && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
242 mMtu = mtu;
243 return this;
244 }
245
246 /**
247 * Return the built NetworkAttributes object.
248 * @return The built NetworkAttributes object.
249 */
250 public NetworkAttributes build() {
251 return new NetworkAttributes(mAssignedAddress, mGroupHint, mDnsAddresses, mMtu);
252 }
253 }
254
255 @Override
256 public boolean equals(@Nullable final Object o) {
257 if (!(o instanceof NetworkAttributes)) return false;
258 final NetworkAttributes other = (NetworkAttributes) o;
259 return Objects.equals(assignedV4Address, other.assignedV4Address)
260 && Objects.equals(groupHint, other.groupHint)
261 && Objects.equals(dnsAddresses, other.dnsAddresses)
262 && Objects.equals(mtu, other.mtu);
263 }
264
265 @Override
266 public int hashCode() {
267 return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu);
268 }
Chalard Jean8fe503f2018-12-14 00:59:53 +0900269
270 /** Pretty print */
271 @Override
272 public String toString() {
273 final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}");
274 final ArrayList<String> nullFields = new ArrayList<>();
275
276 if (null != assignedV4Address) {
277 resultJoiner.add("assignedV4Addr :");
278 resultJoiner.add(assignedV4Address.toString());
279 } else {
280 nullFields.add("assignedV4Addr");
281 }
282
283 if (null != groupHint) {
284 resultJoiner.add("groupHint :");
285 resultJoiner.add(groupHint);
286 } else {
287 nullFields.add("groupHint");
288 }
289
290 if (null != dnsAddresses) {
291 resultJoiner.add("dnsAddr : [");
292 for (final InetAddress addr : dnsAddresses) {
293 resultJoiner.add(addr.getHostAddress());
294 }
295 resultJoiner.add("]");
296 } else {
297 nullFields.add("dnsAddr");
298 }
299
300 if (null != mtu) {
301 resultJoiner.add("mtu :");
302 resultJoiner.add(mtu.toString());
303 } else {
304 nullFields.add("mtu");
305 }
306
307 if (!nullFields.isEmpty()) {
308 resultJoiner.add("; Null fields : [");
309 for (final String field : nullFields) {
310 resultJoiner.add(field);
311 }
312 resultJoiner.add("]");
313 }
314
315 return resultJoiner.toString();
316 }
Chalard Jeanf89d7be2018-12-07 23:09:02 +0900317}