• 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 com.android.internal.util;
18 
19 import static org.junit.Assert.assertArrayEquals;
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.fail;
22 
23 import android.annotation.NonNull;
24 import android.platform.test.ravenwood.RavenwoodRule;
25 import android.util.ExceptionUtils;
26 
27 import com.android.modules.utils.FastDataInput;
28 import com.android.modules.utils.FastDataOutput;
29 
30 import libcore.util.HexEncoding;
31 
32 import org.junit.Assume;
33 import org.junit.Rule;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 import org.junit.runners.Parameterized;
37 import org.junit.runners.Parameterized.Parameters;
38 
39 import java.io.ByteArrayInputStream;
40 import java.io.ByteArrayOutputStream;
41 import java.io.DataInput;
42 import java.io.DataInputStream;
43 import java.io.DataOutput;
44 import java.io.DataOutputStream;
45 import java.io.EOFException;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.OutputStream;
49 import java.nio.charset.StandardCharsets;
50 import java.util.Arrays;
51 import java.util.Collection;
52 import java.util.function.Consumer;
53 
54 @RunWith(Parameterized.class)
55 public class FastDataTest {
56     @Rule
57     public final RavenwoodRule mRavenwood = new RavenwoodRule();
58 
59     private final boolean use4ByteSequence;
60 
61     private static final String TEST_SHORT_STRING = "a";
62     private static final String TEST_LONG_STRING = "com☃example��typical☃package��name";
63     private static final byte[] TEST_BYTES = TEST_LONG_STRING.getBytes(StandardCharsets.UTF_16LE);
64 
65     @Parameters(name = "use4ByteSequence={0}")
data()66     public static Collection<Object[]> data() {
67         if (RavenwoodRule.isUnderRavenwood()) {
68             // TODO: 4-byte sequences are only supported on ART
69             return Arrays.asList(new Object[][]{{false}});
70         } else {
71             return Arrays.asList(new Object[][]{{true}, {false}});
72         }
73     }
74 
FastDataTest(boolean use4ByteSequence)75     public FastDataTest(boolean use4ByteSequence) {
76         this.use4ByteSequence = use4ByteSequence;
77     }
78 
79     @NonNull
createFastDataInput(@onNull InputStream in, int bufferSize)80     private FastDataInput createFastDataInput(@NonNull InputStream in, int bufferSize) {
81         if (use4ByteSequence) {
82             return new ArtFastDataInput(in, bufferSize);
83         } else {
84             return new FastDataInput(in, bufferSize);
85         }
86     }
87 
88     @NonNull
createFastDataOutput(@onNull OutputStream out, int bufferSize)89     private FastDataOutput createFastDataOutput(@NonNull OutputStream out, int bufferSize) {
90         if (use4ByteSequence) {
91             return new ArtFastDataOutput(out, bufferSize);
92         } else {
93             return new FastDataOutput(out, bufferSize);
94         }
95     }
96 
97     @Test
testEndOfFile_Int()98     public void testEndOfFile_Int() throws Exception {
99         try (FastDataInput in = createFastDataInput(new ByteArrayInputStream(
100                 new byte[] { 1 }), 1000)) {
101             assertThrows(EOFException.class, () -> in.readInt());
102         }
103         try (FastDataInput in = createFastDataInput(new ByteArrayInputStream(
104                 new byte[] { 1, 1, 1, 1 }), 1000)) {
105             assertEquals(1, in.readByte());
106             assertThrows(EOFException.class, () -> in.readInt());
107         }
108     }
109 
110     @Test
testEndOfFile_String()111     public void testEndOfFile_String() throws Exception {
112         try (FastDataInput in = createFastDataInput(new ByteArrayInputStream(
113                 new byte[] { 1 }), 1000)) {
114             assertThrows(EOFException.class, () -> in.readUTF());
115         }
116         try (FastDataInput in = createFastDataInput(new ByteArrayInputStream(
117                 new byte[] { 1, 1, 1, 1 }), 1000)) {
118             assertThrows(EOFException.class, () -> in.readUTF());
119         }
120     }
121 
122     @Test
testEndOfFile_Bytes_Small()123     public void testEndOfFile_Bytes_Small() throws Exception {
124         try (FastDataInput in = createFastDataInput(new ByteArrayInputStream(
125                 new byte[] { 1, 1, 1, 1 }), 1000)) {
126             final byte[] tmp = new byte[10];
127             assertThrows(EOFException.class, () -> in.readFully(tmp));
128         }
129         try (FastDataInput in = createFastDataInput(new ByteArrayInputStream(
130                 new byte[] { 1, 1, 1, 1 }), 1000)) {
131             final byte[] tmp = new byte[10_000];
132             assertThrows(EOFException.class, () -> in.readFully(tmp));
133         }
134     }
135 
136     @Test
testUTF_Bounds()137     public void testUTF_Bounds() throws Exception {
138         final char[] buf = new char[65_534];
139         try (FastDataOutput out = createFastDataOutput(new ByteArrayOutputStream(), BOUNCE_SIZE)) {
140             // Writing simple string will fit fine
141             Arrays.fill(buf, '!');
142             final String simple = new String(buf);
143             out.writeUTF(simple);
144             out.writeInternedUTF(simple);
145 
146             // Just one complex char will cause it to overflow
147             buf[0] = '☃';
148             final String complex = new String(buf);
149             assertThrows(IOException.class, () -> out.writeUTF(complex));
150             assertThrows(IOException.class, () -> out.writeInternedUTF(complex));
151 
152             out.flush();
153         }
154     }
155 
156     @Test
testTranscode()157     public void testTranscode() throws Exception {
158         Assume.assumeFalse(use4ByteSequence);
159 
160         // Verify that upstream data can be read by fast
161         {
162             final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
163             final DataOutputStream out = new DataOutputStream(outStream);
164             doTranscodeWrite(out);
165             out.flush();
166 
167             final FastDataInput in = createFastDataInput(
168                     new ByteArrayInputStream(outStream.toByteArray()), BOUNCE_SIZE);
169             doTranscodeRead(in);
170         }
171 
172         // Verify that fast data can be read by upstream
173         {
174             final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
175             final FastDataOutput out = createFastDataOutput(outStream, BOUNCE_SIZE);
176             doTranscodeWrite(out);
177             out.flush();
178 
179             final DataInputStream in = new DataInputStream(
180                     new ByteArrayInputStream(outStream.toByteArray()));
181             doTranscodeRead(in);
182         }
183     }
184 
doTranscodeWrite(DataOutput out)185     private static void doTranscodeWrite(DataOutput out) throws IOException {
186         out.writeBoolean(true);
187         out.writeBoolean(false);
188         out.writeByte(1);
189         out.writeShort(2);
190         out.writeInt(4);
191         out.writeUTF("foo\0bar");
192         out.writeUTF(TEST_SHORT_STRING);
193         out.writeUTF(TEST_LONG_STRING);
194         out.writeLong(8L);
195         out.writeFloat(16f);
196         out.writeDouble(32d);
197     }
198 
doTranscodeRead(DataInput in)199     private static void doTranscodeRead(DataInput in) throws IOException {
200         assertEquals(true, in.readBoolean());
201         assertEquals(false, in.readBoolean());
202         assertEquals(1, in.readByte());
203         assertEquals(2, in.readShort());
204         assertEquals(4, in.readInt());
205         assertEquals("foo\0bar", in.readUTF());
206         assertEquals(TEST_SHORT_STRING, in.readUTF());
207         assertEquals(TEST_LONG_STRING, in.readUTF());
208         assertEquals(8L, in.readLong());
209         assertEquals(16f, in.readFloat(), 0.01);
210         assertEquals(32d, in.readDouble(), 0.01);
211     }
212 
213     @Test
testBounce_Char()214     public void testBounce_Char() throws Exception {
215         doBounce((out) -> {
216             out.writeChar('\0');
217             out.writeChar('☃');
218         }, (in) -> {
219             assertEquals('\0', in.readChar());
220             assertEquals('☃', in.readChar());
221         });
222     }
223 
224     @Test
testBounce_Short()225     public void testBounce_Short() throws Exception {
226         doBounce((out) -> {
227             out.writeShort(0);
228             out.writeShort((short) 0x0f0f);
229             out.writeShort((short) 0xf0f0);
230             out.writeShort(Short.MIN_VALUE);
231             out.writeShort(Short.MAX_VALUE);
232         }, (in) -> {
233             assertEquals(0, in.readShort());
234             assertEquals((short) 0x0f0f, in.readShort());
235             assertEquals((short) 0xf0f0, in.readShort());
236             assertEquals(Short.MIN_VALUE, in.readShort());
237             assertEquals(Short.MAX_VALUE, in.readShort());
238         });
239     }
240 
241     @Test
testBounce_Int()242     public void testBounce_Int() throws Exception {
243         doBounce((out) -> {
244             out.writeInt(0);
245             out.writeInt(0x0f0f0f0f);
246             out.writeInt(0xf0f0f0f0);
247             out.writeInt(Integer.MIN_VALUE);
248             out.writeInt(Integer.MAX_VALUE);
249         }, (in) -> {
250             assertEquals(0, in.readInt());
251             assertEquals(0x0f0f0f0f, in.readInt());
252             assertEquals(0xf0f0f0f0, in.readInt());
253             assertEquals(Integer.MIN_VALUE, in.readInt());
254             assertEquals(Integer.MAX_VALUE, in.readInt());
255         });
256     }
257 
258     @Test
testBounce_Long()259     public void testBounce_Long() throws Exception {
260         doBounce((out) -> {
261             out.writeLong(0);
262             out.writeLong(0x0f0f0f0f0f0f0f0fL);
263             out.writeLong(0xf0f0f0f0f0f0f0f0L);
264             out.writeLong(Long.MIN_VALUE);
265             out.writeLong(Long.MAX_VALUE);
266         }, (in) -> {
267             assertEquals(0, in.readLong());
268             assertEquals(0x0f0f0f0f0f0f0f0fL, in.readLong());
269             assertEquals(0xf0f0f0f0f0f0f0f0L, in.readLong());
270             assertEquals(Long.MIN_VALUE, in.readLong());
271             assertEquals(Long.MAX_VALUE, in.readLong());
272         });
273     }
274 
275     @Test
testBounce_UTF()276     public void testBounce_UTF() throws Exception {
277         doBounce((out) -> {
278             out.writeUTF("");
279             out.writeUTF("☃");
280             out.writeUTF("��");
281             out.writeUTF("example");
282         }, (in) -> {
283             assertEquals("", in.readUTF());
284             assertEquals("☃", in.readUTF());
285             assertEquals("��", in.readUTF());
286             assertEquals("example", in.readUTF());
287         });
288     }
289 
290     @Test
testBounce_UTF_Exact()291     public void testBounce_UTF_Exact() throws Exception {
292         final char[] expectedBuf = new char[BOUNCE_SIZE];
293         Arrays.fill(expectedBuf, '!');
294         final String expected = new String(expectedBuf);
295 
296         doBounce((out) -> {
297             out.writeUTF(expected);
298         }, (in) -> {
299             final String actual = in.readUTF();
300             assertEquals(expected.length(), actual.length());
301             assertEquals(expected, actual);
302         });
303     }
304 
305     @Test
testBounce_UTF_Maximum()306     public void testBounce_UTF_Maximum() throws Exception {
307         final char[] expectedBuf = new char[65_534];
308         Arrays.fill(expectedBuf, '!');
309         final String expected = new String(expectedBuf);
310 
311         doBounce((out) -> {
312             out.writeUTF(expected);
313         }, (in) -> {
314             final String actual = in.readUTF();
315             assertEquals(expected.length(), actual.length());
316             assertEquals(expected, actual);
317         }, 1);
318     }
319 
320     /**
321      * Verify that we encode every valid code-point identically to RI when
322      * running in 3-byte mode.
323      */
324     @Test
testBounce_UTF_Exhaustive()325     public void testBounce_UTF_Exhaustive() throws Exception {
326         Assume.assumeFalse(use4ByteSequence);
327 
328         final ByteArrayOutputStream slowStream = new ByteArrayOutputStream();
329         final DataOutput slowData = new DataOutputStream(slowStream);
330 
331         final ByteArrayOutputStream fastStream = new ByteArrayOutputStream();
332         final FastDataOutput fastData = FastDataOutput.obtain(fastStream);
333 
334         for (int cp = Character.MIN_CODE_POINT; cp < Character.MAX_CODE_POINT; cp++) {
335             if (Character.isValidCodePoint(cp)) {
336                 final String cpString = new String(Character.toChars(cp));
337                 slowStream.reset();
338                 slowData.writeUTF(cpString);
339                 fastStream.reset();
340                 fastData.writeUTF(cpString);
341                 fastData.flush();
342                 assertEquals("Bad encoding for code-point " + Integer.toHexString(cp),
343                         HexEncoding.encodeToString(slowStream.toByteArray()),
344                         HexEncoding.encodeToString(fastStream.toByteArray()));
345             }
346         }
347     }
348 
349     @Test
testBounce_InternedUTF()350     public void testBounce_InternedUTF() throws Exception {
351         doBounce((out) -> {
352             out.writeInternedUTF("foo");
353             out.writeInternedUTF("bar");
354             out.writeInternedUTF("baz");
355             out.writeInternedUTF("bar");
356             out.writeInternedUTF("foo");
357         }, (in) -> {
358             assertEquals("foo", in.readInternedUTF());
359             assertEquals("bar", in.readInternedUTF());
360             assertEquals("baz", in.readInternedUTF());
361             assertEquals("bar", in.readInternedUTF());
362             assertEquals("foo", in.readInternedUTF());
363         });
364     }
365 
366     /**
367      * Verify that when we overflow the maximum number of interned string
368      * references, we still transport the raw string values successfully.
369      */
370     @Test
testBounce_InternedUTF_Maximum()371     public void testBounce_InternedUTF_Maximum() throws Exception {
372         final int num = 70_000;
373         doBounce((out) -> {
374             for (int i = 0; i < num; i++) {
375                 out.writeInternedUTF("foo" + i);
376             }
377         }, (in) -> {
378             for (int i = 0; i < num; i++) {
379                 assertEquals("foo" + i, in.readInternedUTF());
380             }
381         }, 1);
382     }
383 
384     @Test
testBounce_Bytes()385     public void testBounce_Bytes() throws Exception {
386         doBounce((out) -> {
387             out.write(TEST_BYTES, 8, 32);
388             out.writeInt(64);
389         }, (in) -> {
390             final byte[] tmp = new byte[128];
391             in.readFully(tmp, 8, 32);
392             assertArrayEquals(Arrays.copyOfRange(TEST_BYTES, 8, 8 + 32),
393                     Arrays.copyOfRange(tmp, 8, 8 + 32));
394             assertEquals(64, in.readInt());
395         });
396     }
397 
398     @Test
testBounce_Mixed()399     public void testBounce_Mixed() throws Exception {
400         doBounce((out) -> {
401             out.writeBoolean(true);
402             out.writeBoolean(false);
403             out.writeByte(1);
404             out.writeShort(2);
405             out.writeInt(4);
406             out.writeUTF(TEST_SHORT_STRING);
407             out.writeUTF(TEST_LONG_STRING);
408             out.writeLong(8L);
409             out.writeFloat(16f);
410             out.writeDouble(32d);
411         }, (in) -> {
412             assertEquals(true, in.readBoolean());
413             assertEquals(false, in.readBoolean());
414             assertEquals(1, in.readByte());
415             assertEquals(2, in.readShort());
416             assertEquals(4, in.readInt());
417             assertEquals(TEST_SHORT_STRING, in.readUTF());
418             assertEquals(TEST_LONG_STRING, in.readUTF());
419             assertEquals(8L, in.readLong());
420             assertEquals(16f, in.readFloat(), 0.01);
421             assertEquals(32d, in.readDouble(), 0.01);
422         });
423     }
424 
425     /**
426      * Buffer size to use for {@link #doBounce}; purposefully chosen to be a
427      * small prime number to help uncover edge cases.
428      */
429     private static final int BOUNCE_SIZE = 11;
430 
431     /**
432      * Number of times to repeat message when bouncing; repeating is used to
433      * help uncover edge cases.
434      */
435     private static final int BOUNCE_REPEAT = 1_000;
436 
437     /**
438      * Verify that some common data can be written and read back, effectively
439      * "bouncing" it through a serialized representation.
440      */
doBounce(@onNull ThrowingConsumer<FastDataOutput> out, @NonNull ThrowingConsumer<FastDataInput> in)441     private void doBounce(@NonNull ThrowingConsumer<FastDataOutput> out,
442             @NonNull ThrowingConsumer<FastDataInput> in) throws Exception {
443         doBounce(out, in, BOUNCE_REPEAT);
444     }
445 
doBounce(@onNull ThrowingConsumer<FastDataOutput> out, @NonNull ThrowingConsumer<FastDataInput> in, int count)446     private void doBounce(@NonNull ThrowingConsumer<FastDataOutput> out,
447             @NonNull ThrowingConsumer<FastDataInput> in, int count) throws Exception {
448         final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
449         final FastDataOutput outData = createFastDataOutput(outStream, BOUNCE_SIZE);
450         for (int i = 0; i < count; i++) {
451             out.accept(outData);
452         }
453         outData.flush();
454 
455         final ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
456         final FastDataInput inData = createFastDataInput(inStream, BOUNCE_SIZE);
457         for (int i = 0; i < count; i++) {
458             in.accept(inData);
459         }
460     }
461 
assertThrows(Class<T> clazz, ThrowingRunnable r)462     private static <T extends Exception> void assertThrows(Class<T> clazz, ThrowingRunnable r)
463             throws Exception {
464         try {
465             r.run();
466             fail("Expected " + clazz + " to be thrown");
467         } catch (Exception e) {
468             if (!clazz.isAssignableFrom(e.getClass())) {
469                 throw e;
470             }
471         }
472     }
473 
474     public interface ThrowingRunnable {
run()475         void run() throws Exception;
476     }
477 
478     public interface ThrowingConsumer<T> extends Consumer<T> {
acceptOrThrow(T t)479         void acceptOrThrow(T t) throws Exception;
480 
481         @Override
accept(T t)482         default void accept(T t) {
483             try {
484                 acceptOrThrow(t);
485             } catch (Exception ex) {
486                 throw ExceptionUtils.propagate(ex);
487             }
488         }
489     }
490 }
491