Fix handling of encoded ints

Fix encoding of ints to handle ints with high bit set. Modify Parser to
detect when it is decoding a run-on badly encoded int (ie, >5 bytes).

Bug:14817987
Change-Id: I4a21b126d03e2e244b0fd4c4fb4821d165897ad6
diff --git a/src/com/android/exchange/EasOutboxService.java b/src/com/android/exchange/EasOutboxService.java
index 8c8a43f..84d6108 100644
--- a/src/com/android/exchange/EasOutboxService.java
+++ b/src/com/android/exchange/EasOutboxService.java
@@ -182,9 +182,9 @@
             s.start(Tags.COMPOSE_MIME);
             // Send opaque data from the file stream
             if (withData) {
-                s.opaque(mFileStream, (int)mFileLength);
+                s.opaque(mFileStream, (int) mFileLength);
             } else {
-                s.opaqueWithoutData((int)mFileLength);
+                s.writeOpaqueHeader((int) mFileLength);
             }
             // And we're done
             s.end().end().done();
diff --git a/src/com/android/exchange/adapter/Parser.java b/src/com/android/exchange/adapter/Parser.java
index 960e9dd..cc8f747 100644
--- a/src/com/android/exchange/adapter/Parser.java
+++ b/src/com/android/exchange/adapter/Parser.java
@@ -68,9 +68,6 @@
     private static final int NOT_ENDED = Integer.MIN_VALUE;
     private static final int EOF_BYTE = -1;
 
-    // Where tags start in a page
-    private static final int TAG_BASE = 5;
-
     private boolean logging = false;
     private boolean capture = false;
 
@@ -82,50 +79,55 @@
     // The current tag depth
     private int depth;
 
-    // The current tag table (i.e. the tag table for the current page)
-    private String[] tagTable;
-
-    // An array of tag tables, as defined in EasTags
-    static private String[][] tagTables = new String[Tags.pages.length + 1][];
-
     // The stack of names of tags being processed; used when debug = true
     private String[] nameArray = new String[32];
 
     public class Tag {
-        public int index;
+        private final int mPage;
+        private final int mIndex;
         // Whether the tag is associated with content (a value)
-        public boolean noContent;
-        public String name;
+        public final boolean mNoContent;
+        private final String mName;
 
-        public Tag(int id) {
+        public Tag(final int page, final int id) {
+            mPage = page;
             // The tag is in the low 6 bits
-            index = id & Tags.PAGE_MASK;
+            mIndex = id & Tags.PAGE_MASK;
             // If the high bit is set, there is content (a value) to be read
-            noContent = (id & Wbxml.WITH_CONTENT) == 0;
-            if (index < TAG_BASE) {
-                name = "unsupported-WBXML";
-            } else if (tagTable == null || index - TAG_BASE >= tagTable.length) {
-                name = "unknown";
+            mNoContent = (id & Wbxml.WITH_CONTENT) == 0;
+            if (Tags.isGlobalTag(mIndex)) {
+                mName = "unsupported-WBXML";
+            } else if (!Tags.isValidTag(mPage, mIndex)) {
+                mName = "unknown";
             } else {
-                name = tagTable[index - TAG_BASE];
+                mName = Tags.getTagName(mPage, mIndex);
             }
         }
+
+        public int getTagNum() {
+            return (mPage << Tags.PAGE_SHIFT) | mIndex;
+        }
+
+        @Override
+        public String toString() {
+            return mName;
+        }
     }
 
     // The stack of tags being processed
-    private Deque<Tag> startTagArray = new ArrayDeque<Tag>();
+    private final Deque<Tag> startTagArray = new ArrayDeque<Tag>();
 
     private Tag startTag;
 
-    private Tag endTag;
-
-    // The type of the last token read
+    // The type of the last token read (eg, TEXT, OPAQUE, END, etc).
     private int type;
 
-    // The current page
+    // The current page. As of EAS 14.1, this is a value 0-24.
     private int page;
 
-    // The current tag
+    // The current tag. The low order 6 bits contain the tag index and the
+    // higher order bits the page number. The format matches that used for
+    // the tag enums defined in Tags.java.
     public int tag;
 
     // Whether the current tag is associated with content (a value)
@@ -165,7 +167,7 @@
             super("WBXML format error");
         }
 
-        EasParserException(String reason) {
+        EasParserException(final String reason) {
             super(reason);
         }
     }
@@ -174,21 +176,7 @@
         return false;
     }
 
