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 XmlMessageWrapper(String recieverId, int protocolVersion, byte creatorId, byte[] payload)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 */ serializeToOutputStream(OutputStream outputStream)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 */ serializeToByteArray()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 */ fromInputStream(InputStream stream)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 */ getCreatorId()154 public byte getCreatorId() { 155 return mCreatorId; 156 } 157 getPayload()158 public byte[] getPayload() { 159 return mPayload; 160 } 161 162 /** 163 * Get the message payload as an {@link InputStream}. 164 */ getPayloadStream()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 */ intBigEndianBytesToLong(byte[] input)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 */ intToBigEndianIntBytes(int intVal)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 */ shortBigEndianBytesToInt(byte[] input)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 */ intToBigEndianShortBytes(int intVal)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 */ stringToBytesPadded(String string, int byteLen)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 */ readBytes(InputStream stream, int numBytes)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