• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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