1 /*
2  * Copyright (C) 2020 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 package android.net.vcn;
17 
18 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
19 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
20 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
21 
22 import static com.android.internal.annotations.VisibleForTesting.Visibility;
23 import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
24 import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
25 
26 import android.annotation.IntDef;
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.content.Context;
30 import android.net.NetworkCapabilities;
31 import android.net.NetworkRequest;
32 import android.os.Parcel;
33 import android.os.Parcelable;
34 import android.os.PersistableBundle;
35 import android.util.ArraySet;
36 import android.util.Log;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.internal.util.Preconditions;
40 import com.android.server.vcn.util.PersistableBundleUtils;
41 
42 import java.lang.annotation.ElementType;
43 import java.lang.annotation.Retention;
44 import java.lang.annotation.RetentionPolicy;
45 import java.lang.annotation.Target;
46 import java.util.ArrayList;
47 import java.util.Collections;
48 import java.util.Iterator;
49 import java.util.Objects;
50 import java.util.Set;
51 
52 /**
53  * This class represents a configuration for a Virtual Carrier Network.
54  *
55  * <p>Each {@link VcnGatewayConnectionConfig} instance added represents a connection that will be
56  * brought up on demand based on active {@link NetworkRequest}(s).
57  *
58  * @see VcnManager for more information on the Virtual Carrier Network feature
59  */
60 public final class VcnConfig implements Parcelable {
61     @NonNull private static final String TAG = VcnConfig.class.getSimpleName();
62 
63     /** @hide */
64     @Retention(RetentionPolicy.SOURCE)
65     @IntDef(
66             prefix = {"TRANSPORT_"},
67             value = {
68                 NetworkCapabilities.TRANSPORT_CELLULAR,
69                 NetworkCapabilities.TRANSPORT_WIFI,
70             })
71     @Target({ElementType.TYPE_USE})
72     public @interface VcnUnderlyingNetworkTransport {}
73 
74     private static final Set<Integer> ALLOWED_TRANSPORTS = new ArraySet<>();
75 
76     static {
77         ALLOWED_TRANSPORTS.add(TRANSPORT_WIFI);
78         ALLOWED_TRANSPORTS.add(TRANSPORT_CELLULAR);
79         ALLOWED_TRANSPORTS.add(TRANSPORT_TEST);
80     }
81 
82     private static final String PACKAGE_NAME_KEY = "mPackageName";
83     @NonNull private final String mPackageName;
84 
85     private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
86     @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
87 
88     private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT =
89             Collections.singleton(TRANSPORT_WIFI);
90     private static final String RESTRICTED_TRANSPORTS_KEY = "mRestrictedTransports";
91     @NonNull private final Set<Integer> mRestrictedTransports;
92 
93     private static final String IS_TEST_MODE_PROFILE_KEY = "mIsTestModeProfile";
94     private final boolean mIsTestModeProfile;
95 
VcnConfig( @onNull String packageName, @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs, @NonNull Set<Integer> restrictedTransports, boolean isTestModeProfile)96     private VcnConfig(
97             @NonNull String packageName,
98             @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs,
99             @NonNull Set<Integer> restrictedTransports,
100             boolean isTestModeProfile) {
101         mPackageName = packageName;
102         mGatewayConnectionConfigs =
103                 Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
104         mRestrictedTransports = Collections.unmodifiableSet(new ArraySet<>(restrictedTransports));
105         mIsTestModeProfile = isTestModeProfile;
106 
107         validate();
108     }
109 
110     /**
111      * Deserializes a VcnConfig from a PersistableBundle.
112      *
113      * @hide
114      */
115     @VisibleForTesting(visibility = Visibility.PRIVATE)
VcnConfig(@onNull PersistableBundle in)116     public VcnConfig(@NonNull PersistableBundle in) {
117         mPackageName = in.getString(PACKAGE_NAME_KEY);
118 
119         final PersistableBundle gatewayConnectionConfigsBundle =
120                 in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY);
121         mGatewayConnectionConfigs =
122                 new ArraySet<>(
123                         PersistableBundleUtils.toList(
124                                 gatewayConnectionConfigsBundle, VcnGatewayConnectionConfig::new));
125 
126         final PersistableBundle restrictedTransportsBundle =
127                 in.getPersistableBundle(RESTRICTED_TRANSPORTS_KEY);
128         if (restrictedTransportsBundle == null) {
129             // RESTRICTED_TRANSPORTS_KEY was added in U and does not exist in VcnConfigs created in
130             // older platforms
131             mRestrictedTransports = RESTRICTED_TRANSPORTS_DEFAULT;
132         } else {
133             mRestrictedTransports =
134                     new ArraySet<Integer>(
135                             PersistableBundleUtils.toList(
136                                     restrictedTransportsBundle, INTEGER_DESERIALIZER));
137         }
138 
139         mIsTestModeProfile = in.getBoolean(IS_TEST_MODE_PROFILE_KEY);
140 
141         validate();
142     }
143 
validate()144     private void validate() {
145         Objects.requireNonNull(mPackageName, "packageName was null");
146         Preconditions.checkCollectionNotEmpty(
147                 mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty");
148 
149         final Iterator<Integer> iterator = mRestrictedTransports.iterator();
150         while (iterator.hasNext()) {
151             final int transport = iterator.next();
152             if (!ALLOWED_TRANSPORTS.contains(transport)) {
153                 iterator.remove();
154                 Log.w(
155                         TAG,
156                         "Found invalid transport "
157                                 + transport
158                                 + " which might be from a new version of VcnConfig");
159             }
160 
161             if (transport == TRANSPORT_TEST && !mIsTestModeProfile) {
162                 throw new IllegalArgumentException(
163                         "Found TRANSPORT_TEST in a non-test-mode profile");
164             }
165         }
166     }
167 
168     /**
169      * Retrieve the package name of the provisioning app.
170      *
171      * @hide
172      */
173     @NonNull
getProvisioningPackageName()174     public String getProvisioningPackageName() {
175         return mPackageName;
176     }
177 
178     /** Retrieves the set of configured GatewayConnection(s). */
179     @NonNull
getGatewayConnectionConfigs()180     public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() {
181         return Collections.unmodifiableSet(mGatewayConnectionConfigs);
182     }
183 
184     /**
185      * Retrieve the transports that will be restricted by the VCN.
186      *
187      * @see Builder#setRestrictedUnderlyingNetworkTransports(Set)
188      */
189     @NonNull
getRestrictedUnderlyingNetworkTransports()190     public Set<@VcnUnderlyingNetworkTransport Integer> getRestrictedUnderlyingNetworkTransports() {
191         return Collections.unmodifiableSet(mRestrictedTransports);
192     }
193 
194     /**
195      * Returns whether or not this VcnConfig is restricted to test networks.
196      *
197      * @hide
198      */
isTestModeProfile()199     public boolean isTestModeProfile() {
200         return mIsTestModeProfile;
201     }
202 
203     /**
204      * Serializes this object to a PersistableBundle.
205      *
206      * @hide
207      */
208     @NonNull
toPersistableBundle()209     public PersistableBundle toPersistableBundle() {
210         final PersistableBundle result = new PersistableBundle();
211 
212         result.putString(PACKAGE_NAME_KEY, mPackageName);
213 
214         final PersistableBundle gatewayConnectionConfigsBundle =
215                 PersistableBundleUtils.fromList(
216                         new ArrayList<>(mGatewayConnectionConfigs),
217                         VcnGatewayConnectionConfig::toPersistableBundle);
218         result.putPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY, gatewayConnectionConfigsBundle);
219 
220         final PersistableBundle restrictedTransportsBundle =
221                 PersistableBundleUtils.fromList(
222                         new ArrayList<>(mRestrictedTransports), INTEGER_SERIALIZER);
223         result.putPersistableBundle(RESTRICTED_TRANSPORTS_KEY, restrictedTransportsBundle);
224 
225         result.putBoolean(IS_TEST_MODE_PROFILE_KEY, mIsTestModeProfile);
226 
227         return result;
228     }
229 
230     @Override
hashCode()231     public int hashCode() {
232         return Objects.hash(
233                 mPackageName, mGatewayConnectionConfigs, mRestrictedTransports, mIsTestModeProfile);
234     }
235 
236     @Override
equals(@ullable Object other)237     public boolean equals(@Nullable Object other) {
238         if (!(other instanceof VcnConfig)) {
239             return false;
240         }
241 
242         final VcnConfig rhs = (VcnConfig) other;
243         return mPackageName.equals(rhs.mPackageName)
244                 && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs)
245                 && mRestrictedTransports.equals(rhs.mRestrictedTransports)
246                 && mIsTestModeProfile == rhs.mIsTestModeProfile;
247     }
248 
249     // Parcelable methods
250 
251     @Override
describeContents()252     public int describeContents() {
253         return 0;
254     }
255 
256     @Override
writeToParcel(@onNull Parcel out, int flags)257     public void writeToParcel(@NonNull Parcel out, int flags) {
258         out.writeParcelable(toPersistableBundle(), flags);
259     }
260 
261     @NonNull
262     public static final Parcelable.Creator<VcnConfig> CREATOR =
263             new Parcelable.Creator<VcnConfig>() {
264                 @NonNull
265                 public VcnConfig createFromParcel(Parcel in) {
266                     return new VcnConfig((PersistableBundle) in.readParcelable(null, android.os.PersistableBundle.class));
267                 }
268 
269                 @NonNull
270                 public VcnConfig[] newArray(int size) {
271                     return new VcnConfig[size];
272                 }
273             };
274 
275     /** This class is used to incrementally build {@link VcnConfig} objects. */
276     public static final class Builder {
277         @NonNull private final String mPackageName;
278 
279         @NonNull
280         private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();
281 
282         @NonNull private final Set<Integer> mRestrictedTransports = new ArraySet<>();
283 
284         private boolean mIsTestModeProfile = false;
285 
Builder(@onNull Context context)286         public Builder(@NonNull Context context) {
287             Objects.requireNonNull(context, "context was null");
288 
289             mPackageName = context.getOpPackageName();
290             mRestrictedTransports.addAll(RESTRICTED_TRANSPORTS_DEFAULT);
291         }
292 
293         /**
294          * Adds a configuration for an individual gateway connection.
295          *
296          * @param gatewayConnectionConfig the configuration for an individual gateway connection
297          * @return this {@link Builder} instance, for chaining
298          * @throws IllegalArgumentException if a VcnGatewayConnectionConfig has already been set for
299          *     this {@link VcnConfig} with the same GatewayConnection name (as returned via {@link
300          *     VcnGatewayConnectionConfig#getGatewayConnectionName()}).
301          */
302         @NonNull
addGatewayConnectionConfig( @onNull VcnGatewayConnectionConfig gatewayConnectionConfig)303         public Builder addGatewayConnectionConfig(
304                 @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) {
305             Objects.requireNonNull(gatewayConnectionConfig, "gatewayConnectionConfig was null");
306 
307             for (final VcnGatewayConnectionConfig vcnGatewayConnectionConfig :
308                     mGatewayConnectionConfigs) {
309                 if (vcnGatewayConnectionConfig
310                         .getGatewayConnectionName()
311                         .equals(gatewayConnectionConfig.getGatewayConnectionName())) {
312                     throw new IllegalArgumentException(
313                             "GatewayConnection for specified name already exists");
314                 }
315             }
316 
317             mGatewayConnectionConfigs.add(gatewayConnectionConfig);
318             return this;
319         }
320 
validateRestrictedTransportsOrThrow(Set<Integer> restrictedTransports)321         private void validateRestrictedTransportsOrThrow(Set<Integer> restrictedTransports) {
322             Objects.requireNonNull(restrictedTransports, "transports was null");
323 
324             for (int transport : restrictedTransports) {
325                 if (!ALLOWED_TRANSPORTS.contains(transport)) {
326                     throw new IllegalArgumentException("Invalid transport " + transport);
327                 }
328             }
329         }
330 
331         /**
332          * Sets transports that will be restricted by the VCN.
333          *
334          * <p>In general, apps will not be able to bind to, or use a restricted network. In other
335          * words, unless the network type is marked restricted, any app can opt to use underlying
336          * networks, instead of through the VCN.
337          *
338          * @param transports transports that will be restricted by VCN. Networks that include any of
339          *     the transports will be marked as restricted. {@link
340          *     NetworkCapabilities#TRANSPORT_WIFI} is marked restricted by default.
341          * @return this {@link Builder} instance, for chaining
342          * @throws IllegalArgumentException if the input contains unsupported transport types.
343          * @see NetworkCapabilities#NET_CAPABILITY_NOT_RESTRICTED
344          */
345         @NonNull
setRestrictedUnderlyingNetworkTransports( @onNull Set<@VcnUnderlyingNetworkTransport Integer> transports)346         public Builder setRestrictedUnderlyingNetworkTransports(
347                 @NonNull Set<@VcnUnderlyingNetworkTransport Integer> transports) {
348             validateRestrictedTransportsOrThrow(transports);
349 
350             mRestrictedTransports.clear();
351             mRestrictedTransports.addAll(transports);
352             return this;
353         }
354 
355         /**
356          * Restricts this VcnConfig to matching with test networks (only).
357          *
358          * <p>This method is for testing only, and must not be used by apps. Calling {@link
359          * VcnManager#setVcnConfig(ParcelUuid, VcnConfig)} with a VcnConfig where test-network usage
360          * is enabled will require the MANAGE_TEST_NETWORKS permission.
361          *
362          * @return this {@link Builder} instance, for chaining
363          * @hide
364          */
365         @NonNull
setIsTestModeProfile()366         public Builder setIsTestModeProfile() {
367             mIsTestModeProfile = true;
368             return this;
369         }
370 
371         /**
372          * Builds and validates the VcnConfig.
373          *
374          * @return an immutable VcnConfig instance
375          */
376         @NonNull
build()377         public VcnConfig build() {
378             return new VcnConfig(
379                     mPackageName,
380                     mGatewayConnectionConfigs,
381                     mRestrictedTransports,
382                     mIsTestModeProfile);
383         }
384     }
385 }
386