From bd237be4563c5ae24cdcb318bea747bb853a07aa Mon Sep 17 00:00:00 2001
From: Max Loh <mloh@google.com>
Date: Mon, 18 Mar 2024 15:31:04 -0700
Subject: [PATCH] ASL validation logic

Adds validation logic for isSharingOptional, isCollectionOptional, and various expected elements in the input XML file.

Bug: 287487923
Test: TODO in future CLs
Change-Id: I0a2261ec3c71a1d2df977810d065dfc5a4dda5e3
---
 .../aslgen/java/com/android/aslgen/Main.java  |  7 ++-
 .../android/asllib/AndroidSafetyLabel.java    | 10 ++--
 .../asllib/AndroidSafetyLabelFactory.java     |  5 +-
 .../asllib/AslMarshallableFactory.java        |  4 +-
 .../android/asllib/DataCategoryFactory.java   |  8 ++-
 .../com/android/asllib/DataLabelsFactory.java | 55 +++++++++++++++++--
 .../com/android/asllib/DataTypeFactory.java   |  4 +-
 .../android/asllib/SafetyLabelsFactory.java   |  4 +-
 .../lib/java/com/android/asllib/XmlUtils.java | 24 +++++---
 .../asllib/util/MalformedXmlException.java    | 33 +++++++++++
 10 files changed, 130 insertions(+), 24 deletions(-)
 create mode 100644 tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/MalformedXmlException.java

diff --git a/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java b/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java
index df003b6aeab2..fb7a6ab42d95 100644
--- a/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java
+++ b/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java
@@ -18,6 +18,7 @@ package com.android.aslgen;
 
 import com.android.asllib.AndroidSafetyLabel;
 import com.android.asllib.AndroidSafetyLabel.Format;
+import com.android.asllib.util.MalformedXmlException;
 
 import org.xml.sax.SAXException;
 
