/* * 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; } }