Cleaned up serial and protocol packages in preparation for user-defined types.

This commit is contained in:
akwizgran
2011-07-18 14:33:41 +01:00
parent 308a7017be
commit 0bc8a31749
20 changed files with 378 additions and 495 deletions

View File

@@ -1,7 +1,6 @@
package net.sf.briar.protocol;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.PublicKey;
@@ -24,16 +23,12 @@ import net.sf.briar.api.protocol.UniqueId;
import net.sf.briar.api.serial.FormatException;
import net.sf.briar.api.serial.Raw;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
import com.google.inject.Inject;
class BundleReaderImpl implements BundleReader {
private static enum State { START, FIRST_BATCH, MORE_BATCHES, END };
private final SigningDigestingInputStream in;
private final Reader r;
private final Reader reader;
private final PublicKey publicKey;
private final Signature signature;
private final MessageDigest messageDigest;
@@ -42,13 +37,10 @@ class BundleReaderImpl implements BundleReader {
private final BatchFactory batchFactory;
private State state = State.START;
@Inject
BundleReaderImpl(InputStream in, ReaderFactory readerFactory,
PublicKey publicKey, Signature signature,
BundleReaderImpl(Reader reader, PublicKey publicKey, Signature signature,
MessageDigest messageDigest, MessageParser messageParser,
HeaderFactory headerFactory, BatchFactory batchFactory) {
this.in = new SigningDigestingInputStream(in, signature, messageDigest);
r = readerFactory.createReader(this.in);
this.reader = reader;
this.publicKey = publicKey;
this.signature = signature;
this.messageDigest = messageDigest;
@@ -61,29 +53,31 @@ class BundleReaderImpl implements BundleReader {
if(state != State.START) throw new IllegalStateException();
state = State.FIRST_BATCH;
// Initialise the input stream
CountingConsumer counting = new CountingConsumer(Header.MAX_SIZE);
SigningConsumer signing = new SigningConsumer(signature);
signature.initVerify(publicKey);
messageDigest.reset();
// Read the signed data
in.setSigning(true);
r.setReadLimit(Header.MAX_SIZE);
reader.addConsumer(counting);
reader.addConsumer(signing);
Set<BatchId> acks = new HashSet<BatchId>();
for(Raw raw : r.readList(Raw.class)) {
for(Raw raw : reader.readList(Raw.class)) {
byte[] b = raw.getBytes();
if(b.length != UniqueId.LENGTH) throw new FormatException();
acks.add(new BatchId(b));
}
Set<GroupId> subs = new HashSet<GroupId>();
for(Raw raw : r.readList(Raw.class)) {
for(Raw raw : reader.readList(Raw.class)) {
byte[] b = raw.getBytes();
if(b.length != UniqueId.LENGTH) throw new FormatException();
subs.add(new GroupId(b));
}
Map<String, String> transports =
r.readMap(String.class, String.class);
long timestamp = r.readInt64();
in.setSigning(false);
reader.readMap(String.class, String.class);
long timestamp = reader.readInt64();
reader.removeConsumer(signing);
// Read and verify the signature
byte[] sig = r.readRaw();
byte[] sig = reader.readRaw();
reader.removeConsumer(counting);
if(!signature.verify(sig)) throw new SignatureException();
// Build and return the header
return headerFactory.createHeader(acks, subs, transports, timestamp);
@@ -91,29 +85,33 @@ class BundleReaderImpl implements BundleReader {
public Batch getNextBatch() throws IOException, GeneralSecurityException {
if(state == State.FIRST_BATCH) {
r.readListStart();
reader.readListStart();
state = State.MORE_BATCHES;
}
if(state != State.MORE_BATCHES) throw new IllegalStateException();
if(r.hasListEnd()) {
r.readListEnd();
if(reader.hasListEnd()) {
reader.readListEnd();
// That should be all
if(!r.eof()) throw new FormatException();
if(!reader.eof()) throw new FormatException();
state = State.END;
return null;
}
// Initialise the input stream
signature.initVerify(publicKey);
CountingConsumer counting = new CountingConsumer(Batch.MAX_SIZE);
DigestingConsumer digesting = new DigestingConsumer(messageDigest);
messageDigest.reset();
SigningConsumer signing = new SigningConsumer(signature);
signature.initVerify(publicKey);
// Read the signed data
in.setDigesting(true);
in.setSigning(true);
r.setReadLimit(Batch.MAX_SIZE);
List<Raw> rawMessages = r.readList(Raw.class);
in.setSigning(false);
reader.addConsumer(counting);
reader.addConsumer(digesting);
reader.addConsumer(signing);
List<Raw> rawMessages = reader.readList(Raw.class);
reader.removeConsumer(signing);
// Read and verify the signature
byte[] sig = r.readRaw();
in.setDigesting(false);
byte[] sig = reader.readRaw();
reader.removeConsumer(digesting);
reader.removeConsumer(counting);
if(!signature.verify(sig)) throw new SignatureException();
// Parse the messages
List<Message> messages = new ArrayList<Message>(rawMessages.size());
@@ -127,6 +125,6 @@ class BundleReaderImpl implements BundleReader {
}
public void finish() throws IOException {
r.close();
reader.close();
}
}

View File

@@ -40,7 +40,7 @@ class BundleWriterImpl implements BundleWriter {
}
public long getRemainingCapacity() {
return capacity - w.getRawBytesWritten();
return capacity - w.getBytesWritten();
}
public void addHeader(Iterable<BatchId> acks, Iterable<GroupId> subs,

View File

@@ -0,0 +1,39 @@
package net.sf.briar.protocol;
import java.io.IOException;
import net.sf.briar.api.serial.Consumer;
import net.sf.briar.api.serial.FormatException;
/**
* A consumer that counts the number of bytes consumed and throws a
* FormatException if the count exceeds a given limit.
*/
class CountingConsumer implements Consumer {
private final long limit;
private long count = 0L;
CountingConsumer(long limit) {
this.limit = limit;
}
long getCount() {
return count;
}
public void write(byte b) throws IOException {
count++;
if(count > limit) throw new FormatException();
}
public void write(byte[] b) throws IOException {
count += b.length;
if(count > limit) throw new FormatException();
}
public void write(byte[] b, int off, int len) throws IOException {
count += len;
if(count > limit) throw new FormatException();
}
}

View File

@@ -0,0 +1,28 @@
package net.sf.briar.protocol;
import java.io.IOException;
import java.security.MessageDigest;
import net.sf.briar.api.serial.Consumer;
/** A consumer that passes its input through a message digest. */
class DigestingConsumer implements Consumer {
private final MessageDigest messageDigest;
DigestingConsumer(MessageDigest messageDigest) {
this.messageDigest = messageDigest;
}
public void write(byte b) throws IOException {
messageDigest.update(b);
}
public void write(byte[] b) throws IOException {
messageDigest.update(b);
}
public void write(byte[] b, int off, int len) throws IOException {
messageDigest.update(b, off, len);
}
}

View File

@@ -38,7 +38,7 @@ class MessageEncoderImpl implements MessageEncoder {
w.writeRaw(parent);
w.writeRaw(group);
w.writeInt64(timestamp);
w.writeUtf8(nick);
w.writeString(nick);
w.writeRaw(encodedKey);
w.writeRaw(body);
byte[] signable = out.toByteArray();

View File

@@ -40,6 +40,8 @@ class MessageParserImpl implements MessageParser {
if(raw.length > Message.MAX_SIZE) throw new FormatException();
ByteArrayInputStream in = new ByteArrayInputStream(raw);
Reader r = readerFactory.createReader(in);
CountingConsumer counting = new CountingConsumer(Message.MAX_SIZE);
r.addConsumer(counting);
// Read the parent message ID
byte[] idBytes = r.readRaw();
if(idBytes.length != UniqueId.LENGTH) throw new FormatException();
@@ -51,7 +53,7 @@ class MessageParserImpl implements MessageParser {
// Read the timestamp
long timestamp = r.readInt64();
// Hash the author's nick and public key to get the author ID
String nick = r.readUtf8();
String nick = r.readString();
byte[] encodedKey = r.readRaw();
messageDigest.reset();
messageDigest.update(nick.getBytes("UTF-8"));
@@ -61,7 +63,8 @@ class MessageParserImpl implements MessageParser {
// Skip the message body
r.readRaw();
// Record the length of the signed data
int messageLength = (int) r.getRawBytesRead();
int messageLength = (int) counting.getCount();
r.removeConsumer(counting);
// Read the signature
byte[] sig = r.readRaw();
// That should be all

View File

@@ -0,0 +1,41 @@
package net.sf.briar.protocol;
import java.io.IOException;
import java.security.Signature;
import java.security.SignatureException;
import net.sf.briar.api.serial.Consumer;
/** A consumer that passes its input through a signature. */
class SigningConsumer implements Consumer {
private final Signature signature;
SigningConsumer(Signature signature) {
this.signature = signature;
}
public void write(byte b) throws IOException {
try {
signature.update(b);
} catch(SignatureException e) {
throw new IOException(e.getMessage());
}
}
public void write(byte[] b) throws IOException {
try {
signature.update(b);
} catch(SignatureException e) {
throw new IOException(e.getMessage());
}
}
public void write(byte[] b, int off, int len) throws IOException {
try {
signature.update(b, off, len);
} catch(SignatureException e) {
throw new IOException(e.getMessage());
}
}
}

View File

@@ -1,116 +0,0 @@
package net.sf.briar.protocol;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.Signature;
import java.security.SignatureException;
/**
* An input stream that passes its input through a signature and a message
* digest. The signature and message digest lag behind the input by one byte
* until the end of the input is reached, to allow users of this class to
* maintain one byte of lookahead without affecting the signature or digest.
*/
class SigningDigestingInputStream extends FilterInputStream {
private final Signature signature;
private final MessageDigest messageDigest;
private byte nextByte = 0;
private boolean started = false, eof = false;
private boolean signing = false, digesting = false;
protected SigningDigestingInputStream(InputStream in, Signature signature,
MessageDigest messageDigest) {
super(in);
this.signature = signature;
this.messageDigest = messageDigest;
}
public void setSigning(boolean signing) {
this.signing = signing;
}
public void setDigesting(boolean digesting) {
this.digesting = digesting;
}
private void write(byte b) throws IOException {
if(signing) {
try {
signature.update(b);
} catch(SignatureException e) {
throw new IOException(e.getMessage());
}
}
if(digesting) messageDigest.update(b);
}
private void write(byte[] b, int off, int len) throws IOException {
if(signing) {
try {
signature.update(b, off, len);
} catch(SignatureException e) {
throw new IOException(e.getMessage());
}
}
if(digesting) messageDigest.update(b, off, len);
}
@Override
public void mark(int readLimit) {
throw new UnsupportedOperationException();
}
@Override
public boolean markSupported() {
return false;
}
@Override
public int read() throws IOException {
if(eof) return -1;
if(started) write(nextByte);
started = true;
int i = in.read();
if(i == -1) {
eof = true;
return -1;
}
nextByte = (byte) (i > 127 ? i - 256 : i);
return i;
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if(eof) return -1;
if(started) write(nextByte);
started = true;
int read = in.read(b, off, len);
if(read == -1) {
eof = true;
return -1;
}
if(read > 0) {
write(b, off, read - 1);
nextByte = b[off + read - 1];
}
return read;
}
@Override
public void reset() {
throw new UnsupportedOperationException();
}
@Override
public long skip(long n) {
throw new UnsupportedOperationException();
}
}

View File

@@ -7,6 +7,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sf.briar.api.serial.Consumer;
import net.sf.briar.api.serial.FormatException;
import net.sf.briar.api.serial.RawByteArray;
import net.sf.briar.api.serial.Reader;
@@ -14,12 +15,10 @@ import net.sf.briar.api.serial.Tag;
class ReaderImpl implements Reader {
private static final int TOO_LARGE_TO_KEEP = 4096;
private final InputStream in;
private boolean started = false, eof = false, readLimited = false;
private Consumer[] consumers = new Consumer[] {};
private boolean started = false, eof = false;
private byte next;
private long rawBytesRead = 0L, readLimit = 0L;
private byte[] buf = null;
ReaderImpl(InputStream in) {
@@ -32,38 +31,45 @@ class ReaderImpl implements Reader {
}
private byte readNext(boolean eofAcceptable) throws IOException {
assert !eof;
if(started) for(Consumer c : consumers) c.write(next);
started = true;
int i = in.read();
if(i == -1) {
eof = true;
if(!eofAcceptable) throw new FormatException();
} else rawBytesRead++;
started = true;
}
if(i > 127) i -= 256;
next = (byte) i;
return next;
}
public void setReadLimit(long limit) {
assert limit >= 0L && limit < Long.MAX_VALUE;
readLimited = true;
readLimit = limit;
}
public void resetReadLimit() {
readLimited = false;
readLimit = 0L;
}
public long getRawBytesRead() {
if(eof) return rawBytesRead;
else if(started) return rawBytesRead - 1L; // Exclude lookahead byte
else return 0L;
}
public void close() throws IOException {
buf = null;
in.close();
}
public void addConsumer(Consumer c) {
Consumer[] newConsumers = new Consumer[consumers.length + 1];
System.arraycopy(consumers, 0, newConsumers, 0, consumers.length);
newConsumers[consumers.length] = c;
consumers = newConsumers;
}
public void removeConsumer(Consumer c) {
if(consumers.length == 0) throw new IllegalArgumentException();
Consumer[] newConsumers = new Consumer[consumers.length - 1];
boolean found = false;
for(int src = 0, dest = 0; src < consumers.length; src++, dest++) {
if(!found && consumers[src].equals(c)) {
found = true;
src++;
} else newConsumers[dest] = consumers[src];
}
if(found) consumers = newConsumers;
else throw new IllegalArgumentException();
}
public boolean hasBoolean() throws IOException {
if(!started) readNext(true);
if(eof) return false;
@@ -140,15 +146,20 @@ class ReaderImpl implements Reader {
assert length > 0;
if(buf == null || buf.length < length) buf = new byte[length];
buf[0] = next;
int offset = 1, read = 0;
int offset = 1;
while(offset < length) {
read = in.read(buf, offset, length - offset);
int read = in.read(buf, offset, length - offset);
if(read == -1) break;
offset += read;
rawBytesRead += read;
}
if(offset < length) throw new FormatException();
readNext(true);
// Feed the hungry mouths
for(Consumer c : consumers) c.write(buf, 0, length);
// Read the lookahead byte
int i = in.read();
if(i == -1) eof = true;
if(i > 127) i -= 256;
next = (byte) i;
}
public boolean hasInt64() throws IOException {
@@ -212,31 +223,40 @@ class ReaderImpl implements Reader {
return Double.longBitsToDouble(readInt64Bits());
}
public boolean hasUtf8() throws IOException {
public boolean hasString() throws IOException {
if(!started) readNext(true);
if(eof) return false;
return next == Tag.UTF8;
return next == Tag.STRING;
}
public String readUtf8() throws IOException {
if(!hasUtf8()) throw new FormatException();
public String readString() throws IOException {
if(!hasString()) throw new FormatException();
readNext(false);
long l = readIntAny();
if(l < 0 || l > Integer.MAX_VALUE) throw new FormatException();
int length = (int) l;
int length = readLength();
if(length == 0) return "";
checkLimit(length);
readIntoBuffer(length);
String s = new String(buf, 0, length, "UTF-8");
if(length >= TOO_LARGE_TO_KEEP) buf = null;
return s;
return new String(buf, 0, length, "UTF-8");
}
private boolean hasLength() throws IOException {
if(!started) readNext(true);
if(eof) return false;
return next >= 0 || next == Tag.INT8 || next == Tag.INT16
|| next == Tag.INT32;
}
private int readLength() throws IOException {
if(!hasLength()) throw new FormatException();
if(next >= 0) return readUint7();
if(next == Tag.INT8) return readInt8();
if(next == Tag.INT16) return readInt16();
if(next == Tag.INT32) return readInt32();
throw new IllegalStateException();
}
private void checkLimit(long bytes) throws FormatException {
if(readLimited) {
if(bytes > readLimit) throw new FormatException();
readLimit -= bytes;
}
// FIXME
}
public boolean hasRaw() throws IOException {
@@ -248,9 +268,7 @@ class ReaderImpl implements Reader {
public byte[] readRaw() throws IOException {
if(!hasRaw()) throw new FormatException();
readNext(false);
long l = readIntAny();
if(l < 0 || l > Integer.MAX_VALUE) throw new FormatException();
int length = (int) l;
int length = readLength();
if(length == 0) return new byte[] {};
checkLimit(length);
readIntoBuffer(length);
@@ -262,7 +280,7 @@ class ReaderImpl implements Reader {
public boolean hasList() throws IOException {
if(!started) readNext(true);
if(eof) return false;
return next == Tag.LIST_DEF || next == Tag.LIST_INDEF;
return next == Tag.LIST || next == Tag.LIST_START;
}
public List<Object> readList() throws IOException {
@@ -271,13 +289,11 @@ class ReaderImpl implements Reader {
public <E> List<E> readList(Class<E> e) throws IOException {
if(!hasList()) throw new FormatException();
boolean definite = next == Tag.LIST_DEF;
boolean definite = next == Tag.LIST;
readNext(false);
List<E> list = new ArrayList<E>();
if(definite) {
long l = readIntAny();
if(l < 0 || l > Integer.MAX_VALUE) throw new FormatException();
int length = (int) l;
int length = readLength();
for(int i = 0; i < length; i++) list.add(readObject(e));
} else {
while(!hasEnd()) list.add(readObject(e));
@@ -299,6 +315,7 @@ class ReaderImpl implements Reader {
}
private Object readObject() throws IOException {
// FIXME: Use a switch statement
if(!started) throw new IllegalStateException();
if(hasBoolean()) return Boolean.valueOf(readBoolean());
if(hasUint7()) return Byte.valueOf(readUint7());
@@ -308,7 +325,7 @@ class ReaderImpl implements Reader {
if(hasInt64()) return Long.valueOf(readInt64());
if(hasFloat32()) return Float.valueOf(readFloat32());
if(hasFloat64()) return Double.valueOf(readFloat64());
if(hasUtf8()) return readUtf8();
if(hasString()) return readString();
if(hasRaw()) return new RawByteArray(readRaw());
if(hasList()) return readList();
if(hasMap()) return readMap();
@@ -331,7 +348,7 @@ class ReaderImpl implements Reader {
public boolean hasListStart() throws IOException {
if(!started) readNext(true);
if(eof) return false;
return next == Tag.LIST_INDEF;
return next == Tag.LIST_START;
}
public void readListStart() throws IOException {
@@ -350,7 +367,7 @@ class ReaderImpl implements Reader {
public boolean hasMap() throws IOException {
if(!started) readNext(true);
if(eof) return false;
return next == Tag.MAP_DEF || next == Tag.MAP_INDEF;
return next == Tag.MAP || next == Tag.MAP_START;
}
public Map<Object, Object> readMap() throws IOException {
@@ -359,13 +376,11 @@ class ReaderImpl implements Reader {
public <K, V> Map<K, V> readMap(Class<K> k, Class<V> v) throws IOException {
if(!hasMap()) throw new FormatException();
boolean definite = next == Tag.MAP_DEF;
boolean definite = next == Tag.MAP;
readNext(false);
Map<K, V> m = new HashMap<K, V>();
if(definite) {
long l = readIntAny();
if(l < 0 || l > Integer.MAX_VALUE) throw new FormatException();
int length = (int) l;
int length = readLength();
for(int i = 0; i < length; i++) m.put(readObject(k), readObject(v));
} else {
while(!hasEnd()) m.put(readObject(k), readObject(v));
@@ -377,7 +392,7 @@ class ReaderImpl implements Reader {
public boolean hasMapStart() throws IOException {
if(!started) readNext(true);
if(eof) return false;
return next == Tag.MAP_INDEF;
return next == Tag.MAP_START;
}
public void readMapStart() throws IOException {

View File

@@ -13,14 +13,14 @@ import net.sf.briar.api.serial.Writer;
class WriterImpl implements Writer {
private final OutputStream out;
private long rawBytesWritten = 0L;
private long bytesWritten = 0L;
WriterImpl(OutputStream out) {
this.out = out;
}
public long getRawBytesWritten() {
return rawBytesWritten;
public long getBytesWritten() {
return bytesWritten;
}
public void close() throws IOException {
@@ -31,32 +31,32 @@ class WriterImpl implements Writer {
public void writeBoolean(boolean b) throws IOException {
if(b) out.write(Tag.TRUE);
else out.write(Tag.FALSE);
rawBytesWritten++;
bytesWritten++;
}
public void writeUint7(byte b) throws IOException {
if(b < 0) throw new IllegalArgumentException();
out.write(b);
rawBytesWritten++;
bytesWritten++;
}
public void writeInt8(byte b) throws IOException {
out.write(Tag.INT8);
out.write(b);
rawBytesWritten += 2;
bytesWritten += 2;
}
public void writeInt16(short s) throws IOException {
out.write(Tag.INT16);
out.write((byte) (s >> 8));
out.write((byte) ((s << 8) >> 8));
rawBytesWritten += 3;
bytesWritten += 3;
}
public void writeInt32(int i) throws IOException {
out.write(Tag.INT32);
writeInt32Bits(i);
rawBytesWritten += 5;
bytesWritten += 5;
}
private void writeInt32Bits(int i) throws IOException {
@@ -69,7 +69,7 @@ class WriterImpl implements Writer {
public void writeInt64(long l) throws IOException {
out.write(Tag.INT64);
writeInt64Bits(l);
rawBytesWritten += 9;
bytesWritten += 9;
}
private void writeInt64Bits(long l) throws IOException {
@@ -98,28 +98,28 @@ class WriterImpl implements Writer {
public void writeFloat32(float f) throws IOException {
out.write(Tag.FLOAT32);
writeInt32Bits(Float.floatToRawIntBits(f));
rawBytesWritten += 5;
bytesWritten += 5;
}
public void writeFloat64(double d) throws IOException {
out.write(Tag.FLOAT64);
writeInt64Bits(Double.doubleToRawLongBits(d));
rawBytesWritten += 9;
bytesWritten += 9;
}
public void writeUtf8(String s) throws IOException {
out.write(Tag.UTF8);
public void writeString(String s) throws IOException {
out.write(Tag.STRING);
byte[] b = s.getBytes("UTF-8");
writeIntAny(b.length);
out.write(b);
rawBytesWritten += b.length + 1;
bytesWritten += b.length + 1;
}
public void writeRaw(byte[] b) throws IOException {
out.write(Tag.RAW);
writeIntAny(b.length);
out.write(b);
rawBytesWritten += b.length + 1;
bytesWritten += b.length + 1;
}
public void writeRaw(Raw r) throws IOException {
@@ -127,8 +127,8 @@ class WriterImpl implements Writer {
}
public void writeList(List<?> l) throws IOException {
out.write(Tag.LIST_DEF);
rawBytesWritten++;
out.write(Tag.LIST);
bytesWritten++;
writeIntAny(l.size());
for(Object o : l) writeObject(o);
}
@@ -141,7 +141,7 @@ class WriterImpl implements Writer {
else if(o instanceof Long) writeIntAny((Long) o);
else if(o instanceof Float) writeFloat32((Float) o);
else if(o instanceof Double) writeFloat64((Double) o);
else if(o instanceof String) writeUtf8((String) o);
else if(o instanceof String) writeString((String) o);
else if(o instanceof Raw) writeRaw((Raw) o);
else if(o instanceof List) writeList((List<?>) o);
else if(o instanceof Map) writeMap((Map<?, ?>) o);
@@ -150,18 +150,18 @@ class WriterImpl implements Writer {
}
public void writeListStart() throws IOException {
out.write(Tag.LIST_INDEF);
rawBytesWritten++;
out.write(Tag.LIST_START);
bytesWritten++;
}
public void writeListEnd() throws IOException {
out.write(Tag.END);
rawBytesWritten++;
bytesWritten++;
}
public void writeMap(Map<?, ?> m) throws IOException {
out.write(Tag.MAP_DEF);
rawBytesWritten++;
out.write(Tag.MAP);
bytesWritten++;
writeIntAny(m.size());
for(Entry<?, ?> e : m.entrySet()) {
writeObject(e.getKey());
@@ -170,17 +170,17 @@ class WriterImpl implements Writer {
}
public void writeMapStart() throws IOException {
out.write(Tag.MAP_INDEF);
rawBytesWritten++;
out.write(Tag.MAP_START);
bytesWritten++;
}
public void writeMapEnd() throws IOException {
out.write(Tag.END);
rawBytesWritten++;
bytesWritten++;
}
public void writeNull() throws IOException {
out.write(Tag.NULL);
rawBytesWritten++;
bytesWritten++;
}
}