/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.avro.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import org.apache.avro.RandomData; import org.apache.avro.Schema; import org.apache.avro.AvroRuntimeException; import org.apache.avro.generic.GenericDatumReader; import org.apache.avro.generic.GenericDatumWriter; import org.apache.avro.util.ByteBufferInputStream; import org.apache.avro.util.ByteBufferOutputStream; import org.apache.avro.util.Utf8; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class TestBinaryDecoder { // prime number buffer size so that looping tests hit the buffer edge // at different points in the loop. DecoderFactory factory = new DecoderFactory().configureDecoderBufferSize(521); private boolean useDirect = false; static EncoderFactory e_factory = EncoderFactory.get(); public TestBinaryDecoder(boolean useDirect) { this.useDirect = useDirect; } @Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][] { { true }, { false }, }); } private Decoder newDecoderWithNoData() throws IOException { return newDecoder(new byte[0]); } private Decoder newDecoder(byte[] bytes, int start, int len) throws IOException { return factory.binaryDecoder(bytes, start, len, null); } private Decoder newDecoder(InputStream in) { if (useDirect) { return factory.directBinaryDecoder(in, null); } else { return factory.binaryDecoder(in, null); } } private Decoder newDecoder(byte[] bytes) throws IOException { return factory.binaryDecoder(bytes, null); } /** Verify EOFException throw at EOF */ @Test(expected=EOFException.class) public void testEOFBoolean() throws IOException { newDecoderWithNoData().readBoolean(); } @Test(expected=EOFException.class) public void testEOFInt() throws IOException { newDecoderWithNoData().readInt(); } @Test(expected=EOFException.class) public void testEOFLong() throws IOException { newDecoderWithNoData().readLong(); } @Test(expected=EOFException.class) public void testEOFFloat() throws IOException { newDecoderWithNoData().readFloat(); } @Test(expected=EOFException.class) public void testEOFDouble() throws IOException { newDecoderWithNoData().readDouble(); } @Test(expected=EOFException.class) public void testEOFBytes() throws IOException { newDecoderWithNoData().readBytes(null); } @Test(expected=EOFException.class) public void testEOFString() throws IOException { newDecoderWithNoData().readString(new Utf8("a")); } @Test(expected=EOFException.class) public void testEOFFixed() throws IOException { newDecoderWithNoData().readFixed(new byte[1]); } @Test(expected=EOFException.class) public void testEOFEnum() throws IOException { newDecoderWithNoData().readEnum(); } @Test public void testReuse() throws IOException { ByteBufferOutputStream bbo1 = new ByteBufferOutputStream(); ByteBufferOutputStream bbo2 = new ByteBufferOutputStream(); byte[] b1 = new byte[] { 1, 2 }; BinaryEncoder e1 = e_factory.binaryEncoder(bbo1, null); e1.writeBytes(b1); e1.flush(); BinaryEncoder e2 = e_factory.binaryEncoder(bbo2, null); e2.writeBytes(b1); e2.flush(); DirectBinaryDecoder d = new DirectBinaryDecoder( new ByteBufferInputStream(bbo1.getBufferList())); ByteBuffer bb1 = d.readBytes(null); Assert.assertEquals(b1.length, bb1.limit() - bb1.position()); d.configure(new ByteBufferInputStream(bbo2.getBufferList())); ByteBuffer bb2 = d.readBytes(null); Assert.assertEquals(b1.length, bb2.limit() - bb2.position()); } private static byte[] data = null; private static int seed = -1; private static Schema schema = null; private static int count = 200; private static ArrayList<Object> records = new ArrayList<Object>(count); @BeforeClass public static void generateData() throws IOException { seed = (int)System.currentTimeMillis(); // note some tests (testSkipping) rely on this explicitly String jsonSchema = "{\"type\": \"record\", \"name\": \"Test\", \"fields\": [" +"{\"name\":\"intField\", \"type\":\"int\"}," +"{\"name\":\"bytesField\", \"type\":\"bytes\"}," +"{\"name\":\"booleanField\", \"type\":\"boolean\"}," +"{\"name\":\"stringField\", \"type\":\"string\"}," +"{\"name\":\"floatField\", \"type\":\"float\"}," +"{\"name\":\"doubleField\", \"type\":\"double\"}," +"{\"name\":\"arrayField\", \"type\": " + "{\"type\":\"array\", \"items\":\"boolean\"}}," +"{\"name\":\"longField\", \"type\":\"long\"}]}"; schema = Schema.parse(jsonSchema); GenericDatumWriter<Object> writer = new GenericDatumWriter<Object>(); writer.setSchema(schema); ByteArrayOutputStream baos = new ByteArrayOutputStream(8192); BinaryEncoder encoder = e_factory.binaryEncoder(baos, null); for (Object datum : new RandomData(schema, count, seed)) { writer.write(datum, encoder); records.add(datum); } encoder.flush(); data = baos.toByteArray(); } @Test public void testDecodeFromSources() throws IOException { GenericDatumReader<Object> reader = new GenericDatumReader<Object>(); reader.setSchema(schema); ByteArrayInputStream is = new ByteArrayInputStream(data); ByteArrayInputStream is2 = new ByteArrayInputStream(data); ByteArrayInputStream is3 = new ByteArrayInputStream(data); Decoder fromInputStream = newDecoder(is); Decoder fromArray = newDecoder(data); byte[] data2 = new byte[data.length + 30]; Arrays.fill(data2, (byte)0xff); System.arraycopy(data, 0, data2, 15, data.length); Decoder fromOffsetArray = newDecoder(data2, 15, data.length); BinaryDecoder initOnInputStream = factory.binaryDecoder( new byte[50], 0, 30, null); initOnInputStream = factory.binaryDecoder(is2, initOnInputStream); BinaryDecoder initOnArray = factory.binaryDecoder(is3, null); initOnArray = factory.binaryDecoder( data, 0, data.length, initOnArray); for (Object datum : records) { Assert.assertEquals( "InputStream based BinaryDecoder result does not match", datum, reader.read(null, fromInputStream)); Assert.assertEquals( "Array based BinaryDecoder result does not match", datum, reader.read(null, fromArray)); Assert.assertEquals( "offset Array based BinaryDecoder result does not match", datum, reader.read(null, fromOffsetArray)); Assert.assertEquals( "InputStream initialized BinaryDecoder result does not match", datum, reader.read(null, initOnInputStream)); Assert.assertEquals( "Array initialized BinaryDecoder result does not match", datum, reader.read(null, initOnArray)); } } @Test public void testInputStreamProxy() throws IOException { Decoder d = newDecoder(data); if (d instanceof BinaryDecoder) { BinaryDecoder bd = (BinaryDecoder) d; InputStream test = bd.inputStream(); InputStream check = new ByteArrayInputStream(data); validateInputStreamReads(test, check); bd = factory.binaryDecoder(data, bd); test = bd.inputStream(); check = new ByteArrayInputStream(data); validateInputStreamSkips(test, check); // with input stream sources bd = factory.binaryDecoder(new ByteArrayInputStream(data), bd); test = bd.inputStream(); check = new ByteArrayInputStream(data); validateInputStreamReads(test, check); bd = factory.binaryDecoder(new ByteArrayInputStream(data), bd); test = bd.inputStream(); check = new ByteArrayInputStream(data); validateInputStreamSkips(test, check); } } @Test public void testInputStreamProxyDetached() throws IOException { Decoder d = newDecoder(data); if (d instanceof BinaryDecoder) { BinaryDecoder bd = (BinaryDecoder) d; InputStream test = bd.inputStream(); InputStream check = new ByteArrayInputStream(data); // detach input stream and decoder from old source factory.binaryDecoder(new byte[56], null); InputStream bad = bd.inputStream(); InputStream check2 = new ByteArrayInputStream(data); validateInputStreamReads(test, check); Assert.assertFalse(bad.read() == check2.read()); } } @Test public void testInputStreamPartiallyUsed() throws IOException { BinaryDecoder bd = factory.binaryDecoder( new ByteArrayInputStream(data), null); InputStream test = bd.inputStream(); InputStream check = new ByteArrayInputStream(data); // triggers buffer fill if unused and tests isEnd() try { Assert.assertFalse(bd.isEnd()); } catch (UnsupportedOperationException e) { // this is ok if its a DirectBinaryDecoder. if (bd.getClass() != DirectBinaryDecoder.class) { throw e; } } bd.readFloat(); // use data, and otherwise trigger buffer fill check.skip(4); // skip the same # of bytes here validateInputStreamReads(test, check); } private void validateInputStreamReads(InputStream test, InputStream check) throws IOException { byte[] bt = new byte[7]; byte[] bc = new byte[7]; while (true) { int t = test.read(); int c = check.read(); Assert.assertEquals(c, t); if (-1 == t) break; t = test.read(bt); c = check.read(bc); Assert.assertEquals(c, t); Assert.assertArrayEquals(bt, bc); if (-1 == t) break; t = test.read(bt, 1, 4); c = check.read(bc, 1, 4); Assert.assertEquals(c, t); Assert.assertArrayEquals(bt, bc); if (-1 == t) break; } Assert.assertEquals(0, test.skip(5)); Assert.assertEquals(0, test.available()); Assert.assertFalse(test.getClass() != ByteArrayInputStream.class && test.markSupported()); test.close(); } private void validateInputStreamSkips(InputStream test, InputStream check) throws IOException { while(true) { long t2 = test.skip(19); long c2 = check.skip(19); Assert.assertEquals(c2, t2); if (0 == t2) break; } Assert.assertEquals(-1, test.read()); } @Test public void testBadIntEncoding() throws IOException { byte[] badint = new byte[5]; Arrays.fill(badint, (byte)0xff); Decoder bd = factory.binaryDecoder(badint, null); String message = ""; try { bd.readInt(); } catch (IOException ioe) { message = ioe.getMessage(); } Assert.assertEquals("Invalid int encoding", message); } @Test public void testBadLongEncoding() throws IOException { byte[] badint = new byte[10]; Arrays.fill(badint, (byte)0xff); Decoder bd = factory.binaryDecoder(badint, null); String message = ""; try { bd.readLong(); } catch (IOException ioe) { message = ioe.getMessage(); } Assert.assertEquals("Invalid long encoding", message); } @Test public void testBadLengthEncoding() throws IOException { byte[] bad = new byte[] { (byte)1 }; Decoder bd = factory.binaryDecoder(bad, null); String message = ""; try { bd.readString(); } catch (AvroRuntimeException e) { message = e.getMessage(); } Assert.assertEquals("Malformed data. Length is negative: -1", message); } @Test(expected=EOFException.class) public void testIntTooShort() throws IOException { byte[] badint = new byte[4]; Arrays.fill(badint, (byte)0xff); newDecoder(badint).readInt(); } @Test(expected=EOFException.class) public void testLongTooShort() throws IOException { byte[] badint = new byte[9]; Arrays.fill(badint, (byte)0xff); newDecoder(badint).readLong(); } @Test(expected=EOFException.class) public void testFloatTooShort() throws IOException { byte[] badint = new byte[3]; Arrays.fill(badint, (byte)0xff); newDecoder(badint).readInt(); } @Test(expected=EOFException.class) public void testDoubleTooShort() throws IOException { byte[] badint = new byte[7]; Arrays.fill(badint, (byte)0xff); newDecoder(badint).readLong(); } @Test public void testSkipping() throws IOException { Decoder d = newDecoder(data); skipGenerated(d); if (d instanceof BinaryDecoder) { BinaryDecoder bd = (BinaryDecoder) d; try { Assert.assertTrue(bd.isEnd()); } catch (UnsupportedOperationException e) { // this is ok if its a DirectBinaryDecoder. if (bd.getClass() != DirectBinaryDecoder.class) { throw e; } } bd = factory.binaryDecoder(new ByteArrayInputStream(data), bd); skipGenerated(bd); try { Assert.assertTrue(bd.isEnd()); } catch (UnsupportedOperationException e) { // this is ok if its a DirectBinaryDecoder. if (bd.getClass() != DirectBinaryDecoder.class) { throw e; } } } } private void skipGenerated(Decoder bd) throws IOException { for (int i = 0; i < records.size(); i++) { bd.readInt(); bd.skipBytes(); bd.skipFixed(1); bd.skipString(); bd.skipFixed(4); bd.skipFixed(8); long leftover = bd.skipArray(); // booleans are one byte, array trailer is one byte bd.skipFixed((int)leftover + 1); bd.skipFixed(0); bd.readLong(); } EOFException eof = null; try { bd.skipFixed(4); } catch (EOFException e) { eof = e; } Assert.assertTrue(null != eof); } @Test(expected = EOFException.class) public void testEOF() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Encoder e = EncoderFactory.get().binaryEncoder(baos, null); e.writeLong(0x10000000000000l); e.flush(); Decoder d = newDecoder(new ByteArrayInputStream(baos.toByteArray())); Assert.assertEquals(0x10000000000000l, d.readLong()); d.readInt(); } }