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