diff options
author | Wink Saville <wink@google.com> | 2010-05-29 13:13:38 -0700 |
---|---|---|
committer | Wink Saville <wink@google.com> | 2010-05-29 13:13:38 -0700 |
commit | e2d542951c059563a3b7f74c257dac4f222d9dc5 (patch) | |
tree | 2025d0dd0c8963081fef2e340a2789ae6e24f652 /java/src/main | |
parent | d0332953cda33fb4f8e24ebff9c49159b69c43d6 (diff) | |
download | external_protobuf-e2d542951c059563a3b7f74c257dac4f222d9dc5.zip external_protobuf-e2d542951c059563a3b7f74c257dac4f222d9dc5.tar.gz external_protobuf-e2d542951c059563a3b7f74c257dac4f222d9dc5.tar.bz2 |
Add support for Java micro protobuf's to protobuf-2.3.0.
See README.android for additional information.
Change-Id: I6693e405c0d651eacacd3227a876129865dd0d3c
Diffstat (limited to 'java/src/main')
7 files changed, 2410 insertions, 0 deletions
diff --git a/java/src/main/java/com/google/protobuf/micro/ByteStringMicro.java b/java/src/main/java/com/google/protobuf/micro/ByteStringMicro.java new file mode 100644 index 0000000..6e87dc9 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/micro/ByteStringMicro.java @@ -0,0 +1,227 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf.micro; + +import java.io.UnsupportedEncodingException; + +/** + * Immutable array of bytes. + * + * @author crazybob@google.com Bob Lee + * @author kenton@google.com Kenton Varda + */ +public final class ByteStringMicro { + private final byte[] bytes; + + private ByteStringMicro(final byte[] bytes) { + this.bytes = bytes; + } + + /** + * Gets the byte at the given index. + * + * @throws ArrayIndexOutOfBoundsException {@code index} is < 0 or >= size + */ + public byte byteAt(final int index) { + return bytes[index]; + } + + /** + * Gets the number of bytes. + */ + public int size() { + return bytes.length; + } + + /** + * Returns {@code true} if the size is {@code 0}, {@code false} otherwise. + */ + public boolean isEmpty() { + return bytes.length == 0; + } + + // ================================================================= + // byte[] -> ByteStringMicro + + /** + * Empty ByteStringMicro. + */ + public static final ByteStringMicro EMPTY = new ByteStringMicro(new byte[0]); + + /** + * Copies the given bytes into a {@code ByteStringMicro}. + */ + public static ByteStringMicro copyFrom(final byte[] bytes, final int offset, + final int size) { + final byte[] copy = new byte[size]; + System.arraycopy(bytes, offset, copy, 0, size); + return new ByteStringMicro(copy); + } + + /** + * Copies the given bytes into a {@code ByteStringMicro}. + */ + public static ByteStringMicro copyFrom(final byte[] bytes) { + return copyFrom(bytes, 0, bytes.length); + } + + /** + * Encodes {@code text} into a sequence of bytes using the named charset + * and returns the result as a {@code ByteStringMicro}. + */ + public static ByteStringMicro copyFrom(final String text, final String charsetName) + throws UnsupportedEncodingException { + return new ByteStringMicro(text.getBytes(charsetName)); + } + + /** + * Encodes {@code text} into a sequence of UTF-8 bytes and returns the + * result as a {@code ByteStringMicro}. + */ + public static ByteStringMicro copyFromUtf8(final String text) { + try { + return new ByteStringMicro(text.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 not supported?"); + } + } + + // ================================================================= + // ByteStringMicro -> byte[] + + /** + * Copies bytes into a buffer at the given offset. + * + * @param target buffer to copy into + * @param offset in the target buffer + */ + public void copyTo(final byte[] target, final int offset) { + System.arraycopy(bytes, 0, target, offset, bytes.length); + } + + /** + * Copies bytes into a buffer. + * + * @param target buffer to copy into + * @param sourceOffset offset within these bytes + * @param targetOffset offset within the target buffer + * @param size number of bytes to copy + */ + public void copyTo(final byte[] target, final int sourceOffset, + final int targetOffset, + final int size) { + System.arraycopy(bytes, sourceOffset, target, targetOffset, size); + } + + /** + * Copies bytes to a {@code byte[]}. + */ + public byte[] toByteArray() { + final int size = bytes.length; + final byte[] copy = new byte[size]; + System.arraycopy(bytes, 0, copy, 0, size); + return copy; + } + + /** + * Constructs a new {@code String} by decoding the bytes using the + * specified charset. + */ + public String toString(final String charsetName) + throws UnsupportedEncodingException { + return new String(bytes, charsetName); + } + + /** + * Constructs a new {@code String} by decoding the bytes as UTF-8. + */ + public String toStringUtf8() { + try { + return new String(bytes, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 not supported?"); + } + } + + // ================================================================= + // equals() and hashCode() + + //@Override for compatibility with Java 1.3 code we can't use annotations + public boolean equals(final Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof ByteStringMicro)) { + return false; + } + + final ByteStringMicro other = (ByteStringMicro) o; + final int size = bytes.length; + if (size != other.bytes.length) { + return false; + } + + final byte[] thisBytes = bytes; + final byte[] otherBytes = other.bytes; + for (int i = 0; i < size; i++) { + if (thisBytes[i] != otherBytes[i]) { + return false; + } + } + + return true; + } + + private volatile int hash = 0; + + //@Override for compatibility with Java 1.3 code we can't use annotations + public int hashCode() { + int h = hash; + + if (h == 0) { + final byte[] thisBytes = bytes; + final int size = bytes.length; + + h = size; + for (int i = 0; i < size; i++) { + h = h * 31 + thisBytes[i]; + } + if (h == 0) { + h = 1; + } + + hash = h; + } + + return h; + } +} diff --git a/java/src/main/java/com/google/protobuf/micro/CodedInputStreamMicro.java b/java/src/main/java/com/google/protobuf/micro/CodedInputStreamMicro.java new file mode 100644 index 0000000..0791b8f --- /dev/null +++ b/java/src/main/java/com/google/protobuf/micro/CodedInputStreamMicro.java @@ -0,0 +1,804 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf.micro; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Reads and decodes protocol message fields. + * + * This class contains two kinds of methods: methods that read specific + * protocol message constructs and field types (e.g. {@link #readTag()} and + * {@link #readInt32()}) and methods that read low-level values (e.g. + * {@link #readRawVarint32()} and {@link #readRawBytes}). If you are reading + * encoded protocol messages, you should use the former methods, but if you are + * reading some other format of your own design, use the latter. + * + * @author kenton@google.com Kenton Varda + */ +public final class CodedInputStreamMicro { + /** + * Create a new CodedInputStream wrapping the given InputStream. + */ + public static CodedInputStreamMicro newInstance(final InputStream input) { + return new CodedInputStreamMicro(input); + } + + /** + * Create a new CodedInputStream wrapping the given byte array. + */ + public static CodedInputStreamMicro newInstance(final byte[] buf) { + return newInstance(buf, 0, buf.length); + } + + /** + * Create a new CodedInputStream wrapping the given byte array slice. + */ + public static CodedInputStreamMicro newInstance(final byte[] buf, final int off, + final int len) { + return new CodedInputStreamMicro(buf, off, len); + } + + // ----------------------------------------------------------------- + + /** + * Attempt to read a field tag, returning zero if we have reached EOF. + * Protocol message parsers use this to read tags, since a protocol message + * may legally end wherever a tag occurs, and zero is not a valid tag number. + */ + public int readTag() throws IOException { + if (isAtEnd()) { + lastTag = 0; + return 0; + } + + lastTag = readRawVarint32(); + if (lastTag == 0) { + // If we actually read zero, that's not a valid tag. + throw InvalidProtocolBufferMicroException.invalidTag(); + } + return lastTag; + } + + /** + * Verifies that the last call to readTag() returned the given tag value. + * This is used to verify that a nested group ended with the correct + * end tag. + * + * @throws InvalidProtocolBufferMicroException {@code value} does not match the + * last tag. + */ + public void checkLastTagWas(final int value) + throws InvalidProtocolBufferMicroException { + if (lastTag != value) { + throw InvalidProtocolBufferMicroException.invalidEndTag(); + } + } + + /** + * Reads and discards a single field, given its tag value. + * + * @return {@code false} if the tag is an endgroup tag, in which case + * nothing is skipped. Otherwise, returns {@code true}. + */ + public boolean skipField(final int tag) throws IOException { + switch (WireFormatMicro.getTagWireType(tag)) { + case WireFormatMicro.WIRETYPE_VARINT: + readInt32(); + return true; + case WireFormatMicro.WIRETYPE_FIXED64: + readRawLittleEndian64(); + return true; + case WireFormatMicro.WIRETYPE_LENGTH_DELIMITED: + skipRawBytes(readRawVarint32()); + return true; + case WireFormatMicro.WIRETYPE_START_GROUP: + skipMessage(); + checkLastTagWas( + WireFormatMicro.makeTag(WireFormatMicro.getTagFieldNumber(tag), + WireFormatMicro.WIRETYPE_END_GROUP)); + return true; + case WireFormatMicro.WIRETYPE_END_GROUP: + return false; + case WireFormatMicro.WIRETYPE_FIXED32: + readRawLittleEndian32(); + return true; + default: + throw InvalidProtocolBufferMicroException.invalidWireType(); + } + } + + /** + * Reads and discards an entire message. This will read either until EOF + * or until an endgroup tag, whichever comes first. + */ + public void skipMessage() throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag)) { + return; + } + } + } + + // ----------------------------------------------------------------- + + /** Read a {@code double} field value from the stream. */ + public double readDouble() throws IOException { + return Double.longBitsToDouble(readRawLittleEndian64()); + } + + /** Read a {@code float} field value from the stream. */ + public float readFloat() throws IOException { + return Float.intBitsToFloat(readRawLittleEndian32()); + } + + /** Read a {@code uint64} field value from the stream. */ + public long readUInt64() throws IOException { + return readRawVarint64(); + } + + /** Read an {@code int64} field value from the stream. */ + public long readInt64() throws IOException { + return readRawVarint64(); + } + + /** Read an {@code int32} field value from the stream. */ + public int readInt32() throws IOException { + return readRawVarint32(); + } + + /** Read a {@code fixed64} field value from the stream. */ + public long readFixed64() throws IOException { + return readRawLittleEndian64(); + } + + /** Read a {@code fixed32} field value from the stream. */ + public int readFixed32() throws IOException { + return readRawLittleEndian32(); + } + + /** Read a {@code bool} field value from the stream. */ + public boolean readBool() throws IOException { + return readRawVarint32() != 0; + } + + /** Read a {@code string} field value from the stream. */ + public String readString() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final String result = new String(buffer, bufferPos, size, "UTF-8"); + bufferPos += size; + return result; + } else { + // Slow path: Build a byte array first then copy it. + return new String(readRawBytes(size), "UTF-8"); + } + } + + /** Read a {@code group} field value from the stream. */ + public void readGroup(final MessageMicro msg, final int fieldNumber) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferMicroException.recursionLimitExceeded(); + } + ++recursionDepth; + msg.mergeFrom(this); + checkLastTagWas( + WireFormatMicro.makeTag(fieldNumber, WireFormatMicro.WIRETYPE_END_GROUP)); + --recursionDepth; + } + + public void readMessage(final MessageMicro msg) + throws IOException { + final int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferMicroException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + msg.mergeFrom(this); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + } + + /** Read a {@code bytes} field value from the stream. */ + public ByteStringMicro readBytes() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final ByteStringMicro result = ByteStringMicro.copyFrom(buffer, bufferPos, size); + bufferPos += size; + return result; + } else { + // Slow path: Build a byte array first then copy it. + return ByteStringMicro.copyFrom(readRawBytes(size)); + } + } + + /** Read a {@code uint32} field value from the stream. */ + public int readUInt32() throws IOException { + return readRawVarint32(); + } + + /** + * Read an enum field value from the stream. Caller is responsible + * for converting the numeric value to an actual enum. + */ + public int readEnum() throws IOException { + return readRawVarint32(); + } + + /** Read an {@code sfixed32} field value from the stream. */ + public int readSFixed32() throws IOException { + return readRawLittleEndian32(); + } + + /** Read an {@code sfixed64} field value from the stream. */ + public long readSFixed64() throws IOException { + return readRawLittleEndian64(); + } + + /** Read an {@code sint32} field value from the stream. */ + public int readSInt32() throws IOException { + return decodeZigZag32(readRawVarint32()); + } + + /** Read an {@code sint64} field value from the stream. */ + public long readSInt64() throws IOException { + return decodeZigZag64(readRawVarint64()); + } + + // ================================================================= + + /** + * Read a raw Varint from the stream. If larger than 32 bits, discard the + * upper bits. + */ + public int readRawVarint32() throws IOException { + byte tmp = readRawByte(); + if (tmp >= 0) { + return tmp; + } + int result = tmp & 0x7f; + if ((tmp = readRawByte()) >= 0) { + result |= tmp << 7; + } else { + result |= (tmp & 0x7f) << 7; + if ((tmp = readRawByte()) >= 0) { + result |= tmp << 14; + } else { + result |= (tmp & 0x7f) << 14; + if ((tmp = readRawByte()) >= 0) { + result |= tmp << 21; + } else { + result |= (tmp & 0x7f) << 21; + result |= (tmp = readRawByte()) << 28; + if (tmp < 0) { + // Discard upper 32 bits. + for (int i = 0; i < 5; i++) { + if (readRawByte() >= 0) { + return result; + } + } + throw InvalidProtocolBufferMicroException.malformedVarint(); + } + } + } + } + return result; + } + + /** + * Reads a varint from the input one byte at a time, so that it does not + * read any bytes after the end of the varint. If you simply wrapped the + * stream in a CodedInputStream and used {@link #readRawVarint32(InputStream)} + * then you would probably end up reading past the end of the varint since + * CodedInputStream buffers its input. + */ + static int readRawVarint32(final InputStream input) throws IOException { + int result = 0; + int offset = 0; + for (; offset < 32; offset += 7) { + final int b = input.read(); + if (b == -1) { + throw InvalidProtocolBufferMicroException.truncatedMessage(); + } + result |= (b & 0x7f) << offset; + if ((b & 0x80) == 0) { + return result; + } + } + // Keep reading up to 64 bits. + for (; offset < 64; offset += 7) { + final int b = input.read(); + if (b == -1) { + throw InvalidProtocolBufferMicroException.truncatedMessage(); + } + if ((b & 0x80) == 0) { + return result; + } + } + throw InvalidProtocolBufferMicroException.malformedVarint(); + } + + /** Read a raw Varint from the stream. */ + public long readRawVarint64() throws IOException { + int shift = 0; + long result = 0; + while (shift < 64) { + final byte b = readRawByte(); + result |= (long)(b & 0x7F) << shift; + if ((b & 0x80) == 0) { + return result; + } + shift += 7; + } + throw InvalidProtocolBufferMicroException.malformedVarint(); + } + + /** Read a 32-bit little-endian integer from the stream. */ + public int readRawLittleEndian32() throws IOException { + final byte b1 = readRawByte(); + final byte b2 = readRawByte(); + final byte b3 = readRawByte(); + final byte b4 = readRawByte(); + return ((b1 & 0xff) ) | + ((b2 & 0xff) << 8) | + ((b3 & 0xff) << 16) | + ((b4 & 0xff) << 24); + } + + /** Read a 64-bit little-endian integer from the stream. */ + public long readRawLittleEndian64() throws IOException { + final byte b1 = readRawByte(); + final byte b2 = readRawByte(); + final byte b3 = readRawByte(); + final byte b4 = readRawByte(); + final byte b5 = readRawByte(); + final byte b6 = readRawByte(); + final byte b7 = readRawByte(); + final byte b8 = readRawByte(); + return (((long)b1 & 0xff) ) | + (((long)b2 & 0xff) << 8) | + (((long)b3 & 0xff) << 16) | + (((long)b4 & 0xff) << 24) | + (((long)b5 & 0xff) << 32) | + (((long)b6 & 0xff) << 40) | + (((long)b7 & 0xff) << 48) | + (((long)b8 & 0xff) << 56); + } + + /** + * Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n An unsigned 32-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + * @return A signed 32-bit integer. + */ + public static int decodeZigZag32(final int n) { + return (n >>> 1) ^ -(n & 1); + } + + /** + * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n An unsigned 64-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + * @return A signed 64-bit integer. + */ + public static long decodeZigZag64(final long n) { + return (n >>> 1) ^ -(n & 1); + } + + // ----------------------------------------------------------------- + + private final byte[] buffer; + private int bufferSize; + private int bufferSizeAfterLimit; + private int bufferPos; + private final InputStream input; + private int lastTag; + + /** + * The total number of bytes read before the current buffer. The total + * bytes read up to the current position can be computed as + * {@code totalBytesRetired + bufferPos}. + */ + private int totalBytesRetired; + + /** The absolute position of the end of the current message. */ + private int currentLimit = Integer.MAX_VALUE; + + /** See setRecursionLimit() */ + private int recursionDepth; + private int recursionLimit = DEFAULT_RECURSION_LIMIT; + + /** See setSizeLimit() */ + private int sizeLimit = DEFAULT_SIZE_LIMIT; + + private static final int DEFAULT_RECURSION_LIMIT = 64; + private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB + private static final int BUFFER_SIZE = 4096; + + private CodedInputStreamMicro(final byte[] buffer, final int off, final int len) { + this.buffer = buffer; + bufferSize = off + len; + bufferPos = off; + input = null; + } + + private CodedInputStreamMicro(final InputStream input) { + buffer = new byte[BUFFER_SIZE]; + bufferSize = 0; + bufferPos = 0; + this.input = input; + } + + /** + * Set the maximum message recursion depth. In order to prevent malicious + * messages from causing stack overflows, {@code CodedInputStream} limits + * how deeply messages may be nested. The default limit is 64. + * + * @return the old limit. + */ + public int setRecursionLimit(final int limit) { + if (limit < 0) { + throw new IllegalArgumentException( + "Recursion limit cannot be negative: " + limit); + } + final int oldLimit = recursionLimit; + recursionLimit = limit; + return oldLimit; + } + + /** + * Set the maximum message size. In order to prevent malicious + * messages from exhausting memory or causing integer overflows, + * {@code CodedInputStream} limits how large a message may be. + * The default limit is 64MB. You should set this limit as small + * as you can without harming your app's functionality. Note that + * size limits only apply when reading from an {@code InputStream}, not + * when constructed around a raw byte array (nor with + * {@link ByteStringMicro#newCodedInput}). + * <p> + * If you want to read several messages from a single CodedInputStream, you + * could call {@link #resetSizeCounter()} after each one to avoid hitting the + * size limit. + * + * @return the old limit. + */ + public int setSizeLimit(final int limit) { + if (limit < 0) { + throw new IllegalArgumentException( + "Size limit cannot be negative: " + limit); + } + final int oldLimit = sizeLimit; + sizeLimit = limit; + return oldLimit; + } + + /** + * Resets the current size counter to zero (see {@link #setSizeLimit(int)}). + */ + public void resetSizeCounter() { + totalBytesRetired = 0; + } + + /** + * Sets {@code currentLimit} to (current position) + {@code byteLimit}. This + * is called when descending into a length-delimited embedded message. + * + * @return the old limit. + */ + public int pushLimit(int byteLimit) throws InvalidProtocolBufferMicroException { + if (byteLimit < 0) { + throw InvalidProtocolBufferMicroException.negativeSize(); + } + byteLimit += totalBytesRetired + bufferPos; + final int oldLimit = currentLimit; + if (byteLimit > oldLimit) { + throw InvalidProtocolBufferMicroException.truncatedMessage(); + } + currentLimit = byteLimit; + + recomputeBufferSizeAfterLimit(); + + return oldLimit; + } + + private void recomputeBufferSizeAfterLimit() { + bufferSize += bufferSizeAfterLimit; + final int bufferEnd = totalBytesRetired + bufferSize; + if (bufferEnd > currentLimit) { + // Limit is in current buffer. + bufferSizeAfterLimit = bufferEnd - currentLimit; + bufferSize -= bufferSizeAfterLimit; + } else { + bufferSizeAfterLimit = 0; + } + } + + /** + * Discards the current limit, returning to the previous limit. + * + * @param oldLimit The old limit, as returned by {@code pushLimit}. + */ + public void popLimit(final int oldLimit) { + currentLimit = oldLimit; + recomputeBufferSizeAfterLimit(); + } + + /** + * Returns the number of bytes to be read before the current limit. + * If no limit is set, returns -1. + */ + public int getBytesUntilLimit() { + if (currentLimit == Integer.MAX_VALUE) { + return -1; + } + + final int currentAbsolutePosition = totalBytesRetired + bufferPos; + return currentLimit - currentAbsolutePosition; + } + + /** + * Returns true if the stream has reached the end of the input. This is the + * case if either the end of the underlying input source has been reached or + * if the stream has reached a limit created using {@link #pushLimit(int)}. + */ + public boolean isAtEnd() throws IOException { + return bufferPos == bufferSize && !refillBuffer(false); + } + + /** + * Called with {@code this.buffer} is empty to read more bytes from the + * input. If {@code mustSucceed} is true, refillBuffer() gurantees that + * either there will be at least one byte in the buffer when it returns + * or it will throw an exception. If {@code mustSucceed} is false, + * refillBuffer() returns false if no more bytes were available. + */ + private boolean refillBuffer(final boolean mustSucceed) throws IOException { + if (bufferPos < bufferSize) { + throw new IllegalStateException( + "refillBuffer() called when buffer wasn't empty."); + } + + if (totalBytesRetired + bufferSize == currentLimit) { + // Oops, we hit a limit. + if (mustSucceed) { + throw InvalidProtocolBufferMicroException.truncatedMessage(); + } else { + return false; + } + } + + totalBytesRetired += bufferSize; + + bufferPos = 0; + bufferSize = (input == null) ? -1 : input.read(buffer); + if (bufferSize == 0 || bufferSize < -1) { + throw new IllegalStateException( + "InputStream#read(byte[]) returned invalid result: " + bufferSize + + "\nThe InputStream implementation is buggy."); + } + if (bufferSize == -1) { + bufferSize = 0; + if (mustSucceed) { + throw InvalidProtocolBufferMicroException.truncatedMessage(); + } else { + return false; + } + } else { + recomputeBufferSizeAfterLimit(); + final int totalBytesRead = + totalBytesRetired + bufferSize + bufferSizeAfterLimit; + if (totalBytesRead > sizeLimit || totalBytesRead < 0) { + throw InvalidProtocolBufferMicroException.sizeLimitExceeded(); + } + return true; + } + } + + /** + * Read one byte from the input. + * + * @throws InvalidProtocolBufferMicroException The end of the stream or the current + * limit was reached. + */ + public byte readRawByte() throws IOException { + if (bufferPos == bufferSize) { + refillBuffer(true); + } + return buffer[bufferPos++]; + } + + /** + * Read a fixed size of bytes from the input. + * + * @throws InvalidProtocolBufferMicroException The end of the stream or the current + * limit was reached. + */ + public byte[] readRawBytes(final int size) throws IOException { + if (size < 0) { + throw InvalidProtocolBufferMicroException.negativeSize(); + } + + if (totalBytesRetired + bufferPos + size > currentLimit) { + // Read to the end of the stream anyway. + skipRawBytes(currentLimit - totalBytesRetired - bufferPos); + // Then fail. + throw InvalidProtocolBufferMicroException.truncatedMessage(); + } + + if (size <= bufferSize - bufferPos) { + // We have all the bytes we need already. + final byte[] bytes = new byte[size]; + System.arraycopy(buffer, bufferPos, bytes, 0, size); + bufferPos += size; + return bytes; + } else if (size < BUFFER_SIZE) { + // Reading more bytes than are in the buffer, but not an excessive number + // of bytes. We can safely allocate the resulting array ahead of time. + + // First copy what we have. + final byte[] bytes = new byte[size]; + int pos = bufferSize - bufferPos; + System.arraycopy(buffer, bufferPos, bytes, 0, pos); + bufferPos = bufferSize; + + // We want to use refillBuffer() and then copy from the buffer into our + // byte array rather than reading directly into our byte array because + // the input may be unbuffered. + refillBuffer(true); + + while (size - pos > bufferSize) { + System.arraycopy(buffer, 0, bytes, pos, bufferSize); + pos += bufferSize; + bufferPos = bufferSize; + refillBuffer(true); + } + + System.arraycopy(buffer, 0, bytes, pos, size - pos); + bufferPos = size - pos; + + return bytes; + } else { + // The size is very large. For security reasons, we can't allocate the + // entire byte array yet. The size comes directly from the input, so a + // maliciously-crafted message could provide a bogus very large size in + // order to trick the app into allocating a lot of memory. We avoid this + // by allocating and reading only a small chunk at a time, so that the + // malicious message must actually *be* extremely large to cause + // problems. Meanwhile, we limit the allowed size of a message elsewhere. + + // Remember the buffer markers since we'll have to copy the bytes out of + // it later. + final int originalBufferPos = bufferPos; + final int originalBufferSize = bufferSize; + + // Mark the current buffer consumed. + totalBytesRetired += bufferSize; + bufferPos = 0; + bufferSize = 0; + + // Read all the rest of the bytes we need. + int sizeLeft = size - (originalBufferSize - originalBufferPos); + + // For compatibility with Java 1.3 use Vector + final java.util.Vector chunks = new java.util.Vector(); + + while (sizeLeft > 0) { + final byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)]; + int pos = 0; + while (pos < chunk.length) { + final int n = (input == null) ? -1 : + input.read(chunk, pos, chunk.length - pos); + if (n == -1) { + throw InvalidProtocolBufferMicroException.truncatedMessage(); + } + totalBytesRetired += n; + pos += n; + } + sizeLeft -= chunk.length; + chunks.addElement(chunk); + } + + // OK, got everything. Now concatenate it all into one buffer. + final byte[] bytes = new byte[size]; + + // Start by copying the leftover bytes from this.buffer. + int pos = originalBufferSize - originalBufferPos; + System.arraycopy(buffer, originalBufferPos, bytes, 0, pos); + + // And now all the chunks. + for (int i = 0; i < chunks.size(); i++) { + byte [] chunk = (byte [])chunks.elementAt(i); + System.arraycopy(chunk, 0, bytes, pos, chunk.length); + pos += chunk.length; + } + + // Done. + return bytes; + } + } + + /** + * Reads and discards {@code size} bytes. + * + * @throws InvalidProtocolBufferMicroException The end of the stream or the current + * limit was reached. + */ + public void skipRawBytes(final int size) throws IOException { + if (size < 0) { + throw InvalidProtocolBufferMicroException.negativeSize(); + } + + if (totalBytesRetired + bufferPos + size > currentLimit) { + // Read to the end of the stream anyway. + skipRawBytes(currentLimit - totalBytesRetired - bufferPos); + // Then fail. + throw InvalidProtocolBufferMicroException.truncatedMessage(); + } + + if (size <= bufferSize - bufferPos) { + // We have all the bytes we need already. + bufferPos += size; + } else { + // Skipping more bytes than are in the buffer. First skip what we have. + int pos = bufferSize - bufferPos; + totalBytesRetired += pos; + bufferPos = 0; + bufferSize = 0; + + // Then skip directly from the InputStream for the rest. + while (pos < size) { + final int n = (input == null) ? -1 : (int) input.skip(size - pos); + if (n <= 0) { + throw InvalidProtocolBufferMicroException.truncatedMessage(); + } + pos += n; + totalBytesRetired += n; + } + } + } +} diff --git a/java/src/main/java/com/google/protobuf/micro/CodedOutputStreamMicro.java b/java/src/main/java/com/google/protobuf/micro/CodedOutputStreamMicro.java new file mode 100644 index 0000000..68b6e97 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/micro/CodedOutputStreamMicro.java @@ -0,0 +1,996 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf.micro; + +import java.io.OutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +/** + * Encodes and writes protocol message fields. + * + * <p>This class contains two kinds of methods: methods that write specific + * protocol message constructs and field types (e.g. {@link #writeTag} and + * {@link #writeInt32}) and methods that write low-level values (e.g. + * {@link #writeRawVarint32} and {@link #writeRawBytes}). If you are + * writing encoded protocol messages, you should use the former methods, but if + * you are writing some other format of your own design, use the latter. + * + * <p>This class is totally unsynchronized. + * + * @author kneton@google.com Kenton Varda + */ +public final class CodedOutputStreamMicro { + private final byte[] buffer; + private final int limit; + private int position; + + private final OutputStream output; + + /** + * The buffer size used in {@link #newInstance(OutputStream)}. + */ + public static final int DEFAULT_BUFFER_SIZE = 4096; + + private CodedOutputStreamMicro(final byte[] buffer, final int offset, + final int length) { + output = null; + this.buffer = buffer; + position = offset; + limit = offset + length; + } + + private CodedOutputStreamMicro(final OutputStream output, final byte[] buffer) { + this.output = output; + this.buffer = buffer; + position = 0; + limit = buffer.length; + } + + /** + * Create a new {@code CodedOutputStream} wrapping the given + * {@code OutputStream}. + */ + public static CodedOutputStreamMicro newInstance(final OutputStream output) { + return newInstance(output, DEFAULT_BUFFER_SIZE); + } + + /** + * Create a new {@code CodedOutputStream} wrapping the given + * {@code OutputStream} with a given buffer size. + */ + public static CodedOutputStreamMicro newInstance(final OutputStream output, + final int bufferSize) { + return new CodedOutputStreamMicro(output, new byte[bufferSize]); + } + + /** + * Create a new {@code CodedOutputStream} that writes directly to the given + * byte array. If more bytes are written than fit in the array, + * {@link OutOfSpaceException} will be thrown. Writing directly to a flat + * array is faster than writing to an {@code OutputStream}. + */ + public static CodedOutputStreamMicro newInstance(final byte[] flatArray) { + return newInstance(flatArray, 0, flatArray.length); + } + + /** + * Create a new {@code CodedOutputStream} that writes directly to the given + * byte array slice. If more bytes are written than fit in the slice, + * {@link OutOfSpaceException} will be thrown. Writing directly to a flat + * array is faster than writing to an {@code OutputStream}. + */ + public static CodedOutputStreamMicro newInstance(final byte[] flatArray, + final int offset, + final int length) { + return new CodedOutputStreamMicro(flatArray, offset, length); + } + + // ----------------------------------------------------------------- + + /** Write a {@code double} field, including tag, to the stream. */ + public void writeDouble(final int fieldNumber, final double value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_FIXED64); + writeDoubleNoTag(value); + } + + /** Write a {@code float} field, including tag, to the stream. */ + public void writeFloat(final int fieldNumber, final float value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_FIXED32); + writeFloatNoTag(value); + } + + /** Write a {@code uint64} field, including tag, to the stream. */ + public void writeUInt64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_VARINT); + writeUInt64NoTag(value); + } + + /** Write an {@code int64} field, including tag, to the stream. */ + public void writeInt64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_VARINT); + writeInt64NoTag(value); + } + + /** Write an {@code int32} field, including tag, to the stream. */ + public void writeInt32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_VARINT); + writeInt32NoTag(value); + } + + /** Write a {@code fixed64} field, including tag, to the stream. */ + public void writeFixed64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_FIXED64); + writeFixed64NoTag(value); + } + + /** Write a {@code fixed32} field, including tag, to the stream. */ + public void writeFixed32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_FIXED32); + writeFixed32NoTag(value); + } + + /** Write a {@code bool} field, including tag, to the stream. */ + public void writeBool(final int fieldNumber, final boolean value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_VARINT); + writeBoolNoTag(value); + } + + /** Write a {@code string} field, including tag, to the stream. */ + public void writeString(final int fieldNumber, final String value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_LENGTH_DELIMITED); + writeStringNoTag(value); + } + + /** Write a {@code StringUtf8Micro} field, including tag, to the stream. */ + public void writeStringUtf8(final int fieldNumber, final StringUtf8Micro value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_LENGTH_DELIMITED); + writeStringUtf8NoTag(value); + } + + /** Write a {@code group} field, including tag, to the stream. */ + public void writeGroup(final int fieldNumber, final MessageMicro value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_START_GROUP); + writeGroupNoTag(value); + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_END_GROUP); + } + + /** Write an embedded message field, including tag, to the stream. */ + public void writeMessage(final int fieldNumber, final MessageMicro value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_LENGTH_DELIMITED); + writeMessageNoTag(value); + } + + /** Write a {@code bytes} field, including tag, to the stream. */ + public void writeBytes(final int fieldNumber, final ByteStringMicro value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_LENGTH_DELIMITED); + writeBytesNoTag(value); + } + + /** Write a {@code uint32} field, including tag, to the stream. */ + public void writeUInt32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_VARINT); + writeUInt32NoTag(value); + } + + /** + * Write an enum field, including tag, to the stream. Caller is responsible + * for converting the enum value to its numeric value. + */ + public void writeEnum(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_VARINT); + writeEnumNoTag(value); + } + + /** Write an {@code sfixed32} field, including tag, to the stream. */ + public void writeSFixed32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_FIXED32); + writeSFixed32NoTag(value); + } + + /** Write an {@code sfixed64} field, including tag, to the stream. */ + public void writeSFixed64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_FIXED64); + writeSFixed64NoTag(value); + } + + /** Write an {@code sint32} field, including tag, to the stream. */ + public void writeSInt32(final int fieldNumber, final int value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_VARINT); + writeSInt32NoTag(value); + } + + /** Write an {@code sint64} field, including tag, to the stream. */ + public void writeSInt64(final int fieldNumber, final long value) + throws IOException { + writeTag(fieldNumber, WireFormatMicro.WIRETYPE_VARINT); + writeSInt64NoTag(value); + } + + /** + * Write a MessageSet extension field to the stream. For historical reasons, + * the wire format differs from normal fields. + */ +// public void writeMessageSetExtension(final int fieldNumber, +// final MessageMicro value) +// throws IOException { +// writeTag(WireFormatMicro.MESSAGE_SET_ITEM, WireFormatMicro.WIRETYPE_START_GROUP); +// writeUInt32(WireFormatMicro.MESSAGE_SET_TYPE_ID, fieldNumber); +// writeMessage(WireFormatMicro.MESSAGE_SET_MESSAGE, value); +// writeTag(WireFormatMicro.MESSAGE_SET_ITEM, WireFormatMicro.WIRETYPE_END_GROUP); +// } + + /** + * Write an unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ +// public void writeRawMessageSetExtension(final int fieldNumber, +// final ByteStringMicro value) +// throws IOException { +// writeTag(WireFormatMicro.MESSAGE_SET_ITEM, WireFormatMicro.WIRETYPE_START_GROUP); +// writeUInt32(WireFormatMicro.MESSAGE_SET_TYPE_ID, fieldNumber); +// writeBytes(WireFormatMicro.MESSAGE_SET_MESSAGE, value); +// writeTag(WireFormatMicro.MESSAGE_SET_ITEM, WireFormatMicro.WIRETYPE_END_GROUP); +// } + + // ----------------------------------------------------------------- + + /** Write a {@code double} field to the stream. */ + public void writeDoubleNoTag(final double value) throws IOException { + writeRawLittleEndian64(Double.doubleToLongBits(value)); + } + + /** Write a {@code float} field to the stream. */ + public void writeFloatNoTag(final float value) throws IOException { + writeRawLittleEndian32(Float.floatToIntBits(value)); + } + + /** Write a {@code uint64} field to the stream. */ + public void writeUInt64NoTag(final long value) throws IOException { + writeRawVarint64(value); + } + + /** Write an {@code int64} field to the stream. */ + public void writeInt64NoTag(final long value) throws IOException { + writeRawVarint64(value); + } + + /** Write an {@code int32} field to the stream. */ + public void writeInt32NoTag(final int value) throws IOException { + if (value >= 0) { + writeRawVarint32(value); + } else { + // Must sign-extend. + writeRawVarint64(value); + } + } + + /** Write a {@code fixed64} field to the stream. */ + public void writeFixed64NoTag(final long value) throws IOException { + writeRawLittleEndian64(value); + } + + /** Write a {@code fixed32} field to the stream. */ + public void writeFixed32NoTag(final int value) throws IOException { + writeRawLittleEndian32(value); + } + + /** Write a {@code bool} field to the stream. */ + public void writeBoolNoTag(final boolean value) throws IOException { + writeRawByte(value ? 1 : 0); + } + + /** Write a {@code string} field to the stream. */ + public void writeStringNoTag(final String value) throws IOException { + // Unfortunately there does not appear to be any way to tell Java to encode + // UTF-8 directly into our buffer, so we have to let it create its own byte + // array and then copy. + final byte[] bytes = value.getBytes("UTF-8"); + writeRawVarint32(bytes.length); + writeRawBytes(bytes); + } + + /** Write a {@code StringUtf8Micro} field to the stream. */ + public void writeStringUtf8NoTag(final StringUtf8Micro value) throws IOException { + final byte[] bytes = value.getBytes(); + writeRawVarint32(bytes.length); + writeRawBytes(bytes); + } + + /** Write a {@code group} field to the stream. */ + public void writeGroupNoTag(final MessageMicro value) throws IOException { + value.writeTo(this); + } + + /** Write an embedded message field to the stream. */ + public void writeMessageNoTag(final MessageMicro value) throws IOException { + writeRawVarint32(value.getCachedSize()); + value.writeTo(this); + } + + /** Write a {@code bytes} field to the stream. */ + public void writeBytesNoTag(final ByteStringMicro value) throws IOException { + final byte[] bytes = value.toByteArray(); + writeRawVarint32(bytes.length); + writeRawBytes(bytes); + } + + /** Write a {@code uint32} field to the stream. */ + public void writeUInt32NoTag(final int value) throws IOException { + writeRawVarint32(value); + } + + /** + * Write an enum field to the stream. Caller is responsible + * for converting the enum value to its numeric value. + */ + public void writeEnumNoTag(final int value) throws IOException { + writeRawVarint32(value); + } + + /** Write an {@code sfixed32} field to the stream. */ + public void writeSFixed32NoTag(final int value) throws IOException { + writeRawLittleEndian32(value); + } + + /** Write an {@code sfixed64} field to the stream. */ + public void writeSFixed64NoTag(final long value) throws IOException { + writeRawLittleEndian64(value); + } + + /** Write an {@code sint32} field to the stream. */ + public void writeSInt32NoTag(final int value) throws IOException { + writeRawVarint32(encodeZigZag32(value)); + } + + /** Write an {@code sint64} field to the stream. */ + public void writeSInt64NoTag(final long value) throws IOException { + writeRawVarint64(encodeZigZag64(value)); + } + + // ================================================================= + + /** + * Compute the number of bytes that would be needed to encode a + * {@code double} field, including tag. + */ + public static int computeDoubleSize(final int fieldNumber, + final double value) { + return computeTagSize(fieldNumber) + computeDoubleSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code float} field, including tag. + */ + public static int computeFloatSize(final int fieldNumber, final float value) { + return computeTagSize(fieldNumber) + computeFloatSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint64} field, including tag. + */ + public static int computeUInt64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeUInt64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int64} field, including tag. + */ + public static int computeInt64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeInt64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int32} field, including tag. + */ + public static int computeInt32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed64} field, including tag. + */ + public static int computeFixed64Size(final int fieldNumber, + final long value) { + return computeTagSize(fieldNumber) + computeFixed64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed32} field, including tag. + */ + public static int computeFixed32Size(final int fieldNumber, + final int value) { + return computeTagSize(fieldNumber) + computeFixed32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bool} field, including tag. + */ + public static int computeBoolSize(final int fieldNumber, + final boolean value) { + return computeTagSize(fieldNumber) + computeBoolSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code string} field, including tag. + */ + public static int computeStringSize(final int fieldNumber, + final String value) { + return computeTagSize(fieldNumber) + computeStringSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code StringUtf8Micro} field, including tag. + */ + public static int computeStringUtf8Size(final int fieldNumber, + final StringUtf8Micro value) { + return computeTagSize(fieldNumber) + computeStringUtf8SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code group} field, including tag. + */ + public static int computeGroupSize(final int fieldNumber, + final MessageMicro value) { + return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * embedded message field, including tag. + */ + public static int computeMessageSize(final int fieldNumber, + final MessageMicro value) { + return computeTagSize(fieldNumber) + computeMessageSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field, including tag. + */ + public static int computeBytesSize(final int fieldNumber, + final ByteStringMicro value) { + return computeTagSize(fieldNumber) + computeBytesSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint32} field, including tag. + */ + public static int computeUInt32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeUInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * enum field, including tag. Caller is responsible for converting the + * enum value to its numeric value. + */ + public static int computeEnumSize(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeEnumSizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed32} field, including tag. + */ + public static int computeSFixed32Size(final int fieldNumber, + final int value) { + return computeTagSize(fieldNumber) + computeSFixed32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed64} field, including tag. + */ + public static int computeSFixed64Size(final int fieldNumber, + final long value) { + return computeTagSize(fieldNumber) + computeSFixed64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint32} field, including tag. + */ + public static int computeSInt32Size(final int fieldNumber, final int value) { + return computeTagSize(fieldNumber) + computeSInt32SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint64} field, including tag. + */ + public static int computeSInt64Size(final int fieldNumber, final long value) { + return computeTagSize(fieldNumber) + computeSInt64SizeNoTag(value); + } + + /** + * Compute the number of bytes that would be needed to encode a + * MessageSet extension to the stream. For historical reasons, + * the wire format differs from normal fields. + */ +// public static int computeMessageSetExtensionSize( +// final int fieldNumber, final MessageMicro value) { +// return computeTagSize(WireFormatMicro.MESSAGE_SET_ITEM) * 2 + +// computeUInt32Size(WireFormatMicro.MESSAGE_SET_TYPE_ID, fieldNumber) + +// computeMessageSize(WireFormatMicro.MESSAGE_SET_MESSAGE, value); +// } + + /** + * Compute the number of bytes that would be needed to encode an + * unparsed MessageSet extension field to the stream. For + * historical reasons, the wire format differs from normal fields. + */ +// public static int computeRawMessageSetExtensionSize( +// final int fieldNumber, final ByteStringMicro value) { +// return computeTagSize(WireFormatMicro.MESSAGE_SET_ITEM) * 2 + +// computeUInt32Size(WireFormatMicro.MESSAGE_SET_TYPE_ID, fieldNumber) + +// computeBytesSize(WireFormatMicro.MESSAGE_SET_MESSAGE, value); +// } + + // ----------------------------------------------------------------- + + /** + * Compute the number of bytes that would be needed to encode a + * {@code double} field, including tag. + */ + public static int computeDoubleSizeNoTag(final double value) { + return LITTLE_ENDIAN_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code float} field, including tag. + */ + public static int computeFloatSizeNoTag(final float value) { + return LITTLE_ENDIAN_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint64} field, including tag. + */ + public static int computeUInt64SizeNoTag(final long value) { + return computeRawVarint64Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int64} field, including tag. + */ + public static int computeInt64SizeNoTag(final long value) { + return computeRawVarint64Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code int32} field, including tag. + */ + public static int computeInt32SizeNoTag(final int value) { + if (value >= 0) { + return computeRawVarint32Size(value); + } else { + // Must sign-extend. + return 10; + } + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed64} field. + */ + public static int computeFixed64SizeNoTag(final long value) { + return LITTLE_ENDIAN_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code fixed32} field. + */ + public static int computeFixed32SizeNoTag(final int value) { + return LITTLE_ENDIAN_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bool} field. + */ + public static int computeBoolSizeNoTag(final boolean value) { + return 1; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code string} field. + */ + public static int computeStringSizeNoTag(final String value) { + try { + final byte[] bytes = value.getBytes("UTF-8"); + return computeRawVarint32Size(bytes.length) + + bytes.length; + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 not supported."); + } + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code StringUtf8Micro} field. + */ + public static int computeStringUtf8SizeNoTag(final StringUtf8Micro value) { + final byte[] bytes = value.getBytes(); + return computeRawVarint32Size(bytes.length) + + bytes.length; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code group} field. + */ + public static int computeGroupSizeNoTag(final MessageMicro value) { + return value.getCachedSize(); + } + + /** + * Compute the number of bytes that would be needed to encode an embedded + * message field. + */ + public static int computeMessageSizeNoTag(final MessageMicro value) { + final int size = value.getCachedSize(); + return computeRawVarint32Size(size) + size; + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code bytes} field. + */ + public static int computeBytesSizeNoTag(final ByteStringMicro value) { + return computeRawVarint32Size(value.size()) + + value.size(); + } + + /** + * Compute the number of bytes that would be needed to encode a + * {@code uint32} field. + */ + public static int computeUInt32SizeNoTag(final int value) { + return computeRawVarint32Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an enum field. + * Caller is responsible for converting the enum value to its numeric value. + */ + public static int computeEnumSizeNoTag(final int value) { + return computeRawVarint32Size(value); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed32} field. + */ + public static int computeSFixed32SizeNoTag(final int value) { + return LITTLE_ENDIAN_32_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sfixed64} field. + */ + public static int computeSFixed64SizeNoTag(final long value) { + return LITTLE_ENDIAN_64_SIZE; + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint32} field. + */ + public static int computeSInt32SizeNoTag(final int value) { + return computeRawVarint32Size(encodeZigZag32(value)); + } + + /** + * Compute the number of bytes that would be needed to encode an + * {@code sint64} field. + */ + public static int computeSInt64SizeNoTag(final long value) { + return computeRawVarint64Size(encodeZigZag64(value)); + } + + // ================================================================= + + /** + * Internal helper that writes the current buffer to the output. The + * buffer position is reset to its initial value when this returns. + */ + private void refreshBuffer() throws IOException { + if (output == null) { + // We're writing to a single buffer. + throw new OutOfSpaceException(); + } + + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + output.write(buffer, 0, position); + position = 0; + } + + /** + * Flushes the stream and forces any buffered bytes to be written. This + * does not flush the underlying OutputStream. + */ + public void flush() throws IOException { + if (output != null) { + refreshBuffer(); + } + } + + /** + * If writing to a flat array, return the space left in the array. + * Otherwise, throws {@code UnsupportedOperationException}. + */ + public int spaceLeft() { + if (output == null) { + return limit - position; + } else { + throw new UnsupportedOperationException( + "spaceLeft() can only be called on CodedOutputStreams that are " + + "writing to a flat array."); + } + } + + /** + * Verifies that {@link #spaceLeft()} returns zero. It's common to create + * a byte array that is exactly big enough to hold a message, then write to + * it with a {@code CodedOutputStream}. Calling {@code checkNoSpaceLeft()} + * after writing verifies that the message was actually as big as expected, + * which can help catch bugs. + */ + public void checkNoSpaceLeft() { + if (spaceLeft() != 0) { + throw new IllegalStateException( + "Did not write as much data as expected."); + } + } + + /** + * If you create a CodedOutputStream around a simple flat array, you must + * not attempt to write more bytes than the array has space. Otherwise, + * this exception will be thrown. + */ + public static class OutOfSpaceException extends IOException { + private static final long serialVersionUID = -6947486886997889499L; + + OutOfSpaceException() { + super("CodedOutputStream was writing to a flat byte array and ran " + + "out of space."); + } + } + + /** Write a single byte. */ + public void writeRawByte(final byte value) throws IOException { + if (position == limit) { + refreshBuffer(); + } + + buffer[position++] = value; + } + + /** Write a single byte, represented by an integer value. */ + public void writeRawByte(final int value) throws IOException { + writeRawByte((byte) value); + } + + /** Write an array of bytes. */ + public void writeRawBytes(final byte[] value) throws IOException { + writeRawBytes(value, 0, value.length); + } + + /** Write part of an array of bytes. */ + public void writeRawBytes(final byte[] value, int offset, int length) + throws IOException { + if (limit - position >= length) { + // We have room in the current buffer. + System.arraycopy(value, offset, buffer, position, length); + position += length; + } else { + // Write extends past current buffer. Fill the rest of this buffer and + // flush. + final int bytesWritten = limit - position; + System.arraycopy(value, offset, buffer, position, bytesWritten); + offset += bytesWritten; + length -= bytesWritten; + position = limit; + refreshBuffer(); + + // Now deal with the rest. + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + if (length <= limit) { + // Fits in new buffer. + System.arraycopy(value, offset, buffer, 0, length); + position = length; + } else { + // Write is very big. Let's do it all at once. + output.write(value, offset, length); + } + } + } + + /** Encode and write a tag. */ + public void writeTag(final int fieldNumber, final int wireType) + throws IOException { + writeRawVarint32(WireFormatMicro.makeTag(fieldNumber, wireType)); + } + + /** Compute the number of bytes that would be needed to encode a tag. */ + public static int computeTagSize(final int fieldNumber) { + return computeRawVarint32Size(WireFormatMicro.makeTag(fieldNumber, 0)); + } + + /** + * Encode and write a varint. {@code value} is treated as + * unsigned, so it won't be sign-extended if negative. + */ + public void writeRawVarint32(int value) throws IOException { + while (true) { + if ((value & ~0x7F) == 0) { + writeRawByte(value); + return; + } else { + writeRawByte((value & 0x7F) | 0x80); + value >>>= 7; + } + } + } + + /** + * Compute the number of bytes that would be needed to encode a varint. + * {@code value} is treated as unsigned, so it won't be sign-extended if + * negative. + */ + public static int computeRawVarint32Size(final int value) { + if ((value & (0xffffffff << 7)) == 0) return 1; + if ((value & (0xffffffff << 14)) == 0) return 2; + if ((value & (0xffffffff << 21)) == 0) return 3; + if ((value & (0xffffffff << 28)) == 0) return 4; + return 5; + } + + /** Encode and write a varint. */ + public void writeRawVarint64(long value) throws IOException { + while (true) { + if ((value & ~0x7FL) == 0) { + writeRawByte((int)value); + return; + } else { + writeRawByte(((int)value & 0x7F) | 0x80); + value >>>= 7; + } + } + } + + /** Compute the number of bytes that would be needed to encode a varint. */ + public static int computeRawVarint64Size(final long value) { + if ((value & (0xffffffffffffffffL << 7)) == 0) return 1; + if ((value & (0xffffffffffffffffL << 14)) == 0) return 2; + if ((value & (0xffffffffffffffffL << 21)) == 0) return 3; + if ((value & (0xffffffffffffffffL << 28)) == 0) return 4; + if ((value & (0xffffffffffffffffL << 35)) == 0) return 5; + if ((value & (0xffffffffffffffffL << 42)) == 0) return 6; + if ((value & (0xffffffffffffffffL << 49)) == 0) return 7; + if ((value & (0xffffffffffffffffL << 56)) == 0) return 8; + if ((value & (0xffffffffffffffffL << 63)) == 0) return 9; + return 10; + } + + /** Write a little-endian 32-bit integer. */ + public void writeRawLittleEndian32(final int value) throws IOException { + writeRawByte((value ) & 0xFF); + writeRawByte((value >> 8) & 0xFF); + writeRawByte((value >> 16) & 0xFF); + writeRawByte((value >> 24) & 0xFF); + } + + public static final int LITTLE_ENDIAN_32_SIZE = 4; + + /** Write a little-endian 64-bit integer. */ + public void writeRawLittleEndian64(final long value) throws IOException { + writeRawByte((int)(value ) & 0xFF); + writeRawByte((int)(value >> 8) & 0xFF); + writeRawByte((int)(value >> 16) & 0xFF); + writeRawByte((int)(value >> 24) & 0xFF); + writeRawByte((int)(value >> 32) & 0xFF); + writeRawByte((int)(value >> 40) & 0xFF); + writeRawByte((int)(value >> 48) & 0xFF); + writeRawByte((int)(value >> 56) & 0xFF); + } + + public static final int LITTLE_ENDIAN_64_SIZE = 8; + + /** + * Encode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 32-bit integer. + * @return An unsigned 32-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + */ + public static int encodeZigZag32(final int n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 31); + } + + /** + * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers + * into values that can be efficiently encoded with varint. (Otherwise, + * negative values must be sign-extended to 64 bits to be varint encoded, + * thus always taking 10 bytes on the wire.) + * + * @param n A signed 64-bit integer. + * @return An unsigned 64-bit integer, stored in a signed int because + * Java has no explicit unsigned support. + */ + public static long encodeZigZag64(final long n) { + // Note: the right-shift must be arithmetic + return (n << 1) ^ (n >> 63); + } +} diff --git a/java/src/main/java/com/google/protobuf/micro/InvalidProtocolBufferMicroException.java b/java/src/main/java/com/google/protobuf/micro/InvalidProtocolBufferMicroException.java new file mode 100644 index 0000000..050f99b --- /dev/null +++ b/java/src/main/java/com/google/protobuf/micro/InvalidProtocolBufferMicroException.java @@ -0,0 +1,93 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf.micro; + +import java.io.IOException; + +/** + * Thrown when a protocol message being parsed is invalid in some way, + * e.g. it contains a malformed varint or a negative byte length. + * + * @author kenton@google.com Kenton Varda + */ +public class InvalidProtocolBufferMicroException extends IOException { + private static final long serialVersionUID = -1616151763072450476L; + + public InvalidProtocolBufferMicroException(final String description) { + super(description); + } + + static InvalidProtocolBufferMicroException truncatedMessage() { + return new InvalidProtocolBufferMicroException( + "While parsing a protocol message, the input ended unexpectedly " + + "in the middle of a field. This could mean either than the " + + "input has been truncated or that an embedded message " + + "misreported its own length."); + } + + static InvalidProtocolBufferMicroException negativeSize() { + return new InvalidProtocolBufferMicroException( + "CodedInputStream encountered an embedded string or message " + + "which claimed to have negative size."); + } + + static InvalidProtocolBufferMicroException malformedVarint() { + return new InvalidProtocolBufferMicroException( + "CodedInputStream encountered a malformed varint."); + } + + static InvalidProtocolBufferMicroException invalidTag() { + return new InvalidProtocolBufferMicroException( + "Protocol message contained an invalid tag (zero)."); + } + + static InvalidProtocolBufferMicroException invalidEndTag() { + return new InvalidProtocolBufferMicroException( + "Protocol message end-group tag did not match expected tag."); + } + + static InvalidProtocolBufferMicroException invalidWireType() { + return new InvalidProtocolBufferMicroException( + "Protocol message tag had invalid wire type."); + } + + static InvalidProtocolBufferMicroException recursionLimitExceeded() { + return new InvalidProtocolBufferMicroException( + "Protocol message had too many levels of nesting. May be malicious. " + + "Use CodedInputStream.setRecursionLimit() to increase the depth limit."); + } + + static InvalidProtocolBufferMicroException sizeLimitExceeded() { + return new InvalidProtocolBufferMicroException( + "Protocol message was too large. May be malicious. " + + "Use CodedInputStream.setSizeLimit() to increase the size limit."); + } +} diff --git a/java/src/main/java/com/google/protobuf/micro/MessageMicro.java b/java/src/main/java/com/google/protobuf/micro/MessageMicro.java new file mode 100644 index 0000000..0c02793 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/micro/MessageMicro.java @@ -0,0 +1,136 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf.micro; + +import com.google.protobuf.micro.CodedOutputStreamMicro; +import java.io.IOException; + +/** + * Abstract interface implemented by Protocol Message objects. + * + * @author wink@google.com Wink Saville + */ +public abstract class MessageMicro { + /** + * Get the number of bytes required to encode this message. + * Returns the cached size or calls getSerializedSize which + * sets the cached size. This is used internally when serializing + * so the size is only computed once. If a member is modified + * then this could be stale call getSerializedSize if in doubt. + */ + abstract public int getCachedSize(); + + /** + * Computes the number of bytes required to encode this message. + * The size is cached and the cached result can be retrieved + * using getCachedSize(). + */ + abstract public int getSerializedSize(); + + /** + * Serializes the message and writes it to {@code output}. This does not + * flush or close the stream. + */ + abstract public void writeTo(CodedOutputStreamMicro output) throws java.io.IOException; + + /** + * Parse {@code input} as a message of this type and merge it with the + * message being built. + */ + abstract public MessageMicro mergeFrom(final CodedInputStreamMicro input) throws IOException; + + /** + * Serialize to a byte array. + * @return byte array with the serialized data. + */ + public byte[] toByteArray() { + final byte[] result = new byte[getSerializedSize()]; + toByteArray(result, 0, result.length); + return result; + } + + /** + * Serialize to a byte array starting at offset through length. The + * method getSerializedSize must have been called prior to calling + * this method so the proper length is know. If an attempt to + * write more than length bytes OutOfSpaceException will be thrown + * and if length bytes are not written then IllegalStateException + * is thrown. + * @return byte array with the serialized data. + */ + public void toByteArray(byte [] data, int offset, int length) { + try { + final CodedOutputStreamMicro output = CodedOutputStreamMicro.newInstance(data, offset, length); + writeTo(output); + output.checkNoSpaceLeft(); + } catch (IOException e) { + throw new RuntimeException("Serializing to a byte array threw an IOException " + + "(should never happen)."); + } + } + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. + */ + public MessageMicro mergeFrom(final byte[] data) throws InvalidProtocolBufferMicroException { + return mergeFrom(data, 0, data.length); + } + + /** + * Parse {@code data} as a message of this type and merge it with the + * message being built. + */ + public MessageMicro mergeFrom(final byte[] data, final int off, final int len) + throws InvalidProtocolBufferMicroException { + try { + final CodedInputStreamMicro input = CodedInputStreamMicro.newInstance(data, off, len); + mergeFrom(input); + input.checkLastTagWas(0); + return this; + } catch (InvalidProtocolBufferMicroException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException("Reading from a byte array threw an IOException (should " + + "never happen)."); + } + } + + /** + * Called by subclasses to parse an unknown field. + * @return {@code true} unless the tag is an end-group tag. + */ + protected boolean parseUnknownField( + final CodedInputStreamMicro input, + final int tag) throws IOException { + return input.skipField(tag); + } +} diff --git a/java/src/main/java/com/google/protobuf/micro/StringUtf8Micro.java b/java/src/main/java/com/google/protobuf/micro/StringUtf8Micro.java new file mode 100644 index 0000000..0c43e54 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/micro/StringUtf8Micro.java @@ -0,0 +1,67 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf.micro; + +/** + * A surogate for a String with a UTF8 representation. + * + * @author wink@google.com Wink Saville + */ +public final class StringUtf8Micro { + private String string; + private byte[] bytes; + + public StringUtf8Micro(String string) { + setString(string); + } + + public static final StringUtf8Micro EMPTY = new StringUtf8Micro(""); + + public String getString() { + return string; + } + + public void setString(String string) { + this.string = string; + bytes = null; + } + + public byte [] getBytes() { + if (bytes == null) { + try { + bytes = string.getBytes("UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 not supported."); + } + } + return bytes; + } +} diff --git a/java/src/main/java/com/google/protobuf/micro/WireFormatMicro.java b/java/src/main/java/com/google/protobuf/micro/WireFormatMicro.java new file mode 100644 index 0000000..d8a88bd --- /dev/null +++ b/java/src/main/java/com/google/protobuf/micro/WireFormatMicro.java @@ -0,0 +1,87 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// http://code.google.com/p/protobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf.micro; + +/** + * This class is used internally by the Protocol Buffer library and generated + * message implementations. It is public only because those generated messages + * do not reside in the {@code protobuf} package. Others should not use this + * class directly. + * + * This class contains constants and helper functions useful for dealing with + * the Protocol Buffer wire format. + * + * @author kenton@google.com Kenton Varda + */ +public final class WireFormatMicro { + // Do not allow instantiation. + private WireFormatMicro() {} + + static final int WIRETYPE_VARINT = 0; + static final int WIRETYPE_FIXED64 = 1; + static final int WIRETYPE_LENGTH_DELIMITED = 2; + static final int WIRETYPE_START_GROUP = 3; + static final int WIRETYPE_END_GROUP = 4; + static final int WIRETYPE_FIXED32 = 5; + + static final int TAG_TYPE_BITS = 3; + static final int TAG_TYPE_MASK = (1 << TAG_TYPE_BITS) - 1; + + /** Given a tag value, determines the wire type (the lower 3 bits). */ + static int getTagWireType(final int tag) { + return tag & TAG_TYPE_MASK; + } + + /** Given a tag value, determines the field number (the upper 29 bits). */ + public static int getTagFieldNumber(final int tag) { + return tag >>> TAG_TYPE_BITS; + } + + /** Makes a tag value given a field number and wire type. */ + static int makeTag(final int fieldNumber, final int wireType) { + return (fieldNumber << TAG_TYPE_BITS) | wireType; + } + + // Field numbers for feilds in MessageSet wire format. + static final int MESSAGE_SET_ITEM = 1; + static final int MESSAGE_SET_TYPE_ID = 2; + static final int MESSAGE_SET_MESSAGE = 3; + + // Tag numbers. + static final int MESSAGE_SET_ITEM_TAG = + makeTag(MESSAGE_SET_ITEM, WIRETYPE_START_GROUP); + static final int MESSAGE_SET_ITEM_END_TAG = + makeTag(MESSAGE_SET_ITEM, WIRETYPE_END_GROUP); + static final int MESSAGE_SET_TYPE_ID_TAG = + makeTag(MESSAGE_SET_TYPE_ID, WIRETYPE_VARINT); + static final int MESSAGE_SET_MESSAGE_TAG = + makeTag(MESSAGE_SET_MESSAGE, WIRETYPE_LENGTH_DELIMITED); +} |