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