-    /**
-     * Initialize the tag tables; they are constant
-     *
-     */
-    {
-        String[][] pages = Tags.pages;
-        for (int i = 0; i < pages.length; i++) {
-            String[] page = pages[i];
-            if (page.length > 0) {
-                tagTables[i] = page;
-            }
-        }
-    }
-
-    public Parser(InputStream in) throws IOException {
+    public Parser(final InputStream in) throws IOException {
         setInput(in, true);
         logging = Eas.PARSER_LOG;
     }
@@ -198,7 +186,7 @@
      * @param parser an existing, initialized parser
      * @throws IOException
      */
-    public Parser(Parser parser) throws IOException {
+    public Parser(final Parser parser) throws IOException {
         setInput(parser.in, false);
         logging = Eas.PARSER_LOG;
     }
@@ -210,7 +198,7 @@
      * @param val the desired state for debug output
      */
     @VisibleForTesting
-    public void setDebug(boolean val) {
+    public void setDebug(final boolean val) {
         logging = val;
     }
 
@@ -230,9 +218,10 @@
     /**
      * Turns off data capture; writes the captured data to a specified file.
      */
-    public void captureOff(Context context, String file) {
+    public void captureOff(final Context context, final String file) {
         try {
-            FileOutputStream out = context.openFileOutput(file, Context.MODE_WORLD_WRITEABLE);
+            final FileOutputStream out = context.openFileOutput(file,
+                    Context.MODE_WORLD_WRITEABLE);
             out.write(captureArray.toString().getBytes());
             out.close();
         } catch (FileNotFoundException e) {
@@ -251,7 +240,7 @@
      * @throws IOException
      */
     public byte[] getValueBytes() throws IOException {
-        final String name = startTag.name;
+        final String name = startTag.toString();
 
         getNext();
         // This means there was no value given, just <Foo/>; we'll return empty array
@@ -281,7 +270,7 @@
      * @throws IOException
      */
     public String getValue() throws IOException {
-        final String name = startTag.name;
+        final String name = startTag.toString();
 
         getNext();
         // This means there was no value given, just <Foo/>; we'll return empty string for now
@@ -312,17 +301,16 @@
      * @throws IOException
      */
     public int getValueInt() throws IOException {
-        String val = getValue();
+        final String val = getValue();
         if (val.length() == 0) {
             return 0;
         }
 
-        final String name = startTag.name;
         int num;
         try {
             num = Integer.parseInt(val);
         } catch (NumberFormatException e) {
-            throw new EasParserException("Tag " + name + ": " + e.getMessage());
+            throw new EasParserException("Tag " + startTag + ": " + e.getMessage());
         }
         return num;
     }
@@ -338,21 +326,19 @@
      * @return the next tag found
      * @throws IOException
      */
-    public int nextTag(int endingTag) throws IOException {
-        // Lose the page information
-        final int endTagIndex = endingTag & Tags.PAGE_MASK;
+    public int nextTag(final int endingTag) throws IOException {
         while (getNext() != DONE) {
             // If we're a start, set tag to include the page and return it
             if (type == START) {
-                tag = page | startTag.index;
+                tag = startTag.getTagNum();
                 return tag;
             // If we're at the ending tag we're looking for, return the END signal
-            } else if (type == END && startTag.index == endTagIndex) {
+            } else if (type == END && startTag.getTagNum() == endingTag) {
                 return END;
             }
         }
         // We're at end of document here.  If we're looking for it, return END_DOCUMENT
-        if (endTagIndex == START_DOCUMENT) {
+        if (endingTag == START_DOCUMENT) {
             return END_DOCUMENT;
         }
         // Otherwise, we've prematurely hit end of document, so exception out
@@ -368,10 +354,10 @@
      * @throws IOException
      */
     public void skipTag() throws IOException {
-        int thisTag = startTag.index;
+        final int thisTag = startTag.getTagNum();
         // Just loop until we hit the end of the current tag
         while (getNext() != DONE) {
-            if (type == END && startTag.index == thisTag) {
+            if (type == END && startTag.getTagNum() == thisTag) {
                 return;
             }
         }
@@ -388,15 +374,12 @@
      * @param in the InputStream associated with this parser
      * @throws IOException
      */
-    public void setInput(InputStream in, boolean initialize) throws IOException {
+    public void setInput(final InputStream in, final boolean initialize) throws IOException {
         this.in = in;
         if ((in != null) && initialize) {
             // If we fail on the very first byte, report an empty stream
             try {
                 final int version = readByte(); // version
-                if (version > 3) {
-                    log("WBXML version " + version + " is newer than supported version 3");
-                }
             } catch (EofException e) {
                 throw new EmptyStreamException();
             }
@@ -407,11 +390,10 @@
                 throw new EasParserException("WBXML string table unsupported");
             }
         }
-        tagTable = tagTables[0];
     }
 
     @VisibleForTesting
-    void resetInput(InputStream in) {
+    void resetInput(final InputStream in) {
         this.in = in;
         try {
             // Read leading zero
@@ -420,20 +402,23 @@
         }
     }
 
-    void log(String str) {
+    void log(final String str) {
         if (!logging) {
             return;
         }
+        final String logStr;
         int cr = str.indexOf('\n');
         if (cr > 0) {
-            str = str.substring(0, cr);
+            logStr = str.substring(0, cr);
+        } else {
+            logStr = str;
         }
         final char [] charArray = new char[startTagArray.size() * 2];
         Arrays.fill(charArray, ' ');
         final String indent = new String(charArray);
-        LogUtils.v(LOG_TAG, indent + str);
+        LogUtils.v(LOG_TAG, "%s", indent + logStr);
         if (Eas.FILE_LOG) {
-            FileLogger.log(LOG_TAG, str);
+            FileLogger.log(LOG_TAG, logStr);
         }
     }
 
@@ -443,22 +428,21 @@
         }
     }
 
-    protected void pushTag(int id) {
-        page = id >> Tags.PAGE_SHIFT;
-        tagTable = tagTables[page];
+    protected void pushTag(final int id) {
+        page = id >>> Tags.PAGE_SHIFT;
         push(id);
     }
 
     private void pop() {
         // Retrieve the now-current startTag from our stack
         startTag = startTagArray.removeFirst();
-        log("</" + startTag.name + '>');
+        log("</" + startTag + '>');
     }
 
-    private void push(int id) {
-        startTag = new Tag(id);
-        noContent = startTag.noContent;
-        log("<" + startTag.name + (startTag.noContent ? '/' : "") + '>');
+    private void push(final int id) {
+        startTag = new Tag(page, id);
+        noContent = startTag.mNoContent;
+        log("<" + startTag + (noContent ? '/' : "") + '>');
         // Save the startTag to our stack
         startTagArray.addFirst(startTag);
     }
@@ -485,18 +469,14 @@
         int id = read();
         while (id == Wbxml.SWITCH_PAGE) {
             // Get the new page number
-            int pg = readByte();
-            // Save the shifted page to add into the startTag in nextTag
-            page = pg << Tags.PAGE_SHIFT;
+            page = readByte();
             // Retrieve the current tag table
-            if (pg >= tagTables.length) {
+            if (!Tags.isValidPage(page)) {
                 // Unknown code page. These seem to happen mostly because of
                 // invalid data from the server so throw an exception here.
-                throw new EasParserException("Unknown code page " + pg);
-            } else {
-                tagTable = tagTables[pg];
+                throw new EasParserException("Unknown code page " + page);
             }
-            logVerbose((tagTable == null ? "Unknown " : "") + "Page: " + page);
+            logVerbose("Page: " + page);
             id = read();
         }
 
@@ -515,22 +495,22 @@
                 // Inline string
                 type = TEXT;
                 text = readInlineString();
-                log(startTag.name + ": " + text);
+                log(startTag + ": " + text);
                 break;
 
             case Wbxml.OPAQUE:
                 // Integer length + opaque data
                 type = OPAQUE;
-                int length = readInt();
+                final int length = readInt();
                 bytes = new byte[length];
                 for (int i = 0; i < length; i++) {
                     bytes[i] = (byte)readByte();
                 }
-                log(startTag.name + ": (opaque:" + length + ") ");
+                log(startTag + ": (opaque:" + length + ") ");
                 break;
 
             default:
-                if ((id & Tags.PAGE_MASK) < TAG_BASE) {
+                if (Tags.isGlobalTag(id & Tags.PAGE_MASK)) {
                     throw new EasParserException(String.format(
                                     "Unhandled WBXML global token 0x%02X", id));
                 }
@@ -571,11 +551,21 @@
         return i;
     }
 
+    /**
+     * Throws EasParserException if detects integer encoded with more than 5
+     * bytes. A uint_32 needs 5 bytes to fully encode 32 bits so if the high
+     * bit is set for more than 4 bytes, something is wrong with the data
+     * stream.
+     */
     private int readInt() throws IOException {
         int result = 0;
         int i;
+        int numBytes = 0;
 
         do {
+            if (++numBytes > 5) {
+                throw new EasParserException("Invalid integer encoding, too many bytes");
+            }
             i = readByte();
             result = (result << 7) | (i & 0x7f);
         } while ((i & 0x80) != 0);
@@ -590,9 +580,9 @@
      * @throws IOException
      */
     private String readInlineString() throws IOException {
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(256);
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(256);
         while (true) {
-            int i = read();
+            final int i = read();
             if (i == 0) {
                 break;
             } else if (i == EOF_BYTE) {
@@ -601,7 +591,7 @@
             outputStream.write(i);
         }
         outputStream.flush();
-        String res = outputStream.toString("UTF-8");
+        final String res = outputStream.toString("UTF-8");
         outputStream.close();
         return res;
     }
diff --git a/src/com/android/exchange/adapter/Serializer.java b/src/com/android/exchange/adapter/Serializer.java
index 4cd187e..d28e4c2 100644
--- a/src/com/android/exchange/adapter/Serializer.java
+++ b/src/com/android/exchange/adapter/Serializer.java
@@ -24,6 +24,7 @@
 package com.android.exchange.adapter;
 
 import android.content.ContentValues;
+import android.text.TextUtils;
 
 import com.android.exchange.Eas;
 import com.android.exchange.utility.FileLogger;
@@ -34,6 +35,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
 
 public class Serializer {
     private static final String TAG = Eas.LOG_TAG;
@@ -42,10 +46,9 @@
 
     private final OutputStream mOutput;
     private int mPendingTag = NOT_PENDING;
-    private int mDepth;
-    private String[] mNameStack = new String[20];
+    private final Deque<String> mNameStack = new ArrayDeque<String>();
     private int mTagPage = 0;
-    private boolean mLogging = LogUtils.isLoggable(TAG, LogUtils.VERBOSE);
+    private final boolean mLogging = LogUtils.isLoggable(TAG, LogUtils.VERBOSE);
 
     public Serializer() throws IOException {
         this(new ByteArrayOutputStream(), true);
@@ -67,7 +70,8 @@
      * @param _logging whether or not to log our output
      * @throws IOException
      */
-    public Serializer(OutputStream outputStream, boolean startDocument) throws IOException {
+    public Serializer(final OutputStream outputStream, final boolean startDocument)
+            throws IOException {
         super();
         mOutput = outputStream;
         if (startDocument) {
@@ -77,37 +81,47 @@
         }
     }
 
-    void log(String str) {
-        int cr = str.indexOf('\n');
-        if (cr > 0) {
-            str = str.substring(0, cr);
+    void log(final String str) {
+        if (!mLogging) {
+            return;
         }
-        LogUtils.v(TAG, "%s", str);
+        final String logStr;
+        final int cr = str.indexOf('\n');
+        if (cr > 0) {
+            logStr = str.substring(0, cr);
+        } else {
+            logStr = str;
+        }
+        final char [] charArray = new char[mNameStack.size() * 2];
+        Arrays.fill(charArray, ' ');
+        final String indent = new String(charArray);
+        LogUtils.v(TAG, "%s%s", indent, logStr);
         if (Eas.FILE_LOG) {
-            FileLogger.log(TAG, str);
+            FileLogger.log(TAG, logStr);
         }
     }
 
     public void done() throws IOException {
-        if (mDepth != 0) {
+        if (mNameStack.size() != 0 || mPendingTag != NOT_PENDING) {
             throw new IOException("Done received with unclosed tags");
         }
         mOutput.flush();
     }
 
-    public void startDocument() throws IOException{
+    public void startDocument() throws IOException {
         mOutput.write(0x03); // version 1.3
         mOutput.write(0x01); // unknown or missing public identifier
         mOutput.write(106);  // UTF-8
         mOutput.write(0);    // 0 length string array
     }
 
-    public void checkPendingTag(boolean degenerated) throws IOException {
-        if (mPendingTag == NOT_PENDING)
+    private void checkPendingTag(final boolean degenerated) throws IOException {
+        if (mPendingTag == NOT_PENDING) {
             return;
+        }
 
-        int page = mPendingTag >> Tags.PAGE_SHIFT;
-        int tag = mPendingTag & Tags.PAGE_MASK;
+        final int page = mPendingTag >> Tags.PAGE_SHIFT;
+        final int tag = mPendingTag & Tags.PAGE_MASK;
         if (page != mTagPage) {
             mTagPage = page;
             mOutput.write(Wbxml.SWITCH_PAGE);
@@ -115,18 +129,24 @@
         }
 
         mOutput.write(degenerated ? tag : tag | Wbxml.WITH_CONTENT);
-        if (mLogging) {
-            String name = Tags.pages[page][tag - 5];
-            mNameStack[mDepth] = name;
-            log("<" + name + '>');
+        String name = "unknown";
+        if (!Tags.isValidPage(page)) {
+            log("Unrecognized page " + page);
+        } else if (!Tags.isValidTag(page, tag)) {
+            log("Unknown tag " + tag + " on page " + page);
+        } else {
+            name = Tags.getTagName(page, tag);
+        }
+        log("<" + name + (degenerated ? "/>" : ">"));
+        if (!degenerated) {
+            mNameStack.addFirst(name);
         }
         mPendingTag = NOT_PENDING;
     }
 
-    public Serializer start(int tag) throws IOException {
+    public Serializer start(final int tag) throws IOException {
         checkPendingTag(false);
         mPendingTag = tag;
-        mDepth++;
         return this;
     }
 
@@ -135,97 +155,115 @@
             checkPendingTag(true);
         } else {
             mOutput.write(Wbxml.END);
-            if (mLogging) {
-                log("</" + mNameStack[mDepth] + '>');
-            }
+            final String tagName = mNameStack.removeFirst();
+            log("</" + tagName + '>');
         }
-        mDepth--;
         return this;
     }
 
-    public Serializer tag(int t) throws IOException {
-        start(t);
+    public Serializer tag(final int tag) throws IOException {
+        start(tag);
         end();
         return this;
     }
 
-    public Serializer data(int tag, String value) throws IOException {
-        if (value == null) {
-            LogUtils.e(TAG, "Writing null data for tag: " + tag);
-        }
+    /**
+     * Writes <tag>value</tag>. Throws IOException for null strings.
+     */
+    public Serializer data(final int tag, final String value) throws IOException {
         start(tag);
         text(value);
         end();
         return this;
     }
 
-    public Serializer text(String text) throws IOException {
+    /**
+     * Writes out inline string. Throws IOException for null strings.
+     */
+    public Serializer text(final String text) throws IOException {
         if (text == null) {
-            LogUtils.e(TAG, "Writing null text for pending tag: " + mPendingTag);
+            throw new IOException("Null text write for pending tag: " + mPendingTag);
         }
         checkPendingTag(false);
-        mOutput.write(Wbxml.STR_I);
-        writeLiteralString(mOutput, text);
-        if (mLogging) {
-            log(text);
-        }
+        writeInlineString(mOutput, text);
+        log(text);
         return this;
     }
 
-    public Serializer opaque(InputStream is, int length) throws IOException {
-        checkPendingTag(false);
-        mOutput.write(Wbxml.OPAQUE);
-        writeInteger(mOutput, length);
-        if (mLogging) {
-            log("Opaque, length: " + length);
-        }
+    /**
+     * Writes out opaque data blocks. Throws IOException for negative buffer
+     * sizes or if is unable to read sufficient bytes from input stream.
+     */
+    public Serializer opaque(final InputStream is, final int length) throws IOException {
+        writeOpaqueHeader(length);
+        log("opaque: " + length);
         // Now write out the opaque data in batches
-        byte[] buffer = new byte[BUFFER_SIZE];
-        while (length > 0) {
-            int bytesRead = is.read(buffer, 0, Math.min(BUFFER_SIZE, length));
+        final byte[] buffer = new byte[BUFFER_SIZE];
+        int totalBytesRead = 0;
+        while (totalBytesRead < length) {
+            final int bytesRead = is.read(buffer, 0, Math.min(BUFFER_SIZE, length));
             if (bytesRead == -1) {
-                break;
+                throw new IOException("Invalid opaque data block; read "
+                        + totalBytesRead + " bytes but expected " + length);
             }
             mOutput.write(buffer, 0, bytesRead);
-            length -= bytesRead;
+            totalBytesRead += bytesRead;
         }
         return this;
     }
 
-    public Serializer opaqueWithoutData(int length) throws IOException {
+    /**
+     * Writes out opaque data header, without the actual opaque data bytes.
+     * Used internally by opaque(), and externally to calculate content length
+     * without having to allocate the memory for the data copy.
+     * Throws IOException if length is negative; is a no-op for length 0.
+     */
+    public Serializer writeOpaqueHeader(final int length) throws IOException {
+        if (length < 0) {
+            throw new IOException("Invalid negative opaque data length " + length);
+        }
+        if (length == 0) {
+            return this;
+        }
         checkPendingTag(false);
         mOutput.write(Wbxml.OPAQUE);
         writeInteger(mOutput, length);
         return this;
     }
 
-    void writeInteger(OutputStream out, int i) throws IOException {
-        byte[] buf = new byte[5];
+    @VisibleForTesting
+    static void writeInteger(final OutputStream out, int i) throws IOException {
+        final byte[] buf = new byte[5];
         int idx = 0;
 
         do {
             buf[idx++] = (byte) (i & 0x7f);
-            i = i >> 7;
+            // Use >>> to shift in 0s so loop terminates
+            i = i >>> 7;
         } while (i != 0);
 
         while (idx > 1) {
             out.write(buf[--idx] | 0x80);
         }
         out.write(buf[0]);
-        if (mLogging) {
-            log(Integer.toString(i));
-        }
     }
 
-    void writeLiteralString(OutputStream out, String s) throws IOException {
-        byte[] data = s.getBytes("UTF-8");
+    private static void writeInlineString(final OutputStream out, final String s)
+        throws IOException {
+        out.write(Wbxml.STR_I);
+        final byte[] data = s.getBytes("UTF-8");
         out.write(data);
         out.write(0);
     }
 
-    public void writeStringValue (ContentValues cv, String key, int tag) throws IOException {
-        String value = cv.getAsString(key);
-        if (value != null && value.length() > 0) {
+    /**
+     * Looks up key in cv; if absent or empty writes out <tag/> otherwise
+     * writes out <tag>value</tag>.
+     */
+    public void writeStringValue (final ContentValues cv, final String key,
+            final int tag) throws IOException {
+        final String value = cv.getAsString(key);
+        if (!TextUtils.isEmpty(value)) {
             data(tag, value);
         } else {
             tag(tag);
diff --git a/src/com/android/exchange/adapter/Tags.java b/src/com/android/exchange/adapter/Tags.java
index dffa079..f93f78f 100644
--- a/src/com/android/exchange/adapter/Tags.java
+++ b/src/com/android/exchange/adapter/Tags.java
@@ -59,6 +59,7 @@
     // Shift applied to page numbers to generate tag
     public static final int PAGE_SHIFT = 6;
     public static final int PAGE_MASK = 0x3F;  // 6 bits
+    public static final int TAG_BASE = 5;
 
     // AirSync code page 0
     public static final int SYNC_PAGE = 0 << PAGE_SHIFT;
@@ -729,7 +730,24 @@
     public static final int RIGHTS_CONTENT_OWNER = RIGHTS_PAGE + 0x17;
     public static final int RIGHTS_REMOVE_RM_DISTRIBUTION = RIGHTS_PAGE + 0x18;
 
-    static public String[][] pages = {
+    public static boolean isValidPage(final int page) {
+        return page >= 0 && page < mPages.length;
+    }
+
+    public static boolean isValidTag(final int page, final int tag) {
+        final int tagIndex = tag - TAG_BASE;
+        return isValidPage(page) && tagIndex >= 0 && tagIndex < mPages[page].length;
+    }
+
+    public static boolean isGlobalTag(final int tag) {
+        return tag >= 0 && tag < TAG_BASE;
+    }
+
+    public static String getTagName(final int page, final int tag) {
+        return mPages[page][tag - TAG_BASE];
+    }
+
+    static final String[][] mPages = {
         {    // 0x00 AirSync
             "Sync", "Responses", "Add", "Change", "Delete", "Fetch", "SyncKey", "ClientId",
             "ServerId", "Status", "Collection", "Class", "Version", "CollectionId", "GetChanges",
diff --git a/src/com/android/exchange/eas/EasOutboxSync.java b/src/com/android/exchange/eas/EasOutboxSync.java
index d93dae1..ba25971 100644
--- a/src/com/android/exchange/eas/EasOutboxSync.java
+++ b/src/com/android/exchange/eas/EasOutboxSync.java
@@ -514,9 +514,9 @@
             s.start(Tags.COMPOSE_MIME);
             // Send opaque data from the file stream
             if (withData) {
-                s.opaque(mFileStream, (int)mFileLength);
+                s.opaque(mFileStream, (int) mFileLength);
             } else {
-                s.opaqueWithoutData((int)mFileLength);
+                s.writeOpaqueHeader((int) mFileLength);
             }
             // And we're done
             s.end().end().done();
diff --git a/tests/src/com/android/exchange/TagsTests.java b/tests/src/com/android/exchange/TagsTests.java
deleted file mode 100644
index 55b27c0..0000000
--- a/tests/src/com/android/exchange/TagsTests.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed 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 com.android.exchange;
-
-import com.android.exchange.adapter.Tags;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import java.util.HashMap;
-@SmallTest
-public class TagsTests extends AndroidTestCase {
-
-    // Make sure there are no duplicates in the tags table
-    // This test is no longer required - tags can be duplicated
-    public void disable_testNoDuplicates() {
-        String[][] allTags = Tags.pages;
-        HashMap<String, Boolean> map = new HashMap<String, Boolean>();
-        for (String[] page: allTags) {
-            for (String tag: page) {
-                assertTrue(tag, !map.containsKey(tag));
-                map.put(tag, true);
-            }
-        }
-    }
-}
diff --git a/tests/src/com/android/exchange/adapter/FolderSyncParserTests.java b/tests/src/com/android/exchange/adapter/FolderSyncParserTests.java
index 53fdc52..3eb59e7 100644
--- a/tests/src/com/android/exchange/adapter/FolderSyncParserTests.java
+++ b/tests/src/com/android/exchange/adapter/FolderSyncParserTests.java
@@ -170,7 +170,7 @@
         private void initTagMap() {
             mTagMap = new HashMap<String, Integer>();
             int pageNum = 0;
-            for (String[] page: Tags.pages) {
+            for (String[] page: Tags.mPages) {
                 int tagNum = 5;
                 for (String tag: page) {
                     if (mTagMap.containsKey(tag)) {
diff --git a/tests/src/com/android/exchange/adapter/ParserTest.java b/tests/src/com/android/exchange/adapter/ParserTest.java
index b643623..8b36433 100644
--- a/tests/src/com/android/exchange/adapter/ParserTest.java
+++ b/tests/src/com/android/exchange/adapter/ParserTest.java
@@ -109,17 +109,6 @@
     }
 
     @SmallTest
-    public void testParser() throws Exception {
-        // Test parser with sample valid data
-        final String wbxmlDataStr =
-            "03 01 6A 00 45 5C 4F 50 03 43 6F 6E 74 61 63 74 73 00 01 4B 03 32 00 01 52 03 32 00 01 4E 03 " +
-            "31 00 01 56 47 4D 03 32 3A 31 00 01 5D 00 11 4A 46 03 31 00 01 4C 03 30 00 01 4D 03 31 00 01 " +
-            "01 00 01 5E 03 46 75 6E 6B 2C 20 44 6F 6E 00 01 5F 03 44 6F 6E 00 01 69 03 46 75 6E 6B 00 01 " +
-            "00 11 56 03 31 00 01 01 01 01 01 01 01";
-        testParserHelper(wbxmlDataStr);
-    }
-
-    @SmallTest
     public void testUnsupportedWbxmlTag() throws Exception {
         // Test parser with unsupported Wbxml tag (EXT_2 = 0xC2)
         final String unsupportedWbxmlTag = "03 01 6A 00 45 5F C2 05 11 22 33 44 00 01 01";
@@ -229,6 +218,17 @@
     }
 
     @SmallTest
+    public void testRunOnInteger() throws Exception {
+        final String runOnIntegerEncoding = "03 01 6A 00 45 4D C3 81 82 83 84 85 06 11 22 33 01 01";
+        try {
+            testParserHelper(runOnIntegerEncoding);
+            fail("Expected EasParserException for improperly encoded integer");
+        } catch (Parser.EasParserException e) {
+            // expected
+        }
+    }
+
+    @SmallTest
     public void testAttributeTag() throws Exception {
         // Test parser with known tag with attributes
         final String tagWithAttributes = "03 01 6A 00 45 DF 06 01 03 31 00 01 01";
diff --git a/tests/src/com/android/exchange/adapter/SerializerTests.java b/tests/src/com/android/exchange/adapter/SerializerTests.java
index 921c226..75132dc 100644
--- a/tests/src/com/android/exchange/adapter/SerializerTests.java
+++ b/tests/src/com/android/exchange/adapter/SerializerTests.java
@@ -22,7 +22,10 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 
 /** You can run this entire test case with:
  *   runtest -c com.android.exchange.adapter.SerializerTests exchange
@@ -93,4 +96,12 @@
         // Make sure we get what's expected
         MoreAsserts.assertEquals("Serializer mismatch", bytes, expectedBytes);
     }
+
+    @SmallTest
+    public void testWriteInteger() throws IOException {
+        OutputStream output = new ByteArrayOutputStream();
+        Serializer.writeInteger(output, 384);
+        Serializer.writeInteger(output, 0);
+        Serializer.writeInteger(output, -1);
+    }
 }