Importing Google TV Pairing Protocol library

This has been modified to compile using nanobuf

Change-Id: If8f092b422d4be7b78ff0f6ef92c94beaea4300c
diff --git a/java/src/com/google/polo/wire/PoloWireInterface.java b/java/src/com/google/polo/wire/PoloWireInterface.java
new file mode 100644
index 0000000..5fa1c8c
--- /dev/null
+++ b/java/src/com/google/polo/wire/PoloWireInterface.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009 Google Inc.  All rights reserved.
+ *
+ * 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.google.polo.wire;
+
+import java.io.IOException;
+
+import com.google.polo.exception.PoloException;
+import com.google.polo.pairing.message.PoloMessage;
+
+/**
+ * Public interface for transport-layer implementations of the pairing
+ * protocol.
+ */
+public interface PoloWireInterface {
+
+  /**
+   * Returns the next message from the wire.
+   * 
+   * @return a new PoloMessage instance
+   * @throws IOException  if an error occurred while reading
+   * @throws PoloException  if a protocol fault occurred
+   */
+  public PoloMessage getNextMessage() throws IOException, PoloException;
+  
+  /**
+   * Returns the next message from the wire.
+   * 
+   * @param type  the required message type to be read
+   * @return a new PoloMessage instance
+   * @throws IOException  if an error occurred while reading
+   * @throws PoloException  if the next message did not match the requested
+   *                        type.
+   */
+  public PoloMessage getNextMessage(PoloMessage.PoloMessageType type)
+      throws IOException, PoloException;
+  
+  /**
+   * Send a normal message out on the wire.
+   * 
+   * @param message  the message to send
+   * @throws IOException  if an error occurred while sending
+   * @throws PoloException  if the message was not well formed
+   */
+  public void sendMessage(PoloMessage message)
+      throws IOException, PoloException;
+  
+  /**
+   * Send an error message out on the wire, based on an exception.
+   * 
+   * @param e  the exception causing the error
+   * @throws IOException  if an error occurred while sending
+   */
+  public void sendErrorMessage(Exception e) throws IOException;
+  
+}
diff --git a/java/src/com/google/polo/wire/WireFormat.java b/java/src/com/google/polo/wire/WireFormat.java
new file mode 100644
index 0000000..4060c67
--- /dev/null
+++ b/java/src/com/google/polo/wire/WireFormat.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2009 Google Inc.  All rights reserved.
+ *
+ * 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.google.polo.wire;
+
+import com.google.polo.pairing.PairingContext;
+import com.google.polo.wire.json.JsonWireAdapter;
+import com.google.polo.wire.protobuf.ProtobufWireAdapter;
+import com.google.polo.wire.xml.XmlWireAdapter;
+
+/**
+ * Represents the various wire formats available.
+ */
+public enum WireFormat {
+  PROTOCOL_BUFFERS,   // Protocol Buffers, implemented by ProtobufWireInterface
+  JSON,               // JSON, implemented by JsonWireInterface
+  XML;                // XML, implemented by XmlWireInterface
+  
+  /**
+   * Returns a new {@link PoloWireInterface} for this enum value.
+   * 
+   * @param context  the {@link PairingContext} to use in construction
+   * @return         the new {@link PoloWireInterface}
+   */
+  public PoloWireInterface getWireInterface(PairingContext context) {
+    switch (this) {
+      case PROTOCOL_BUFFERS:
+        return ProtobufWireAdapter.fromContext(context);
+      case JSON:
+        return JsonWireAdapter.fromContext(context);
+      case XML:
+        return XmlWireAdapter.fromContext(context);
+    }
+    return null;
+  }
+}
diff --git a/java/src/com/google/polo/wire/json/Base64.java b/java/src/com/google/polo/wire/json/Base64.java
new file mode 100644
index 0000000..2fdadc5
--- /dev/null
+++ b/java/src/com/google/polo/wire/json/Base64.java
@@ -0,0 +1,175 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+/*
+ * NOTE: This class copied from the Android Open Source Project:
+ * dalvik/libcore/luni/src/main/java/org/apache/harmony/luni/util/Base64.java
+ */
+
+/**
+* @author Alexander Y. Kleymenov
+* @version $Revision$
+*/
+
+package com.google.polo.wire.json;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * This class implements Base64 encoding/decoding functionality
+ * as specified in RFC 2045 (http://www.ietf.org/rfc/rfc2045.txt).
+ */
+public class Base64 {
+    
+    public static byte[] decode(byte[] in) {
+        return decode(in, in.length);
+    }
+    
+    public static byte[] decode(byte[] in, int len) {
+        // approximate output length
+        int length = len / 4 * 3;
+        // return an empty array on emtpy or short input without padding
+        if (length == 0) {
+            return new byte[0];
+        }
+        // temporary array
+        byte[] out = new byte[length];
+        // number of padding characters ('=')
+        int pad = 0;
+        byte chr;
+        // compute the number of the padding characters
+        // and adjust the length of the input
+        for (;;len--) {
+            chr = in[len-1];
+            // skip the neutral characters
+            if ((chr == '\n') || (chr == '\r') || 
+                    (chr == ' ') || (chr == '\t')) {
+                continue;
+            }
+            if (chr == '=') {
+                pad++;
+            } else {
+                break;
+            }
+        }
+        // index in the output array
+        int out_index = 0;
+        // index in the input array
+        int in_index = 0;
+        // holds the value of the input character
+        int bits = 0;
+        // holds the value of the input quantum
+        int quantum = 0;
+        for (int i=0; i<len; i++) {
+            chr = in[i];
+            // skip the neutral characters
+            if ((chr == '\n') || (chr == '\r') || 
+                    (chr == ' ') || (chr == '\t')) {
+                continue;
+            }
+            if ((chr >= 'A') && (chr <= 'Z')) {
+                // char ASCII value
+                //  A    65    0
+                //  Z    90    25 (ASCII - 65)
+                bits = chr - 65;
+            } else if ((chr >= 'a') && (chr <= 'z')) {
+                // char ASCII value
+                //  a    97    26
+                //  z    122   51 (ASCII - 71)
+                bits = chr - 71;
+            } else if ((chr >= '0') && (chr <= '9')) {
+                // char ASCII value
+                //  0    48    52
+                //  9    57    61 (ASCII + 4)
+                bits = chr + 4;
+            } else if (chr == '+') {
+                bits = 62;
+            } else if (chr == '/') {
+                bits = 63;
+            } else {
+                return null;
+            }
+            // append the value to the quantum
+            quantum = (quantum << 6) | (byte) bits;
+            if (in_index%4 == 3) {
+                // 4 characters were read, so make the output:
+                out[out_index++] = (byte) ((quantum & 0x00FF0000) >> 16);
+                out[out_index++] = (byte) ((quantum & 0x0000FF00) >> 8);
+                out[out_index++] = (byte) (quantum & 0x000000FF);
+            }
+            in_index++;
+        }
+        if (pad > 0) {
+            // adjust the quantum value according to the padding
+            quantum = quantum << (6*pad);
+            // make output
+            out[out_index++] = (byte) ((quantum & 0x00FF0000) >> 16);
+            if (pad == 1) {
+                out[out_index++] = (byte) ((quantum & 0x0000FF00) >> 8);
+            }
+        }
+        // create the resulting array
+        byte[] result = new byte[out_index];
+        System.arraycopy(out, 0, result, 0, out_index);
+        return result;
+    }
+
+    private static final byte[] map = new byte[]
+        {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 
+         'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 
+         'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 
+         'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', 
+         '4', '5', '6', '7', '8', '9', '+', '/'};
+
+    public static String encode(byte[] in, String charsetName) throws UnsupportedEncodingException {
+        int length = in.length * 4 / 3;
+        length += length / 76 + 3; // for crlr
+        byte[] out = new byte[length];
+        int index = 0, i, crlr = 0, end = in.length - in.length%3;
+        for (i=0; i<end; i+=3) {
+            out[index++] = map[(in[i] & 0xff) >> 2];
+            out[index++] = map[((in[i] & 0x03) << 4) 
+                                | ((in[i+1] & 0xff) >> 4)];
+            out[index++] = map[((in[i+1] & 0x0f) << 2) 
+                                | ((in[i+2] & 0xff) >> 6)];
+            out[index++] = map[(in[i+2] & 0x3f)];
+            if (((index - crlr)%76 == 0) && (index != 0)) {
+                out[index++] = '\n';
+                crlr++;
+                //out[index++] = '\r';
+                //crlr++;
+            }
+        }
+        switch (in.length % 3) {
+            case 1:
+                out[index++] = map[(in[end] & 0xff) >> 2];
+                out[index++] = map[(in[end] & 0x03) << 4];
+                out[index++] = '=';
+                out[index++] = '=';
+                break;
+            case 2:
+                out[index++] = map[(in[end] & 0xff) >> 2];
+                out[index++] = map[((in[end] & 0x03) << 4) 
+                                    | ((in[end+1] & 0xff) >> 4)];
+                out[index++] = map[((in[end+1] & 0x0f) << 2)];     
+                out[index++] = '=';
+                break;
+        }
+        return new String(out, 0, index, charsetName);
+    }
+}
+
diff --git a/java/src/com/google/polo/wire/json/JsonMessageBuilder.java b/java/src/com/google/polo/wire/json/JsonMessageBuilder.java
new file mode 100644
index 0000000..67deba0
--- /dev/null
+++ b/java/src/com/google/polo/wire/json/JsonMessageBuilder.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2009 Google Inc.  All rights reserved.
+ *
+ * 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.google.polo.wire.json;
+
+import com.google.polo.exception.BadSecretException;
+import com.google.polo.exception.NoConfigurationException;
+import com.google.polo.exception.PoloException;
+import com.google.polo.exception.ProtocolErrorException;
+import com.google.polo.json.JSONArray;
+import com.google.polo.json.JSONException;
+import com.google.polo.json.JSONObject;
+import com.google.polo.pairing.message.ConfigurationAckMessage;
+import com.google.polo.pairing.message.ConfigurationMessage;
+import com.google.polo.pairing.message.EncodingOption;
+import com.google.polo.pairing.message.EncodingOption.EncodingType;
+import com.google.polo.pairing.message.OptionsMessage;
+import com.google.polo.pairing.message.OptionsMessage.ProtocolRole;
+import com.google.polo.pairing.message.PairingRequestAckMessage;
+import com.google.polo.pairing.message.PairingRequestMessage;
+import com.google.polo.pairing.message.PoloMessage;
+import com.google.polo.pairing.message.PoloMessage.PoloMessageType;
+import com.google.polo.pairing.message.SecretAckMessage;
+import com.google.polo.pairing.message.SecretMessage;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+/**
+ * A collection of methods to convert {@link PoloMessage}s to and from JSON
+ * format.
+ * <p>
+ * Messages are based on the descriptions found in the file polo.proto.  This
+ * mimics the field name and message compositions found in that file.
+ */
+public class JsonMessageBuilder {
+
+  public static final int PROTOCOL_VERSION = 1;
+
+  /*
+   * Status types. These match the values defined by OuterMessage.MessageType
+   * in polo.proto.
+   */
+
+  public static final int STATUS_OK = 200;
+  public static final int STATUS_ERROR = 400;
+  public static final int STATUS_BAD_CONFIGURATION = 401;
+  public static final int STATUS_BAD_SECRET = 403;
+
+  /*
+   * Key names for JSON versions of messages.
+   */
+
+  // OuterMessage JSON key names
+  private static final String OUTER_FIELD_PAYLOAD = "payload";
+  private static final String OUTER_FIELD_TYPE = "type";
+  private static final String OUTER_FIELD_STATUS = "status";
+  private static final String OUTER_FIELD_PROTOCOL_VERSION = "protocol_version";
+
+  // PairingRequestMessage JSON key names
+  private static final String PAIRING_REQUEST_FIELD_SERVICE_NAME =
+      "service_name";
+  private static final String PAIRING_REQUEST_FIELD_CLIENT_NAME =
+      "client_name";
+
+  // PairingRequestAckMessage JSON key names
+  private static final String PAIRING_REQUEST_ACK_FIELD_SERVER_NAME =
+      "server_name";
+
+  // OptionsMessage JSON key names
+  private static final String OPTIONS_FIELD_PREFERRED_ROLE = "preferred_role";
+  private static final String OPTIONS_FIELD_OUTPUT_ENCODINGS =
+      "output_encodings";
+  private static final String OPTIONS_FIELD_INPUT_ENCODINGS = "input_encodings";
+
+  // ConfigurationMessage JSON key names
+  private static final String CONFIG_FIELD_CLIENT_ROLE = "client_role";
+  private static final String CONFIG_FIELD_ENCODING = "encoding";
+
+  // EncodingOption JSON key names
+  private static final String ENCODING_FIELD_TYPE = "type";
+  private static final String ENCODING_FIELD_SYMBOL_LENGTH = "symbol_length";
+
+  // SecretMessage JSON key names
+  private static final String SECRET_FIELD_SECRET = "secret";
+
+  // SecretAckMessage JSON key names
+  private static final String SECRET_ACK_FIELD_SECRET = "secret";
+
+
+  /**
+   * Builds a {@link PoloMessage} from the JSON version of the outer message.
+   *
+   * @param outerMessage    a {@link JSONObject} corresponding to the
+   *                        outermost wire message
+   * @return                a new {@link PoloMessage}
+   * @throws PoloException  on error parsing the {@link JSONObject}
+   */
+  public static PoloMessage outerJsonToPoloMessage(JSONObject outerMessage)
+      throws PoloException {
+    JSONObject payload;
+    int status;
+    PoloMessageType messageType;
+
+    try {
+      status = outerMessage.getInt(OUTER_FIELD_STATUS);
+      if (status != STATUS_OK) {
+        throw new ProtocolErrorException("Peer reported an error.");
+      }
+      payload = outerMessage.getJSONObject(OUTER_FIELD_PAYLOAD);
+      int msgIntVal = outerMessage.getInt(OUTER_FIELD_TYPE);
+      messageType = PoloMessageType.fromIntVal(msgIntVal);
+    } catch (JSONException e) {
+      throw new PoloException("Bad outer message.", e);
+    }
+
+    switch (messageType) {
+      case PAIRING_REQUEST:
+        return getPairingRequest(payload);
+      case PAIRING_REQUEST_ACK:
+        return getPairingRequestAck(payload);
+      case OPTIONS:
+        return getOptionsMessage(payload);
+      case CONFIGURATION:
+        return getConfigMessage(payload);
+      case CONFIGURATION_ACK:
+        return getConfigAckMessage(payload);
+      case SECRET:
+        return getSecretMessage(payload);
+      case SECRET_ACK:
+        return getSecretAckMessage(payload);
+      default:
+        return null;
+    }
+  }
+
+  //
+  // Methods to convert JSON messages to PoloMessage instances
+  //
+
+  /**
+   * Generates a new {@link PairingRequestMessage} from a JSON payload.
+   *
+   * @param  body           the JSON payload
+   * @return                the new message
+   * @throws PoloException  on error parsing the {@link JSONObject}
+   */
+  static PairingRequestMessage getPairingRequest(JSONObject body)
+      throws PoloException {
+    try {
+      String serviceName = body.getString(PAIRING_REQUEST_FIELD_SERVICE_NAME);
+      String clientName = null;
+      if (body.has(PAIRING_REQUEST_FIELD_CLIENT_NAME)) {
+        clientName = body.getString(PAIRING_REQUEST_FIELD_CLIENT_NAME);
+      }
+      return new PairingRequestMessage(serviceName, clientName);
+    } catch (JSONException e) {
+      throw new PoloException("Malformed message.", e);
+    }
+  }
+
+  /**
+   * Generates a new {@link PairingRequestAckMessage} from a JSON payload.
+   *
+   * @param  body           the JSON payload
+   * @return                the new message
+   */
+  static PairingRequestAckMessage getPairingRequestAck(JSONObject body)
+      throws PoloException {
+    try {
+      String serverName = null;
+      if (body.has(PAIRING_REQUEST_ACK_FIELD_SERVER_NAME)) {
+        serverName = body.getString(PAIRING_REQUEST_ACK_FIELD_SERVER_NAME);
+      }
+      return new PairingRequestAckMessage(serverName);
+    } catch (JSONException e) {
+      throw new PoloException("Malformed message.", e);
+    }
+  }
+
+  /**
+   * Generates a new {@link OptionsMessage} from a JSON payload.
+   *
+   * @param  body           the JSON payload
+   * @return                the new message
+   * @throws PoloException  on error parsing the {@link JSONObject}
+   */
+  static OptionsMessage getOptionsMessage(JSONObject body)
+      throws PoloException {
+    OptionsMessage options = new OptionsMessage();
+    try {
+      // Input encodings
+      JSONArray inEncodings = new JSONArray();
+      try {
+        if (body.has(OPTIONS_FIELD_INPUT_ENCODINGS)) {
+          inEncodings = body.getJSONArray(OPTIONS_FIELD_INPUT_ENCODINGS);
+        }
+      } catch (JSONException e) {
+        throw new PoloException("Bad input encodings", e);
+      }
+
+      for (int i = 0; i < inEncodings.length(); i++) {
+        JSONObject enc = inEncodings.getJSONObject(i);
+        options.addInputEncoding(getEncodingOption(enc));
+      }
+
+      // Output encodings
+      JSONArray outEncodings = new JSONArray();
+      try {
+        if (body.has(OPTIONS_FIELD_OUTPUT_ENCODINGS)) {
+          outEncodings = body.getJSONArray(OPTIONS_FIELD_OUTPUT_ENCODINGS);
+        }
+      } catch (JSONException e) {
+        throw new PoloException("Bad output encodings", e);
+      }
+
+      for (int i = 0; i < outEncodings.length(); i++) {
+        JSONObject enc = outEncodings.getJSONObject(i);
+        options.addOutputEncoding(getEncodingOption(enc));
+      }
+
+      // Role
+      ProtocolRole role = ProtocolRole.fromIntVal(
+          body.getInt(OPTIONS_FIELD_PREFERRED_ROLE));
+      options.setProtocolRolePreference(role);
+    } catch (JSONException e) {
+      throw new PoloException("Malformed message.", e);
+    }
+
+    return options;
+  }
+
+  /**
+   * Generates a new {@link ConfigurationMessage} from a JSON payload.
+   *
+   * @param  body           the JSON payload
+   * @return                the new message
+   * @throws PoloException  on error parsing the {@link JSONObject}
+   */
+  static ConfigurationMessage getConfigMessage(JSONObject body)
+      throws PoloException {
+    try {
+      EncodingOption encoding = getEncodingOption(
+          body.getJSONObject(CONFIG_FIELD_ENCODING));
+      ProtocolRole role = ProtocolRole.fromIntVal(
+          body.getInt(CONFIG_FIELD_CLIENT_ROLE));
+      return new ConfigurationMessage(encoding, role);
+    } catch (JSONException e) {
+      throw new PoloException("Malformed message.", e);
+    }
+  }
+
+  /**
+   * Generates a new {@link ConfigurationAckMessage} from a JSON payload.
+   *
+   * @param  body           the JSON payload
+   * @return                the new message
+   */
+  static ConfigurationAckMessage getConfigAckMessage(JSONObject body) {
+    return new ConfigurationAckMessage();
+  }
+
+  /**
+   * Generates a new {@link SecretMessage} from a JSON payload.
+   *
+   * @param  body           the JSON payload
+   * @return                the new message
+   * @throws PoloException  on error parsing the {@link JSONObject}
+   */
+  static SecretMessage getSecretMessage(JSONObject body) throws PoloException {
+    try {
+      byte[] secretBytes = Base64.decode(
+          body.getString(SECRET_FIELD_SECRET).getBytes());
+      return new SecretMessage(secretBytes);
+    } catch (JSONException e) {
+      throw new PoloException("Malformed message.", e);
+    }
+  }
+
+  /**
+   * Generates a new {@link SecretAckMessage} from a JSON payload.
+   *
+   * @param body  the JSON payload
+   * @return      the new message
+   * @throws PoloException  on error parsing the {@link JSONObject}
+   */
+  static SecretAckMessage getSecretAckMessage(JSONObject body)
+      throws PoloException {
+    try {
+      byte[] secretBytes = Base64.decode(
+          body.getString(SECRET_ACK_FIELD_SECRET).getBytes());
+      return new SecretAckMessage(secretBytes);
+    } catch (JSONException e) {
+      throw new PoloException("Malformed message.", e);
+    }
+  }
+
+  /**
+   * Generates a new {@link EncodingOption} from a JSON sub-dictionary.
+   *
+   * @param  option         the JSON sub-dictionary describing the option
+   * @return                the new {@link EncodingOption}
+   * @throws JSONException  on error parsing the {@link JSONObject}
+   */
+  static EncodingOption getEncodingOption(JSONObject option)
+      throws JSONException {
+    int length = option.getInt(ENCODING_FIELD_SYMBOL_LENGTH);
+    int intType = option.getInt(ENCODING_FIELD_TYPE);
+    EncodingType type = EncodingType.fromIntVal(intType);
+    return new EncodingOption(type, length);
+  }
+
+  /**
+   * Converts a {@link PoloMessage} to a {@link JSONObject}
+   *
+   * @param message         the message to convert
+   * @return                the same message, as translated to JSON
+   * @throws PoloException  if the message could not be generated
+   */
+  public static JSONObject poloMessageToJson(PoloMessage message)
+      throws PoloException {
+    try {
+      if (message instanceof PairingRequestMessage) {
+        return toJson((PairingRequestMessage) message);
+      } else if (message instanceof PairingRequestAckMessage) {
+        return toJson((PairingRequestAckMessage) message);
+      } else if (message instanceof OptionsMessage) {
+        return toJson((OptionsMessage) message);
+      } else if (message instanceof ConfigurationMessage) {
+        return toJson((ConfigurationMessage) message);
+      } else if (message instanceof ConfigurationAckMessage) {
+        return toJson((ConfigurationAckMessage) message);
+      } else if (message instanceof SecretMessage) {
+        return toJson((SecretMessage) message);
+      } else if (message instanceof SecretAckMessage) {
+        return toJson((SecretAckMessage) message);
+      }
+    } catch (JSONException e) {
+      throw new PoloException("Error generating message.", e);
+    }
+    throw new PoloException("Unknown PoloMessage type.");
+  }
+
+  /**
+   * Generates a JSONObject corresponding to a full wire message (wrapped in
+   * an outer message) for the given payload.
+   *
+   * @param message         the payload to wrap
+   * @return                a {@link JSONObject} corresponding to the complete
+   *                        wire message
+   * @throws PoloException  on error building the {@link JSONObject}
+   */
+  public static JSONObject getOuterJson(PoloMessage message)
+      throws PoloException {
+    JSONObject out = new JSONObject();
+    int msgType = message.getType().getAsInt();
+    JSONObject innerJson = poloMessageToJson(message);
+
+    try {
+      out.put(OUTER_FIELD_PROTOCOL_VERSION, PROTOCOL_VERSION);
+      out.put(OUTER_FIELD_STATUS, STATUS_OK);
+      out.put(OUTER_FIELD_TYPE, msgType);
+      out.put(OUTER_FIELD_PAYLOAD, innerJson);
+    } catch (JSONException e) {
+      throw new PoloException("Error serializing outer message", e);
+    }
+    return out;
+  }
+
+  /**
+   * Generates a {@link JSONObject} corresponding to a wire message with an
+   * error code in the status field.  The error code is determined by the type
+   * of the exception.
+   *
+   * @param exception       the {@link Exception} to use to determine the error
+   *                        code
+   * @return                a {@link JSONObject} corresponding to the complete
+   *                        wire message
+   * @throws PoloException  on error building the {@link JSONObject}
+   */
+  public static JSONObject getErrorJson(Exception exception)
+      throws PoloException {
+    JSONObject out = new JSONObject();
+
+    int errorStatus = STATUS_ERROR;
+
+    if (exception instanceof NoConfigurationException) {
+      errorStatus = STATUS_BAD_CONFIGURATION;
+    } else if (exception instanceof BadSecretException) {
+      errorStatus = STATUS_BAD_SECRET;
+    }
+
+    try {
+      out.put(OUTER_FIELD_PROTOCOL_VERSION, PROTOCOL_VERSION);
+      out.put(OUTER_FIELD_STATUS, errorStatus);
+    } catch (JSONException e) {
+      throw new PoloException("Error serializing outer message", e);
+    }
+    return out;
+
+  }
+
+  /**
+   * Translates a {@link PairingRequestMessage} to a {@link JSONObject}.
+   *
+   * @throws JSONException  on error generating the {@link JSONObject}
+   */
+  static JSONObject toJson(PairingRequestMessage message) throws JSONException {
+    JSONObject jsonObj = new JSONObject();
+    jsonObj.put(PAIRING_REQUEST_FIELD_SERVICE_NAME, message.getServiceName());
+    if (message.hasClientName()) {
+      jsonObj.put(PAIRING_REQUEST_FIELD_CLIENT_NAME, message.getClientName());
+    }
+    return jsonObj;
+  }
+
+  /**
+   * Translates a {@link PairingRequestAckMessage} to a {@link JSONObject}.
+   * @throws JSONException
+   */
+  static JSONObject toJson(PairingRequestAckMessage message)
+      throws JSONException {
+    JSONObject jsonObj = new JSONObject();
+    if (message.hasServerName()) {
+      jsonObj.put(PAIRING_REQUEST_ACK_FIELD_SERVER_NAME,
+          message.getServerName());
+    }
+    return jsonObj;
+  }
+
+  /**
+   * Translates a {@link OptionsMessage} to a {@link JSONObject}.
+   *
+   * @throws JSONException  on error generating the {@link JSONObject}
+   */
+  static JSONObject toJson(OptionsMessage message) throws JSONException {
+    JSONObject jsonObj = new JSONObject();
+
+    JSONArray inEncsArray = new JSONArray();
+    for (EncodingOption encoding : message.getInputEncodingSet()) {
+      inEncsArray.put(toJson(encoding));
+    }
+    jsonObj.put(OPTIONS_FIELD_INPUT_ENCODINGS, inEncsArray);
+
+    JSONArray outEncsArray = new JSONArray();
+    for (EncodingOption encoding : message.getOutputEncodingSet()) {
+      outEncsArray.put(toJson(encoding));
+    }
+    jsonObj.put(OPTIONS_FIELD_OUTPUT_ENCODINGS, outEncsArray);
+
+    int intRole = message.getProtocolRolePreference().getAsInt();
+    jsonObj.put(OPTIONS_FIELD_PREFERRED_ROLE, intRole);
+    return jsonObj;
+  }
+
+  /**
+   * Translates a {@link ConfigurationMessage} to a {@link JSONObject}.
+   *
+   * @throws JSONException  on error generating the {@link JSONObject}
+   */
+  static JSONObject toJson(ConfigurationMessage message) throws JSONException {
+    JSONObject jsonObj = new JSONObject();
+    JSONObject encoding = toJson(message.getEncoding());
+    jsonObj.put(CONFIG_FIELD_ENCODING, encoding);
+    int intRole = message.getClientRole().getAsInt();
+    jsonObj.put(CONFIG_FIELD_CLIENT_ROLE, intRole);
+    return jsonObj;
+  }
+
+  /**
+   * Translates a {@link ConfigurationAckMessage} to a {@link JSONObject}.
+   */
+  static JSONObject toJson(ConfigurationAckMessage message) {
+    return new JSONObject();
+  }
+
+  /**
+   * Translates a {@link SecretMessage} to a {@link JSONObject}.
+   *
+   * @throws JSONException  on error generating the {@link JSONObject}
+   */
+  static JSONObject toJson(SecretMessage message) throws JSONException {
+    JSONObject jsonObj = new JSONObject();
+    String bytesStr;
+    String charsetName = Charset.defaultCharset().name();
+    try {
+      bytesStr = new String(Base64.encode(message.getSecret(), charsetName));
+    } catch (UnsupportedEncodingException e) {
+      // Should never happen.
+      bytesStr = "";
+    }
+    jsonObj.put(SECRET_FIELD_SECRET, bytesStr);
+    return jsonObj;
+  }
+
+  /**
+   * Translates a {@link SecretAckMessage} to a {@link JSONObject}.
+   *
+   * @throws JSONException  on error generating the {@link JSONObject}
+   */
+  static JSONObject toJson(SecretAckMessage message) throws JSONException {
+    JSONObject jsonObj = new JSONObject();
+    String bytesStr;
+    String charsetName = Charset.defaultCharset().name();
+    try {
+      bytesStr = new String(Base64.encode(message.getSecret(), charsetName));
+    } catch (UnsupportedEncodingException e) {
+      // Should never happen.
+      bytesStr = "";
+    }
+    jsonObj.put(SECRET_ACK_FIELD_SECRET, bytesStr);
+    return jsonObj;
+  }
+
+  /**
+   * Translates a {@link EncodingOption} to a {@link JSONObject}.
+   *
+   * @throws JSONException  on error generating the {@link JSONObject}
+   */
+  static JSONObject toJson(EncodingOption encoding) throws JSONException {
+    JSONObject result = new JSONObject();
+    int intType = encoding.getType().getAsInt();
+    result.put(ENCODING_FIELD_TYPE, intType);
+    result.put(ENCODING_FIELD_SYMBOL_LENGTH, encoding.getSymbolLength());
+    return result;
+  }
+
+}
diff --git a/java/src/com/google/polo/wire/json/JsonWireAdapter.java b/java/src/com/google/polo/wire/json/JsonWireAdapter.java
new file mode 100644
index 0000000..30a29a1
--- /dev/null
+++ b/java/src/com/google/polo/wire/json/JsonWireAdapter.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2009 Google Inc.  All rights reserved.
+ *
+ * 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.google.polo.wire.json;
+
+import com.google.polo.exception.PoloException;
+import com.google.polo.json.JSONException;
+import com.google.polo.json.JSONObject;
+import com.google.polo.pairing.PairingContext;
+import com.google.polo.pairing.PoloUtil;
+import com.google.polo.pairing.message.PoloMessage;
+import com.google.polo.pairing.message.PoloMessage.PoloMessageType;
+import com.google.polo.wire.PoloWireInterface;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A {@link PoloWireInterface} which uses JavaScript Object Notation (JSON) for
+ * the message representation.
+ * <p>
+ * Messages are streamed over the wire prepended with an integer which indicates
+ * the total length, in bytes, of the message which follows. The format of the
+ * message is JSON.
+ * <p>
+ * See {@link JsonMessageBuilder} for the underlying message translation
+ * implementation.
+ */
+public class JsonWireAdapter implements PoloWireInterface {
+
+  /**
+   * The output coming from the peer.
+   */
+  private final DataInputStream mInputStream;
+
+  /**
+   * The input going to the peer.
+   */
+  private final DataOutputStream mOutputStream;
+
+  /**
+   * Constructor.
+   * 
+   * @param input the {@link InputStream} from the peer
+   * @param output the {@link OutputStream} to the peer
+   */
+  public JsonWireAdapter(InputStream input, OutputStream output) {
+    mInputStream = new DataInputStream(input);
+    mOutputStream = new DataOutputStream(output);
+  }
+
+  /**
+   * Generates a new instance from a {@link PairingContext}.
+   * 
+   * @param context the {@link PairingContext}
+   * @return the new instance
+   */
+  public static JsonWireAdapter fromContext(PairingContext context) {
+    return new JsonWireAdapter(context.getPeerInputStream(), context
+        .getPeerOutputStream());
+  }
+
+  public PoloMessage getNextMessage() throws IOException, PoloException {
+    byte[] payloadLenBytes = new byte[4];
+    mInputStream.readFully(payloadLenBytes);
+    long payloadLen = PoloUtil.intBigEndianBytesToLong(payloadLenBytes);
+    byte[] outerJsonBytes = new byte[(int) payloadLen];
+    mInputStream.readFully(outerJsonBytes);
+    return parseOuterMessageString(new String(outerJsonBytes));
+  }
+
+  public PoloMessage parseOuterMessageString(String outerString)
+      throws PoloException {
+    JSONObject outerMessage;
+    try {
+      outerMessage = new JSONObject(outerString);
+    } catch (JSONException e) {
+      throw new PoloException("Error parsing incoming message", e);
+    }
+    return JsonMessageBuilder.outerJsonToPoloMessage(outerMessage);
+  }
+
+  public PoloMessage getNextMessage(PoloMessageType type) throws IOException,
+      PoloException {
+    PoloMessage message = getNextMessage();
+    if (message.getType() != type) {
+      throw new PoloException("Wrong message type (wanted " + type + ", got "
+          + message.getType() + ")");
+    }
+    return message;
+  }
+
+  public void sendErrorMessage(Exception exception) throws IOException {
+    try {
+      writeJson(JsonMessageBuilder.getErrorJson(exception));
+    } catch (PoloException e) {
+      throw new IOException("Error sending error message");
+    }
+  }
+
+  public void sendMessage(PoloMessage message) throws IOException {
+    String outString;
+    JSONObject outerJson;
+
+    try {
+      outerJson = JsonMessageBuilder.getOuterJson(message);
+    } catch (PoloException e) {
+      throw new IOException("Error generating message");
+    }
+
+    System.out.println("Sending JSON: " + outerJson.toString());
+    writeJson(outerJson);
+  }
+
+  /**
+   * Writes a {@link JSONObject} to the output stream as a {@link String}.
+   * 
+   * @param  message      the message to write
+   * @throws IOException  on error generating the serialized message
+   */
+  private void writeJson(JSONObject message) throws IOException {
+    byte[] outBytes = message.toString().getBytes();
+    mOutputStream.write(PoloUtil.intToBigEndianIntBytes(outBytes.length));
+    mOutputStream.write(outBytes);
+  }
+
+}
diff --git a/java/src/com/google/polo/wire/protobuf/ProtobufWireAdapter.java b/java/src/com/google/polo/wire/protobuf/ProtobufWireAdapter.java
new file mode 100644
index 0000000..ea3a7cb
--- /dev/null
+++ b/java/src/com/google/polo/wire/protobuf/ProtobufWireAdapter.java
@@ -0,0 +1,579 @@
+/*
+ * Copyright (C) 2009 Google Inc.  All rights reserved.
+ *
+ * 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.google.polo.wire.protobuf;
+
+import com.google.polo.exception.BadSecretException;
+import com.google.polo.exception.NoConfigurationException;
+import com.google.polo.exception.PoloException;
+import com.google.polo.exception.ProtocolErrorException;
+import com.google.polo.pairing.PairingContext;
+import com.google.polo.pairing.PoloUtil;
+import com.google.polo.pairing.message.ConfigurationAckMessage;
+import com.google.polo.pairing.message.ConfigurationMessage;
+import com.google.polo.pairing.message.EncodingOption;
+import com.google.polo.pairing.message.OptionsMessage;
+import com.google.polo.pairing.message.PairingRequestAckMessage;
+import com.google.polo.pairing.message.PairingRequestMessage;
+import com.google.polo.pairing.message.PoloMessage;
+import com.google.polo.pairing.message.SecretAckMessage;
+import com.google.polo.pairing.message.SecretMessage;
+import com.google.polo.wire.PoloWireInterface;
+import com.google.polo.wire.protobuf.PoloProto.OuterMessage;
+import com.google.protobuf.nano.MessageNano;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Implementation of {@link PoloWireInterface} that uses Protocol Buffers for
+ * the data representation.
+ * <p/>
+ * The primary work of this class is to translate Protocol Buffer messages
+ * instances (derived from {@link MessageNano} to an internal message
+ * instance (derived from {@link PoloMessage}, and vice versa.
+ * <p/>
+ * The reason we are going through all this trouble, and not using protocol
+ * buffer objects directly, is that we'd like to limit the scope of protocol
+ * buffers to the wire protocol only.  Some applications may prefer to use
+ * a different wire format, where the requirement of adding the protobuf library
+ * could be an impediment.
+ */
+public class ProtobufWireAdapter implements PoloWireInterface {
+
+    /**
+     * The output coming from the peer.
+     */
+    private final InputStream mInputStream;
+    /**
+     * The input going to the peer.
+     */
+    private final OutputStream mOutputStream;
+
+    /**
+     * Constructor.
+     *
+     * @param input  the {@link InputStream} from the peer
+     * @param output the {@link OutputStream} to the peer
+     */
+    public ProtobufWireAdapter(InputStream input, OutputStream output) {
+        mInputStream = input;
+        mOutputStream = output;
+    }
+
+    /**
+     * Generates a new instance from a {@link PairingContext}.
+     *
+     * @param context the {@link PairingContext}
+     * @return the new instance
+     */
+    public static ProtobufWireAdapter fromContext(PairingContext context) {
+        return new ProtobufWireAdapter(context.getPeerInputStream(),
+                context.getPeerOutputStream());
+    }
+
+    /**
+     * Returns the next message sent over the wire, blocking as necessary.
+     */
+    public PoloMessage getNextMessage() throws IOException, PoloException {
+        return protoToPoloMessage(readNextInnerMessage());
+    }
+
+    /**
+     * Returns the next message read over the wire, requiring it to be a certain
+     * type.
+     *
+     * @param type the required message type
+     * @throws IOException   on error during read
+     * @throws PoloException if the wrong message type was read, or on protocol
+     *                       error
+     */
+    public PoloMessage getNextMessage(PoloMessage.PoloMessageType type)
+            throws IOException, PoloException {
+        PoloMessage message = getNextMessage();
+        if (message.getType() != type) {
+            throw new PoloException("Wrong message type (wanted " + type +
+                    ", got " + message.getType() + ")");
+        }
+        return message;
+    }
+
+    /**
+     * Returns the next message seen on the input stream.
+     *
+     * @return the next OuterMessage read from the wire
+     * @throws IOException on error during read
+     */
+    private OuterMessage readNextOuterMessage() throws IOException, PoloException {
+        // Read the preamble (length of payload)
+        byte[] preambleBuffer = readBytesBlocking(4);
+        int messageLen = (int) PoloUtil.intBigEndianBytesToLong(preambleBuffer);
+
+        // Read the payload (serialized PoloMessage)
+        byte[] messageBuffer = readBytesBlocking(messageLen);
+
+        // Decode and return the payload
+        OuterMessage message = OuterMessage.parseFrom(messageBuffer);
+
+        if (message.status != OuterMessage.STATUS_OK) {
+            throw new ProtocolErrorException();
+        }
+
+        return message;
+    }
+
+    /**
+     * Reads the next inner message from the wire, decoding and handling the outer
+     * message in the process.
+     *
+     * @return a protocol buffer message
+     * @throws IOException   on error during read
+     * @throws PoloException on protocol error
+     */
+    private MessageNano readNextInnerMessage()
+            throws IOException, PoloException {
+        OuterMessage message = readNextOuterMessage();
+
+        byte[] payload = message.payload;
+
+        if (message.type == OuterMessage.MESSAGE_TYPE_OPTIONS) {
+            return PoloProto.Options.parseFrom(payload);
+        } else if (message.type == OuterMessage.MESSAGE_TYPE_PAIRING_REQUEST) {
+            return PoloProto.PairingRequest.parseFrom(payload);
+        } else if (message.type == OuterMessage.MESSAGE_TYPE_PAIRING_REQUEST_ACK) {
+            return PoloProto.PairingRequestAck.parseFrom(payload);
+        } else if (message.type == OuterMessage.MESSAGE_TYPE_CONFIGURATION) {
+            return PoloProto.Configuration.parseFrom(payload);
+        } else if (message.type == OuterMessage.MESSAGE_TYPE_CONFIGURATION_ACK) {
+            return PoloProto.ConfigurationAck.parseFrom(payload);
+        } else if (message.type == OuterMessage.MESSAGE_TYPE_SECRET) {
+            return PoloProto.Secret.parseFrom(payload);
+        } else if (message.type == OuterMessage.MESSAGE_TYPE_SECRET_ACK) {
+            return PoloProto.SecretAck.parseFrom(payload);
+        }
+
+        throw new IOException("Could not unparse message");
+    }
+
+    /**
+     * Convenience method to read a fixed number of bytes from the client
+     * InputStream, blocking if necessary.
+     *
+     * @param numBytes the number of bytes to read
+     * @return the bytes read
+     * @throws IOException on error during read
+     */
+    private byte[] readBytesBlocking(int numBytes) throws IOException {
+        byte[] buf = new byte[numBytes];
+        int bytesRead = 0;
+
+        // For an SSLSocket, read() can frequently return zero bytes,
+        // or fewer bytes than desired, due to SSL unwrapping and other
+        // non-application-data events.
+        while (bytesRead < numBytes) {
+            int inc = mInputStream.read(buf, bytesRead, numBytes - bytesRead);
+            if (inc < 0) {
+                throw new IOException("Stream closed while reading.");
+            }
+            bytesRead += inc;
+        }
+        return buf;
+    }
+
+    /**
+     * Wraps an outer message in an inner message.
+     *
+     * @param message the {@link MessageNano} to wrap
+     * @throws PoloException if the message was not well formed
+     */
+    private OuterMessage wrapInnerMessage(MessageNano message)
+            throws PoloException {
+        int type;
+        if (message instanceof PoloProto.Options) {
+            type = OuterMessage.MESSAGE_TYPE_OPTIONS;
+        } else if (message instanceof PoloProto.PairingRequest) {
+            type = OuterMessage.MESSAGE_TYPE_PAIRING_REQUEST;
+        } else if (message instanceof PoloProto.PairingRequestAck) {
+            type = OuterMessage.MESSAGE_TYPE_PAIRING_REQUEST_ACK;
+        } else if (message instanceof PoloProto.Configuration) {
+            type = OuterMessage.MESSAGE_TYPE_CONFIGURATION;
+        } else if (message instanceof PoloProto.ConfigurationAck) {
+            type = OuterMessage.MESSAGE_TYPE_CONFIGURATION_ACK;
+        } else if (message instanceof PoloProto.Secret) {
+            type = OuterMessage.MESSAGE_TYPE_SECRET;
+        } else if (message instanceof PoloProto.SecretAck) {
+            type = OuterMessage.MESSAGE_TYPE_SECRET_ACK;
+        } else {
+            throw new PoloException("Bad inner message type.");
+        }
+
+        // compose outer message
+        OuterMessage outerMessage = new OuterMessage();
+        outerMessage.status = OuterMessage.STATUS_OK;
+        outerMessage.protocolVersion = 1;
+        outerMessage.type = type;
+        outerMessage.payload = MessageNano.toByteArray(message);
+        return outerMessage;
+    }
+
+    /**
+     * Writes an {@link OuterMessage} to the wire.
+     *
+     * @param message the message
+     * @throws IOException on error during write
+     */
+    private void writeMessage(OuterMessage message) throws IOException {
+        byte[] messageBytes = message.payload;
+        int messageLength = messageBytes.length;
+
+        mOutputStream.write(PoloUtil.intToBigEndianIntBytes(messageLength));
+        mOutputStream.write(messageBytes);
+    }
+
+    /**
+     * Writes a new message to the wire.
+     */
+    public void sendMessage(PoloMessage message)
+            throws IOException, PoloException {
+        MessageNano pb = poloMessageToProto(message);
+        OuterMessage outerMessage = wrapInnerMessage(pb);
+        writeMessage(outerMessage);
+    }
+
+    /**
+     * Sends a new error message to the wire.
+     */
+    public void sendErrorMessage(Exception e) throws IOException {
+        OuterMessage outerMessage = new OuterMessage();
+        outerMessage.protocolVersion = 1;
+
+        if (e instanceof NoConfigurationException) {
+            outerMessage.status = OuterMessage.STATUS_BAD_CONFIGURATION;
+        } else if (e instanceof BadSecretException) {
+            outerMessage.status = OuterMessage.STATUS_BAD_SECRET;
+        } else {
+            outerMessage.status = OuterMessage.STATUS_ERROR;
+        }
+
+        writeMessage(outerMessage);
+    }
+
+    /**
+     * Converts an internal message to the corresponding protocol buffer message.
+     *
+     * @param poloMessage the internal message
+     * @return a new {@link MessageNano} instance
+     */
+    private MessageNano poloMessageToProto(PoloMessage poloMessage) {
+        if (poloMessage instanceof PairingRequestMessage) {
+            return toProto((PairingRequestMessage) poloMessage);
+        } else if (poloMessage instanceof PairingRequestAckMessage) {
+            return toProto((PairingRequestAckMessage) poloMessage);
+        } else if (poloMessage instanceof OptionsMessage) {
+            return toProto((OptionsMessage) poloMessage);
+        } else if (poloMessage instanceof ConfigurationMessage) {
+            return toProto((ConfigurationMessage) poloMessage);
+        } else if (poloMessage instanceof ConfigurationAckMessage) {
+            return toProto((ConfigurationAckMessage) poloMessage);
+        } else if (poloMessage instanceof SecretMessage) {
+            return toProto((SecretMessage) poloMessage);
+        } else if (poloMessage instanceof SecretAckMessage) {
+            return toProto((SecretAckMessage) poloMessage);
+        }
+        return null;
+    }
+
+    /**
+     * Converts a {@link PairingRequestMessage} to a
+     * {@link PoloProto.PairingRequest}.
+     */
+    private PoloProto.PairingRequest toProto(PairingRequestMessage poloMessage) {
+        PoloProto.PairingRequest pairingRequest = new PoloProto.PairingRequest();
+        pairingRequest.serviceName = poloMessage.getServiceName();
+
+        if (poloMessage.hasClientName()) {
+            pairingRequest.clientName = poloMessage.getClientName();
+        }
+        return pairingRequest;
+    }
+
+    /**
+     * Converts a {@link PairingRequestAckMessage} to a
+     * {@link PoloProto.PairingRequestAck}.
+     */
+    private PoloProto.PairingRequestAck toProto(PairingRequestAckMessage poloMessage) {
+        PoloProto.PairingRequestAck pairingRequestAck = new PoloProto.PairingRequestAck();
+        if (poloMessage.hasServerName()) {
+            pairingRequestAck.serverName = poloMessage.getServerName();
+        }
+        return pairingRequestAck;
+    }
+
+    /**
+     * Converts a {@link OptionsMessage} to a {@link PoloProto.Options}.
+     */
+    private PoloProto.Options toProto(OptionsMessage poloMessage) {
+        PoloProto.Options options = new PoloProto.Options();
+
+        switch (poloMessage.getProtocolRolePreference()) {
+            case DISPLAY_DEVICE:
+                options.preferredRole = PoloProto.Options.ROLE_TYPE_INPUT;
+                break;
+            case INPUT_DEVICE:
+                options.preferredRole = PoloProto.Options.ROLE_TYPE_OUTPUT;
+                break;
+        }
+
+        int i = 0, n = poloMessage.getOutputEncodingSet().size();
+        options.outputEncodings = new PoloProto.Options.Encoding[n];
+        for (EncodingOption enc : poloMessage.getOutputEncodingSet()) {
+            options.outputEncodings[i++] = toProto(enc);
+        }
+
+        i = 0;
+        n = poloMessage.getInputEncodingSet().size();
+        options.inputEncodings = new PoloProto.Options.Encoding[n];
+        for (EncodingOption enc : poloMessage.getInputEncodingSet()) {
+            options.inputEncodings[i++] = toProto(enc);
+        }
+
+        return options;
+    }
+
+    /**
+     * Converts a {@link ConfigurationMessage} to a
+     * {@link PoloProto.Configuration}.
+     */
+    private PoloProto.Configuration toProto(ConfigurationMessage poloMessage) {
+        PoloProto.Configuration configuration = new PoloProto.Configuration();
+        configuration.encoding = toProto(poloMessage.getEncoding());
+        configuration.clientRole = toProto(poloMessage.getClientRole());
+        return configuration;
+    }
+
+    /**
+     * Converts a {@link EncodingOption} to a {@link PoloProto.Options.Encoding}.
+     */
+    private PoloProto.Options.Encoding toProto(EncodingOption enc) {
+        PoloProto.Options.Encoding encoding = new PoloProto.Options.Encoding();
+
+        switch (enc.getType()) {
+            case ENCODING_ALPHANUMERIC:
+                encoding.type = PoloProto.Options.Encoding.ENCODING_TYPE_ALPHANUMERIC;
+                break;
+            case ENCODING_HEXADECIMAL:
+                encoding.type = PoloProto.Options.Encoding.ENCODING_TYPE_HEXADECIMAL;
+                break;
+            case ENCODING_NUMERIC:
+                encoding.type = PoloProto.Options.Encoding.ENCODING_TYPE_NUMERIC;
+                break;
+            case ENCODING_QRCODE:
+                encoding.type = PoloProto.Options.Encoding.ENCODING_TYPE_QRCODE;
+                break;
+            default:
+                encoding.type = PoloProto.Options.Encoding.ENCODING_TYPE_UNKNOWN;
+                break;
+        }
+
+        encoding.symbolLength = enc.getSymbolLength();
+        return encoding;
+    }
+
+    /**
+     * Converts a {@link OptionsMessage.ProtocolRole} to a
+     * {@link PoloProto.Options}.
+     */
+    private int toProto(OptionsMessage.ProtocolRole role) {
+        switch (role) {
+            case DISPLAY_DEVICE:
+                return PoloProto.Options.ROLE_TYPE_OUTPUT;
+            case INPUT_DEVICE:
+                return PoloProto.Options.ROLE_TYPE_INPUT;
+            default:
+                return PoloProto.Options.ROLE_TYPE_UNKNOWN;
+        }
+    }
+
+    /**
+     * Converts a {@link ConfigurationAckMessage} to a
+     * {@link PoloProto.ConfigurationAck}.
+     */
+    private PoloProto.ConfigurationAck toProto(ConfigurationAckMessage poloMessage) {
+        PoloProto.ConfigurationAck configurationAck = new PoloProto.ConfigurationAck();
+        return configurationAck;
+    }
+
+    /**
+     * Converts a {@link SecretMessage} to a {@link PoloProto.Secret}.
+     */
+    private PoloProto.Secret toProto(SecretMessage poloMessage) {
+        PoloProto.Secret secret = new PoloProto.Secret();
+        secret.secret = poloMessage.getSecret();
+        return secret;
+    }
+
+    /**
+     * Converts a {@link SecretAckMessage} to a {@link PoloProto.SecretAck}.
+     */
+    private PoloProto.SecretAck toProto(SecretAckMessage poloMessage) {
+        PoloProto.SecretAck secretAck = new PoloProto.SecretAck();
+        secretAck.secret = poloMessage.getSecret();
+        return secretAck;
+    }
+
+    //
+    // polo -> protocol buffer routines
+    //
+
+    /**
+     * Converts a protocol buffer message to the corresponding internal
+     * message.
+     *
+     * @param protoMessage the protobuf message to convert
+     * @return the new {@link PoloMessage}
+     */
+    private PoloMessage protoToPoloMessage(MessageNano protoMessage) {
+        if (protoMessage instanceof PoloProto.PairingRequest) {
+            return fromProto((PoloProto.PairingRequest) protoMessage);
+        } else if (protoMessage instanceof PoloProto.PairingRequestAck) {
+            return fromProto((PoloProto.PairingRequestAck) protoMessage);
+        } else if (protoMessage instanceof PoloProto.Options) {
+            return fromProto((PoloProto.Options) protoMessage);
+        } else if (protoMessage instanceof PoloProto.Configuration) {
+            return fromProto((PoloProto.Configuration) protoMessage);
+        } else if (protoMessage instanceof PoloProto.ConfigurationAck) {
+            return fromProto((PoloProto.ConfigurationAck) protoMessage);
+        } else if (protoMessage instanceof PoloProto.Secret) {
+            return fromProto((PoloProto.Secret) protoMessage);
+        } else if (protoMessage instanceof PoloProto.SecretAck) {
+            return fromProto((PoloProto.SecretAck) protoMessage);
+        }
+        return null;
+    }
+
+    /**
+     * Converts a {@link PoloProto.PairingRequest} to a
+     * {@link PairingRequestMessage}.
+     */
+    private PairingRequestMessage fromProto(PoloProto.PairingRequest protoMessage) {
+        return new PairingRequestMessage(protoMessage.serviceName, protoMessage.clientName);
+    }
+
+    /**
+     * Converts a {@link PoloProto.PairingRequestAck} to a
+     * {@link PairingRequestAckMessage}.
+     */
+    private PairingRequestAckMessage fromProto(PoloProto.PairingRequestAck protoMessage) {
+        return new PairingRequestAckMessage(protoMessage.serverName);
+    }
+
+    /**
+     * Converts a {@link PoloProto.Options} to a {@link OptionsMessage}.
+     */
+    private OptionsMessage fromProto(PoloProto.Options protoMessage) {
+        OptionsMessage optionsMessage = new OptionsMessage();
+
+        switch (protoMessage.preferredRole) {
+            case PoloProto.Options.ROLE_TYPE_INPUT:
+                optionsMessage.setProtocolRolePreference(OptionsMessage.ProtocolRole.INPUT_DEVICE);
+                break;
+            case PoloProto.Options.ROLE_TYPE_OUTPUT:
+                optionsMessage.setProtocolRolePreference(OptionsMessage.ProtocolRole.DISPLAY_DEVICE);
+                break;
+        }
+
+        for (PoloProto.Options.Encoding e : protoMessage.inputEncodings) {
+            optionsMessage.addInputEncoding(fromProto(e));
+        }
+
+        for (PoloProto.Options.Encoding e : protoMessage.outputEncodings) {
+            optionsMessage.addOutputEncoding(fromProto(e));
+        }
+
+        return optionsMessage;
+    }
+
+    /**
+     * Converts a {@link PoloProto.Configuration} to a
+     * {@link ConfigurationMessage}.
+     */
+    private ConfigurationMessage fromProto(PoloProto.Configuration protoMessage) {
+        EncodingOption enc = fromProto(protoMessage.encoding);
+        OptionsMessage.ProtocolRole role = OptionsMessage.ProtocolRole.UNKNOWN;
+
+        switch (protoMessage.clientRole) {
+            case PoloProto.Options.ROLE_TYPE_INPUT:
+                role = OptionsMessage.ProtocolRole.INPUT_DEVICE;
+                break;
+            case PoloProto.Options.ROLE_TYPE_OUTPUT:
+                role = OptionsMessage.ProtocolRole.DISPLAY_DEVICE;
+                break;
+        }
+
+        return new ConfigurationMessage(enc, role);
+    }
+
+    /**
+     * Converts a {@link PoloProto.ConfigurationAck} to a
+     * {@link ConfigurationAckMessage}.
+     */
+    private ConfigurationAckMessage fromProto(PoloProto.ConfigurationAck protoMessage) {
+        return new ConfigurationAckMessage();
+    }
+
+    /**
+     * Converts a {@link PoloProto.Secret} to a {@link SecretMessage}.
+     */
+    private SecretMessage fromProto(PoloProto.Secret protoMessage) {
+        return new SecretMessage(protoMessage.secret);
+    }
+
+    /**
+     * Converts a {@link PoloProto.SecretAck} to a {@link SecretAckMessage}.
+     */
+    private SecretAckMessage fromProto(PoloProto.SecretAck protoMessage) {
+        return new SecretAckMessage(protoMessage.secret);
+    }
+
+    /**
+     * Converts a {@link PoloProto.Options.Encoding} to a {@link EncodingOption}.
+     */
+    private EncodingOption fromProto(PoloProto.Options.Encoding enc) {
+        EncodingOption.EncodingType type;
+
+        switch (enc.type) {
+            case PoloProto.Options.Encoding.ENCODING_TYPE_ALPHANUMERIC:
+                type = EncodingOption.EncodingType.ENCODING_ALPHANUMERIC;
+                break;
+            case PoloProto.Options.Encoding.ENCODING_TYPE_HEXADECIMAL:
+                type = EncodingOption.EncodingType.ENCODING_HEXADECIMAL;
+                break;
+            case PoloProto.Options.Encoding.ENCODING_TYPE_NUMERIC:
+                type = EncodingOption.EncodingType.ENCODING_NUMERIC;
+                break;
+            case PoloProto.Options.Encoding.ENCODING_TYPE_QRCODE:
+                type = EncodingOption.EncodingType.ENCODING_QRCODE;
+                break;
+            default:
+                type = EncodingOption.EncodingType.ENCODING_UNKNOWN;
+        }
+
+        return new EncodingOption(type, enc.symbolLength);
+
+    }
+
+}
diff --git a/java/src/com/google/polo/wire/xml/XmlMessageBuilder.java b/java/src/com/google/polo/wire/xml/XmlMessageBuilder.java
new file mode 100644
index 0000000..f30c17b
--- /dev/null
+++ b/java/src/com/google/polo/wire/xml/XmlMessageBuilder.java
@@ -0,0 +1,647 @@
+/*
+ * Copyright (C) 2009 Google Inc.  All rights reserved.
+ *
+ * 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.google.polo.wire.xml;
+
+import com.google.polo.exception.PoloException;
+import com.google.polo.exception.ProtocolErrorException;
+import com.google.polo.json.JSONArray;
+import com.google.polo.json.JSONException;
+import com.google.polo.json.JSONObject;
+import com.google.polo.json.XML;
+import com.google.polo.pairing.PoloUtil;
+import com.google.polo.pairing.message.ConfigurationAckMessage;
+import com.google.polo.pairing.message.ConfigurationMessage;
+import com.google.polo.pairing.message.EncodingOption;
+import com.google.polo.pairing.message.EncodingOption.EncodingType;
+import com.google.polo.pairing.message.OptionsMessage;
+import com.google.polo.pairing.message.OptionsMessage.ProtocolRole;
+import com.google.polo.pairing.message.PairingRequestAckMessage;
+import com.google.polo.pairing.message.PairingRequestMessage;
+import com.google.polo.pairing.message.PoloMessage;
+import com.google.polo.pairing.message.PoloMessage.PoloMessageType;
+import com.google.polo.pairing.message.SecretAckMessage;
+import com.google.polo.pairing.message.SecretMessage;
+
+/**
+ * A collection of methods to convert {@link PoloMessage}s to and from XML
+ * format.
+ * <p>
+ * This wire format was specified by a third party; it uses a proprietary
+ * 64-byte message header/delimiter, and message internals are inconsistent with
+ * the protocol buffer in several places.
+ */
+public class XmlMessageBuilder {
+
+  /*
+   * Status types.
+   * NOTE(mikey): These do not match the values defined by
+   * OuterMessage.MessageType in polo.proto.
+   */
+
+  public static final int STATUS_OK = 1;
+  public static final int STATUS_ERROR = 2;
+
+  /*
+   * Key names for XML versions of messages.
+   */
+  
+  // OuterMessage XML key names
+  private static final String OUTER_FIELD_TYPE = "msg_type";
+  private static final String OUTER_FIELD_STATUS = "status";
+  private static final String OUTER_FIELD_MSG_ID = "msg_id";
+  private static final String OUTER_FIELD_PAYLOAD = "pairing_msg";
+
+  // PairingRequestMessage XML key names
+  private static final String PAIRING_REQUEST_FIELD_PROTOCOL_VERSION =
+      "proto_version";
+  
+  // OptionsMessage XML key names
+  private static final String OPTIONS_FIELD_PREFERRED_ROLE = "pref_role";
+  private static final String OPTIONS_FIELD_OUTPUT_ENCODINGS = "out_encodings";
+  private static final String OPTIONS_FIELD_INPUT_ENCODINGS = "in_encodings";
+
+  // ConfigurationMessage XML key names
+  private static final String CONFIG_FIELD_CLIENT_ROLE = "role";
+  
+  // EncodingOption XML key names
+  private static final String ENCODING_FIELD_TYPE = "type";
+  private static final String ENCODING_FIELD_SYMBOL_LENGTH = "min_length";
+  private static final String ENCODING_FIELD_MAX_LENGTH = "max_length";
+  private static final String ENCODING_SUBFIELD_ENCODING = "encoding";
+
+  // SecretMessage XML key names
+  private static final String SECRET_FIELD_SECRET = "bytes";
+
+  // Payload container names
+  private static final String MESSAGE_CONTAINER_NAME_PAIRING_REQUEST =
+      "pairing_req";
+  private static final String MESSAGE_CONTAINER_NAME_PAIRING_REQUEST_ACK =
+      "pairing_req_ack";
+  private static final String MESSAGE_CONTAINER_NAME_OPTIONS = "config_options";
+  private static final String MESSAGE_CONTAINER_NAME_CONFIG = "config";
+  private static final String MESSAGE_CONTAINER_NAME_SECRET = "secret";
+  private static final String PAIRING_REQUEST_FIELD_SERVICE_NAME = "svc_name";
+  private static final String PAIRING_REQUEST_FIELD_CLIENT_NAME = "client_name";
+  private static final String PAIRING_REQUEST_ACK_FIELD_SERVER_NAME =
+      "server_name";
+
+  //
+  // Encoding types -- these do not match polo.proto's enum.
+  //
+  
+  public static final int ENCODING_TYPE_NUMERIC = 1;
+  public static final int ENCODING_TYPE_HEXADECIMAL = 2;
+  public static final int ENCODING_TYPE_ALPHANUMERIC = 3;
+  public static final int ENCODING_TYPE_QRCODE = 4;
+
+  /**
+   * Cache of the last message id header value received.  The value should be
+   * copied to any response.
+   */
+  private String mLastMessageId;
+
+  public XmlMessageBuilder() {
+    mLastMessageId = null;
+  }
+
+  /**
+   * Builds a {@link PoloMessage} from the XML version of the outer message.
+   * 
+   * @param outerXml        the outermost XML string
+   * @return  a new {@link PoloMessage}
+   * @throws PoloException  on error parsing the message
+   */
+  PoloMessage outerXMLToPoloMessage(String outerXml) throws PoloException {
+    JSONObject outerMessage;
+    try {
+      outerMessage = XML.toJSONObject(outerXml);
+    } catch (JSONException e) {
+        throw new PoloException(e);
+    }
+
+    JSONObject payload;
+    PoloMessageType messageType;
+    try {
+      payload = outerMessage.getJSONObject(OUTER_FIELD_PAYLOAD);
+
+      int status = payload.getInt(OUTER_FIELD_STATUS);
+      if (status != STATUS_OK) {
+        throw new ProtocolErrorException("Peer reported an error.");
+      }
+      
+      int msgIntVal = payload.getInt(OUTER_FIELD_TYPE);
+      messageType = PoloMessageType.fromIntVal(msgIntVal);
+    } catch (JSONException e) {
+      throw new PoloException("Bad outer message.", e);
+    }
+
+    if (outerMessage.has("msg_id")) {
+      try {
+        mLastMessageId = outerMessage.getString("msg_id");
+      } catch (JSONException e) {
+      }
+    } else {
+      mLastMessageId = null;
+    }
+
+    switch (messageType) {
+      case PAIRING_REQUEST:
+        return getPairingRequest(payload);
+      case PAIRING_REQUEST_ACK:
+        return getPairingRequestAck(payload);
+      case OPTIONS:
+        return getOptionsMessage(payload);
+      case CONFIGURATION:
+        return getConfigMessage(payload);
+      case CONFIGURATION_ACK:
+        return getConfigAckMessage(payload);
+      case SECRET:
+        return getSecretMessage(payload);
+      case SECRET_ACK:
+        return getSecretAckMessage(payload);
+      default:
+        return null;
+    }
+
+  }
+
+  /*
+   * Methods to convert XML inner messages to PoloMessage instances.
+   *
+   * NOTE(mikey): These methods are implemented in terms of JSONObject
+   * as a convenient way to represent hierarchical key->(dict|list|value)
+   * structures.
+   * 
+   * Note that these methods are very similar to those found in
+   * JsonWireAdapter.  However, the XML wire format was specified with slight
+   * differences compared to the protocol buffer definition.  For example,
+   * in the OptionsMessage, encodings are wrapped in an "<options>" container.
+   * 
+   * Also, many fields names have slight differences compared to the names in
+   * the protocol buffer (for example, in PairingRequestMessage, "service_name"
+   * is called "svc_name".)
+   */
+
+  /**
+   * Generates a new {@link PairingRequestMessage} from a JSON payload.
+   * 
+   * @param  body           the JSON payload
+   * @return  the new message
+   * @throws PoloException  on error parsing the {@link JSONObject}
+   */
+  PairingRequestMessage getPairingRequest(JSONObject body)
+      throws PoloException {
+    try {
+      JSONObject jsonObj = body.getJSONObject(
+          MESSAGE_CONTAINER_NAME_PAIRING_REQUEST);
+      String serviceName = jsonObj.getString(
+          PAIRING_REQUEST_FIELD_SERVICE_NAME);
+      String clientName = null;
+      if (jsonObj.has(PAIRING_REQUEST_FIELD_CLIENT_NAME)) {
+        clientName = jsonObj.getString(PAIRING_REQUEST_FIELD_CLIENT_NAME);
+      }
+      return new PairingRequestMessage(serviceName, clientName);
+    } catch (JSONException e) {
+      throw new PoloException("Malformed message.", e);
+    }
+  }
+
+  /**
+   * Generates a new {@link PairingRequestAckMessage} from a JSON payload.
+   * 
+   * @param  body           the JSON payload
+   * @return                the new message
+   * @throws PoloException  on error parsing the {@link JSONObject}
+   */
+  PairingRequestAckMessage getPairingRequestAck(JSONObject body)
+      throws PoloException {
+    try {
+      JSONObject jsonObj = body.getJSONObject(
+          MESSAGE_CONTAINER_NAME_PAIRING_REQUEST_ACK);
+      String serverName = null;
+      if (jsonObj.has(PAIRING_REQUEST_ACK_FIELD_SERVER_NAME)) {
+        serverName = jsonObj.getString(PAIRING_REQUEST_ACK_FIELD_SERVER_NAME);
+      }
+      return new PairingRequestAckMessage(serverName);
+    } catch (JSONException e) {
+      throw new PoloException("Malformed message.", e);
+    }
+  }
+
+  /**
+   * Generates a new {@link OptionsMessage} from a JSON payload.
+   * 
+   * @param  body           the JSON payload
+   * @return                the new message
+   * @throws PoloException  on error parsing the {@link JSONObject}
+   */
+  OptionsMessage getOptionsMessage(JSONObject body) throws PoloException {
+    OptionsMessage options = new OptionsMessage();
+    JSONObject jsonOptions;
+    try {
+      jsonOptions = body.getJSONObject(MESSAGE_CONTAINER_NAME_OPTIONS);
+
+      JSONObject inEnc = jsonOptions.getJSONObject(
+          OPTIONS_FIELD_INPUT_ENCODINGS);
+      JSONObject outEnc = jsonOptions.getJSONObject(
+          OPTIONS_FIELD_OUTPUT_ENCODINGS);
+
+      // Input encodings
+      JSONArray inEncodings = new JSONArray();
+      try {
+        inEncodings = inEnc.getJSONArray(ENCODING_SUBFIELD_ENCODING);
+      } catch (JSONException e) {
+        if (inEnc.has(ENCODING_SUBFIELD_ENCODING)) {
+          JSONObject enc = inEnc.getJSONObject(ENCODING_SUBFIELD_ENCODING);
+          inEncodings.put(enc);
+        }
+      }
+
+      for (int i = 0; i < inEncodings.length(); i++) {
+        JSONObject enc = inEncodings.getJSONObject(i);
+        options.addInputEncoding(getEncodingOption(enc));
+      }
+
+      // Output encodings
+      JSONArray outEncodings = new JSONArray();
+      try {
+        outEncodings = outEnc.getJSONArray(ENCODING_SUBFIELD_ENCODING);
+      } catch (JSONException e) {
+        if (outEnc.has(ENCODING_SUBFIELD_ENCODING)) {
+          JSONObject enc = outEnc.getJSONObject(ENCODING_SUBFIELD_ENCODING);
+          outEncodings.put(enc);
+        }
+      }
+
+      for (int i = 0; i < outEncodings.length(); i++) {
+        JSONObject enc = outEncodings.getJSONObject(i);
+        options.addOutputEncoding(getEncodingOption(enc));
+      }
+
+      // Role
+      ProtocolRole role = ProtocolRole.fromIntVal(
+          jsonOptions.getInt(OPTIONS_FIELD_PREFERRED_ROLE));
+      options.setProtocolRolePreference(role);
+    } catch (JSONException e) {
+      throw new PoloException("Malformed message.", e);
+    }
+
+    return options;
+  }
+
+  /**
+   * Generates a new {@link ConfigurationMessage} from a JSON payload.
+   * 
+   * @param  body           the JSON payload
+   * @return                the new message
+   * @throws PoloException  on error parsing the {@link JSONObject}
+   */
+  ConfigurationMessage getConfigMessage(JSONObject body)
+  throws PoloException {
+    try {  
+      EncodingOption encoding = getEncodingOption(
+          body.getJSONObject(MESSAGE_CONTAINER_NAME_CONFIG)
+              .getJSONObject(ENCODING_SUBFIELD_ENCODING));
+      ProtocolRole role = ProtocolRole.fromIntVal(
+          body.getJSONObject(MESSAGE_CONTAINER_NAME_CONFIG)
+              .getInt(CONFIG_FIELD_CLIENT_ROLE));
+      return new ConfigurationMessage(encoding, role);
+    } catch (JSONException e) {
+      throw new PoloException("Malformed message.", e);
+    }
+  }
+
+  /**
+   * Generates a new {@link ConfigurationAckMessage} from a JSON payload.
+   * 
+   * @param  body           the JSON payload
+   * @return                the new message
+   */
+  ConfigurationAckMessage getConfigAckMessage(JSONObject body) {
+    return new ConfigurationAckMessage();
+  }
+
+  /**
+   * Generates a new {@link SecretMessage} from a JSON payload.
+   * 
+   * @param  body           the JSON payload
+   * @return                the new message
+   * @throws PoloException  on error parsing the {@link JSONObject}
+   */
+  SecretMessage getSecretMessage(JSONObject body) throws PoloException {
+    String secret;
+    try {
+      secret = body.getJSONObject(MESSAGE_CONTAINER_NAME_SECRET)
+          .getString(SECRET_FIELD_SECRET);
+    } catch (JSONException e) {
+      throw new PoloException("Malformed message.", e);
+    }
+    byte[] secretBytes = PoloUtil.hexStringToBytes(secret);
+    return new SecretMessage(secretBytes);
+  }
+
+  /**
+   * Generates a new {@link SecretAckMessage} from a JSON payload.
+   * 
+   * @param body  the JSON payload
+   * @return      the new message
+   */
+  SecretAckMessage getSecretAckMessage(JSONObject body) {
+    return new SecretAckMessage(null);
+  }
+
+  /**
+   * Generates a new {@link EncodingOption} from a JSON sub-dictionary.
+   * 
+   * @param  option         the JSON sub-dictionary describing the option
+   * @return                the new {@link EncodingOption}
+   * @throws JSONException  on error parsing the {@link JSONObject}
+   */
+  EncodingOption getEncodingOption(JSONObject option) throws JSONException {
+    int length = option.getInt(ENCODING_FIELD_SYMBOL_LENGTH);
+    int intType = option.getInt(ENCODING_FIELD_TYPE);
+    EncodingType type = encodingTypeFromIntValue(intType);
+    return new EncodingOption(type, length);
+  }
+
+  /**
+   * Converts a {@link PoloMessage} to an XML string.
+   * 
+   * @param message         the message to convert
+   * @return                the same message, as translated to XML
+   */
+  public String poloMessageToXML(PoloMessage message) {
+    try {
+      if (message instanceof PairingRequestMessage) {
+        return toXML((PairingRequestMessage) message);
+      } else if (message instanceof PairingRequestAckMessage) {
+        return toXML((PairingRequestAckMessage) message);
+      } else if (message instanceof OptionsMessage) {
+        return toXML((OptionsMessage) message);
+      } else if (message instanceof ConfigurationMessage) {
+        return toXML((ConfigurationMessage) message);
+      } else if (message instanceof ConfigurationAckMessage) {
+        return toXML((ConfigurationAckMessage) message);
+      } else if (message instanceof SecretMessage) {
+        return toXML((SecretMessage) message);
+      } else if (message instanceof SecretAckMessage) {
+        return toXML((SecretAckMessage) message);
+      }
+      return null;
+    } catch (JSONException e) {
+      e.printStackTrace();
+      return "";
+    }
+
+  }
+
+  /**
+   * Generates a String corresponding to a full wire message (wrapped in
+   * an outer message) for the given payload.
+   */
+  public String getOuterXML(PoloMessage message, int status) {
+    StringBuffer out = new StringBuffer();
+    
+    
+    out.append("<" + OUTER_FIELD_PAYLOAD + ">\n");
+    
+    // status
+    out.append("<" + OUTER_FIELD_STATUS + ">");
+    out.append(status);
+    out.append("</" + OUTER_FIELD_STATUS + ">\n");
+    
+    // msg_id (optional)
+    if (mLastMessageId != null) {
+      out.append("<" + OUTER_FIELD_MSG_ID + ">");
+      out.append(mLastMessageId);
+      out.append("</" + OUTER_FIELD_MSG_ID + ">\n");
+    }
+    
+    // payload
+    if (message != null) {
+      int msgType = message.getType().getAsInt();
+      out.append("<" + OUTER_FIELD_TYPE + ">");
+      out.append(msgType);
+      out.append("</" + OUTER_FIELD_TYPE + ">\n");
+
+      out.append(poloMessageToXML(message));
+      out.append("\n");
+    }
+    
+    out.append("</" + OUTER_FIELD_PAYLOAD + ">\n");
+    return out.toString();
+  }
+  
+  
+  
+  /**
+   * Generates an error payload corresponding to an outer message with an
+   * error code in the status field.  The error code is determined by the type
+   * of the exception.
+   * 
+   * @param exception       the {@link Exception} to use to determine the error
+   *                        code
+   * @return                a string outer message
+   * @throws PoloException  on error building the message
+   */
+  public String getErrorXML(Exception exception)
+      throws PoloException {
+    return getOuterXML(null, STATUS_ERROR);
+  }
+
+  /**
+   * Translates a {@link PairingRequestMessage} to an XML string.
+   *
+   * @throws JSONException  on error generating the {@link String}.
+   */
+  String toXML(PairingRequestMessage message) throws JSONException {
+    JSONObject jsonObj = new JSONObject();
+    JSONObject pairingReq = new JSONObject();
+    jsonObj.put(MESSAGE_CONTAINER_NAME_PAIRING_REQUEST, pairingReq);
+    pairingReq.put(PAIRING_REQUEST_FIELD_SERVICE_NAME,
+        message.getServiceName());
+    if (message.hasClientName()) {
+      pairingReq.put(PAIRING_REQUEST_FIELD_CLIENT_NAME,
+          message.getServiceName());
+    }
+    pairingReq.put(PAIRING_REQUEST_FIELD_PROTOCOL_VERSION, 1);
+    return XML.toString(jsonObj);
+  }
+
+  /**
+   * Translates a {@link PairingRequestAckMessage} to an XML string.
+   *
+   * @throws JSONException  on error generating the {@link String}.
+   */
+  String toXML(PairingRequestAckMessage message) throws JSONException {
+    JSONObject jsonObj = new JSONObject();
+    JSONObject pairingReq = new JSONObject();
+    jsonObj.put(MESSAGE_CONTAINER_NAME_PAIRING_REQUEST_ACK, pairingReq);
+    if (message.hasServerName()) {
+      jsonObj.put(PAIRING_REQUEST_ACK_FIELD_SERVER_NAME,
+          message.getServerName());
+    }
+    pairingReq.put(PAIRING_REQUEST_FIELD_PROTOCOL_VERSION, 1);
+    return XML.toString(jsonObj);
+  }
+
+  /**
+   * Translates a {@link OptionsMessage} to an XML string.
+   * 
+   * @throws JSONException  on error generating the {@link String}. 
+   */
+ String toXML(OptionsMessage message) throws JSONException {
+    JSONObject jsonObj = new JSONObject();
+    JSONObject options = new JSONObject();
+
+    JSONObject inEncs = new JSONObject();
+    JSONArray inEncsArray = new JSONArray();
+    for (EncodingOption encoding : message.getInputEncodingSet()) {
+      inEncsArray.put(encodingToJson(encoding));
+    }
+    inEncs.put(ENCODING_SUBFIELD_ENCODING, inEncsArray);
+    options.put(OPTIONS_FIELD_INPUT_ENCODINGS, inEncs);
+
+    JSONObject outEncs = new JSONObject();
+    JSONArray outEncsArray = new JSONArray();
+    for (EncodingOption encoding : message.getOutputEncodingSet()) {
+      outEncsArray.put(encodingToJson(encoding));
+    }
+    outEncs.put(ENCODING_SUBFIELD_ENCODING, outEncsArray);
+    options.put(OPTIONS_FIELD_OUTPUT_ENCODINGS, outEncs);
+
+    options.put(OPTIONS_FIELD_PREFERRED_ROLE,
+        message.getProtocolRolePreference().ordinal());
+    jsonObj.put(MESSAGE_CONTAINER_NAME_OPTIONS, options);
+    return XML.toString(jsonObj);
+  }
+
+  /**
+   * Translates a {@link ConfigurationMessage} to an XML string.
+   * 
+   * @throws JSONException  on error generating the {@link String}. 
+   */
+  String toXML(ConfigurationMessage message) throws JSONException {
+    JSONObject jsonObj = new JSONObject();
+    JSONObject config = new JSONObject();
+    JSONObject encoding = encodingToJson(message.getEncoding());
+    config.put(ENCODING_SUBFIELD_ENCODING, encoding);
+    config.put(CONFIG_FIELD_CLIENT_ROLE, message.getClientRole().ordinal());
+    jsonObj.put(MESSAGE_CONTAINER_NAME_CONFIG, config);
+    return XML.toString(jsonObj);
+  }
+
+  /**
+   * Translates a {@link ConfigurationAckMessage} to an XML string.
+   */
+ String toXML(ConfigurationAckMessage message) {
+    return "";
+  }
+
+ /**
+  * Translates a {@link SecretMessage} to an XML string.
+  * 
+  * @throws JSONException  on error generating the {@link String}. 
+  */
+  String toXML(SecretMessage message) throws JSONException {
+    JSONObject jsonObj = new JSONObject();
+    JSONObject secret = new JSONObject();
+    String bytesStr = PoloUtil.bytesToHexString(message.getSecret());
+    secret.put(SECRET_FIELD_SECRET, bytesStr);
+    jsonObj.put(MESSAGE_CONTAINER_NAME_SECRET, secret);
+    return XML.toString(jsonObj);
+  }
+
+  /**
+   * Translates a {@link SecretAckMessage} to an XML string.
+   */
+  String toXML(SecretAckMessage message) {
+    return "";
+  }
+
+  /**
+   * Translates a {@link EncodingOption} to a {@link JSONObject}. 
+   * 
+   * @throws JSONException  on error generating the {@link JSONObject}
+   */
+  JSONObject encodingToJson(EncodingOption encoding) throws JSONException {
+    JSONObject result = new JSONObject();
+    int intType = encodingTypeToIntVal(encoding.getType());
+    result.put(ENCODING_FIELD_TYPE, intType);
+    result.put(ENCODING_FIELD_SYMBOL_LENGTH, encoding.getSymbolLength());
+    result.put(ENCODING_FIELD_MAX_LENGTH, encoding.getSymbolLength());
+    return result;
+  }
+
+  /**
+   * Converts an {@link EncodingType} to the numeric value used on the wire.
+   * <p>
+   * Note that in this implementation, the values used on the wire do not match
+   * those returned by {@link EncodingType#getAsInt()}, hence the extra method.
+   * 
+   * @param type  the {@link EncodingType}
+   * @return      an integer representation
+   */
+  private static int encodingTypeToIntVal(EncodingType type) {
+    switch (type) {
+      case ENCODING_ALPHANUMERIC:
+        return ENCODING_TYPE_ALPHANUMERIC;
+      case ENCODING_NUMERIC:
+        return ENCODING_TYPE_NUMERIC;
+      case ENCODING_HEXADECIMAL:
+        return ENCODING_TYPE_HEXADECIMAL;
+      case ENCODING_QRCODE:
+        return ENCODING_TYPE_QRCODE;
+      case ENCODING_UNKNOWN:
+      default:
+        return 0;
+    }
+  }
+  
+  /**
+   * Converts a numeric value used on the wire to the corresponding
+   * {@link EncodingType}.
+   * <p>
+   * Note that in this implementation, the values used on the wire do not match
+   * those returned by {@link EncodingType#getAsInt()}, hence the extra method.
+   * 
+   * @param intType  the value used on the wire
+   * @return         the corresponding {@link EncodingType}
+   */
+  private static EncodingType encodingTypeFromIntValue(int intType) {
+    EncodingType type = EncodingType.ENCODING_UNKNOWN;
+    switch (intType) {
+      case ENCODING_TYPE_ALPHANUMERIC:
+        type = EncodingType.ENCODING_ALPHANUMERIC;
+        break;
+      case ENCODING_TYPE_NUMERIC:
+        type = EncodingType.ENCODING_NUMERIC;
+        break;
+      case ENCODING_TYPE_HEXADECIMAL:
+        type = EncodingType.ENCODING_HEXADECIMAL;
+        break;
+      case ENCODING_TYPE_QRCODE:
+        type = EncodingType.ENCODING_QRCODE;
+        break;
+      default:
+        type = EncodingType.ENCODING_UNKNOWN;
+      break;
+    }
+    return type;
+  }
+
+}
diff --git a/java/src/com/google/polo/wire/xml/XmlMessageWrapper.java b/java/src/com/google/polo/wire/xml/XmlMessageWrapper.java
new file mode 100644
index 0000000..b11697c
--- /dev/null
+++ b/java/src/com/google/polo/wire/xml/XmlMessageWrapper.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2009 Google Inc.  All rights reserved.
+ *
+ * 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.google.polo.wire.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Representation of a message sent by the XML protocol.
+ */
+public class XmlMessageWrapper {
+
+    /**
+     * Number of bytes in the header for the "receiver id" field.
+     */
+    private static final int HEADER_FIELD_RECEIVER_ID_LENGTH = 32;
+
+    /**
+     * Number of bytes in the header for the "payload length" field.
+     */
+    private static final int HEADER_FIELD_PAYLOAD_LENGTH = 4;
+
+    /**
+     * Number of bytes in the header for the "protocol version" field.
+     */
+    private static final int HEADER_FIELD_PROTOCOL_VERSION_LENGTH = 2;
+
+    /**
+     * Number of bytes in the header reserved for future use.
+     */
+    private static final int HEADER_FIELD_PADDING_LENGTH = 25;
+
+    private static final int HEADER_SIZE = 64;
+
+    /**
+     * The id of the receiver.
+     */
+    private String mReceiverId;
+
+    /**
+     * Protocol version.
+     */
+    private int mProtocolVersion;
+
+    /**
+     * Creator ID
+     */
+    private byte mCreatorId;
+
+    /**
+     * XML message.
+     */
+    private byte[] mPayload;
+
+    public XmlMessageWrapper(String recieverId, int protocolVersion,
+            byte creatorId, byte[] payload) {
+        mReceiverId = recieverId;
+        mProtocolVersion = protocolVersion;
+        mCreatorId = creatorId;
+        mPayload = payload;
+    }
+
+    /**
+     * Writes the serialized form of this message to an {@link OutputStream}
+     *
+     * @param  outputStream  the destination output stream
+     * @throws IOException  if an error occurred during write
+     */
+    public void serializeToOutputStream(OutputStream outputStream)
+            throws IOException {
+        // Receiver ID
+        outputStream.write(stringToBytesPadded(mReceiverId,
+                HEADER_FIELD_RECEIVER_ID_LENGTH));
+
+        // Payload length
+        outputStream.write(intToBigEndianIntBytes(mPayload.length));
+
+        // Protocol version
+        outputStream.write(intToBigEndianShortBytes(mProtocolVersion));
+
+        // Creator ID
+        outputStream.write(mCreatorId);
+
+        // Padding
+        byte[] pad = new byte[HEADER_FIELD_PADDING_LENGTH];
+        outputStream.write(pad);
+
+        // Payload
+        outputStream.write(mPayload);
+    }
+
+    /**
+     * Returns the serialized form of this message in a newly-allocated byte
+     * array.
+     *
+     * @return  a new byte array
+     * @throws  IOException  if an error occurred during write
+     */
+    public byte[] serializeToByteArray() throws IOException {
+        int len = mPayload.length + HEADER_SIZE;
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(len);
+        serializeToOutputStream(outputStream);
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Construct a new {@link XmlMessageWrapper} from an InputStream.
+     *
+     * @param stream  the {@link InputStream} to read
+     * @return  a new {@link XmlMessageWrapper}
+     * @throws IOException  if an error occurs during read
+     */
+    public static XmlMessageWrapper fromInputStream(InputStream stream)
+            throws IOException {
+        String receiverId = new String(readBytes(stream,
+                HEADER_FIELD_RECEIVER_ID_LENGTH));
+        receiverId = receiverId.replace("\0", "");
+
+        byte[] payloadLenBytes = readBytes(stream, HEADER_FIELD_PAYLOAD_LENGTH);
+        long payloadLen = intBigEndianBytesToLong(payloadLenBytes);
+
+        int protocolVersion = shortBigEndianBytesToInt(readBytes(stream,
+                HEADER_FIELD_PROTOCOL_VERSION_LENGTH));
+
+        byte createorId = readBytes(stream, 1)[0];
+        byte[] padding = readBytes(stream, HEADER_FIELD_PADDING_LENGTH);
+        byte[] payload = readBytes(stream, (int)payloadLen);
+
+        return new XmlMessageWrapper(receiverId, protocolVersion, createorId,
+                payload);
+
+    }
+
+    /**
+     * Get creator id to indicate the program is playing on TV1 or TV2.
+     */
+    public byte getCreatorId() {
+        return mCreatorId;
+    }
+
+    public byte[] getPayload() {
+        return mPayload;
+    }
+
+    /**
+     * Get the message payload as an {@link InputStream}.
+     */
+    public InputStream getPayloadStream() {
+        return new ByteArrayInputStream(mPayload);
+    }
+
+    /**
+     * Converts a 4-byte array of bytes to an unsigned long value.
+     */
+    private static final long intBigEndianBytesToLong(byte[] input) {
+        assert (input.length == 4);
+        long ret = (long)(input[0]) & 0xff;
+        ret <<= 8;
+        ret |= (long)(input[1]) & 0xff;
+        ret <<= 8;
+        ret |= (long)(input[2]) & 0xff;
+        ret <<= 8;
+        ret |= (long)(input[3]) & 0xff;
+        return ret;
+    }
+
+    /**
+     * Converts an integer value to the big endian 4-byte representation.
+     */
+    public static final byte[] intToBigEndianIntBytes(int intVal) {
+        byte[] outBuf = new byte[4];
+        outBuf[0] = (byte)((intVal >> 24) & 0xff);
+        outBuf[1] = (byte)((intVal >> 16) & 0xff);
+        outBuf[2] = (byte)((intVal >> 8) & 0xff);
+        outBuf[3] = (byte)(intVal & 0xff);
+        return outBuf;
+    }
+
+    /**
+     * Converts a 2-byte array of bytes to an unsigned long value.
+     */
+    public static final int shortBigEndianBytesToInt(byte[] input) {
+        assert (input.length == 2);
+        int ret = (input[0]) & 0xff;
+        ret <<= 8;
+        ret |= input[1] & 0xff;
+        return ret;
+    }
+
+    /**
+     * Converts an integer value to the 2-byte short representation.  The two
+     * most significant bytes are ignored.
+     */
+    public static final byte[] intToBigEndianShortBytes(int intVal) {
+        byte[] outBuf = new byte[2];
+        outBuf[0] = (byte)((intVal >> 8) & 0xff);
+        outBuf[1] = (byte)(intVal & 0xff);
+        return outBuf;
+    }
+
+    /**
+     * Converts a string to a byte sequence of exactly byteLen bytes,
+     * padding with null characters if needed.
+     *
+     * @param byteLen  the size of the byte array to return
+     * @return  a byte array
+     */
+    public static final byte[] stringToBytesPadded(String string, int byteLen) {
+        byte[] outBuf = new byte[byteLen];
+        byte[] stringBytes = string.getBytes();
+
+        for (int i=0; i < outBuf.length; i++) {
+            if (i < stringBytes.length) {
+                outBuf[i] = stringBytes[i];
+            } else {
+                outBuf[i] = '\0';
+            }
+        }
+        return outBuf;
+    }
+
+    /**
+     * Reads an exact number of bytes from an input stream.
+     *
+     * @param stream  the stream to read
+     * @param numBytes  the number of bytes desired
+     * @return  a byte array of results
+     * @throws IOException  if an error occurred during read, or stream closed
+     */
+    private static byte[] readBytes(InputStream stream, int numBytes)
+            throws IOException {
+        byte buffer[] = new byte[numBytes];
+        int bytesRead = 0;
+
+        while (bytesRead < numBytes) {
+            int inc = stream.read(buffer, bytesRead, numBytes - bytesRead);
+            if (inc < 0) {
+                throw new IOException("Stream closed while reading.");
+            }
+            bytesRead += inc;
+        }
+        
+        return buffer;
+    }
+}
diff --git a/java/src/com/google/polo/wire/xml/XmlWireAdapter.java b/java/src/com/google/polo/wire/xml/XmlWireAdapter.java
new file mode 100644
index 0000000..7202d98
--- /dev/null
+++ b/java/src/com/google/polo/wire/xml/XmlWireAdapter.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2009 Google Inc.  All rights reserved.
+ *
+ * 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.google.polo.wire.xml;
+
+import com.google.polo.exception.PoloException;
+import com.google.polo.pairing.HexDump;
+import com.google.polo.pairing.PairingContext;
+import com.google.polo.pairing.message.PoloMessage;
+import com.google.polo.pairing.message.PoloMessage.PoloMessageType;
+import com.google.polo.wire.PoloWireInterface;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class XmlWireAdapter implements PoloWireInterface {
+  
+    /**
+     * Enables extra verbose debug logging.
+     */
+    private static final boolean DEBUG_VERBOSE = false;
+    
+    private static final int STATUS_OK = 1;
+
+    /**
+     * The output coming from the peer.
+     */
+    private final InputStream mInputStream;
+    
+    /**
+     * The input going to the peer.
+     */
+    private final OutputStream mOutputStream;
+    
+    private final XmlMessageBuilder mBuilder;
+
+    /**
+     * Constructor.
+     * 
+     * @param input  the {@link InputStream} from the peer
+     * @param output  the {@link OutputStream} to the peer
+     */
+    public XmlWireAdapter(InputStream input, OutputStream output) {
+      mInputStream = input;
+      mOutputStream = output;
+      mBuilder = new XmlMessageBuilder();
+    }
+    
+    /**
+     * Generates a new instance from a {@link PairingContext}.
+     * @param context  the {@link PairingContext}
+     * @return  the new instance
+     */
+    public static XmlWireAdapter fromContext(PairingContext context) {
+      return new XmlWireAdapter(context.getPeerInputStream(),
+          context.getPeerOutputStream());
+    }
+    
+    public PoloMessage getNextMessage() throws IOException, PoloException {
+        XmlMessageWrapper outerMessage =
+                XmlMessageWrapper.fromInputStream(mInputStream);
+        if (DEBUG_VERBOSE) {
+            debug(">>> Incoming Message:");
+            debug(HexDump.dumpHexString(outerMessage.serializeToByteArray()));
+        }
+        
+        String outerXML = new String(outerMessage.getPayload());
+        return mBuilder.outerXMLToPoloMessage(outerXML);
+    }
+    
+    public PoloMessage getNextMessage(PoloMessageType type) throws IOException, PoloException {
+        PoloMessage message = getNextMessage();
+        if (message.getType() != type) {
+            throw new PoloException("Wrong message type (wanted " + type +
+                ", got " + message.getType() + ")");
+          }
+          return message;
+    }
+
+    public void sendErrorMessage(Exception exception) throws IOException {
+        String errorXml;
+        try {
+          errorXml = mBuilder.getErrorXML(exception);
+        } catch (PoloException e) {
+          // just ignore it; nothing we can do
+          return;
+        }
+        byte[] outBytes = errorXml.getBytes();
+        XmlMessageWrapper message = new XmlMessageWrapper("client", 1,
+            (byte) 0, outBytes);
+        writeXML(message);
+    }
+    
+    public void sendMessage(PoloMessage poloMessage) throws IOException {
+        String outString = mBuilder.getOuterXML(poloMessage, STATUS_OK);
+        
+        // NOTE(mikey): A particular parser is very sensitive to newline
+        // placement. Strip all newlines, then add them to separate adjacent
+        // XML entities.
+        outString = outString.replace("\n", "");
+        outString = outString.replace("><", ">\n<");
+        
+        byte[] outBytes = outString.getBytes();
+        XmlMessageWrapper message = new XmlMessageWrapper("client", 1,
+            (byte) 0, outBytes);
+        writeXML(message);
+    }
+    
+    /**
+     * Writes a {@link XmlMessageWrapper} to the output stream as a
+     * {@link String}.
+     * 
+     * @param message       the message to write
+     * @throws IOException  on error generating the serialized message
+     */
+    private void writeXML(XmlMessageWrapper message) throws IOException {
+      if (DEBUG_VERBOSE) {
+          debug("<<< Outgoing Message:");
+          debug(HexDump.dumpHexString(message.serializeToByteArray()));
+      }
+      mOutputStream.write(message.serializeToByteArray());
+    }
+    
+    private void debug(String message) {
+      System.out.println(message);
+    }
+ 
+}