Add L2capNetworkSpecifier
Adds the API surface of the L2capSpecifier. Matching will be implemented in a follow up change. Test is *very* basic and will be expanded in follow up (once API is approved). Test: atest L2capNetworkSpecifierTest API-Coverage-Bug: 372936361 Bug: 372936361 Change-Id: I6d36b77c3ae05fb8d251da9e197e82af187ead0c
This commit is contained in:
parent
121ab15fdf
commit
961528d1cc
3 changed files with 347 additions and 0 deletions
|
|
@ -233,6 +233,32 @@ package android.net {
|
|||
field @NonNull public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
|
||||
}
|
||||
|
||||
@FlaggedApi("com.android.net.flags.ipv6_over_ble") public final class L2capNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public int getHeaderCompression();
|
||||
method public int getPsm();
|
||||
method @Nullable public android.net.MacAddress getRemoteAddress();
|
||||
method public int getRole();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.net.L2capNetworkSpecifier> CREATOR;
|
||||
field public static final int HEADER_COMPRESSION_6LOWPAN = 2; // 0x2
|
||||
field public static final int HEADER_COMPRESSION_ANY = 0; // 0x0
|
||||
field public static final int HEADER_COMPRESSION_NONE = 1; // 0x1
|
||||
field public static final int PSM_ANY = -1; // 0xffffffff
|
||||
field public static final int ROLE_ANY = 0; // 0x0
|
||||
field public static final int ROLE_CLIENT = 1; // 0x1
|
||||
field public static final int ROLE_SERVER = 2; // 0x2
|
||||
}
|
||||
|
||||
public static final class L2capNetworkSpecifier.Builder {
|
||||
ctor public L2capNetworkSpecifier.Builder();
|
||||
method @NonNull public android.net.L2capNetworkSpecifier build();
|
||||
method @NonNull public android.net.L2capNetworkSpecifier.Builder setHeaderCompression(int);
|
||||
method @NonNull public android.net.L2capNetworkSpecifier.Builder setPsm(int);
|
||||
method @NonNull public android.net.L2capNetworkSpecifier.Builder setRemoteAddress(@NonNull android.net.MacAddress);
|
||||
method @NonNull public android.net.L2capNetworkSpecifier.Builder setRole(int);
|
||||
}
|
||||
|
||||
public class LinkAddress implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public java.net.InetAddress getAddress();
|
||||
|
|
|
|||
275
framework/src/android/net/L2capNetworkSpecifier.java
Normal file
275
framework/src/android/net/L2capNetworkSpecifier.java
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net;
|
||||
|
||||
import android.annotation.FlaggedApi;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.net.flags.Flags;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A {@link NetworkSpecifier} used to identify an L2CAP network.
|
||||
*
|
||||
* An L2CAP network is not symmetrical, meaning there exists both a server (Bluetooth peripheral)
|
||||
* and a client (Bluetooth central) node. This specifier contains information required to request or
|
||||
* reserve an L2CAP network.
|
||||
*
|
||||
* An L2CAP server network allocates a PSM to be advertised to the client. Therefore, the server
|
||||
* network must always be reserved using {@link ConnectivityManager#reserveNetwork}. The subsequent
|
||||
* {@link ConnectivityManager.NetworkCallback#onReserved(NetworkCapabilities)} includes information
|
||||
* (i.e. the PSM) for the server to advertise to the client.
|
||||
* Under the hood, an L2CAP server network is represented by a {@link
|
||||
* android.bluetooth.BluetoothServerSocket} which can, in theory, accept many connections. However,
|
||||
* before Android 15 Bluetooth APIs do not expose the channel ID, so these connections are
|
||||
* indistinguishable. In practice, this means that network matching semantics in {@link
|
||||
* ConnectivityService} will tear down all but the first connection.
|
||||
*
|
||||
* The L2cap client network can be connected using {@link ConnectivityManager#requestNetwork}
|
||||
* including passing in the relevant information (i.e. PSM and destination MAC address) using the
|
||||
* {@link L2capNetworkSpecifier}.
|
||||
*
|
||||
*/
|
||||
@FlaggedApi(Flags.FLAG_IPV6_OVER_BLE)
|
||||
public final class L2capNetworkSpecifier extends NetworkSpecifier implements Parcelable {
|
||||
/** Accept any role. */
|
||||
public static final int ROLE_ANY = 0;
|
||||
/** Specifier describes a client network. */
|
||||
public static final int ROLE_CLIENT = 1;
|
||||
/** Specifier describes a server network. */
|
||||
public static final int ROLE_SERVER = 2;
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(flag = false, prefix = "ROLE_", value = {
|
||||
ROLE_ANY,
|
||||
ROLE_CLIENT,
|
||||
ROLE_SERVER
|
||||
})
|
||||
public @interface Role {}
|
||||
/** Role used to distinguish client from server networks. */
|
||||
@Role
|
||||
private final int mRole;
|
||||
|
||||
/** Accept any form of header compression. */
|
||||
public static final int HEADER_COMPRESSION_ANY = 0;
|
||||
/** Do not compress packets on this network. */
|
||||
public static final int HEADER_COMPRESSION_NONE = 1;
|
||||
/** Use 6lowpan header compression as specified in rfc6282. */
|
||||
public static final int HEADER_COMPRESSION_6LOWPAN = 2;
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(flag = false, prefix = "HEADER_COMPRESSION_", value = {
|
||||
HEADER_COMPRESSION_ANY,
|
||||
HEADER_COMPRESSION_NONE,
|
||||
HEADER_COMPRESSION_6LOWPAN
|
||||
})
|
||||
public @interface HeaderCompression {}
|
||||
/** Header compression mechanism used on this network. */
|
||||
@HeaderCompression
|
||||
private final int mHeaderCompression;
|
||||
|
||||
/**
|
||||
* The MAC address of the remote.
|
||||
*/
|
||||
@Nullable
|
||||
private final MacAddress mRemoteAddress;
|
||||
|
||||
/** Match any PSM. */
|
||||
public static final int PSM_ANY = -1;
|
||||
|
||||
/** The Bluetooth L2CAP Protocol Service Multiplexer (PSM). */
|
||||
private final int mPsm;
|
||||
|
||||
private L2capNetworkSpecifier(Parcel in) {
|
||||
mRole = in.readInt();
|
||||
mHeaderCompression = in.readInt();
|
||||
mRemoteAddress = in.readParcelable(getClass().getClassLoader());
|
||||
mPsm = in.readInt();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public L2capNetworkSpecifier(@Role int role, @HeaderCompression int headerCompression,
|
||||
MacAddress remoteAddress, int psm) {
|
||||
mRole = role;
|
||||
mHeaderCompression = headerCompression;
|
||||
mRemoteAddress = remoteAddress;
|
||||
mPsm = psm;
|
||||
}
|
||||
|
||||
/** Returns the role to be used for this network. */
|
||||
@Role
|
||||
public int getRole() {
|
||||
return mRole;
|
||||
}
|
||||
|
||||
/** Returns the compression mechanism for this network. */
|
||||
@HeaderCompression
|
||||
public int getHeaderCompression() {
|
||||
return mHeaderCompression;
|
||||
}
|
||||
|
||||
/** Returns the remote MAC address for this network to connect to. */
|
||||
public @Nullable MacAddress getRemoteAddress() {
|
||||
return mRemoteAddress;
|
||||
}
|
||||
|
||||
/** Returns the PSM for this network to connect to. */
|
||||
public int getPsm() {
|
||||
return mPsm;
|
||||
}
|
||||
|
||||
/** A builder class for L2capNetworkSpecifier. */
|
||||
public static final class Builder {
|
||||
@Role
|
||||
private int mRole;
|
||||
@HeaderCompression
|
||||
private int mHeaderCompression;
|
||||
@Nullable
|
||||
private MacAddress mRemoteAddress;
|
||||
private int mPsm = PSM_ANY;
|
||||
|
||||
/**
|
||||
* Set the role to use for this network.
|
||||
*
|
||||
* @param role the role to use.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setRole(@Role int role) {
|
||||
mRole = role;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the header compression mechanism to use for this network.
|
||||
*
|
||||
* @param headerCompression the header compression mechanism to use.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setHeaderCompression(@HeaderCompression int headerCompression) {
|
||||
mHeaderCompression = headerCompression;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the remote address for the client to connect to.
|
||||
*
|
||||
* Only valid for client networks. A null MacAddress matches *any* MacAddress.
|
||||
*
|
||||
* @param remoteAddress the MAC address to connect to.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setRemoteAddress(@NonNull MacAddress remoteAddress) {
|
||||
Objects.requireNonNull(remoteAddress);
|
||||
mRemoteAddress = remoteAddress;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the PSM for the client to connect to.
|
||||
*
|
||||
* Can only be configured on client networks.
|
||||
*
|
||||
* @param psm the Protocol Service Multiplexer (PSM) to connect to.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setPsm(int psm) {
|
||||
mPsm = psm;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Create the L2capNetworkSpecifier object. */
|
||||
@NonNull
|
||||
public L2capNetworkSpecifier build() {
|
||||
// TODO: throw an exception for combinations that cannot be supported.
|
||||
return new L2capNetworkSpecifier(mRole, mHeaderCompression, mRemoteAddress, mPsm);
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public boolean canBeSatisfiedBy(NetworkSpecifier other) {
|
||||
// TODO: implement matching semantics.
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
@Nullable
|
||||
public NetworkSpecifier redact() {
|
||||
// Redact the remote MAC address and the PSM (for non-server roles).
|
||||
final NetworkSpecifier redactedSpecifier = new Builder()
|
||||
.setRole(mRole)
|
||||
.setHeaderCompression(mHeaderCompression)
|
||||
// TODO: consider not redacting the specifier in onReserved, so the redaction can be
|
||||
// more strict (i.e. the PSM could always be redacted).
|
||||
.setPsm(mRole == ROLE_SERVER ? mPsm : PSM_ANY)
|
||||
.build();
|
||||
return redactedSpecifier;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mRole, mHeaderCompression, mRemoteAddress, mPsm);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof L2capNetworkSpecifier)) return false;
|
||||
|
||||
final L2capNetworkSpecifier rhs = (L2capNetworkSpecifier) obj;
|
||||
return mRole == rhs.mRole
|
||||
&& mHeaderCompression == rhs.mHeaderCompression
|
||||
&& Objects.equals(mRemoteAddress, rhs.mRemoteAddress)
|
||||
&& mPsm == rhs.mPsm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeInt(mRole);
|
||||
dest.writeInt(mHeaderCompression);
|
||||
dest.writeParcelable(mRemoteAddress, flags);
|
||||
dest.writeInt(mPsm);
|
||||
}
|
||||
|
||||
public static final @NonNull Creator<L2capNetworkSpecifier> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public L2capNetworkSpecifier createFromParcel(Parcel in) {
|
||||
return new L2capNetworkSpecifier(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public L2capNetworkSpecifier[] newArray(int size) {
|
||||
return new L2capNetworkSpecifier[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.cts
|
||||
|
||||
import android.net.L2capNetworkSpecifier
|
||||
import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN
|
||||
import android.net.L2capNetworkSpecifier.ROLE_CLIENT
|
||||
import android.net.MacAddress
|
||||
import android.os.Build
|
||||
import com.android.testutils.ConnectivityModuleTest
|
||||
import com.android.testutils.DevSdkIgnoreRule
|
||||
import com.android.testutils.DevSdkIgnoreRunner
|
||||
import com.android.testutils.assertParcelingIsLossless
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@ConnectivityModuleTest
|
||||
@RunWith(DevSdkIgnoreRunner::class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
class L2capNetworkSpecifierTest {
|
||||
@Test
|
||||
fun testParcelUnparcel() {
|
||||
val remoteMac = MacAddress.fromString("01:02:03:04:05:06")
|
||||
val specifier = L2capNetworkSpecifier.Builder()
|
||||
.setRole(ROLE_CLIENT)
|
||||
.setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
|
||||
.setPsm(42)
|
||||
.setRemoteAddress(remoteMac)
|
||||
.build()
|
||||
assertParcelingIsLossless(specifier)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue