/*
* Copyright (C) 2023 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 com.android.server.connectivity;
import android.annotation.NonNull;
import android.net.NetworkCapabilities;
import android.util.Log;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayDeque;
/**
* The class for parsing and checking the self-declared application network capabilities.
*
* ApplicationSelfCertifiedNetworkCapabilities is an immutable class that
* can parse the self-declared application network capabilities in the application resources. The
* class also provides a helper method to check whether the requested network capabilities
* already self-declared.
*/
public final class ApplicationSelfCertifiedNetworkCapabilities {
public static final String PRIORITIZE_LATENCY = "NET_CAPABILITY_PRIORITIZE_LATENCY";
public static final String PRIORITIZE_BANDWIDTH = "NET_CAPABILITY_PRIORITIZE_BANDWIDTH";
private static final String TAG =
ApplicationSelfCertifiedNetworkCapabilities.class.getSimpleName();
private static final String NETWORK_CAPABILITIES_DECLARATION_TAG =
"network-capabilities-declaration";
private static final String USES_NETWORK_CAPABILITY_TAG = "uses-network-capability";
private static final String NAME_TAG = "name";
private long mRequestedNetworkCapabilities = 0;
/**
* Creates {@link ApplicationSelfCertifiedNetworkCapabilities} from a xml parser.
*
*
Here is an example of the xml syntax:
*
*
* {@code
*
*
*
*
* }
*
*
*
* @param xmlParser The underlying {@link XmlPullParser} that will read the xml.
* @return An ApplicationSelfCertifiedNetworkCapabilities object.
* @throws InvalidTagException if the capabilities in xml config contains invalid tag.
* @throws XmlPullParserException if xml parsing failed.
* @throws IOException if unable to read the xml file properly.
*/
@NonNull
public static ApplicationSelfCertifiedNetworkCapabilities createFromXml(
@NonNull final XmlPullParser xmlParser)
throws InvalidTagException, XmlPullParserException, IOException {
return new ApplicationSelfCertifiedNetworkCapabilities(parseXml(xmlParser));
}
private static long parseXml(@NonNull final XmlPullParser xmlParser)
throws InvalidTagException, XmlPullParserException, IOException {
long requestedNetworkCapabilities = 0;
final ArrayDeque openTags = new ArrayDeque<>();
while (checkedNextTag(xmlParser, openTags) != XmlPullParser.START_TAG) {
continue;
}
// Validates the tag is "network-capabilities-declaration"
if (!xmlParser.getName().equals(NETWORK_CAPABILITIES_DECLARATION_TAG)) {
throw new InvalidTagException("Invalid tag: " + xmlParser.getName());
}
checkedNextTag(xmlParser, openTags);
int eventType = xmlParser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
// USES_NETWORK_CAPABILITY_TAG should directly be declared under
// NETWORK_CAPABILITIES_DECLARATION_TAG.
if (xmlParser.getName().equals(USES_NETWORK_CAPABILITY_TAG)
&& openTags.size() == 1) {
int capability = parseDeclarationTag(xmlParser);
if (capability >= 0) {
requestedNetworkCapabilities |= 1L << capability;
}
} else {
Log.w(TAG, "Unknown tag: " + xmlParser.getName() + " ,tags stack size: "
+ openTags.size());
}
break;
default:
break;
}
eventType = checkedNextTag(xmlParser, openTags);
}
// Checks all the tags are parsed.
if (!openTags.isEmpty()) {
throw new InvalidTagException("Unbalanced tag: " + openTags.peek());
}
return requestedNetworkCapabilities;
}
private static int parseDeclarationTag(@NonNull final XmlPullParser xmlParser) {
String name = null;
for (int i = 0; i < xmlParser.getAttributeCount(); i++) {
final String attrName = xmlParser.getAttributeName(i);
if (attrName.equals(NAME_TAG)) {
name = xmlParser.getAttributeValue(i);
} else {
Log.w(TAG, "Unknown attribute name: " + attrName);
}
}
if (name != null) {
switch (name) {
case PRIORITIZE_BANDWIDTH:
return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
case PRIORITIZE_LATENCY:
return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
default:
Log.w(TAG, "Unknown capability declaration name: " + name);
}
} else {
Log.w(TAG, "uses-network-capability name must be specified");
}
// Invalid capability
return -1;
}
private static int checkedNextTag(@NonNull final XmlPullParser xmlParser,
@NonNull final ArrayDeque openTags)
throws XmlPullParserException, IOException, InvalidTagException {
if (xmlParser.getEventType() == XmlPullParser.START_TAG) {
openTags.addFirst(xmlParser.getName());
} else if (xmlParser.getEventType() == XmlPullParser.END_TAG) {
if (!openTags.isEmpty() && openTags.peekFirst().equals(xmlParser.getName())) {
openTags.removeFirst();
} else {
throw new InvalidTagException("Unbalanced tag: " + xmlParser.getName());
}
}
return xmlParser.next();
}
private ApplicationSelfCertifiedNetworkCapabilities(long requestedNetworkCapabilities) {
mRequestedNetworkCapabilities = requestedNetworkCapabilities;
}
/**
* Enforces self-certified capabilities are declared.
*
* @param networkCapabilities the input NetworkCapabilities to check against.
* @throws SecurityException if the capabilities are not properly self-declared.
*/
public void enforceSelfCertifiedNetworkCapabilitiesDeclared(
@NonNull final NetworkCapabilities networkCapabilities) {
if (networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
&& !hasPrioritizeBandwidth()) {
throw new SecurityException(
"Missing " + ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH
+ " declaration");
}
if (networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
&& !hasPrioritizeLatency()) {
throw new SecurityException(
"Missing " + ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY
+ " declaration");
}
}
/**
* Checks if NET_CAPABILITY_PRIORITIZE_LATENCY is declared.
*/
private boolean hasPrioritizeLatency() {
return (mRequestedNetworkCapabilities & (1L
<< NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)) != 0;
}
/**
* Checks if NET_CAPABILITY_PRIORITIZE_BANDWIDTH is declared.
*/
private boolean hasPrioritizeBandwidth() {
return (mRequestedNetworkCapabilities & (1L
<< NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)) != 0;
}
}