Modifying Protocol Buffers (or Thrift, or MessagePack, or any of the free ASN.1 implementations I could find) to support length constraints was more work than writing a custom serialisation format, so I wrote a custom format.

This commit is contained in:
akwizgran
2011-07-10 14:44:15 +01:00
parent 4deb52478d
commit 1f5e52c31b
21 changed files with 1212 additions and 1142 deletions

View File

@@ -2,9 +2,11 @@ package net.sf.briar.invitation;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import net.sf.briar.TestUtils;
@@ -12,6 +14,8 @@ import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.invitation.InvitationCallback;
import net.sf.briar.api.invitation.InvitationParameters;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;
import org.jmock.Expectations;
import org.jmock.Mockery;
@@ -38,13 +42,14 @@ public class InvitationWorkerTest extends TestCase {
context.mock(InvitationParameters.class);
final DatabaseComponent database =
context.mock(DatabaseComponent.class);
final WriterFactory writerFactory = context.mock(WriterFactory.class);
context.checking(new Expectations() {{
oneOf(params).getChosenLocation();
will(returnValue(nonExistent));
oneOf(callback).notFound(nonExistent);
}});
new InvitationWorker(callback, params, database).run();
new InvitationWorker(callback, params, database, writerFactory).run();
context.assertIsSatisfied();
File[] children = testDir.listFiles();
@@ -64,13 +69,14 @@ public class InvitationWorkerTest extends TestCase {
context.mock(InvitationParameters.class);
final DatabaseComponent database =
context.mock(DatabaseComponent.class);
final WriterFactory writerFactory = context.mock(WriterFactory.class);
context.checking(new Expectations() {{
oneOf(params).getChosenLocation();
will(returnValue(exists));
oneOf(callback).notDirectory(exists);
}});
new InvitationWorker(callback, params, database).run();
new InvitationWorker(callback, params, database, writerFactory).run();
context.assertIsSatisfied();
File[] children = testDir.listFiles();
@@ -101,6 +107,8 @@ public class InvitationWorkerTest extends TestCase {
private void testInstallerCreation(final boolean createExe,
final boolean createJar) throws IOException, DbException {
final Map<String, String> transports =
Collections.singletonMap("foo", "bar");
final File setup = new File(testDir, "setup.dat");
TestUtils.createFile(setup, "foo bar baz");
final File invitation = new File(testDir, "invitation.dat");
@@ -121,6 +129,8 @@ public class InvitationWorkerTest extends TestCase {
context.mock(InvitationParameters.class);
final DatabaseComponent database =
context.mock(DatabaseComponent.class);
final WriterFactory writerFactory = context.mock(WriterFactory.class);
final Writer writer = context.mock(Writer.class);
context.checking(new Expectations() {{
oneOf(params).getChosenLocation();
will(returnValue(testDir));
@@ -130,7 +140,10 @@ public class InvitationWorkerTest extends TestCase {
will(returnValue(new char[] {'x', 'y', 'z', 'z', 'y'}));
oneOf(callback).encryptingFile(invitation);
oneOf(database).getTransports();
will(returnValue(Collections.singletonMap("foo", "bar")));
will(returnValue(transports));
oneOf(writerFactory).createWriter(with(any(OutputStream.class)));
will(returnValue(writer));
oneOf(writer).writeMap(transports);
oneOf(params).shouldCreateExe();
will(returnValue(createExe));
oneOf(params).shouldCreateJar();
@@ -148,7 +161,7 @@ public class InvitationWorkerTest extends TestCase {
oneOf(callback).created(expectedFiles);
}});
new InvitationWorker(callback, params, database).run();
new InvitationWorker(callback, params, database, writerFactory).run();
assertTrue(invitation.exists());
assertEquals(createExe, exe.exists());

View File

@@ -0,0 +1,249 @@
package net.sf.briar.serial;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import junit.framework.TestCase;
import net.sf.briar.api.serial.FormatException;
import net.sf.briar.api.serial.Raw;
import net.sf.briar.util.StringUtils;
import org.junit.Test;
public class ReaderImplTest extends TestCase {
private ByteArrayInputStream in = null;
private ReaderImpl r = null;
@Test
public void testReadBoolean() throws IOException {
setContents("FFFE");
assertFalse(r.readBoolean());
assertTrue(r.readBoolean());
assertTrue(r.eof());
}
@Test
public void testReadInt8() throws IOException {
setContents("FD00" + "FDFF" + "FD7F" + "FD80");
assertEquals((byte) 0, r.readInt8());
assertEquals((byte) -1, r.readInt8());
assertEquals(Byte.MAX_VALUE, r.readInt8());
assertEquals(Byte.MIN_VALUE, r.readInt8());
assertTrue(r.eof());
}
@Test
public void testReadInt16() throws IOException {
setContents("FC0000" + "FCFFFF" + "FC7FFF" + "FC8000");
assertEquals((short) 0, r.readInt16());
assertEquals((short) -1, r.readInt16());
assertEquals(Short.MAX_VALUE, r.readInt16());
assertEquals(Short.MIN_VALUE, r.readInt16());
assertTrue(r.eof());
}
@Test
public void testReadInt32() throws IOException {
setContents("FB00000000" + "FBFFFFFFFF" + "FB7FFFFFFF" + "FB80000000");
assertEquals(0, r.readInt32());
assertEquals(-1, r.readInt32());
assertEquals(Integer.MAX_VALUE, r.readInt32());
assertEquals(Integer.MIN_VALUE, r.readInt32());
assertTrue(r.eof());
}
@Test
public void testReadInt64() throws IOException {
setContents("FA0000000000000000" + "FAFFFFFFFFFFFFFFFF" +
"FA7FFFFFFFFFFFFFFF" + "FA8000000000000000");
assertEquals(0L, r.readInt64());
assertEquals(-1L, r.readInt64());
assertEquals(Long.MAX_VALUE, r.readInt64());
assertEquals(Long.MIN_VALUE, r.readInt64());
assertTrue(r.eof());
}
@Test
public void testReadIntAny() throws IOException {
setContents("00" + "7F" + "FD80" + "FDFF" + "FC0080" + "FC7FFF" +
"FB00008000" + "FB7FFFFFFF" + "FA0000000080000000");
assertEquals(0L, r.readIntAny());
assertEquals(127L, r.readIntAny());
assertEquals(-128L, r.readIntAny());
assertEquals(-1L, r.readIntAny());
assertEquals(128L, r.readIntAny());
assertEquals(32767L, r.readIntAny());
assertEquals(32768L, r.readIntAny());
assertEquals(2147483647L, r.readIntAny());
assertEquals(2147483648L, r.readIntAny());
assertTrue(r.eof());
}
@Test
public void testReadFloat32() throws IOException {
// http://babbage.cs.qc.edu/IEEE-754/Decimal.html
// http://steve.hollasch.net/cgindex/coding/ieeefloat.html
setContents("F900000000" + "F93F800000" + "F940000000" + "F9BF800000" +
"F980000000" + "F9FF800000" + "F97F800000" + "F97FC00000");
assertEquals(0F, r.readFloat32());
assertEquals(1F, r.readFloat32());
assertEquals(2F, r.readFloat32());
assertEquals(-1F, r.readFloat32());
assertEquals(-0F, r.readFloat32());
assertEquals(Float.NEGATIVE_INFINITY, r.readFloat32());
assertEquals(Float.POSITIVE_INFINITY, r.readFloat32());
assertTrue(Float.isNaN(r.readFloat32()));
assertTrue(r.eof());
}
@Test
public void testReadFloat64() throws IOException {
setContents("F80000000000000000" + "F83FF0000000000000" +
"F84000000000000000" + "F8BFF0000000000000" +
"F88000000000000000" + "F8FFF0000000000000" +
"F87FF0000000000000" + "F87FF8000000000000");
assertEquals(0.0, r.readFloat64());
assertEquals(1.0, r.readFloat64());
assertEquals(2.0, r.readFloat64());
assertEquals(-1.0, r.readFloat64());
assertEquals(-0.0, r.readFloat64());
assertEquals(Double.NEGATIVE_INFINITY, r.readFloat64());
assertEquals(Double.POSITIVE_INFINITY, r.readFloat64());
assertTrue(Double.isNaN(r.readFloat64()));
assertTrue(r.eof());
}
@Test
public void testReadUtf8() throws IOException {
setContents("F703666F6F" + "F703666F6F" + "F700");
assertEquals("foo", r.readUtf8());
assertEquals("foo", r.readUtf8());
assertEquals("", r.readUtf8());
assertTrue(r.eof());
}
@Test
public void testReadUtf8MaxLengthNotExceeded() throws IOException {
setContents("F703666F6F");
assertEquals("foo", r.readUtf8(3));
assertTrue(r.eof());
}
@Test
public void testReadUtf8MaxLengthExceeded() throws IOException {
setContents("F703666F6F");
try {
r.readUtf8(2);
assertTrue(false);
} catch(FormatException expected) {}
}
@Test
public void testReadRaw() throws IOException {
setContents("F603010203" + "F603010203" + "F600");
assertTrue(Arrays.equals(new byte[] {1, 2, 3}, r.readRaw()));
assertTrue(Arrays.equals(new byte[] {1, 2, 3}, r.readRaw()));
assertTrue(Arrays.equals(new byte[] {}, r.readRaw()));
assertTrue(r.eof());
}
@Test
public void testReadRawMaxLengthNotExceeded() throws IOException {
setContents("F603010203");
assertTrue(Arrays.equals(new byte[] {1, 2, 3}, r.readRaw(3)));
assertTrue(r.eof());
}
@Test
public void testReadRawMaxLengthExceeded() throws IOException {
setContents("F603010203");
try {
r.readRaw(2);
assertTrue(false);
} catch(FormatException expected) {}
}
@Test
@SuppressWarnings("rawtypes")
public void testReadDefiniteList() throws IOException {
setContents("F5" + "03" + "01" + "F703666F6F" + "FC0080");
List l = r.readList(true);
assertNotNull(l);
assertEquals(3, l.size());
assertEquals((byte) 1, l.get(0));
assertEquals("foo", l.get(1));
assertEquals((short) 128, l.get(2));
assertTrue(r.eof());
}
@Test
@SuppressWarnings("rawtypes")
public void testReadDefiniteMap() throws IOException {
setContents("F4" + "02" + "F703666F6F" + "7B" + "F600" + "F0");
Map m = r.readMap(true);
assertNotNull(m);
assertEquals(2, m.size());
assertEquals((byte) 123, m.get("foo"));
Raw raw = new RawImpl(new byte[] {});
assertTrue(m.containsKey(raw));
assertNull(m.get(raw));
assertTrue(r.eof());
}
@Test
@SuppressWarnings("rawtypes")
public void testReadIndefiniteList() throws IOException {
setContents("F3" + "01" + "F703666F6F" + "FC0080" + "F1");
List l = r.readList(false);
assertNotNull(l);
assertEquals(3, l.size());
assertEquals((byte) 1, l.get(0));
assertEquals("foo", l.get(1));
assertEquals((short) 128, l.get(2));
assertTrue(r.eof());
}
@Test
@SuppressWarnings("rawtypes")
public void testReadIndefiniteMap() throws IOException {
setContents("F2" + "F703666F6F" + "7B" + "F600" + "F0" + "F1");
Map m = r.readMap(false);
assertNotNull(m);
assertEquals(2, m.size());
assertEquals((byte) 123, m.get("foo"));
Raw raw = new RawImpl(new byte[] {});
assertTrue(m.containsKey(raw));
assertNull(m.get(raw));
assertTrue(r.eof());
}
@Test
@SuppressWarnings("rawtypes")
public void testReadNestedMapsAndLists() throws IOException {
setContents("F4" + "01" + "F4" + "01" + "F703666F6F" + "7B" +
"F5" + "01" + "01");
Map m = r.readMap();
assertNotNull(m);
assertEquals(1, m.size());
Entry e = (Entry) m.entrySet().iterator().next();
Map m1 = (Map) e.getKey();
assertNotNull(m1);
assertEquals(1, m1.size());
assertEquals((byte) 123, m1.get("foo"));
List l = (List) e.getValue();
assertNotNull(l);
assertEquals(1, l.size());
assertEquals((byte) 1, l.get(0));
assertTrue(r.eof());
}
private void setContents(String hex) {
in = new ByteArrayInputStream(StringUtils.fromHexString(hex));
r = new ReaderImpl(in);
}
}

View File

@@ -0,0 +1,219 @@
package net.sf.briar.serial;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import net.sf.briar.util.StringUtils;
import org.junit.Before;
import org.junit.Test;
public class WriterImplTest extends TestCase {
private ByteArrayOutputStream out = null;
private WriterImpl w = null;
@Before
public void setUp() {
out = new ByteArrayOutputStream();
w = new WriterImpl(out);
}
@Test
public void testWriteBoolean() throws IOException {
w.writeBoolean(true);
w.writeBoolean(false);
checkContents("FEFF");
}
@Test
public void testWriteUint7() throws IOException {
w.writeUint7((byte) 0);
w.writeUint7((byte) 127);
checkContents("00" + "7F");
}
@Test
public void testWriteInt8() throws IOException {
w.writeInt8((byte) 0);
w.writeInt8((byte) -1);
w.writeInt8((byte) -128);
w.writeInt8((byte) 127);
checkContents("FD00" + "FDFF" + "FD80" + "FD7F");
}
@Test
public void testWriteInt16() throws IOException {
w.writeInt16((short) 0);
w.writeInt16((short) -1);
w.writeInt16((short) -32768);
w.writeInt16((short) 32767);
checkContents("FC0000" + "FCFFFF" + "FC8000" + "FC7FFF");
}
@Test
public void testWriteInt32() throws IOException {
w.writeInt32(0);
w.writeInt32(-1);
w.writeInt32(-2147483648);
w.writeInt32(2147483647);
checkContents("FB00000000" + "FBFFFFFFFF" +
"FB80000000" + "FB7FFFFFFF");
}
@Test
public void testWriteInt64() throws IOException {
w.writeInt64(0L);
w.writeInt64(-1L);
w.writeInt64(-9223372036854775808L);
w.writeInt64(9223372036854775807L);
checkContents("FA0000000000000000" + "FAFFFFFFFFFFFFFFFF" +
"FA8000000000000000" + "FA7FFFFFFFFFFFFFFF");
}
@Test
public void testWriteIntAny() throws IOException {
w.writeIntAny(0L); // uint7
w.writeIntAny(127L); // uint7
w.writeIntAny(-1L); // int8
w.writeIntAny(128L); // int16
w.writeIntAny(32767L); // int16
w.writeIntAny(32768L); // int32
w.writeIntAny(2147483647L); // int32
w.writeIntAny(2147483648L); // int64
checkContents("00" + "7F" + "FDFF" + "FC0080" + "FC7FFF" +
"FB00008000" + "FB7FFFFFFF" + "FA0000000080000000");
}
@Test
public void testWriteFloat32() throws IOException {
// http://babbage.cs.qc.edu/IEEE-754/Decimal.html
// 1 bit for sign, 8 for exponent, 23 for significand
w.writeFloat32(0F); // 0 0 0 -> 0x00000000
w.writeFloat32(1F); // 0 127 1 -> 0x3F800000
w.writeFloat32(2F); // 0 128 1 -> 0x40000000
w.writeFloat32(-1F); // 1 127 1 -> 0xBF800000
w.writeFloat32(-0F); // 1 0 0 -> 0x80000000
// http://steve.hollasch.net/cgindex/coding/ieeefloat.html
w.writeFloat32(Float.NEGATIVE_INFINITY); // 1 255 0 -> 0xFF800000
w.writeFloat32(Float.POSITIVE_INFINITY); // 0 255 0 -> 0x7F800000
w.writeFloat32(Float.NaN); // 0 255 1 -> 0x7FC00000
checkContents("F900000000" + "F93F800000" + "F940000000" +
"F9BF800000" + "F980000000" + "F9FF800000" +
"F97F800000" + "F97FC00000");
}
@Test
public void testWriteFloat64() throws IOException {
// 1 bit for sign, 11 for exponent, 52 for significand
w.writeFloat64(0.0); // 0 0 0 -> 0x0000000000000000
w.writeFloat64(1.0); // 0 1023 1 -> 0x3FF0000000000000
w.writeFloat64(2.0); // 0 1024 1 -> 0x4000000000000000
w.writeFloat64(-1.0); // 1 1023 1 -> 0xBFF0000000000000
w.writeFloat64(-0.0); // 1 0 0 -> 0x8000000000000000
w.writeFloat64(Double.NEGATIVE_INFINITY); // 1 2047 0 -> 0xFFF00000...
w.writeFloat64(Double.POSITIVE_INFINITY); // 0 2047 0 -> 0x7FF00000...
w.writeFloat64(Double.NaN); // 0 2047 1 -> 0x7FF8000000000000
checkContents("F80000000000000000" + "F83FF0000000000000" +
"F84000000000000000" + "F8BFF0000000000000" +
"F88000000000000000" + "F8FFF0000000000000" +
"F87FF0000000000000" + "F87FF8000000000000");
}
@Test
public void testWriteUtf8() throws IOException {
w.writeUtf8("foo");
// UTF-8 tag, length as uint7, UTF-8 bytes
checkContents("F7" + "03" + "666F6F");
}
@Test
public void testWriteRawBytes() throws IOException {
w.writeRaw(new byte[] {0, 1, -1, 127, -128});
checkContents("F6" + "05" + "0001FF7F80");
}
@Test
public void testWriteRawObject() throws IOException {
w.writeRaw(new RawImpl(new byte[] {0, 1, -1, 127, -128}));
checkContents("F6" + "05" + "0001FF7F80");
}
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
public void testWriteDefiniteList() throws IOException {
List l = new ArrayList();
l.add(Byte.valueOf((byte) 1)); // Written as a uint7
l.add("foo");
l.add(Long.valueOf(128L)); // Written as an int16
w.writeList(l, true);
checkContents("F5" + "03" + "01" + "F703666F6F" + "FC0080");
}
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
public void testWriteDefiniteMap() throws IOException {
// Use LinkedHashMap to get predictable iteration order
Map m = new LinkedHashMap();
m.put("foo", Integer.valueOf(123)); // Written as a uint7
m.put(new RawImpl(new byte[] {}), null); // Empty array != null
w.writeMap(m, true);
checkContents("F4" + "02" + "F703666F6F" + "7B" + "F600" + "F0");
}
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
public void testWriteIndefiniteList() throws IOException {
List l = new ArrayList();
l.add(Byte.valueOf((byte) 1)); // Written as a uint7
l.add("foo");
l.add(Long.valueOf(128L)); // Written as an int16
w.writeList(l, false);
checkContents("F3" + "01" + "F703666F6F" + "FC0080" + "F1");
}
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
public void testWriteIndefiniteMap() throws IOException {
// Use LinkedHashMap to get predictable iteration order
Map m = new LinkedHashMap();
m.put("foo", Integer.valueOf(123)); // Written as a uint7
m.put(new RawImpl(new byte[] {}), null); // Empty array != null
w.writeMap(m, false);
checkContents("F2" + "F703666F6F" + "7B" + "F600" + "F0" + "F1");
}
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
public void testWriteNestedMapsAndLists() throws IOException {
Map m = new LinkedHashMap();
m.put("foo", Integer.valueOf(123));
List l = new ArrayList();
l.add(Byte.valueOf((byte) 1));
Map m1 = new LinkedHashMap();
m1.put(m, l);
w.writeMap(m1);
checkContents("F4" + "01" + "F4" + "01" + "F703666F6F" + "7B" +
"F5" + "01" + "01");
}
@Test
public void testWriteNull() throws IOException {
w.writeNull();
checkContents("F0");
}
private void checkContents(String hex) throws IOException {
out.flush();
out.close();
byte[] expected = StringUtils.fromHexString(hex);
assertTrue(StringUtils.toHexString(out.toByteArray()),
Arrays.equals(expected, out.toByteArray()));
}
}