1 /*
2  * Copyright (C) 2023 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 com.android.server.connectivity;
18 
19 
20 import android.annotation.NonNull;
21 import android.net.NetworkCapabilities;
22 import android.util.Log;
23 
24 import org.xmlpull.v1.XmlPullParser;
25 import org.xmlpull.v1.XmlPullParserException;
26 
27 import java.io.IOException;
28 import java.util.ArrayDeque;
29 
30 
31 /**
32  * The class for parsing and checking the self-declared application network capabilities.
33  *
34  * ApplicationSelfCertifiedNetworkCapabilities is an immutable class that
35  * can parse the self-declared application network capabilities in the application resources. The
36  * class also provides a helper method to check whether the requested network capabilities
37  * already self-declared.
38  */
39 public final class ApplicationSelfCertifiedNetworkCapabilities {
40 
41     public static final String PRIORITIZE_LATENCY = "NET_CAPABILITY_PRIORITIZE_LATENCY";
42     public static final String PRIORITIZE_BANDWIDTH = "NET_CAPABILITY_PRIORITIZE_BANDWIDTH";
43 
44     private static final String TAG =
45             ApplicationSelfCertifiedNetworkCapabilities.class.getSimpleName();
46     private static final String NETWORK_CAPABILITIES_DECLARATION_TAG =
47             "network-capabilities-declaration";
48     private static final String USES_NETWORK_CAPABILITY_TAG = "uses-network-capability";
49     private static final String NAME_TAG = "name";
50 
51     private long mRequestedNetworkCapabilities = 0;
52 
53     /**
54      * Creates {@link ApplicationSelfCertifiedNetworkCapabilities} from a xml parser.
55      *
56      * <p> Here is an example of the xml syntax:
57      *
58      * <pre>
59      * {@code
60      *  <network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
61      *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
62      *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
63      * </network-capabilities-declaration>
64      * }
65      * </pre>
66      * <p>
67      *
68      * @param xmlParser The underlying {@link XmlPullParser} that will read the xml.
69      * @return An ApplicationSelfCertifiedNetworkCapabilities object.
70      * @throws InvalidTagException    if the capabilities in xml config contains invalid tag.
71      * @throws XmlPullParserException if xml parsing failed.
72      * @throws IOException            if unable to read the xml file properly.
73      */
74     @NonNull
createFromXml( @onNull final XmlPullParser xmlParser)75     public static ApplicationSelfCertifiedNetworkCapabilities createFromXml(
76             @NonNull final XmlPullParser xmlParser)
77             throws InvalidTagException, XmlPullParserException, IOException {
78         return new ApplicationSelfCertifiedNetworkCapabilities(parseXml(xmlParser));
79     }
80 
parseXml(@onNull final XmlPullParser xmlParser)81     private static long parseXml(@NonNull final XmlPullParser xmlParser)
82             throws InvalidTagException, XmlPullParserException, IOException {
83         long requestedNetworkCapabilities = 0;
84         final ArrayDeque<String> openTags = new ArrayDeque<>();
85 
86         while (checkedNextTag(xmlParser, openTags) != XmlPullParser.START_TAG) {
87             continue;
88         }
89 
90         // Validates the tag is "network-capabilities-declaration"
91         if (!xmlParser.getName().equals(NETWORK_CAPABILITIES_DECLARATION_TAG)) {
92             throw new InvalidTagException("Invalid tag: " + xmlParser.getName());
93         }
94 
95         checkedNextTag(xmlParser, openTags);
96         int eventType = xmlParser.getEventType();
97         while (eventType != XmlPullParser.END_DOCUMENT) {
98             switch (eventType) {
99                 case XmlPullParser.START_TAG:
100                     // USES_NETWORK_CAPABILITY_TAG should directly be declared under
101                     // NETWORK_CAPABILITIES_DECLARATION_TAG.
102                     if (xmlParser.getName().equals(USES_NETWORK_CAPABILITY_TAG)
103                             && openTags.size() == 1) {
104                         int capability = parseDeclarationTag(xmlParser);
105                         if (capability >= 0) {
106                             requestedNetworkCapabilities |= 1L << capability;
107                         }
108                     } else {
109                         Log.w(TAG, "Unknown tag: " + xmlParser.getName() + " ,tags stack size: "
110                                 + openTags.size());
111                     }
112                     break;
113                 default:
114                     break;
115             }
116             eventType = checkedNextTag(xmlParser, openTags);
117         }
118         // Checks all the tags are parsed.
119         if (!openTags.isEmpty()) {
120             throw new InvalidTagException("Unbalanced tag: " + openTags.peek());
121         }
122         return requestedNetworkCapabilities;
123     }
124 
parseDeclarationTag(@onNull final XmlPullParser xmlParser)125     private static int parseDeclarationTag(@NonNull final XmlPullParser xmlParser) {
126         String name = null;
127         for (int i = 0; i < xmlParser.getAttributeCount(); i++) {
128             final String attrName = xmlParser.getAttributeName(i);
129             if (attrName.equals(NAME_TAG)) {
130                 name = xmlParser.getAttributeValue(i);
131             } else {
132                 Log.w(TAG, "Unknown attribute name: " + attrName);
133             }
134         }
135         if (name != null) {
136             switch (name) {
137                 case PRIORITIZE_BANDWIDTH:
138                     return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
139                 case PRIORITIZE_LATENCY:
140                     return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
141                 default:
142                     Log.w(TAG, "Unknown capability declaration name: " + name);
143             }
144         } else {
145             Log.w(TAG, "uses-network-capability name must be specified");
146         }
147         // Invalid capability
148         return -1;
149     }
150 
checkedNextTag(@onNull final XmlPullParser xmlParser, @NonNull final ArrayDeque<String> openTags)151     private static int checkedNextTag(@NonNull final XmlPullParser xmlParser,
152             @NonNull final ArrayDeque<String> openTags)
153             throws XmlPullParserException, IOException, InvalidTagException {
154         if (xmlParser.getEventType() == XmlPullParser.START_TAG) {
155             openTags.addFirst(xmlParser.getName());
156         } else if (xmlParser.getEventType() == XmlPullParser.END_TAG) {
157             if (!openTags.isEmpty() && openTags.peekFirst().equals(xmlParser.getName())) {
158                 openTags.removeFirst();
159             } else {
160                 throw new InvalidTagException("Unbalanced tag: " + xmlParser.getName());
161             }
162         }
163         return xmlParser.next();
164     }
165 
ApplicationSelfCertifiedNetworkCapabilities(long requestedNetworkCapabilities)166     private ApplicationSelfCertifiedNetworkCapabilities(long requestedNetworkCapabilities) {
167         mRequestedNetworkCapabilities = requestedNetworkCapabilities;
168     }
169 
170     /**
171      * Enforces self-certified capabilities are declared.
172      *
173      * @param networkCapabilities the input NetworkCapabilities to check against.
174      * @throws SecurityException if the capabilities are not properly self-declared.
175      */
enforceSelfCertifiedNetworkCapabilitiesDeclared( @onNull final NetworkCapabilities networkCapabilities)176     public void enforceSelfCertifiedNetworkCapabilitiesDeclared(
177             @NonNull final NetworkCapabilities networkCapabilities) {
178         if (networkCapabilities.hasCapability(
179                 NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
180                 && !hasPrioritizeBandwidth()) {
181             throw new SecurityException(
182                     "Missing " + ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH
183                             + " declaration");
184         }
185         if (networkCapabilities.hasCapability(
186                 NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
187                 && !hasPrioritizeLatency()) {
188             throw new SecurityException(
189                     "Missing " + ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY
190                             + " declaration");
191         }
192     }
193 
194     /**
195      * Checks if NET_CAPABILITY_PRIORITIZE_LATENCY is declared.
196      */
hasPrioritizeLatency()197     private boolean hasPrioritizeLatency() {
198         return (mRequestedNetworkCapabilities & (1L
199                 << NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)) != 0;
200     }
201 
202     /**
203      * Checks if NET_CAPABILITY_PRIORITIZE_BANDWIDTH is declared.
204      */
hasPrioritizeBandwidth()205     private boolean hasPrioritizeBandwidth() {
206         return (mRequestedNetworkCapabilities & (1L
207                 << NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)) != 0;
208     }
209 }
210