@@ -32,7 +33,11 @@ public class Main {
 
     /** Takes the options to make file conversion. */
     public static void main(String[] args)
-            throws IOException, ParserConfigurationException, SAXException, TransformerException {
+            throws IOException,
+                    ParserConfigurationException,
+                    SAXException,
+                    TransformerException,
+                    MalformedXmlException {
 
         String inFile = null;
         String outFile = null;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
index 0f7ce6894063..bc8063ef7b5f 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
@@ -16,6 +16,8 @@
 
 package com.android.asllib;
 
+import com.android.asllib.util.MalformedXmlException;
+
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.xml.sax.SAXException;
@@ -53,7 +55,7 @@ public class AndroidSafetyLabel implements AslMarshallable {
     /** Reads a {@link AndroidSafetyLabel} from an {@link InputStream}. */
     // TODO(b/329902686): Support parsing from on-device.
     public static AndroidSafetyLabel readFromStream(InputStream in, Format format)
-            throws IOException, ParserConfigurationException, SAXException {
+            throws IOException, ParserConfigurationException, SAXException, MalformedXmlException {
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         factory.setNamespaceAware(true);
         Document document = factory.newDocumentBuilder().parse(in);
@@ -65,9 +67,9 @@ public class AndroidSafetyLabel implements AslMarshallable {
 
                 return new AndroidSafetyLabelFactory()
                         .createFromHrElements(
-                                XmlUtils.asElementList(
-                                        document.getElementsByTagName(
-                                                XmlUtils.HR_TAG_APP_METADATA_BUNDLES)));
+                                List.of(
+                                        XmlUtils.getSingleElement(
+                                                document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES)));
             case ON_DEVICE:
                 throw new IllegalArgumentException(
                         "Parsing from on-device format is not supported at this time.");
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
index 9b0f05b0c633..7e7fcf9c08ba 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
@@ -16,6 +16,8 @@
 
 package com.android.asllib;
 
+import com.android.asllib.util.MalformedXmlException;
+
 import org.w3c.dom.Element;
 
 import java.util.List;
@@ -24,7 +26,8 @@ public class AndroidSafetyLabelFactory implements AslMarshallableFactory<Android
 
     /** Creates an {@link AndroidSafetyLabel} from human-readable DOM element */
     @Override
-    public AndroidSafetyLabel createFromHrElements(List<Element> appMetadataBundles) {
+    public AndroidSafetyLabel createFromHrElements(List<Element> appMetadataBundles)
+            throws MalformedXmlException {
         Element appMetadataBundlesEle = XmlUtils.getSingleElement(appMetadataBundles);
         Element safetyLabelsEle =
                 XmlUtils.getSingleChildElement(
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java
index b607353791ff..b8f9f0ef6235 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java
@@ -16,6 +16,8 @@
 
 package com.android.asllib;
 
+import com.android.asllib.util.MalformedXmlException;
+
 import org.w3c.dom.Element;
 
 import java.util.List;
@@ -23,5 +25,5 @@ import java.util.List;
 public interface AslMarshallableFactory<T extends AslMarshallable> {
 
     /** Creates an {@link AslMarshallableFactory} from human-readable DOM element */
-    T createFromHrElements(List<Element> elements);
+    T createFromHrElements(List<Element> elements) throws MalformedXmlException;
 }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
index 5a52591eaf8c..d9463452d7bc 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
@@ -16,6 +16,8 @@
 
 package com.android.asllib;
 
+import com.android.asllib.util.MalformedXmlException;
+
 import org.w3c.dom.Element;
 
 import java.util.HashMap;
@@ -24,12 +26,16 @@ import java.util.Map;
 
 public class DataCategoryFactory implements AslMarshallableFactory<DataCategory> {
     @Override
-    public DataCategory createFromHrElements(List<Element> elements) {
+    public DataCategory createFromHrElements(List<Element> elements) throws MalformedXmlException {
         String categoryName = null;
         Map<String, DataType> dataTypeMap = new HashMap<String, DataType>();
         for (Element ele : elements) {
             categoryName = ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY);
             String dataTypeName = ele.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE);
+            if (!DataTypeConstants.getValidDataTypes().contains(dataTypeName)) {
+                throw new MalformedXmlException(
+                        String.format("Unrecognized data type name: %s", dataTypeName));
+            }
             dataTypeMap.put(dataTypeName, new DataTypeFactory().createFromHrElements(List.of(ele)));
         }
 
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
index c758ab923bbf..1adb140f446d 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
@@ -16,6 +16,8 @@
 
 package com.android.asllib;
 
+import com.android.asllib.util.MalformedXmlException;
+
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 
@@ -29,7 +31,7 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
 
     /** Creates a {@link DataLabels} from the human-readable DOM element. */
     @Override
-    public DataLabels createFromHrElements(List<Element> elements) {
+    public DataLabels createFromHrElements(List<Element> elements) throws MalformedXmlException {
         Element ele = XmlUtils.getSingleElement(elements);
         Map<String, DataCategory> dataAccessed =
                 getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_ACCESSED);
@@ -37,13 +39,54 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
                 getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED);
         Map<String, DataCategory> dataShared =
                 getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED);
+
+        // Validate booleans such as isCollectionOptional, isSharingOptional.
+        for (DataCategory dataCategory : dataAccessed.values()) {
+            for (DataType dataType : dataCategory.getDataTypes().values()) {
+                if (dataType.getIsSharingOptional() != null) {
+                    throw new MalformedXmlException(
+                            String.format(
+                                    "isSharingOptional was unexpectedly defined on a DataType"
+                                            + " belonging to data accessed: %s",
+                                    dataType.getDataTypeName()));
+                }
+                if (dataType.getIsCollectionOptional() != null) {
+                    throw new MalformedXmlException(
+                            String.format(
+                                    "isCollectionOptional was unexpectedly defined on a DataType"
+                                            + " belonging to data accessed: %s",
+                                    dataType.getDataTypeName()));
+                }
+            }
+        }
+        for (DataCategory dataCategory : dataCollected.values()) {
+            for (DataType dataType : dataCategory.getDataTypes().values()) {
+                if (dataType.getIsSharingOptional() != null) {
+                    throw new MalformedXmlException(
+                            String.format(
+                                    "isSharingOptional was unexpectedly defined on a DataType"
+                                            + " belonging to data collected: %s",
+                                    dataType.getDataTypeName()));
+                }
+            }
+        }
+        for (DataCategory dataCategory : dataShared.values()) {
+            for (DataType dataType : dataCategory.getDataTypes().values()) {
+                if (dataType.getIsCollectionOptional() != null) {
+                    throw new MalformedXmlException(
+                            String.format(
+                                    "isCollectionOptional was unexpectedly defined on a DataType"
+                                            + " belonging to data shared: %s",
+                                    dataType.getDataTypeName()));
+                }
+            }
+        }
+
         return new DataLabels(dataAccessed, dataCollected, dataShared);
     }
 
     private static Map<String, DataCategory> getDataCategoriesWithTag(
-            Element dataLabelsEle, String dataCategoryUsageTypeTag) {
-        Map<String, Map<String, DataType>> dataTypeMap =
-                new HashMap<String, Map<String, DataType>>();
+            Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException {
         NodeList dataUsedNodeList = dataLabelsEle.getElementsByTagName(dataCategoryUsageTypeTag);
         Map<String, DataCategory> dataCategoryMap = new HashMap<String, DataCategory>();
 
@@ -51,6 +94,10 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> {
         for (int i = 0; i < dataUsedNodeList.getLength(); i++) {
             Element dataUsedEle = (Element) dataUsedNodeList.item(i);
             String dataCategoryName = dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY);
+            if (!DataCategoryConstants.getValidDataCategories().contains(dataCategoryName)) {
+                throw new MalformedXmlException(
+                        String.format("Unrecognized category name: %s", dataCategoryName));
+            }
             dataCategoryNames.add(dataCategoryName);
         }
         for (String dataCategoryName : dataCategoryNames) {
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
index 99f8a8b1b152..e3d1587d860c 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
@@ -35,10 +35,10 @@ public class DataTypeFactory implements AslMarshallableFactory<DataType> {
                         .collect(Collectors.toUnmodifiableSet());
         Boolean isCollectionOptional =
                 XmlUtils.fromString(
-                        hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL));
+                        hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL));
         Boolean isSharingOptional =
                 XmlUtils.fromString(
-                        hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL));
+                        hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL));
         Boolean ephemeral =
                 XmlUtils.fromString(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_EPHEMERAL));
         return new DataType(
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
index 68e83fe30db1..80b9f5783b9d 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
@@ -16,6 +16,8 @@
 
 package com.android.asllib;
 
+import com.android.asllib.util.MalformedXmlException;
+
 import org.w3c.dom.Element;
 
 import java.util.List;
@@ -24,7 +26,7 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels>
 
     /** Creates a {@link SafetyLabels} from the human-readable DOM element. */
     @Override
-    public SafetyLabels createFromHrElements(List<Element> elements) {
+    public SafetyLabels createFromHrElements(List<Element> elements) throws MalformedXmlException {
         Element safetyLabelsEle = XmlUtils.getSingleElement(elements);
         Long version;
         try {
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
index 3c89a308036f..3bc9ccc2138b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
@@ -16,6 +16,8 @@
 
 package com.android.asllib;
 
+import com.android.asllib.util.MalformedXmlException;
+
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
@@ -61,30 +63,34 @@ public class XmlUtils {
     public static final String FALSE_STR = "false";
 
     /** Gets the single top-level {@link Element} having the {@param tagName}. */
-    public static Element getSingleElement(Document doc, String tagName) {
+    public static Element getSingleElement(Document doc, String tagName)
+            throws MalformedXmlException {
         var elements = doc.getElementsByTagName(tagName);
-        return getSingleElement(elements);
+        return getSingleElement(elements, tagName);
     }
 
     /**
      * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
      */
-    public static Element getSingleChildElement(Element parentEle, String tagName) {
+    public static Element getSingleChildElement(Element parentEle, String tagName)
+            throws MalformedXmlException {
         var elements = parentEle.getElementsByTagName(tagName);
-        return getSingleElement(elements);
+        return getSingleElement(elements, tagName);
     }
 
     /** Gets the single {@link Element} from {@param elements} */
-    public static Element getSingleElement(NodeList elements) {
+    public static Element getSingleElement(NodeList elements, String tagName)
+            throws MalformedXmlException {
         if (elements.getLength() != 1) {
-            throw new IllegalArgumentException(
+            throw new MalformedXmlException(
                     String.format(
-                            "Expected 1 element in NodeList but got %s.", elements.getLength()));
+                            "Expected 1 element \"%s\" in NodeList but got %s.",
+                            tagName, elements.getLength()));
         }
         var elementAsNode = elements.item(0);
         if (!(elementAsNode instanceof Element)) {
-            throw new IllegalStateException(
-                    String.format("%s was not an element.", elementAsNode.getNodeName()));
+            throw new MalformedXmlException(
+                    String.format("%s was not a valid XML element.", tagName));
         }
         return ((Element) elementAsNode);
     }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/MalformedXmlException.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/MalformedXmlException.java
new file mode 100644
index 000000000000..216df56c453e
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/MalformedXmlException.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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.asllib.util;
+
+public class MalformedXmlException extends Exception {
+    /** Constructs an {@code MalformedXmlException} with no detail message. */
+    public MalformedXmlException() {
+        super();
+    }
+
+    /**
+     * Constructs an {@code MalformedXmlException} with the specified detail message.
+     *
+     * @param s the detail message.
+     */
+    public MalformedXmlException(String s) {
+        super(s);
+    }
+}
-- 
GitLab