io.protostuff.KvpInput Maven / Gradle / Ivy
Show all versions of protostuff-kvp Show documentation
//========================================================================
//Copyright 2007-2010 David Yu [email protected]
//------------------------------------------------------------------------
//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 io.protostuff;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import io.protostuff.StringSerializer.STRING;
import static io.protostuff.NumberParser.parseInt;
import static io.protostuff.NumberParser.parseLong;
/**
* An input for deserializing kvp-encoded messages. A kvp encoding is a binary encoding w/c contains a key-value
* sequence. On the wire, a serialized field (key-value) would look like: [key-len][key][value-len][value]
*
* The keys and values are length-delimited (uint16 little endian).
*
* Note that this encoding does not support nested messages. This encoding is mostly useful for headers w/c contain
* information about the content it carries (see http://projects.unbit.it/uwsgi/wiki/uwsgiProtocol).
*
* @author David Yu
* @created Nov 30, 2010
*/
public final class KvpInput implements Input
{
static final int DEFAULT_BUFFER_SIZE =
Integer.getInteger("kvpinput.default_buffer_size", 1024);
static final int MAX_VALUE_SIZE =
Integer.getInteger("kvpinput.max_value_size", 8192);
final InputStream in;
final byte[] buffer;
final boolean numeric;
int offset, limit;
public KvpInput(InputStream in, boolean numeric)
{
this(in, new byte[DEFAULT_BUFFER_SIZE], numeric);
}
public KvpInput(InputStream in, byte[] buffer, boolean numeric)
{
this(in, buffer, 0, 0, numeric);
}
public KvpInput(InputStream in, byte[] buffer, int offset, int limit,
boolean numeric)
{
this.in = in;
this.buffer = buffer;
this.offset = offset;
this.limit = limit;
this.numeric = numeric;
}
/**
* Returns true if there are {@code minimum} bytes available for reading.
*
* The caller is responsible that the arg {@code minimum} is not larger than the buffer size.
*/
private boolean readable(final int minimum) throws IOException
{
int existing = limit - offset;
final int available = buffer.length - limit;
if (minimum > available + existing)
{
// move to front.
System.arraycopy(buffer, offset, buffer, 0, existing);
offset = 0;
limit = existing;
int read;
do
{
read = in.read(buffer, limit, buffer.length - limit);
if (read == -1)
return false;
limit += read;
existing += read;
} while (existing < minimum);
return true;
}
int read;
do
{
read = in.read(buffer, limit, buffer.length - limit);
if (read == -1)
return false;
limit += read;
existing += read;
} while (existing < minimum);
return true;
}
private byte[] fill(final byte[] data, int dataOffset, final int len)
throws IOException
{
final int existing = limit - offset;
int toRead = len - existing, read = 0;
if (existing != 0)
{
// copy existing
System.arraycopy(buffer, offset, data, dataOffset, existing);
dataOffset += existing;
}
// reset
offset = 0;
limit = 0;
do
{
read = in.read(data, dataOffset, toRead);
if (read == -1)
throw new ProtostuffException("Truncated message.");
dataOffset += read;
toRead -= read;
} while (toRead > 0);
return data;
}
@Override
public int readFieldNumber(Schema schema) throws IOException
{
if (offset + 2 > limit && !readable(2))
{
if (offset != limit)
throw new ProtostuffException("Truncated message.");
return 0;
}
final int size = buffer[offset++] | (buffer[offset++] << 8);
if (offset + size > limit && !readable(size))
throw new ProtostuffException("Truncated message.");
final int number = numeric ? parseInt(buffer, offset, size, 10, true) :
schema.getFieldNumber(STRING.deser(buffer, offset, size));
offset += size;
if (number == 0)
{
// skip unknown fields.
handleUnknownField(number, schema);
return readFieldNumber(schema);
}
return number;
}
@Override
public void handleUnknownField(int fieldNumber, Schema schema) throws IOException
{
if (offset + 2 > limit && !readable(2))
throw new ProtostuffException("Truncated message.");
final int size = buffer[offset++] | (buffer[offset++] << 8);
if (offset + size > limit)
{
int toRead = size - (limit - offset), read = 0;
// read until toRead
do
{
read = in.read(buffer, 0, buffer.length);
if (read == -1)
throw new ProtostuffException("Truncated message.");
toRead -= read;
} while (toRead > 0);
offset = read - (-toRead);
limit = read;
return;
}
offset += size;
}
@Override
public T mergeObject(T value, Schema schema) throws IOException
{
throw new ProtostuffException("Unsupported.");
}
@Override
public boolean readBool() throws IOException
{
if (offset + 3 > limit && !readable(3))
throw new ProtostuffException("Truncated message.");
final int size = buffer[offset++] | (buffer[offset++] << 8);
if (size != 1)
throw new ProtostuffException("Invalid kvp boolean");
return buffer[offset++] != 0x30;
}
@Override
public ByteString readBytes() throws IOException
{
return ByteString.wrap(readByteArray());
}
@Override
public void readBytes(final ByteBuffer bb) throws IOException
{
if (offset + 2 > limit && !readable(2))
throw new ProtostuffException("Truncated message.");
final int size = buffer[offset++] | (buffer[offset++] << 8);
if (size == 0)
{
bb.put(ByteString.EMPTY_BYTE_ARRAY);
return;
}
if (size > MAX_VALUE_SIZE)
throw new ProtostuffException("Exceeded kvp max value size.");
if (offset + size > limit)
{
fill(bb.array(), 0, size);
}
bb.put(buffer, offset, size);
offset += size;
}
@Override
public double readDouble() throws IOException
{
// TODO efficiency
return Double.parseDouble(readString());
}
@Override
public float readFloat() throws IOException
{
// TODO efficiency
return Float.parseFloat(readString());
}
@Override
public int readUInt32() throws IOException
{
return readInt32();
}
@Override
public long readUInt64() throws IOException
{
return readInt64();
}
@Override
public int readInt32() throws IOException
{
if (offset + 2 > limit && !readable(2))
throw new ProtostuffException("Truncated message.");
final int size = buffer[offset++] | (buffer[offset++] << 8);
if (size == 0)
return 0;
if (offset + size > limit && !readable(size))
throw new ProtostuffException("Truncated message.");
final int number = parseInt(buffer, offset, size, 10);
offset += size;
return number;
}
@Override
public long readInt64() throws IOException
{
if (offset + 2 > limit && !readable(2))
throw new ProtostuffException("Truncated message.");
final int size = buffer[offset++] | (buffer[offset++] << 8);
if (size == 0)
return 0;
if (offset + size > limit && !readable(size))
throw new ProtostuffException("Truncated message.");
final long number = parseLong(buffer, offset, size, 10);
offset += size;
return number;
}
@Override
public int readEnum() throws IOException
{
return readInt32();
}
@Override
public int readFixed32() throws IOException
{
return readUInt32();
}
@Override
public long readFixed64() throws IOException
{
return readUInt64();
}
@Override
public int readSFixed32() throws IOException
{
return readInt32();
}
@Override
public long readSFixed64() throws IOException
{
return readInt64();
}
@Override
public int readSInt32() throws IOException
{
return readInt32();
}
@Override
public long readSInt64() throws IOException
{
return readInt64();
}
@Override
public byte[] readByteArray() throws IOException
{
if (offset + 2 > limit && !readable(2))
throw new ProtostuffException("Truncated message.");
final int size = buffer[offset++] | (buffer[offset++] << 8);
if (size == 0)
return ByteString.EMPTY_BYTE_ARRAY;
if (size > MAX_VALUE_SIZE)
throw new ProtostuffException("Exceeded kvp max value size.");
if (offset + size > limit)
return fill(new byte[size], 0, size);
final byte[] data = new byte[size];
System.arraycopy(buffer, offset, data, 0, size);
offset += size;
return data;
}
@Override
public String readString() throws IOException
{
if (offset + 2 > limit && !readable(2))
throw new ProtostuffException("Truncated message.");
final int size = buffer[offset++] | (buffer[offset++] << 8);
if (size == 0)
return ByteString.EMPTY_STRING;
if (size > MAX_VALUE_SIZE)
throw new ProtostuffException("Exceeded kvp max value size.");
if (offset + size > limit)
{
if (size > buffer.length)
{
// need to create a copy.
return STRING.deser(fill(new byte[size], 0, size));
}
// it can fit in the buffer.
if (!readable(size))
throw new ProtostuffException("Truncated Message.");
// final String str = STRING.deser(buffer, offset, size);
// offset += size;
// return str;
}
final String str = STRING.deser(buffer, offset, size);
offset += size;
return str;
}
@Override
public void transferByteRangeTo(Output output, boolean utf8String, int fieldNumber,
boolean repeated) throws IOException
{
throw new UnsupportedOperationException();
}
/**
* Reads a byte array/ByteBuffer value.
*/
@Override
public ByteBuffer readByteBuffer() throws IOException
{
return ByteBuffer.wrap(readByteArray());
}
}