io.opentelemetry.exporter.internal.marshal.CodedOutputStream Maven / Gradle / Ivy
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
// Includes work from:
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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 io.opentelemetry.exporter.internal.marshal;
import static io.opentelemetry.exporter.internal.marshal.WireFormat.FIXED32_SIZE;
import static io.opentelemetry.exporter.internal.marshal.WireFormat.FIXED64_SIZE;
import static io.opentelemetry.exporter.internal.marshal.WireFormat.MAX_VARINT32_SIZE;
import static io.opentelemetry.exporter.internal.marshal.WireFormat.MAX_VARINT_SIZE;
import io.opentelemetry.api.internal.ConfigUtil;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* Protobuf wire encoder.
*
* This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
// Copied in and trimmed from
// https://github.com/protocolbuffers/protobuf/blob/master/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java
//
// Differences
// - No support for Message/Lite
// - No support for ByteString
// - No support for message set extensions
// - No support for Unsafe
// - No support for Java String, only UTF-8 bytes
// - No support for writing fields with tag, we alway write tags separately
// - Allow resetting and use a ThreadLocal instance
//
@SuppressWarnings({"UnnecessaryFinal", "UngroupedOverloads", "InlineMeSuggester", "UnusedVariable"})
public abstract class CodedOutputStream {
/** The buffer size used in {@link #newInstance(OutputStream)}. */
private static final int DEFAULT_BUFFER_SIZE;
static {
int bufferSize = 50 * 1024;
try {
String bufferSizeConfig = ConfigUtil.getString("otel.experimental.otlp.buffer-size", "");
if (!bufferSizeConfig.isEmpty()) {
bufferSize = Integer.parseInt(bufferSizeConfig);
}
} catch (Throwable t) {
// Ignore.
}
DEFAULT_BUFFER_SIZE = bufferSize;
}
private static final ThreadLocal THREAD_LOCAL_CODED_OUTPUT_STREAM =
new ThreadLocal<>();
/**
* Create a new {@code CodedOutputStream} wrapping the given {@code OutputStream}.
*
* NOTE: The provided {@link OutputStream} MUST NOT retain access or modify
* the provided byte arrays. Doing so may result in corrupted data, which would be difficult to
* debug.
*/
static CodedOutputStream newInstance(final OutputStream output) {
OutputStreamEncoder cos = THREAD_LOCAL_CODED_OUTPUT_STREAM.get();
if (cos == null) {
cos = new OutputStreamEncoder(output);
THREAD_LOCAL_CODED_OUTPUT_STREAM.set(cos);
} else {
cos.reset(output);
}
return cos;
}
// Disallow construction outside of this class.
private CodedOutputStream() {}
/** Write an array of bytes. */
final void writeRawBytes(final byte[] value) throws IOException {
write(value, 0, value.length);
}
// -----------------------------------------------------------------
/** Write an {@code int32} field to the stream. */
// Abstract to avoid overhead of additional virtual method calls.
abstract void writeInt32NoTag(final int value) throws IOException;
/** Write a {@code uint32} field to the stream. */
// Abstract to avoid overhead of additional virtual method calls.
abstract void writeUInt32NoTag(int value) throws IOException;
/** Write a {@code sint32} field to the stream. */
final void writeSInt32NoTag(final int value) throws IOException {
writeUInt32NoTag(encodeZigZag32(value));
}
/** Write a {@code fixed32} field to the stream. */
// Abstract to avoid overhead of additional virtual method calls.
abstract void writeFixed32NoTag(int value) throws IOException;
/** Write a {@code sfixed32} field to the stream. */
final void writeSFixed32NoTag(final int value) throws IOException {
writeFixed32NoTag(value);
}
/** Write an {@code int64} field to the stream. */
final void writeInt64NoTag(final long value) throws IOException {
writeUInt64NoTag(value);
}
/** Write a {@code uint64} field to the stream. */
// Abstract to avoid overhead of additional virtual method calls.
abstract void writeUInt64NoTag(long value) throws IOException;
/** Write a {@code sint64} field to the stream. */
final void writeSInt64NoTag(final long value) throws IOException {
writeUInt64NoTag(encodeZigZag64(value));
}
/** Write a {@code fixed64} field to the stream. */
// Abstract to avoid overhead of additional virtual method calls.
abstract void writeFixed64NoTag(long value) throws IOException;
/** Write a {@code sfixed64} field to the stream. */
final void writeSFixed64NoTag(final long value) throws IOException {
writeFixed64NoTag(value);
}
/** Write a {@code float} field to the stream. */
final void writeFloatNoTag(final float value) throws IOException {
writeFixed32NoTag(Float.floatToRawIntBits(value));
}
/** Write a {@code double} field to the stream. */
final void writeDoubleNoTag(final double value) throws IOException {
writeFixed64NoTag(Double.doubleToRawLongBits(value));
}
/** Write a {@code bool} field to the stream. */
final void writeBoolNoTag(final boolean value) throws IOException {
write((byte) (value ? 1 : 0));
}
/**
* Write an enum field to the stream. The provided value is the numeric value used to represent
* the enum value on the wire (not the enum ordinal value).
*/
final void writeEnumNoTag(final int value) throws IOException {
writeInt32NoTag(value);
}
/** Write a {@code bytes} field to the stream. */
final void writeByteArrayNoTag(final byte[] value) throws IOException {
writeByteArrayNoTag(value, 0, value.length);
}
// =================================================================
abstract void write(byte value) throws IOException;
abstract void write(byte[] value, int offset, int length) throws IOException;
// =================================================================
/** Compute the number of bytes that would be needed to encode a tag. */
static int computeTagSize(final int fieldNumber) {
return computeUInt32SizeNoTag(WireFormat.makeTag(fieldNumber, 0));
}
/**
* Compute the number of bytes that would be needed to encode an {@code int32} field, including
* tag.
*/
static int computeInt32SizeNoTag(final int value) {
if (value >= 0) {
return computeUInt32SizeNoTag(value);
} else {
// Must sign-extend.
return MAX_VARINT_SIZE;
}
}
/** Compute the number of bytes that would be needed to encode a {@code uint32} field. */
static int computeUInt32SizeNoTag(final int value) {
if ((value & (~0 << 7)) == 0) {
return 1;
}
if ((value & (~0 << 14)) == 0) {
return 2;
}
if ((value & (~0 << 21)) == 0) {
return 3;
}
if ((value & (~0 << 28)) == 0) {
return 4;
}
return 5;
}
/** Compute the number of bytes that would be needed to encode an {@code sint32} field. */
static int computeSInt32SizeNoTag(final int value) {
return computeUInt32SizeNoTag(encodeZigZag32(value));
}
/** Compute the number of bytes that would be needed to encode a {@code fixed32} field. */
static int computeFixed32SizeNoTag(@SuppressWarnings("unused") final int unused) {
return FIXED32_SIZE;
}
/** Compute the number of bytes that would be needed to encode an {@code sfixed32} field. */
static int computeSFixed32SizeNoTag(@SuppressWarnings("unused") final int unused) {
return FIXED32_SIZE;
}
/**
* 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 computeUInt64SizeNoTag(value);
}
/**
* Compute the number of bytes that would be needed to encode a {@code uint64} field, including
* tag.
*/
static int computeUInt64SizeNoTag(long value) {
// handle two popular special cases up front ...
if ((value & (~0L << 7)) == 0L) {
return 1;
}
if (value < 0L) {
return 10;
}
// ... leaving us with 8 remaining, which we can divide and conquer
int n = 2;
if ((value & (~0L << 35)) != 0L) {
n += 4;
value >>>= 28;
}
if ((value & (~0L << 21)) != 0L) {
n += 2;
value >>>= 14;
}
if ((value & (~0L << 14)) != 0L) {
n += 1;
}
return n;
}
/** Compute the number of bytes that would be needed to encode an {@code sint64} field. */
static int computeSInt64SizeNoTag(final long value) {
return computeUInt64SizeNoTag(encodeZigZag64(value));
}
/** Compute the number of bytes that would be needed to encode a {@code fixed64} field. */
static int computeFixed64SizeNoTag(@SuppressWarnings("unused") final long unused) {
return FIXED64_SIZE;
}
/** Compute the number of bytes that would be needed to encode an {@code sfixed64} field. */
static int computeSFixed64SizeNoTag(@SuppressWarnings("unused") final long unused) {
return FIXED64_SIZE;
}
/**
* Compute the number of bytes that would be needed to encode a {@code float} field, including
* tag.
*/
static int computeFloatSizeNoTag(@SuppressWarnings("unused") final float unused) {
return FIXED32_SIZE;
}
/**
* Compute the number of bytes that would be needed to encode a {@code double} field, including
* tag.
*/
public static int computeDoubleSizeNoTag(@SuppressWarnings("unused") final double unused) {
return FIXED64_SIZE;
}
/** Compute the number of bytes that would be needed to encode a {@code bool} field. */
public static int computeBoolSizeNoTag(@SuppressWarnings("unused") final boolean unused) {
return 1;
}
/**
* Compute the number of bytes that would be needed to encode an enum field. The provided value is
* the numeric value used to represent the enum value on the wire (not the enum ordinal value).
*/
static int computeEnumSizeNoTag(final int value) {
return computeInt32SizeNoTag(value);
}
/** Compute the number of bytes that would be needed to encode a {@code bytes} field. */
public static int computeByteArraySizeNoTag(final byte[] value) {
return computeLengthDelimitedFieldSize(value.length);
}
/** Compute the number of bytes that would be needed to encode a {@code bytes} field. */
public static int computeByteBufferSizeNoTag(final ByteBuffer value) {
return computeLengthDelimitedFieldSize(value.capacity());
}
static int computeLengthDelimitedFieldSize(int fieldLength) {
return computeUInt32SizeNoTag(fieldLength) + fieldLength;
}
/**
* 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.
*/
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.
*/
static long encodeZigZag64(final long n) {
// Note: the right-shift must be arithmetic
return (n << 1) ^ (n >> 63);
}
// =================================================================
/**
* Flushes the stream and forces any buffered bytes to be written. This does not flush the
* underlying OutputStream.
*/
abstract void flush() throws IOException;
// =================================================================
/** Write a {@code bytes} field to the stream. */
abstract void writeByteArrayNoTag(final byte[] value, final int offset, final int length)
throws IOException;
abstract void writeByteBufferNoTag(final ByteBuffer value) throws IOException;
// =================================================================
/** Abstract base class for buffered encoders. */
private abstract static class AbstractBufferedEncoder extends CodedOutputStream {
final byte[] buffer;
final int limit;
int position;
int totalBytesWritten;
AbstractBufferedEncoder(int bufferSize) {
this.buffer = new byte[bufferSize];
this.limit = buffer.length;
}
/**
* This method does not perform bounds checking on the array. Checking array bounds is the
* responsibility of the caller.
*/
final void buffer(byte value) {
buffer[position++] = value;
totalBytesWritten++;
}
/**
* This method does not perform bounds checking on the array. Checking array bounds is the
* responsibility of the caller.
*/
final void bufferUInt32NoTag(int value) {
while (true) {
if ((value & ~0x7F) == 0) {
buffer[position++] = (byte) value;
totalBytesWritten++;
return;
} else {
buffer[position++] = (byte) ((value & 0x7F) | 0x80);
totalBytesWritten++;
value >>>= 7;
}
}
}
/**
* This method does not perform bounds checking on the array. Checking array bounds is the
* responsibility of the caller.
*/
final void bufferUInt64NoTag(long value) {
while (true) {
if ((value & ~0x7FL) == 0) {
buffer[position++] = (byte) value;
totalBytesWritten++;
return;
} else {
buffer[position++] = (byte) (((int) value & 0x7F) | 0x80);
totalBytesWritten++;
value >>>= 7;
}
}
}
/**
* This method does not perform bounds checking on the array. Checking array bounds is the
* responsibility of the caller.
*/
final void bufferFixed32NoTag(int value) {
buffer[position++] = (byte) (value & 0xFF);
buffer[position++] = (byte) ((value >> 8) & 0xFF);
buffer[position++] = (byte) ((value >> 16) & 0xFF);
buffer[position++] = (byte) ((value >> 24) & 0xFF);
totalBytesWritten += FIXED32_SIZE;
}
/**
* This method does not perform bounds checking on the array. Checking array bounds is the
* responsibility of the caller.
*/
final void bufferFixed64NoTag(long value) {
buffer[position++] = (byte) (value & 0xFF);
buffer[position++] = (byte) ((value >> 8) & 0xFF);
buffer[position++] = (byte) ((value >> 16) & 0xFF);
buffer[position++] = (byte) ((value >> 24) & 0xFF);
buffer[position++] = (byte) ((int) (value >> 32) & 0xFF);
buffer[position++] = (byte) ((int) (value >> 40) & 0xFF);
buffer[position++] = (byte) ((int) (value >> 48) & 0xFF);
buffer[position++] = (byte) ((int) (value >> 56) & 0xFF);
totalBytesWritten += FIXED64_SIZE;
}
}
/**
* An {@link CodedOutputStream} that decorates an {@link OutputStream}. It performs internal
* buffering to optimize writes to the {@link OutputStream}.
*/
private static final class OutputStreamEncoder extends AbstractBufferedEncoder {
private OutputStream out;
OutputStreamEncoder(OutputStream out) {
super(DEFAULT_BUFFER_SIZE);
this.out = out;
}
void reset(OutputStream out) {
this.out = out;
position = 0;
totalBytesWritten = 0;
}
@Override
void writeByteArrayNoTag(final byte[] value, int offset, int length) throws IOException {
writeUInt32NoTag(length);
write(value, offset, length);
}
@Override
void writeByteBufferNoTag(final ByteBuffer value) throws IOException {
writeUInt32NoTag(value.capacity());
if (value.hasArray()) {
write(value.array(), value.arrayOffset(), value.capacity());
} else {
write((ByteBuffer) value.duplicate().clear());
}
}
void write(ByteBuffer value) throws IOException {
int length = value.remaining();
if (limit - position >= length) {
// We have room in the current buffer.
value.get(buffer, position, length);
position += length;
totalBytesWritten += length;
} else {
// Write extends past current buffer. Fill the rest of this buffer and
// flush.
final int bytesWritten = limit - position;
value.get(buffer, position, bytesWritten);
length -= bytesWritten;
position = limit;
totalBytesWritten += bytesWritten;
doFlush();
// Now deal with the rest.
// Since we have an output stream, this is our buffer
// and buffer offset == 0
while (length > limit) {
// Copy data into the buffer before writing it to OutputStream.
value.get(buffer, 0, limit);
out.write(buffer, 0, limit);
length -= limit;
totalBytesWritten += limit;
}
value.get(buffer, 0, length);
position = length;
totalBytesWritten += length;
}
}
@Override
void write(byte value) throws IOException {
if (position == limit) {
doFlush();
}
buffer(value);
}
@Override
void writeInt32NoTag(int value) throws IOException {
if (value >= 0) {
writeUInt32NoTag(value);
} else {
// Must sign-extend.
writeUInt64NoTag(value);
}
}
@Override
void writeUInt32NoTag(int value) throws IOException {
flushIfNotAvailable(MAX_VARINT32_SIZE);
bufferUInt32NoTag(value);
}
@Override
void writeFixed32NoTag(final int value) throws IOException {
flushIfNotAvailable(FIXED32_SIZE);
bufferFixed32NoTag(value);
}
@Override
void writeUInt64NoTag(long value) throws IOException {
flushIfNotAvailable(MAX_VARINT_SIZE);
bufferUInt64NoTag(value);
}
@Override
void writeFixed64NoTag(final long value) throws IOException {
flushIfNotAvailable(FIXED64_SIZE);
bufferFixed64NoTag(value);
}
@Override
void flush() throws IOException {
if (position > 0) {
// Flush the buffer.
doFlush();
}
}
@Override
void write(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;
totalBytesWritten += 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;
totalBytesWritten += bytesWritten;
doFlush();
// 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.
out.write(value, offset, length);
}
totalBytesWritten += length;
}
}
private void flushIfNotAvailable(int requiredSize) throws IOException {
if (limit - position < requiredSize) {
doFlush();
}
}
private void doFlush() throws IOException {
out.write(buffer, 0, position);
position = 0;
}
}
}