diff --git a/api/net/sf/briar/api/serial/Reader.java b/api/net/sf/briar/api/serial/Reader.java
index defa89ca374cf875c232f69b9a007174c4f86931..bcac81bde5540af0d11e55020ccfeb7aa720efee 100644
--- a/api/net/sf/briar/api/serial/Reader.java
+++ b/api/net/sf/briar/api/serial/Reader.java
@@ -56,4 +56,7 @@ public interface Reader {
 
 	boolean hasNull() throws IOException;
 	void readNull() throws IOException;
+
+	boolean hasUserDefinedTag() throws IOException;
+	int readUserDefinedTag() throws IOException;
 }
diff --git a/api/net/sf/briar/api/serial/Tag.java b/api/net/sf/briar/api/serial/Tag.java
index 0cf44dbc271d71f6eb3c20c7f49eb1a7451bdd2f..09528efaf0c5ce552f4592cfa6dda10afc7b2f17 100644
--- a/api/net/sf/briar/api/serial/Tag.java
+++ b/api/net/sf/briar/api/serial/Tag.java
@@ -19,12 +19,13 @@ public interface Tag {
 	public static final byte END = -15; // 1111 0001
 	public static final byte NULL = -16; // 1111 0000
 
+	public static final byte USER = -32; // 1110 0000
+
 	public static final int SHORT_MASK = 0xF0; // Match first four bits
 	public static final int SHORT_STRING = 0x80; // 1000 xxxx
 	public static final int SHORT_RAW = 0x90; // 1001 xxxx
 	public static final int SHORT_LIST = 0xA0; // 1010 xxxx
 	public static final int SHORT_MAP = 0xB0; // 1011 xxxx
-	public static final int USER_MASK = 0xE0; // Match first three bits
-	public static final int USER = 0xC0; // 110x xxxx
-	public static final byte USER_EXT = -32; // 1110 0000
+	public static final int SHORT_USER_MASK = 0xE0; // Match first three bits
+	public static final int SHORT_USER = 0xC0; // 110x xxxx
 }
diff --git a/api/net/sf/briar/api/serial/Writer.java b/api/net/sf/briar/api/serial/Writer.java
index 284c5b5072df02bf3e479338ac5244e57c477cc7..e1c4a2721e5611d63c8af6ff13821c8ddf4d816c 100644
--- a/api/net/sf/briar/api/serial/Writer.java
+++ b/api/net/sf/briar/api/serial/Writer.java
@@ -34,4 +34,6 @@ public interface Writer {
 	void writeMapEnd() throws IOException;
 
 	void writeNull() throws IOException;
+
+	void writeUserDefinedTag(int tag) throws IOException;
 }
diff --git a/components/net/sf/briar/serial/ReaderImpl.java b/components/net/sf/briar/serial/ReaderImpl.java
index 3d417d047f50eb36b90a99263f26f9d4c79fb15c..89ca72441dead46f2fe30548c6da3ce4ce83c978 100644
--- a/components/net/sf/briar/serial/ReaderImpl.java
+++ b/components/net/sf/briar/serial/ReaderImpl.java
@@ -459,4 +459,23 @@ class ReaderImpl implements Reader {
 		if(!hasNull()) throw new FormatException();
 		readNext(true);
 	}
+
+	public boolean hasUserDefinedTag() throws IOException {
+		if(!started) readNext(true);
+		if(eof) return false;
+		return next == Tag.USER ||
+		(next & Tag.SHORT_USER_MASK) == Tag.SHORT_USER;
+	}
+
+	public int readUserDefinedTag() throws IOException {
+		if(!hasUserDefinedTag()) throw new FormatException();
+		if(next == Tag.USER) {
+			readNext(false);
+			return readLength();
+		} else {
+			int tag = 0xFF & next ^ Tag.SHORT_USER;
+			readNext(true);
+			return tag;
+		}
+	}
 }
diff --git a/components/net/sf/briar/serial/WriterImpl.java b/components/net/sf/briar/serial/WriterImpl.java
index 1a9359aec186bd5f8f6e761dcc96b5c2956bee96..5f5f06de8e2f29ef5f4975b6ff22c41e6e0539f5 100644
--- a/components/net/sf/briar/serial/WriterImpl.java
+++ b/components/net/sf/briar/serial/WriterImpl.java
@@ -211,4 +211,14 @@ class WriterImpl implements Writer {
 		out.write(Tag.NULL);
 		bytesWritten++;
 	}
+
+	public void writeUserDefinedTag(int tag) throws IOException {
+		if(tag < 0) throw new IllegalArgumentException();
+		if(tag < 32) out.write((byte) (Tag.SHORT_USER | tag));
+		else {
+			out.write(Tag.USER);
+			writeLength(tag);
+		}
+		bytesWritten++;
+	}
 }
diff --git a/test/net/sf/briar/serial/ReaderImplTest.java b/test/net/sf/briar/serial/ReaderImplTest.java
index ff1d5921187ed3eb2acfbf66abad0ad0e61c2066..0636a85b28bbba5516deefdaee3ee07c38bea0a5 100644
--- a/test/net/sf/briar/serial/ReaderImplTest.java
+++ b/test/net/sf/briar/serial/ReaderImplTest.java
@@ -316,6 +316,16 @@ public class ReaderImplTest extends TestCase {
 		assertTrue(r.eof());
 	}
 
