• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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