package com.openxc.messages.streamers; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import org.apache.commons.io.IOUtils; import android.util.Log; import com.google.common.io.ByteStreams; import com.google.protobuf.CodedInputStream; import com.google.protobuf.MessageLite; import com.openxc.messages.SerializationException; import com.openxc.messages.UnrecognizedMessageTypeException; import com.openxc.messages.VehicleMessage; import com.openxc.messages.formatters.BinaryFormatter; /** * A class to deserialize and serialize binary-formatted vehicle messages from * byte streams. * * The BinaryStreamer wraps the BinaryFormatter and handles messages delimiting. * It uses standard message length delimiters as recommended by the protobuf * docs and specific in the OpenXC message format. * * Unlike the BinaryFormatter, the BinaryStreamer is not stateless. It maintains * an internal buffer of bytes so that if partial messages is received it can * eventually receive an parse the entire thing. */ public class BinaryStreamer extends VehicleMessageStreamer { private static String TAG = "BinaryStreamer"; private ByteArrayOutputStream mBuffer = new ByteArrayOutputStream(); @Override public VehicleMessage parseNextMessage() { // TODO we could move this to a separate thread and use a // piped input stream, where it would block on the // bytestream until more data was available - but that might // be messy rather than this approach, which is just // inefficient InputStream input = new ByteArrayInputStream(mBuffer.toByteArray()); VehicleMessage message = null; int bytesRemaining = mBuffer.size(); while(message == null) { try { int firstByte = input.read(); bytesRemaining -= 1; if (firstByte == -1) { return null; } int size = CodedInputStream.readRawVarint32(firstByte, input); if(size > 0 && bytesRemaining >= size) { message = BinaryFormatter.deserialize( ByteStreams.limit(input, size)); } else { break; } } catch(IOException e) { Log.e(TAG, "Unexpected errror copying buffers"); } catch(UnrecognizedMessageTypeException e) { Log.w(TAG, "Deserialized protobuf had was unrecognized message type", e); } } if(message != null) { mBuffer = new ByteArrayOutputStream(); try { IOUtils.copy(input, mBuffer); } catch(IOException e) { Log.e(TAG, "Unexpected error copying buffers"); } } return message; } @Override public byte[] serializeForStream(VehicleMessage message) throws SerializationException { ByteArrayOutputStream stream = new ByteArrayOutputStream(); try { MessageLite preSerialized = BinaryFormatter.preSerialize(message); preSerialized.writeDelimitedTo(stream); } catch(IOException e) { throw new SerializationException( "Unable to serialize message to stream", e); } return stream.toByteArray(); } @Override public void receive(byte[] bytes, int length) { super.receive(bytes, length); mBuffer.write(bytes, 0, length); } }