1 /* 2 * Copyright (C) 2025 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; 18 19 import android.annotation.FlaggedApi; 20 import android.annotation.IntDef; 21 import android.annotation.IntRange; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.os.Build; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 28 import com.android.net.flags.Flags; 29 30 import java.lang.annotation.Retention; 31 import java.lang.annotation.RetentionPolicy; 32 import java.util.Objects; 33 34 /** 35 * A {@link NetworkSpecifier} used to identify an L2CAP network over BLE. 36 * 37 * An L2CAP network is not symmetrical, meaning there exists both a server (Bluetooth peripheral) 38 * and a client (Bluetooth central) node. This specifier contains the information required to 39 * request a client L2CAP network using {@link ConnectivityManager#requestNetwork} while specifying 40 * the remote MAC address, and Protocol/Service Multiplexer (PSM). It can also contain information 41 * allocated by the system when reserving a server network using {@link 42 * ConnectivityManager#reserveNetwork} such as the Protocol/Service Multiplexer (PSM). In both 43 * cases, the header compression option must be specified. 44 * 45 * An L2CAP server network allocates a Protocol/Service Multiplexer (PSM) to be advertised to the 46 * client. A new server network must always be reserved using {@code 47 * ConnectivityManager#reserveNetwork}. The subsequent {@link 48 * ConnectivityManager.NetworkCallback#onReserved(NetworkCapabilities)} callback includes an {@code 49 * L2CapNetworkSpecifier}. The {@link getPsm()} method will return the Protocol/Service Multiplexer 50 * (PSM) of the reserved network so that the server can advertise it to the client and the client 51 * can connect. 52 * An L2CAP server network is backed by a {@link android.bluetooth.BluetoothServerSocket} which can, 53 * in theory, accept many connections. However, before SDK version {@link 54 * Build.VERSION_CODES.VANILLA_ICE_CREAM} Bluetooth APIs do not expose the channel ID, so these 55 * connections are indistinguishable. In practice, this means that the network matching semantics in 56 * ConnectivityService will tear down all but the first connection. 57 * 58 * When the connection between client and server completes, a {@link Network} whose capabilities 59 * satisfy this {@code L2capNetworkSpecifier} will connect and the usual callbacks, such as {@link 60 * NetworkCallback#onAvailable}, will be called on the callback object passed to {@code 61 * ConnectivityManager#reserveNetwork} or {@code ConnectivityManager#requestNetwork}. 62 */ 63 @FlaggedApi(Flags.FLAG_IPV6_OVER_BLE) 64 public final class L2capNetworkSpecifier extends NetworkSpecifier implements Parcelable { 65 /** 66 * Match any role. 67 * 68 * This role is only meaningful in {@link NetworkRequest}s. Specifiers for actual L2CAP 69 * networks never have this role set. 70 */ 71 public static final int ROLE_ANY = 0; 72 /** Specifier describes a client network, i.e., the device is the Bluetooth central. */ 73 public static final int ROLE_CLIENT = 1; 74 /** Specifier describes a server network, i.e., the device is the Bluetooth peripheral. */ 75 public static final int ROLE_SERVER = 2; 76 77 /** @hide */ 78 @Retention(RetentionPolicy.SOURCE) 79 @IntDef(flag = false, prefix = "ROLE_", value = { 80 ROLE_ANY, 81 ROLE_CLIENT, 82 ROLE_SERVER 83 }) 84 public @interface Role {} 85 /** Role used to distinguish client from server networks. */ 86 @Role 87 private final int mRole; 88 89 /** 90 * Accept any form of header compression. 91 * 92 * This option is only meaningful in {@link NetworkRequest}s. Specifiers for actual L2CAP 93 * networks never have this option set. 94 */ 95 public static final int HEADER_COMPRESSION_ANY = 0; 96 /** Do not compress packets on this network. */ 97 public static final int HEADER_COMPRESSION_NONE = 1; 98 /** Use 6lowpan header compression as specified in rfc6282. */ 99 public static final int HEADER_COMPRESSION_6LOWPAN = 2; 100 101 /** @hide */ 102 @Retention(RetentionPolicy.SOURCE) 103 @IntDef(flag = false, prefix = "HEADER_COMPRESSION_", value = { 104 HEADER_COMPRESSION_ANY, 105 HEADER_COMPRESSION_NONE, 106 HEADER_COMPRESSION_6LOWPAN 107 }) 108 public @interface HeaderCompression {} 109 /** Header compression mechanism used on this network. */ 110 @HeaderCompression 111 private final int mHeaderCompression; 112 113 /** The MAC address of the remote. */ 114 @Nullable 115 private final MacAddress mRemoteAddress; 116 117 /** 118 * Match any Protocol/Service Multiplexer (PSM). 119 * 120 * This PSM value is only meaningful in {@link NetworkRequest}s. Specifiers for actual L2CAP 121 * networks never have this value set. 122 */ 123 public static final int PSM_ANY = 0; 124 125 /** The Bluetooth L2CAP Protocol/Service Multiplexer (PSM). */ 126 private final int mPsm; 127 L2capNetworkSpecifier(Parcel in)128 private L2capNetworkSpecifier(Parcel in) { 129 mRole = in.readInt(); 130 mHeaderCompression = in.readInt(); 131 mRemoteAddress = in.readParcelable(getClass().getClassLoader()); 132 mPsm = in.readInt(); 133 } 134 135 /** @hide */ L2capNetworkSpecifier(@ole int role, @HeaderCompression int headerCompression, MacAddress remoteAddress, int psm)136 public L2capNetworkSpecifier(@Role int role, @HeaderCompression int headerCompression, 137 MacAddress remoteAddress, int psm) { 138 mRole = role; 139 mHeaderCompression = headerCompression; 140 mRemoteAddress = remoteAddress; 141 mPsm = psm; 142 } 143 144 /** Returns the role to be used for this network. */ 145 @Role getRole()146 public int getRole() { 147 return mRole; 148 } 149 150 /** Returns the compression mechanism for this network. */ 151 @HeaderCompression getHeaderCompression()152 public int getHeaderCompression() { 153 return mHeaderCompression; 154 } 155 156 /** 157 * Returns the remote MAC address for this network to connect to. 158 * 159 * The remote address is only meaningful for networks that have ROLE_CLIENT. 160 * 161 * When receiving this {@link L2capNetworkSpecifier} from Connectivity APIs such as a {@link 162 * ConnectivityManager.NetworkCallback}, the MAC address is redacted. 163 */ getRemoteAddress()164 public @Nullable MacAddress getRemoteAddress() { 165 return mRemoteAddress; 166 } 167 168 /** Returns the Protocol/Service Multiplexer (PSM) for this network to connect to. */ getPsm()169 public int getPsm() { 170 return mPsm; 171 } 172 173 /** 174 * Checks whether the given L2capNetworkSpecifier is valid as part of a server network 175 * reservation request. 176 * 177 * @hide 178 */ isValidServerReservationSpecifier()179 public boolean isValidServerReservationSpecifier() { 180 // The ROLE_SERVER offer can be satisfied by a ROLE_ANY request. 181 if (mRole != ROLE_SERVER) return false; 182 183 // HEADER_COMPRESSION_ANY is never valid in a request. 184 if (mHeaderCompression == HEADER_COMPRESSION_ANY) return false; 185 186 // Remote address must be null for ROLE_SERVER requests. 187 if (mRemoteAddress != null) return false; 188 189 // reservation must allocate a PSM, so only PSM_ANY can be passed. 190 if (mPsm != PSM_ANY) return false; 191 192 return true; 193 } 194 195 /** 196 * Checks whether the given L2capNetworkSpecifier is valid as part of a client network request. 197 * 198 * @hide 199 */ isValidClientRequestSpecifier()200 public boolean isValidClientRequestSpecifier() { 201 // The ROLE_CLIENT offer can be satisfied by a ROLE_ANY request. 202 if (mRole != ROLE_CLIENT) return false; 203 204 // HEADER_COMPRESSION_ANY is never valid in a request. 205 if (mHeaderCompression == HEADER_COMPRESSION_ANY) return false; 206 207 // Remote address must not be null for ROLE_CLIENT requests. 208 if (mRemoteAddress == null) return false; 209 210 // Client network requests require a PSM to be specified. 211 // Ensure the PSM is within the valid range of dynamic BLE L2CAP values. 212 if (mPsm < 0x80) return false; 213 if (mPsm > 0xFF) return false; 214 215 return true; 216 } 217 218 /** A builder class for L2capNetworkSpecifier. */ 219 public static final class Builder { 220 @Role 221 private int mRole = ROLE_ANY; 222 @HeaderCompression 223 private int mHeaderCompression = HEADER_COMPRESSION_ANY; 224 @Nullable 225 private MacAddress mRemoteAddress; 226 private int mPsm = PSM_ANY; 227 228 /** 229 * Set the role to use for this network. 230 * 231 * If not set, defaults to {@link ROLE_ANY}. 232 * 233 * @param role the role to use. 234 */ 235 @NonNull setRole(@ole int role)236 public Builder setRole(@Role int role) { 237 mRole = role; 238 return this; 239 } 240 241 /** 242 * Set the header compression mechanism to use for this network. 243 * 244 * If not set, defaults to {@link HEADER_COMPRESSION_ANY}. This option must be specified 245 * (i.e. must not be set to {@link HEADER_COMPRESSION_ANY}) when requesting or reserving a 246 * new network. 247 * 248 * @param headerCompression the header compression mechanism to use. 249 */ 250 @NonNull setHeaderCompression(@eaderCompression int headerCompression)251 public Builder setHeaderCompression(@HeaderCompression int headerCompression) { 252 mHeaderCompression = headerCompression; 253 return this; 254 } 255 256 /** 257 * Set the remote address for the client to connect to. 258 * 259 * Only valid for client networks. If not set, the specifier matches any MAC address. 260 * 261 * @param remoteAddress the MAC address to connect to, or null to match any MAC address. 262 */ 263 @NonNull setRemoteAddress(@ullable MacAddress remoteAddress)264 public Builder setRemoteAddress(@Nullable MacAddress remoteAddress) { 265 mRemoteAddress = remoteAddress; 266 return this; 267 } 268 269 /** 270 * Set the Protocol/Service Multiplexer (PSM) for the client to connect to. 271 * 272 * If not set, defaults to {@link PSM_ANY}. 273 * 274 * @param psm the Protocol/Service Multiplexer (PSM) to connect to. 275 */ 276 @NonNull setPsm(@ntRangefrom = 0, to = 255) int psm)277 public Builder setPsm(@IntRange(from = 0, to = 255) int psm) { 278 if (psm < 0 /* PSM_ANY */ || psm > 0xFF) { 279 throw new IllegalArgumentException("PSM must be PSM_ANY or within range [1, 255]"); 280 } 281 mPsm = psm; 282 return this; 283 } 284 285 /** Create the L2capNetworkSpecifier object. */ 286 @NonNull build()287 public L2capNetworkSpecifier build() { 288 if (mRole == ROLE_SERVER && mRemoteAddress != null) { 289 throw new IllegalArgumentException( 290 "Specifying a remote address is not valid for server role."); 291 } 292 return new L2capNetworkSpecifier(mRole, mHeaderCompression, mRemoteAddress, mPsm); 293 } 294 } 295 296 /** @hide */ 297 @Override canBeSatisfiedBy(NetworkSpecifier other)298 public boolean canBeSatisfiedBy(NetworkSpecifier other) { 299 if (!(other instanceof L2capNetworkSpecifier)) return false; 300 final L2capNetworkSpecifier rhs = (L2capNetworkSpecifier) other; 301 302 // A network / offer cannot be ROLE_ANY, but it is added for consistency. 303 if (mRole != rhs.mRole && mRole != ROLE_ANY && rhs.mRole != ROLE_ANY) { 304 return false; 305 } 306 307 if (mHeaderCompression != rhs.mHeaderCompression 308 && mHeaderCompression != HEADER_COMPRESSION_ANY 309 && rhs.mHeaderCompression != HEADER_COMPRESSION_ANY) { 310 return false; 311 } 312 313 if (!Objects.equals(mRemoteAddress, rhs.mRemoteAddress) 314 && mRemoteAddress != null && rhs.mRemoteAddress != null) { 315 return false; 316 } 317 318 if (mPsm != rhs.mPsm && mPsm != PSM_ANY && rhs.mPsm != PSM_ANY) { 319 return false; 320 } 321 return true; 322 } 323 324 /** @hide */ 325 @Override 326 @Nullable redact()327 public NetworkSpecifier redact() { 328 final NetworkSpecifier redactedSpecifier = new Builder() 329 .setRole(mRole) 330 .setHeaderCompression(mHeaderCompression) 331 // The remote address is redacted. 332 .setRemoteAddress(null) 333 .setPsm(mPsm) 334 .build(); 335 return redactedSpecifier; 336 } 337 338 /** @hide */ 339 @Override hashCode()340 public int hashCode() { 341 return Objects.hash(mRole, mHeaderCompression, mRemoteAddress, mPsm); 342 } 343 344 /** @hide */ equals(Object obj)345 public boolean equals(Object obj) { 346 if (this == obj) return true; 347 if (!(obj instanceof L2capNetworkSpecifier)) return false; 348 349 final L2capNetworkSpecifier rhs = (L2capNetworkSpecifier) obj; 350 return mRole == rhs.mRole 351 && mHeaderCompression == rhs.mHeaderCompression 352 && Objects.equals(mRemoteAddress, rhs.mRemoteAddress) 353 && mPsm == rhs.mPsm; 354 } 355 356 /** @hide */ 357 @Override toString()358 public String toString() { 359 final String role; 360 switch (mRole) { 361 case ROLE_CLIENT: 362 role = "ROLE_CLIENT"; 363 break; 364 case ROLE_SERVER: 365 role = "ROLE_SERVER"; 366 break; 367 default: 368 role = "ROLE_ANY"; 369 break; 370 } 371 372 final String headerCompression; 373 switch (mHeaderCompression) { 374 case HEADER_COMPRESSION_NONE: 375 headerCompression = "HEADER_COMPRESSION_NONE"; 376 break; 377 case HEADER_COMPRESSION_6LOWPAN: 378 headerCompression = "HEADER_COMPRESSION_6LOWPAN"; 379 break; 380 default: 381 headerCompression = "HEADER_COMPRESSION_ANY"; 382 break; 383 } 384 385 final String psm = (mPsm == PSM_ANY) ? "PSM_ANY" : String.valueOf(mPsm); 386 387 return String.format("L2capNetworkSpecifier(%s, %s, RemoteAddress=%s, PSM=%s)", 388 role, headerCompression, Objects.toString(mRemoteAddress), psm); 389 } 390 391 @Override describeContents()392 public int describeContents() { 393 return 0; 394 } 395 396 @Override writeToParcel(@onNull Parcel dest, int flags)397 public void writeToParcel(@NonNull Parcel dest, int flags) { 398 dest.writeInt(mRole); 399 dest.writeInt(mHeaderCompression); 400 dest.writeParcelable(mRemoteAddress, flags); 401 dest.writeInt(mPsm); 402 } 403 404 public static final @NonNull Creator<L2capNetworkSpecifier> CREATOR = new Creator<>() { 405 @Override 406 public L2capNetworkSpecifier createFromParcel(Parcel in) { 407 return new L2capNetworkSpecifier(in); 408 } 409 410 @Override 411 public L2capNetworkSpecifier[] newArray(int size) { 412 return new L2capNetworkSpecifier[size]; 413 } 414 }; 415 } 416