diff --git a/briar-api/src/org/briarproject/api/data/MetadataEncoder.java b/briar-api/src/org/briarproject/api/data/MetadataEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..4b2e3ab8e41f59ff35a41e682661f155a49b5130 --- /dev/null +++ b/briar-api/src/org/briarproject/api/data/MetadataEncoder.java @@ -0,0 +1,9 @@ +package org.briarproject.api.data; + +import org.briarproject.api.FormatException; +import org.briarproject.api.db.Metadata; + +public interface MetadataEncoder { + + Metadata encode(BdfDictionary d) throws FormatException; +} diff --git a/briar-core/src/org/briarproject/data/DataModule.java b/briar-core/src/org/briarproject/data/DataModule.java index 85d7b311a0cfc508a9b215a6b651d848202615f7..c3fe841aee351adf938a17fd13dae8a095da52b1 100644 --- a/briar-core/src/org/briarproject/data/DataModule.java +++ b/briar-core/src/org/briarproject/data/DataModule.java @@ -4,6 +4,7 @@ import com.google.inject.AbstractModule; import org.briarproject.api.data.BdfReaderFactory; import org.briarproject.api.data.BdfWriterFactory; +import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.data.MetadataParser; public class DataModule extends AbstractModule { @@ -13,5 +14,6 @@ public class DataModule extends AbstractModule { bind(BdfReaderFactory.class).to(BdfReaderFactoryImpl.class); bind(BdfWriterFactory.class).to(BdfWriterFactoryImpl.class); bind(MetadataParser.class).to(MetadataParserImpl.class); + bind(MetadataEncoder.class).to(MetadataEncoderImpl.class); } } diff --git a/briar-core/src/org/briarproject/data/MetadataEncoderImpl.java b/briar-core/src/org/briarproject/data/MetadataEncoderImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..313b62f4b1752e6b26fe7b45dc233314ae430085 --- /dev/null +++ b/briar-core/src/org/briarproject/data/MetadataEncoderImpl.java @@ -0,0 +1,173 @@ +package org.briarproject.data; + +import org.briarproject.api.FormatException; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.db.Metadata; +import org.briarproject.util.StringUtils; + +import java.io.ByteArrayOutputStream; +import java.util.Map; + +import static org.briarproject.data.Types.DICTIONARY; +import static org.briarproject.data.Types.END; +import static org.briarproject.data.Types.FALSE; +import static org.briarproject.data.Types.FLOAT_64; +import static org.briarproject.data.Types.INT_16; +import static org.briarproject.data.Types.INT_32; +import static org.briarproject.data.Types.INT_64; +import static org.briarproject.data.Types.INT_8; +import static org.briarproject.data.Types.LIST; +import static org.briarproject.data.Types.NULL; +import static org.briarproject.data.Types.RAW_16; +import static org.briarproject.data.Types.RAW_32; +import static org.briarproject.data.Types.RAW_8; +import static org.briarproject.data.Types.STRING_16; +import static org.briarproject.data.Types.STRING_32; +import static org.briarproject.data.Types.STRING_8; +import static org.briarproject.data.Types.TRUE; + +class MetadataEncoderImpl implements MetadataEncoder { + + @Override + public Metadata encode(BdfDictionary d) throws FormatException { + Metadata m = new Metadata(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (Map.Entry<String, Object> e : d.entrySet()) { + if (e.getValue() == null) { + // Special case: if the value is null, the key is being removed + m.put(e.getKey(), null); + } else { + encodeObject(out, e.getValue()); + m.put(e.getKey(), out.toByteArray()); + out.reset(); + } + } + return m; + } + + private void encodeObject(ByteArrayOutputStream out, Object o) + throws FormatException { + if (o == null) out.write(NULL); + else if (o instanceof Boolean) out.write((Boolean) o ? TRUE : FALSE); + else if (o instanceof Byte) encodeInteger(out, (Byte) o); + else if (o instanceof Short) encodeInteger(out, (Short) o); + else if (o instanceof Integer) encodeInteger(out, (Integer) o); + else if (o instanceof Long) encodeInteger(out, (Long) o); + else if (o instanceof Float) encodeFloat(out, (Float) o); + else if (o instanceof Double) encodeFloat(out, (Double) o); + else if (o instanceof String) encodeString(out, (String) o); + else if (o instanceof byte[]) encodeRaw(out, (byte[]) o); + else if (o instanceof BdfList) encodeList(out, (BdfList) o); + else if (o instanceof BdfDictionary) encodeDictionary(out, + (BdfDictionary) o); + else throw new FormatException(); + } + + private void encodeInteger(ByteArrayOutputStream out, byte i) { + out.write(INT_8); + out.write(i); + } + + private void encodeInteger(ByteArrayOutputStream out, short i) { + if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) { + encodeInteger(out, (byte) i); + } else { + out.write(INT_16); + out.write((byte) (i >> 8)); + out.write((byte) ((i << 8) >> 8)); + } + } + + private void encodeInteger(ByteArrayOutputStream out, int i) { + if (i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) { + encodeInteger(out, (short) i); + } else { + out.write(INT_32); + out.write((byte) (i >> 24)); + out.write((byte) ((i << 8) >> 24)); + out.write((byte) ((i << 16) >> 24)); + out.write((byte) ((i << 24) >> 24)); + } + } + + private void encodeInteger(ByteArrayOutputStream out, long i) { + if (i >= Integer.MIN_VALUE && i <= Integer.MAX_VALUE) { + encodeInteger(out, (int) i); + } else { + out.write(INT_64); + out.write((byte) (i >> 56)); + out.write((byte) ((i << 8) >> 56)); + out.write((byte) ((i << 16) >> 56)); + out.write((byte) ((i << 24) >> 56)); + out.write((byte) ((i << 32) >> 56)); + out.write((byte) ((i << 40) >> 56)); + out.write((byte) ((i << 48) >> 56)); + out.write((byte) ((i << 56) >> 56)); + } + } + + private void encodeFloat(ByteArrayOutputStream out, float f) { + encodeFloat(out, (double) f); + } + + private void encodeFloat(ByteArrayOutputStream out, double d) { + long i = Double.doubleToLongBits(d); + out.write(FLOAT_64); + out.write((byte) (i >> 56)); + out.write((byte) ((i << 8) >> 56)); + out.write((byte) ((i << 16) >> 56)); + out.write((byte) ((i << 24) >> 56)); + out.write((byte) ((i << 32) >> 56)); + out.write((byte) ((i << 40) >> 56)); + out.write((byte) ((i << 48) >> 56)); + out.write((byte) ((i << 56) >> 56)); + } + + private void encodeString(ByteArrayOutputStream out, String s) { + byte[] b = StringUtils.toUtf8(s); + if (b.length <= Byte.MAX_VALUE) { + out.write(STRING_8); + encodeInteger(out, (byte) b.length); + } else if (b.length <= Short.MAX_VALUE) { + out.write(STRING_16); + encodeInteger(out, (short) b.length); + } else { + out.write(STRING_32); + encodeInteger(out, b.length); + } + out.write(b, 0, b.length); + } + + private void encodeRaw(ByteArrayOutputStream out, byte[] b) { + if (b.length <= Byte.MAX_VALUE) { + out.write(RAW_8); + encodeInteger(out, (byte) b.length); + } else if (b.length <= Short.MAX_VALUE) { + out.write(RAW_16); + encodeInteger(out, (short) b.length); + } else { + out.write(RAW_32); + encodeInteger(out, b.length); + } + out.write(b, 0, b.length); + } + + private void encodeList(ByteArrayOutputStream out, BdfList list) + throws FormatException { + out.write(LIST); + for (Object o : list) encodeObject(out, o); + out.write(END); + } + + private void encodeDictionary(ByteArrayOutputStream out, + BdfDictionary dict) throws FormatException { + out.write(DICTIONARY); + for (Map.Entry<String, Object> e : dict.entrySet()) { + encodeString(out, e.getKey()); + encodeObject(out, e.getValue()); + } + out.write(END); + } +} diff --git a/briar-core/src/org/briarproject/data/MetadataParserImpl.java b/briar-core/src/org/briarproject/data/MetadataParserImpl.java index 954838072c98c90489f3eb6ee088a00df70031ab..e705bcca597c65caf31a8bdb79666fb7d5ef0289 100644 --- a/briar-core/src/org/briarproject/data/MetadataParserImpl.java +++ b/briar-core/src/org/briarproject/data/MetadataParserImpl.java @@ -5,9 +5,9 @@ import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfList; import org.briarproject.api.data.MetadataParser; import org.briarproject.api.db.Metadata; +import org.briarproject.util.StringUtils; import java.io.ByteArrayInputStream; -import java.io.UnsupportedEncodingException; import java.util.Map; import static org.briarproject.data.Types.DICTIONARY; @@ -129,11 +129,7 @@ class MetadataParserImpl implements MetadataParser { if (len < 0) throw new FormatException(); byte[] b = new byte[len]; if (in.read(b, 0, len) != len) throw new FormatException(); - try { - return new String(b, 0, len, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(); - } + return StringUtils.fromUtf8(b, 0, len); } private byte[] parseRaw(ByteArrayInputStream in, int len) diff --git a/briar-core/src/org/briarproject/util/StringUtils.java b/briar-core/src/org/briarproject/util/StringUtils.java index 0f62c62a8f09470e823964c54c8168989d7af2c0..822831ac05b347daa88b041144a6bf100a93cd04 100644 --- a/briar-core/src/org/briarproject/util/StringUtils.java +++ b/briar-core/src/org/briarproject/util/StringUtils.java @@ -39,6 +39,14 @@ public class StringUtils { } } + public static String fromUtf8(byte[] bytes, int off, int len) { + try { + return new String(bytes, off, len, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + /** Converts the given byte array to a hex character array. */ public static char[] toHexChars(byte[] bytes) { char[] hex = new char[bytes.length * 2];