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