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 17 package android.util; 18 19 import static android.util.XmlTest.assertNext; 20 import static android.util.XmlTest.buildPersistableBundle; 21 import static android.util.XmlTest.doPersistableBundleRead; 22 import static android.util.XmlTest.doPersistableBundleWrite; 23 import static android.util.XmlTest.doVerifyRead; 24 import static android.util.XmlTest.doVerifyWrite; 25 26 import static org.junit.Assert.assertEquals; 27 import static org.junit.Assert.assertThrows; 28 import static org.junit.Assert.fail; 29 import static org.xmlpull.v1.XmlPullParser.START_TAG; 30 31 import android.os.PersistableBundle; 32 33 import androidx.test.runner.AndroidJUnit4; 34 35 import com.android.modules.utils.TypedXmlPullParser; 36 import com.android.modules.utils.TypedXmlSerializer; 37 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 41 import java.io.ByteArrayInputStream; 42 import java.io.ByteArrayOutputStream; 43 import java.io.File; 44 import java.io.FileInputStream; 45 import java.io.FileOutputStream; 46 import java.io.IOException; 47 import java.io.InputStream; 48 import java.io.OutputStream; 49 import java.nio.charset.StandardCharsets; 50 51 @RunWith(AndroidJUnit4.class) 52 public class BinaryXmlTest { 53 private static final int MAX_UNSIGNED_SHORT = 65_535; 54 55 /** 56 * Verify that we can write and read large numbers of interned 57 * {@link String} values. 58 */ 59 @Test testLargeInterned_Binary()60 public void testLargeInterned_Binary() throws Exception { 61 // We're okay with the tag itself being interned 62 final int count = (1 << 16) - 2; 63 64 final TypedXmlSerializer out = Xml.newBinarySerializer(); 65 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 66 out.setOutput(os, StandardCharsets.UTF_8.name()); 67 out.startTag(null, "tag"); 68 for (int i = 0; i < count; i++) { 69 out.attribute(null, "name" + i, "value"); 70 } 71 out.endTag(null, "tag"); 72 out.flush(); 73 74 final TypedXmlPullParser in = Xml.newBinaryPullParser(); 75 final ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); 76 in.setInput(is, StandardCharsets.UTF_8.name()); 77 assertNext(in, START_TAG, "tag"); 78 assertEquals(count, in.getAttributeCount()); 79 } 80 81 @Test testTranscode_FastToBinary()82 public void testTranscode_FastToBinary() throws Exception { 83 doTranscode(Xml.newFastSerializer(), Xml.newFastPullParser(), 84 Xml.newBinarySerializer(), Xml.newBinaryPullParser()); 85 } 86 87 @Test testTranscode_BinaryToFast()88 public void testTranscode_BinaryToFast() throws Exception { 89 doTranscode(Xml.newBinarySerializer(), Xml.newBinaryPullParser(), 90 Xml.newFastSerializer(), Xml.newFastPullParser()); 91 } 92 93 /** 94 * Verify that a complex {@link PersistableBundle} can be transcoded using 95 * the two given formats with the original structure intact. 96 */ doTranscode(TypedXmlSerializer firstOut, TypedXmlPullParser firstIn, TypedXmlSerializer secondOut, TypedXmlPullParser secondIn)97 private static void doTranscode(TypedXmlSerializer firstOut, TypedXmlPullParser firstIn, 98 TypedXmlSerializer secondOut, TypedXmlPullParser secondIn) throws Exception { 99 final PersistableBundle expected = buildPersistableBundle(); 100 final byte[] firstRaw = doPersistableBundleWrite(firstOut, expected); 101 102 // Perform actual transcoding between the two formats 103 final ByteArrayInputStream is = new ByteArrayInputStream(firstRaw); 104 firstIn.setInput(is, StandardCharsets.UTF_8.name()); 105 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 106 secondOut.setOutput(os, StandardCharsets.UTF_8.name()); 107 Xml.copy(firstIn, secondOut); 108 109 // Yes, this string-based check is fragile, but kindofEquals() is broken 110 // when working with nested objects and arrays 111 final PersistableBundle actual = doPersistableBundleRead(secondIn, os.toByteArray()); 112 assertEquals(expected.toString(), actual.toString()); 113 } 114 115 @Test testResolve_File()116 public void testResolve_File() throws Exception { 117 { 118 final File file = File.createTempFile("fast", ".xml"); 119 try (OutputStream os = new FileOutputStream(file)) { 120 TypedXmlSerializer xml = Xml.newFastSerializer(); 121 xml.setOutput(os, StandardCharsets.UTF_8.name()); 122 doVerifyWrite(xml); 123 } 124 try (InputStream is = new FileInputStream(file)) { 125 doVerifyRead(Xml.resolvePullParser(is)); 126 } 127 } 128 { 129 final File file = File.createTempFile("binary", ".xml"); 130 try (OutputStream os = new FileOutputStream(file)) { 131 TypedXmlSerializer xml = Xml.newBinarySerializer(); 132 xml.setOutput(os, StandardCharsets.UTF_8.name()); 133 doVerifyWrite(xml); 134 } 135 try (InputStream is = new FileInputStream(file)) { 136 doVerifyRead(Xml.resolvePullParser(is)); 137 } 138 } 139 } 140 141 @Test testResolve_Memory()142 public void testResolve_Memory() throws Exception { 143 { 144 final byte[] data; 145 try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { 146 TypedXmlSerializer xml = Xml.newFastSerializer(); 147 xml.setOutput(os, StandardCharsets.UTF_8.name()); 148 doVerifyWrite(xml); 149 data = os.toByteArray(); 150 } 151 try (InputStream is = new ByteArrayInputStream(data) { 152 @Override 153 public boolean markSupported() { 154 return false; 155 } 156 }) { 157 doVerifyRead(Xml.resolvePullParser(is)); 158 } 159 } 160 { 161 final byte[] data; 162 try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { 163 TypedXmlSerializer xml = Xml.newBinarySerializer(); 164 xml.setOutput(os, StandardCharsets.UTF_8.name()); 165 doVerifyWrite(xml); 166 data = os.toByteArray(); 167 } 168 try (InputStream is = new ByteArrayInputStream(data) { 169 @Override 170 public boolean markSupported() { 171 return false; 172 } 173 }) { 174 doVerifyRead(Xml.resolvePullParser(is)); 175 } 176 } 177 } 178 179 @Test testAttributeBytes_BinaryDataOverflow()180 public void testAttributeBytes_BinaryDataOverflow() throws Exception { 181 final TypedXmlSerializer out = Xml.newBinarySerializer(); 182 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 183 out.setOutput(os, StandardCharsets.UTF_8.name()); 184 185 final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT + 1]; 186 assertThrows(IOException.class, 187 () -> out.attributeBytesHex(/* namespace */ null, /* name */ "attributeBytesHex", 188 testBytes)); 189 190 assertThrows(IOException.class, 191 () -> out.attributeBytesBase64(/* namespace */ null, /* name */ 192 "attributeBytesBase64", testBytes)); 193 } 194 195 @Test testAttributeBytesHex_MaximumBinaryData()196 public void testAttributeBytesHex_MaximumBinaryData() throws Exception { 197 final TypedXmlSerializer out = Xml.newBinarySerializer(); 198 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 199 out.setOutput(os, StandardCharsets.UTF_8.name()); 200 201 final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT]; 202 try { 203 out.attributeBytesHex(/* namespace */ null, /* name */ "attributeBytesHex", testBytes); 204 } catch (Exception e) { 205 fail("testAttributeBytesHex fails with exception: " + e.toString()); 206 } 207 } 208 209 @Test testAttributeBytesBase64_MaximumBinaryData()210 public void testAttributeBytesBase64_MaximumBinaryData() throws Exception { 211 final TypedXmlSerializer out = Xml.newBinarySerializer(); 212 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 213 out.setOutput(os, StandardCharsets.UTF_8.name()); 214 215 final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT]; 216 try { 217 out.attributeBytesBase64(/* namespace */ null, /* name */ "attributeBytesBase64", 218 testBytes); 219 } catch (Exception e) { 220 fail("testAttributeBytesBase64 fails with exception: " + e.toString()); 221 } 222 } 223 } 224