+	@Test
+	public void testReadUserDefinedTag() throws IOException {
+		setContents("C0" + "DF" + "E0" + "20" + "E0" + "FB7FFFFFFF");
+		assertEquals(0, r.readUserDefinedTag());
+		assertEquals(31, r.readUserDefinedTag());
+		assertEquals(32, r.readUserDefinedTag());
+		assertEquals(Integer.MAX_VALUE, r.readUserDefinedTag());
+		assertTrue(r.eof());
+	}
+
 	@Test
 	public void testReadEmptyInput() throws IOException {
 		setContents("");
diff --git a/test/net/sf/briar/serial/WriterImplTest.java b/test/net/sf/briar/serial/WriterImplTest.java
index b5b91a427f64712afb3878a9c89b507700d875b4..a6f364d07adde13261392fc147fbf448e610fab9 100644
--- a/test/net/sf/briar/serial/WriterImplTest.java
+++ b/test/net/sf/briar/serial/WriterImplTest.java
@@ -37,7 +37,7 @@ public class WriterImplTest extends TestCase {
 	@Test
 	public void testWriteUint7() throws IOException {
 		w.writeUint7((byte) 0);
-		w.writeUint7((byte) 127);
+		w.writeUint7(Byte.MAX_VALUE);
 		// 0, 127
 		checkContents("00" + "7F");
 	}
@@ -46,8 +46,8 @@ public class WriterImplTest extends TestCase {
 	public void testWriteInt8() throws IOException {
 		w.writeInt8((byte) 0);
 		w.writeInt8((byte) -1);
-		w.writeInt8((byte) -128);
-		w.writeInt8((byte) 127);
+		w.writeInt8(Byte.MIN_VALUE);
+		w.writeInt8(Byte.MAX_VALUE);
 		// INT8 tag, 0, INT8 tag, -1, INT8 tag, -128, INT8 tag, 127
 		checkContents("FD" + "00" + "FD" + "FF" + "FD" + "80" + "FD" + "7F");
 	}
@@ -56,8 +56,8 @@ public class WriterImplTest extends TestCase {
 	public void testWriteInt16() throws IOException {
 		w.writeInt16((short) 0);
 		w.writeInt16((short) -1);
-		w.writeInt16((short) -32768);
-		w.writeInt16((short) 32767);
+		w.writeInt16(Short.MIN_VALUE);
+		w.writeInt16(Short.MAX_VALUE);
 		// INT16 tag, 0, INT16 tag, -1, INT16 tag, -32768, INT16 tag, 32767
 		checkContents("FC" + "0000" + "FC" + "FFFF" + "FC" + "8000"
 				+ "FC" + "7FFF");
@@ -67,8 +67,8 @@ public class WriterImplTest extends TestCase {
 	public void testWriteInt32() throws IOException {
 		w.writeInt32(0);
 		w.writeInt32(-1);
-		w.writeInt32(-2147483648);
-		w.writeInt32(2147483647);
+		w.writeInt32(Integer.MIN_VALUE);
+		w.writeInt32(Integer.MAX_VALUE);
 		// INT32 tag, 0, INT32 tag, -1, etc
 		checkContents("FB" + "00000000" + "FB" + "FFFFFFFF" + "FB" + "80000000"
 				+ "FB" + "7FFFFFFF");
@@ -78,8 +78,8 @@ public class WriterImplTest extends TestCase {
 	public void testWriteInt64() throws IOException {
 		w.writeInt64(0L);
 		w.writeInt64(-1L);
-		w.writeInt64(-9223372036854775808L);
-		w.writeInt64(9223372036854775807L);
+		w.writeInt64(Long.MIN_VALUE);
+		w.writeInt64(Long.MAX_VALUE);
 		// INT64 tag, 0, INT64 tag, -1, etc
 		checkContents("FA" + "0000000000000000" + "FA" + "FFFFFFFFFFFFFFFF"
 				+ "FA" + "8000000000000000" + "FA" + "7FFFFFFFFFFFFFFF");
@@ -87,15 +87,15 @@ public class WriterImplTest extends TestCase {
 
 	@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"
+		w.writeIntAny(0); // uint7
+		w.writeIntAny(-1); // int8
+		w.writeIntAny(Byte.MAX_VALUE); // uint7
+		w.writeIntAny(Byte.MAX_VALUE + 1); // int16
+		w.writeIntAny(Short.MAX_VALUE); // int16
+		w.writeIntAny(Short.MAX_VALUE + 1); // int32
+		w.writeIntAny(Integer.MAX_VALUE); // int32
+		w.writeIntAny(Integer.MAX_VALUE + 1L); // int64
+		checkContents("00" + "FDFF" + "7F" + "FC0080" + "FC7FFF"
 				+ "FB00008000" + "FB7FFFFFFF" + "FA0000000080000000");
 	}
 
@@ -283,6 +283,23 @@ public class WriterImplTest extends TestCase {
 		checkContents("F0");
 	}
 
+	@Test
+	public void testWriteShortUserDefinedTag() throws IOException {
+		w.writeUserDefinedTag(0);
+		w.writeUserDefinedTag(31);
+		// SHORT_USER tag (3 bits), 0 (5 bits), SHORT_USER tag (3 bits),
+		// 31 (5 bits)
+		checkContents("C0" + "DF");
+	}
+
+	@Test
+	public void testWriteUserDefinedTag() throws IOException {
+		w.writeUserDefinedTag(32);
+		w.writeUserDefinedTag(Integer.MAX_VALUE);
+		// USER tag, 32 as uint7, USER tag, 2147483647 as int32
+		checkContents("E0" + "20" + "E0" + "FB7FFFFFFF");
+	}
+
 	private void checkContents(String hex) throws IOException {
 		out.flush();
 		out.close();