Home | History | Annotate | Download | only in xml
      1 /*
      2  * Copyright (C) 2009 Google Inc.  All rights reserved.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.polo.wire.xml;
     18 
     19 import java.io.ByteArrayInputStream;
     20 import java.io.ByteArrayOutputStream;
     21 import java.io.IOException;
     22 import java.io.InputStream;
     23 import java.io.OutputStream;
     24 
     25 /**
     26  * Representation of a message sent by the XML protocol.
     27  */
     28 public class XmlMessageWrapper {
     29 
     30     /**
     31      * Number of bytes in the header for the "receiver id" field.
     32      */
     33     private static final int HEADER_FIELD_RECEIVER_ID_LENGTH = 32;
     34 
     35     /**
     36      * Number of bytes in the header for the "payload length" field.
     37      */
     38     private static final int HEADER_FIELD_PAYLOAD_LENGTH = 4;
     39 
     40     /**
     41      * Number of bytes in the header for the "protocol version" field.
     42      */
     43     private static final int HEADER_FIELD_PROTOCOL_VERSION_LENGTH = 2;
     44 
     45     /**
     46      * Number of bytes in the header reserved for future use.
     47      */
     48     private static final int HEADER_FIELD_PADDING_LENGTH = 25;
     49 
     50     private static final int HEADER_SIZE = 64;
     51 
     52     /**
     53      * The id of the receiver.
     54      */
     55     private String mReceiverId;
     56 
     57     /**
     58      * Protocol version.
     59      */
     60     private int mProtocolVersion;
     61 
     62     /**
     63      * Creator ID
     64      */
     65     private byte mCreatorId;
     66 
     67     /**
     68      * XML message.
     69      */
     70     private byte[] mPayload;
     71 
     72     public XmlMessageWrapper(String recieverId, int protocolVersion,
     73             byte creatorId, byte[] payload) {
     74         mReceiverId = recieverId;
     75         mProtocolVersion = protocolVersion;
     76         mCreatorId = creatorId;
     77         mPayload = payload;
     78     }
     79 
     80     /**
     81      * Writes the serialized form of this message to an {@link OutputStream}
     82      *
     83      * @param  outputStream  the destination output stream
     84      * @throws IOException  if an error occurred during write
     85      */
     86     public void serializeToOutputStream(OutputStream outputStream)
     87             throws IOException {
     88         // Receiver ID
     89         outputStream.write(stringToBytesPadded(mReceiverId,
     90                 HEADER_FIELD_RECEIVER_ID_LENGTH));
     91 
     92         // Payload length
     93         outputStream.write(intToBigEndianIntBytes(mPayload.length));
     94 
     95         // Protocol version
     96         outputStream.write(intToBigEndianShortBytes(mProtocolVersion));
     97 
     98         // Creator ID
     99         outputStream.write(mCreatorId);
    100 
    101         // Padding
    102         byte[] pad = new byte[HEADER_FIELD_PADDING_LENGTH];
    103         outputStream.write(pad);
    104 
    105         // Payload
    106         outputStream.write(mPayload);
    107     }
    108 
    109     /**
    110      * Returns the serialized form of this message in a newly-allocated byte
    111      * array.
    112      *
    113      * @return  a new byte array
    114      * @throws  IOException  if an error occurred during write
    115      */
    116     public byte[] serializeToByteArray() throws IOException {
    117         int len = mPayload.length + HEADER_SIZE;
    118         ByteArrayOutputStream outputStream = new ByteArrayOutputStream(len);
    119         serializeToOutputStream(outputStream);
    120         return outputStream.toByteArray();
    121     }
    122 
    123     /**
    124      * Construct a new {@link XmlMessageWrapper} from an InputStream.
    125      *
    126      * @param stream  the {@link InputStream} to read
    127      * @return  a new {@link XmlMessageWrapper}
    128      * @throws IOException  if an error occurs during read
    129      */
    130     public static XmlMessageWrapper fromInputStream(InputStream stream)
    131             throws IOException {
    132         String receiverId = new String(readBytes(stream,
    133                 HEADER_FIELD_RECEIVER_ID_LENGTH));
    134         receiverId = receiverId.replace("\0", "");
    135 
    136         byte[] payloadLenBytes = readBytes(stream, HEADER_FIELD_PAYLOAD_LENGTH);
    137         long payloadLen = intBigEndianBytesToLong(payloadLenBytes);
    138 
    139         int protocolVersion = shortBigEndianBytesToInt(readBytes(stream,
    140                 HEADER_FIELD_PROTOCOL_VERSION_LENGTH));
    141 
    142         byte createorId = readBytes(stream, 1)[0];
    143         byte[] padding = readBytes(stream, HEADER_FIELD_PADDING_LENGTH);
    144         byte[] payload = readBytes(stream, (int)payloadLen);
    145 
    146         return new XmlMessageWrapper(receiverId, protocolVersion, createorId,
    147                 payload);
    148 
    149     }
    150 
    151     /**
    152      * Get creator id to indicate the program is playing on TV1 or TV2.
    153      */
    154     public byte getCreatorId() {
    155         return mCreatorId;
    156     }
    157 
    158     public byte[] getPayload() {
    159         return mPayload;
    160     }
    161 
    162     /**
    163      * Get the message payload as an {@link InputStream}.
    164      */
    165     public InputStream getPayloadStream() {
    166         return new ByteArrayInputStream(mPayload);
    167     }
    168 
    169     /**
    170      * Converts a 4-byte array of bytes to an unsigned long value.
    171      */
    172     private static final long intBigEndianBytesToLong(byte[] input) {
    173         assert (input.length == 4);
    174         long ret = (long)(input[0]) & 0xff;
    175         ret <<= 8;
    176         ret |= (long)(input[1]) & 0xff;
    177         ret <<= 8;
    178         ret |= (long)(input[2]) & 0xff;
    179         ret <<= 8;
    180         ret |= (long)(input[3]) & 0xff;
    181         return ret;
    182     }
    183 
    184     /**
    185      * Converts an integer value to the big endian 4-byte representation.
    186      */
    187     public static final byte[] intToBigEndianIntBytes(int intVal) {
    188         byte[] outBuf = new byte[4];
    189         outBuf[0] = (byte)((intVal >> 24) & 0xff);
    190         outBuf[1] = (byte)((intVal >> 16) & 0xff);
    191         outBuf[2] = (byte)((intVal >> 8) & 0xff);
    192         outBuf[3] = (byte)(intVal & 0xff);
    193         return outBuf;
    194     }
    195 
    196     /**
    197      * Converts a 2-byte array of bytes to an unsigned long value.
    198      */
    199     public static final int shortBigEndianBytesToInt(byte[] input) {
    200         assert (input.length == 2);
    201         int ret = (input[0]) & 0xff;
    202         ret <<= 8;
    203         ret |= input[1] & 0xff;
    204         return ret;
    205     }
    206 
    207     /**
    208      * Converts an integer value to the 2-byte short representation.  The two
    209      * most significant bytes are ignored.
    210      */
    211     public static final byte[] intToBigEndianShortBytes(int intVal) {
    212         byte[] outBuf = new byte[2];
    213         outBuf[0] = (byte)((intVal >> 8) & 0xff);
    214         outBuf[1] = (byte)(intVal & 0xff);
    215         return outBuf;
    216     }
    217 
    218     /**
    219      * Converts a string to a byte sequence of exactly byteLen bytes,
    220      * padding with null characters if needed.
    221      *
    222      * @param byteLen  the size of the byte array to return
    223      * @return  a byte array
    224      */
    225     public static final byte[] stringToBytesPadded(String string, int byteLen) {
    226         byte[] outBuf = new byte[byteLen];
    227         byte[] stringBytes = string.getBytes();
    228 
    229         for (int i=0; i < outBuf.length; i++) {
    230             if (i < stringBytes.length) {
    231                 outBuf[i] = stringBytes[i];
    232             } else {
    233                 outBuf[i] = '\0';
    234             }
    235         }
    236         return outBuf;
    237     }
    238 
    239     /**
    240      * Reads an exact number of bytes from an input stream.
    241      *
    242      * @param stream  the stream to read
    243      * @param numBytes  the number of bytes desired
    244      * @return  a byte array of results
    245      * @throws IOException  if an error occurred during read, or stream closed
    246      */
    247     private static byte[] readBytes(InputStream stream, int numBytes)
    248             throws IOException {
    249         byte buffer[] = new byte[numBytes];
    250         int bytesRead = 0;
    251 
    252         while (bytesRead < numBytes) {
    253             int inc = stream.read(buffer, bytesRead, numBytes - bytesRead);
    254             if (inc < 0) {
    255                 throw new IOException("Stream closed while reading.");
    256             }
    257             bytesRead += inc;
    258         }
    259 
    260         return buffer;
    261     }
    262 }
    263