1 /* 2 * Copyright (C) 2021 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.telephony.data; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 24 import java.math.BigInteger; 25 import java.nio.ByteBuffer; 26 import java.util.Objects; 27 import java.util.Set; 28 import java.util.UUID; 29 import java.util.regex.Matcher; 30 import java.util.regex.Pattern; 31 32 /** 33 * A traffic descriptor, as defined in 3GPP TS 24.526 Section 5.2. It is used for UE Route Selection 34 * Policy(URSP) traffic matching as described in 3GPP TS 24.526 Section 4.2.2. It includes an 35 * optional Data Network Name(DNN), which, if present, must be used for traffic matching; it does 36 * not specify the end point to be used for the data call. 37 */ 38 public final class TrafficDescriptor implements Parcelable { 39 /** 40 * The OS/App id 41 * 42 * @hide 43 */ 44 public static final class OsAppId { 45 /** 46 * OSId for "Android", using UUID version 5 with namespace ISO OSI. 47 * Prepended to the OsAppId in TrafficDescriptor to use for URSP matching. 48 */ 49 public static final UUID ANDROID_OS_ID = 50 UUID.fromString("97a498e3-fc92-5c94-8986-0333d06e4e47"); 51 52 /** 53 * Allowed app ids. 54 */ 55 // The following app ids are the only apps id Android supports. OEMs or vendors are 56 // prohibited to modify/extend the allowed list, especially passing the real package name to 57 // the network. 58 private static final Set<String> ALLOWED_APP_IDS = Set.of( 59 "ENTERPRISE", "PRIORITIZE_LATENCY", "PRIORITIZE_BANDWIDTH", "CBS" 60 ); 61 62 /** OS id in UUID format. */ 63 private final @NonNull UUID mOsId; 64 65 /** 66 * App id in string format. Note that Android will not allow use specific app id. This must 67 * be a category/capability identifier. 68 */ 69 private final @NonNull String mAppId; 70 71 /** 72 * The differentiator when multiple traffic descriptor has the same OS and app id. Must be 73 * greater than 1. 74 */ 75 private final int mDifferentiator; 76 77 /** 78 * Constructor 79 * 80 * @param osId OS id in UUID format. 81 * @param appId App id in string format. Note that Android will not allow use specific app 82 * id. This must be a category/capability identifier. 83 */ OsAppId(@onNull UUID osId, @NonNull String appId)84 public OsAppId(@NonNull UUID osId, @NonNull String appId) { 85 this(osId, appId, 1); 86 } 87 88 /** 89 * Constructor 90 * 91 * @param osId OS id in UUID format. 92 * @param appId App id in string format. Note that Android will not allow use specific app 93 * id. This must be a category/capability identifier. 94 * @param differentiator The differentiator when multiple traffic descriptor has the same 95 * OS and app id. Must be greater than 0. 96 */ OsAppId(@onNull UUID osId, @NonNull String appId, int differentiator)97 public OsAppId(@NonNull UUID osId, @NonNull String appId, int differentiator) { 98 Objects.requireNonNull(osId); 99 Objects.requireNonNull(appId); 100 if (differentiator < 1) { 101 throw new IllegalArgumentException("Invalid differentiator " + differentiator); 102 } 103 104 mOsId = osId; 105 mAppId = appId; 106 mDifferentiator = differentiator; 107 } 108 109 /** 110 * Constructor from raw byte array. 111 * 112 * @param rawOsAppId The raw OS/App id. 113 */ OsAppId(@onNull byte[] rawOsAppId)114 public OsAppId(@NonNull byte[] rawOsAppId) { 115 try { 116 ByteBuffer bb = ByteBuffer.wrap(rawOsAppId); 117 // OS id is the first 16 bytes. 118 mOsId = new UUID(bb.getLong(), bb.getLong()); 119 // App id length is 1 byte. 120 int appIdLen = bb.get(); 121 // The remaining is the app id + differentiator. 122 byte[] appIdAndDifferentiator = new byte[appIdLen]; 123 bb.get(appIdAndDifferentiator, 0, appIdLen); 124 // Extract trailing numbers, for example, "ENTERPRISE", "ENTERPRISE3". 125 String appIdAndDifferentiatorStr = new String(appIdAndDifferentiator); 126 Pattern pattern = Pattern.compile("[^0-9]+([0-9]+)$"); 127 Matcher matcher = pattern.matcher(new String(appIdAndDifferentiator)); 128 if (matcher.find()) { 129 mDifferentiator = Integer.parseInt(matcher.group(1)); 130 mAppId = appIdAndDifferentiatorStr.replace(matcher.group(1), ""); 131 } else { 132 mDifferentiator = 1; 133 mAppId = appIdAndDifferentiatorStr; 134 } 135 } catch (Exception e) { 136 throw new IllegalArgumentException("Failed to decode " + (rawOsAppId != null 137 ? new BigInteger(1, rawOsAppId).toString(16) : null)); 138 } 139 } 140 141 /** 142 * @return The OS id in UUID format. 143 */ getOsId()144 public @NonNull UUID getOsId() { 145 return mOsId; 146 } 147 148 /** 149 * @return App id in string format. Note that Android will not allow use specific app id. 150 * This must be a category/capability identifier. 151 */ getAppId()152 public @NonNull String getAppId() { 153 return mAppId; 154 } 155 156 /** 157 * @return The differentiator when multiple traffic descriptor has the same OS and app id. 158 * Must be greater than 1. 159 */ getDifferentiator()160 public int getDifferentiator() { 161 return mDifferentiator; 162 } 163 164 /** 165 * @return OS/App id in raw byte format. 166 */ getBytes()167 public @NonNull byte[] getBytes() { 168 byte[] osAppId = (mAppId + (mDifferentiator > 1 ? mDifferentiator : "")).getBytes(); 169 // 16 bytes for UUID, 1 byte for length of osAppId, and up to 255 bytes for osAppId 170 ByteBuffer bb = ByteBuffer.allocate(16 + 1 + osAppId.length); 171 bb.putLong(mOsId.getMostSignificantBits()); 172 bb.putLong(mOsId.getLeastSignificantBits()); 173 bb.put((byte) osAppId.length); 174 bb.put(osAppId); 175 return bb.array(); 176 } 177 178 @Override toString()179 public String toString() { 180 return "[OsAppId: OS=" + mOsId + ", App=" + mAppId + ", differentiator=" 181 + mDifferentiator + ", raw=" 182 + new BigInteger(1, getBytes()).toString(16) + "]"; 183 } 184 185 @Override equals(Object o)186 public boolean equals(Object o) { 187 if (this == o) return true; 188 if (o == null || getClass() != o.getClass()) return false; 189 OsAppId osAppId = (OsAppId) o; 190 return mDifferentiator == osAppId.mDifferentiator && mOsId.equals(osAppId.mOsId) 191 && mAppId.equals(osAppId.mAppId); 192 } 193 194 @Override hashCode()195 public int hashCode() { 196 return Objects.hash(mOsId, mAppId, mDifferentiator); 197 } 198 } 199 200 private final String mDnn; 201 private final OsAppId mOsAppId; 202 TrafficDescriptor(@onNull Parcel in)203 private TrafficDescriptor(@NonNull Parcel in) { 204 mDnn = in.readString(); 205 byte[] osAppIdBytes = in.createByteArray(); 206 OsAppId osAppId = null; 207 if (osAppIdBytes != null) { 208 osAppId = new OsAppId(osAppIdBytes); 209 } 210 mOsAppId = osAppId; 211 212 enforceAllowedIds(); 213 } 214 215 /** 216 * Create a traffic descriptor, as defined in 3GPP TS 24.526 Section 5.2 217 * @param dnn optional DNN, which must be used for traffic matching, if present 218 * @param osAppIdRawBytes Raw bytes of OsId + osAppId of the traffic descriptor 219 * 220 * @hide 221 */ TrafficDescriptor(String dnn, @Nullable byte[] osAppIdRawBytes)222 public TrafficDescriptor(String dnn, @Nullable byte[] osAppIdRawBytes) { 223 mDnn = dnn; 224 OsAppId osAppId = null; 225 if (osAppIdRawBytes != null) { 226 osAppId = new OsAppId(osAppIdRawBytes); 227 } 228 mOsAppId = osAppId; 229 230 enforceAllowedIds(); 231 } 232 233 /** 234 * Enforce the OS id and app id are in the allowed list. 235 * 236 * @throws IllegalArgumentException if ids are not allowed. 237 */ enforceAllowedIds()238 private void enforceAllowedIds() { 239 if (mOsAppId != null && !mOsAppId.getOsId().equals(OsAppId.ANDROID_OS_ID)) { 240 throw new IllegalArgumentException("OS id " + mOsAppId.getOsId() + " does not match " 241 + OsAppId.ANDROID_OS_ID); 242 } 243 244 if (mOsAppId != null && !OsAppId.ALLOWED_APP_IDS.contains(mOsAppId.getAppId())) { 245 throw new IllegalArgumentException("Illegal app id " + mOsAppId.getAppId() 246 + ". Only allowing one of the following " + OsAppId.ALLOWED_APP_IDS); 247 } 248 } 249 250 /** 251 * DNN stands for Data Network Name and represents an APN as defined in 3GPP TS 23.003. 252 * @return the DNN of this traffic descriptor if one is included by the network, null 253 * otherwise. 254 */ getDataNetworkName()255 public @Nullable String getDataNetworkName() { 256 return mDnn; 257 } 258 259 /** 260 * OsAppId identifies a broader traffic category. Although it names Os/App id, it only includes 261 * OS version with a general/broader category id used as app id. 262 * 263 * @return The id in byte format. {@code null} if not available. 264 */ getOsAppId()265 public @Nullable byte[] getOsAppId() { 266 return mOsAppId != null ? mOsAppId.getBytes() : null; 267 } 268 269 @Override describeContents()270 public int describeContents() { 271 return 0; 272 } 273 274 @NonNull @Override toString()275 public String toString() { 276 return "TrafficDescriptor={mDnn=" + mDnn + ", " + mOsAppId + "}"; 277 } 278 279 @Override writeToParcel(@onNull Parcel dest, int flags)280 public void writeToParcel(@NonNull Parcel dest, int flags) { 281 dest.writeString(mDnn); 282 dest.writeByteArray(mOsAppId != null ? mOsAppId.getBytes() : null); 283 } 284 285 public static final @NonNull Parcelable.Creator<TrafficDescriptor> CREATOR = 286 new Parcelable.Creator<TrafficDescriptor>() { 287 @Override 288 public @NonNull TrafficDescriptor createFromParcel(@NonNull Parcel source) { 289 return new TrafficDescriptor(source); 290 } 291 292 @Override 293 public @NonNull TrafficDescriptor[] newArray(int size) { 294 return new TrafficDescriptor[size]; 295 } 296 }; 297 298 @Override equals(@ullable Object o)299 public boolean equals(@Nullable Object o) { 300 if (this == o) return true; 301 if (o == null || getClass() != o.getClass()) return false; 302 TrafficDescriptor that = (TrafficDescriptor) o; 303 return Objects.equals(mDnn, that.mDnn) && Objects.equals(mOsAppId, that.mOsAppId); 304 } 305 306 @Override hashCode()307 public int hashCode() { 308 return Objects.hash(mDnn, mOsAppId); 309 } 310 311 /** 312 * Provides a convenient way to set the fields of a {@link TrafficDescriptor} when creating a 313 * new instance. 314 * 315 * <p>The example below shows how you might create a new {@code TrafficDescriptor}: 316 * 317 * <pre><code> 318 * 319 * TrafficDescriptor response = new TrafficDescriptor.Builder() 320 * .setDnn("") 321 * .build(); 322 * </code></pre> 323 * 324 */ 325 public static final class Builder { 326 private String mDnn = null; 327 private byte[] mOsAppId = null; 328 329 /** 330 * Default constructor for Builder. 331 */ Builder()332 public Builder() { 333 } 334 335 /** 336 * Set the Data Network Name(DNN). 337 * 338 * @return The same instance of the builder. 339 */ 340 @NonNull setDataNetworkName(@onNull String dnn)341 public Builder setDataNetworkName(@NonNull String dnn) { 342 this.mDnn = dnn; 343 return this; 344 } 345 346 /** 347 * Set the OS App ID (including OS Id as defined in the specs). 348 * 349 * @return The same instance of the builder. 350 */ 351 @NonNull setOsAppId(@onNull byte[] osAppId)352 public Builder setOsAppId(@NonNull byte[] osAppId) { 353 this.mOsAppId = osAppId; 354 return this; 355 } 356 357 /** 358 * Build the {@link TrafficDescriptor}. 359 * 360 * @throws IllegalArgumentException if DNN and OS App ID are null. 361 * 362 * @return the {@link TrafficDescriptor} object. 363 */ 364 @NonNull build()365 public TrafficDescriptor build() { 366 if (this.mDnn == null && this.mOsAppId == null) { 367 throw new IllegalArgumentException("DNN and OS App ID are null"); 368 } 369 return new TrafficDescriptor(this.mDnn, this.mOsAppId); 370 } 371 } 372 } 373