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);
+ }
+
+}