// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.mojo.bindings; import org.chromium.mojo.bindings.Interface.Proxy.Handler; import org.chromium.mojo.system.Core; import org.chromium.mojo.system.Handle; import org.chromium.mojo.system.MessagePipeHandle; import org.chromium.mojo.system.Pair; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; /** * Helper class to encode a mojo struct. It keeps track of the output buffer, resizing it as needed. * It also keeps track of the associated handles, and the offset of the current data section. */ public class Encoder { /** * Container class for all state that must be shared between the main encoder and any used sub * encoder. */ private static class EncoderState { /** * The core used to encode interfaces. */ public final Core core; /** * The ByteBuffer to which the message will be encoded. */ public ByteBuffer byteBuffer; /** * The list of encountered handles. */ public final List handles = new ArrayList(); /** * The current absolute position for the next data section. */ public int dataEnd; /** * @param core the |Core| implementation used to generate handles. Only used if the data * structure being encoded contains interfaces, can be |null| otherwise. * @param bufferSize A hint on the size of the message. Used to build the initial byte * buffer. */ private EncoderState(Core core, int bufferSize) { assert bufferSize % BindingsHelper.ALIGNMENT == 0; this.core = core; byteBuffer = ByteBuffer.allocateDirect( bufferSize > 0 ? bufferSize : INITIAL_BUFFER_SIZE); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); dataEnd = 0; } /** * Claim the given amount of memory at the end of the buffer, resizing it if needed. */ public void claimMemory(int size) { dataEnd += size; growIfNeeded(); } /** * Grow the associated ByteBuffer if needed. */ private void growIfNeeded() { if (byteBuffer.capacity() >= dataEnd) { return; } int targetSize = byteBuffer.capacity() * 2; while (targetSize < dataEnd) { targetSize *= 2; } ByteBuffer newBuffer = ByteBuffer.allocateDirect(targetSize); newBuffer.order(ByteOrder.nativeOrder()); byteBuffer.position(0); byteBuffer.limit(byteBuffer.capacity()); newBuffer.put(byteBuffer); byteBuffer = newBuffer; } } /** * Default initial size of the data buffer. This must be a multiple of 8 bytes. */ private static final int INITIAL_BUFFER_SIZE = 1024; /** * Base offset in the byte buffer for writing. */ private int mBaseOffset; /** * The encoder state shared by the main encoder and all its sub-encoder. */ private final EncoderState mEncoderState; /** * Returns the result message. */ public Message getMessage() { mEncoderState.byteBuffer.position(0); mEncoderState.byteBuffer.limit(mEncoderState.dataEnd); return new Message(mEncoderState.byteBuffer, mEncoderState.handles); } /** * Constructor. * * @param core the |Core| implementation used to generate handles. Only used if the data * structure being encoded contains interfaces, can be |null| otherwise. * @param sizeHint A hint on the size of the message. Used to build the initial byte buffer. */ public Encoder(Core core, int sizeHint) { this(new EncoderState(core, sizeHint)); } /** * Private constructor for sub-encoders. */ private Encoder(EncoderState bufferInformation) { mEncoderState = bufferInformation; mBaseOffset = bufferInformation.dataEnd; } /** * Returns a new encoder that will append to the current buffer. */ public Encoder getEncoderAtDataOffset(DataHeader dataHeader) { Encoder result = new Encoder(mEncoderState); result.encode(dataHeader); return result; } /** * Encode a {@link DataHeader} and claim the amount of memory required for the data section * (resizing the buffer if required). */ public void encode(DataHeader s) { mEncoderState.claimMemory(BindingsHelper.align(s.size)); encode(s.size, DataHeader.SIZE_OFFSET); encode(s.elementsOrVersion, DataHeader.ELEMENTS_OR_VERSION_OFFSET); } /** * Encode a byte at the given offset. */ public void encode(byte v, int offset) { mEncoderState.byteBuffer.put(mBaseOffset + offset, v); } /** * Encode a boolean at the given offset. */ public void encode(boolean v, int offset, int bit) { if (v) { byte encodedValue = mEncoderState.byteBuffer.get(mBaseOffset + offset); encodedValue |= (byte) (1 << bit); mEncoderState.byteBuffer.put(mBaseOffset + offset, encodedValue); } } /** * Encode a short at the given offset. */ public void encode(short v, int offset) { mEncoderState.byteBuffer.putShort(mBaseOffset + offset, v); } /** * Encode an int at the given offset. */ public void encode(int v, int offset) { mEncoderState.byteBuffer.putInt(mBaseOffset + offset, v); } /** * Encode a float at the given offset. */ public void encode(float v, int offset) { mEncoderState.byteBuffer.putFloat(mBaseOffset + offset, v); } /** * Encode a long at the given offset. */ public void encode(long v, int offset) { mEncoderState.byteBuffer.putLong(mBaseOffset + offset, v); } /** * Encode a double at the given offset. */ public void encode(double v, int offset) { mEncoderState.byteBuffer.putDouble(mBaseOffset + offset, v); } /** * Encode a {@link Struct} at the given offset. */ public void encode(Struct v, int offset, boolean nullable) { if (v == null) { encodeNullPointer(offset, nullable); return; } encodePointerToNextUnclaimedData(offset); v.encode(this); } /** * Encode a {@link Union} at the given offset. */ public void encode(Union v, int offset, boolean nullable) { if (v == null && !nullable) { throw new SerializationException( "Trying to encode a null pointer for a non-nullable type."); } if (v == null) { encode(0L, offset); encode(0L, offset + DataHeader.HEADER_SIZE); return; } v.encode(this, offset); } /** * Encodes a String. */ public void encode(String v, int offset, boolean nullable) { if (v == null) { encodeNullPointer(offset, nullable); return; } final int arrayNullability = nullable ? BindingsHelper.ARRAY_NULLABLE : BindingsHelper.NOTHING_NULLABLE; encode(v.getBytes(Charset.forName("utf8")), offset, arrayNullability, BindingsHelper.UNSPECIFIED_ARRAY_LENGTH); } /** * Encodes a {@link Handle}. */ public void encode(Handle v, int offset, boolean nullable) { if (v == null || !v.isValid()) { encodeInvalidHandle(offset, nullable); } else { encode(mEncoderState.handles.size(), offset); mEncoderState.handles.add(v); } } /** * Encode an {@link Interface}. */ public void encode(T v, int offset, boolean nullable, Interface.Manager manager) { if (v == null) { encodeInvalidHandle(offset, nullable); encode(0, offset + BindingsHelper.SERIALIZED_HANDLE_SIZE); return; } if (mEncoderState.core == null) { throw new UnsupportedOperationException( "The encoder has been created without a Core. It can't encode an interface."); } // If the instance is a proxy, pass the proxy's handle instead of creating a new stub. if (v instanceof Interface.Proxy) { Handler handler = ((Interface.Proxy) v).getProxyHandler(); encode(handler.passHandle(), offset, nullable); encode(handler.getVersion(), offset + BindingsHelper.SERIALIZED_HANDLE_SIZE); return; } Pair handles = mEncoderState.core.createMessagePipe(null); manager.bind(v, handles.first); encode(handles.second, offset, nullable); encode(manager.getVersion(), offset + BindingsHelper.SERIALIZED_HANDLE_SIZE); } /** * Encode an {@link InterfaceRequest}. */ public void encode(InterfaceRequest v, int offset, boolean nullable) { if (v == null) { encodeInvalidHandle(offset, nullable); return; } if (mEncoderState.core == null) { throw new UnsupportedOperationException( "The encoder has been created without a Core. It can't encode an interface."); } encode(v.passHandle(), offset, nullable); } /** * Encode an associated interface. Not yet supported. */ public void encode(AssociatedInterfaceNotSupported v, int offset, boolean nullable) { } /** * Encode an associated interface request. Not yet supported. */ public void encode(AssociatedInterfaceRequestNotSupported v, int offset, boolean nullable) { } /** * Returns an {@link Encoder} suitable for encoding an array of pointer of the given length. */ public Encoder encodePointerArray(int length, int offset, int expectedLength) { return encoderForArray(BindingsHelper.POINTER_SIZE, length, offset, expectedLength); } /** * Returns an {@link Encoder} suitable for encoding an array of union of the given length. */ public Encoder encodeUnionArray(int length, int offset, int expectedLength) { return encoderForArray(BindingsHelper.UNION_SIZE, length, offset, expectedLength); } /** * Encodes an array of booleans. */ public void encode(boolean[] v, int offset, int arrayNullability, int expectedLength) { if (v == null) { encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); return; } if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH && expectedLength != v.length) { throw new SerializationException("Trying to encode a fixed array of incorrect length."); } byte[] bytes = new byte[(v.length + 7) / BindingsHelper.ALIGNMENT]; for (int i = 0; i < bytes.length; ++i) { for (int j = 0; j < BindingsHelper.ALIGNMENT; ++j) { int booleanIndex = BindingsHelper.ALIGNMENT * i + j; if (booleanIndex < v.length && v[booleanIndex]) { bytes[i] |= (byte) (1 << j); } } } encodeByteArray(bytes, v.length, offset); } /** * Encodes an array of bytes. */ public void encode(byte[] v, int offset, int arrayNullability, int expectedLength) { if (v == null) { encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); return; } if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH && expectedLength != v.length) { throw new SerializationException("Trying to encode a fixed array of incorrect length."); } encodeByteArray(v, v.length, offset); } /** * Encodes an array of shorts. */ public void encode(short[] v, int offset, int arrayNullability, int expectedLength) { if (v == null) { encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); return; } encoderForArray(2, v.length, offset, expectedLength).append(v); } /** * Encodes an array of ints. */ public void encode(int[] v, int offset, int arrayNullability, int expectedLength) { if (v == null) { encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); return; } encoderForArray(4, v.length, offset, expectedLength).append(v); } /** * Encodes an array of floats. */ public void encode(float[] v, int offset, int arrayNullability, int expectedLength) { if (v == null) { encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); return; } encoderForArray(4, v.length, offset, expectedLength).append(v); } /** * Encodes an array of longs. */ public void encode(long[] v, int offset, int arrayNullability, int expectedLength) { if (v == null) { encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); return; } encoderForArray(8, v.length, offset, expectedLength).append(v); } /** * Encodes an array of doubles. */ public void encode(double[] v, int offset, int arrayNullability, int expectedLength) { if (v == null) { encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); return; } encoderForArray(8, v.length, offset, expectedLength).append(v); } /** * Encodes an array of {@link Handle}. */ public void encode(Handle[] v, int offset, int arrayNullability, int expectedLength) { if (v == null) { encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); return; } Encoder e = encoderForArray( BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset, expectedLength); for (int i = 0; i < v.length; ++i) { e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, BindingsHelper.isElementNullable(arrayNullability)); } } /** * Encodes an array of {@link Interface}. */ public void encode(T[] v, int offset, int arrayNullability, int expectedLength, Interface.Manager manager) { if (v == null) { encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); return; } Encoder e = encoderForArray( BindingsHelper.SERIALIZED_INTERFACE_SIZE, v.length, offset, expectedLength); for (int i = 0; i < v.length; ++i) { e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_INTERFACE_SIZE * i, BindingsHelper.isElementNullable(arrayNullability), manager); } } public Encoder encoderForMap(int offset) { encodePointerToNextUnclaimedData(offset); return getEncoderAtDataOffset(BindingsHelper.MAP_STRUCT_HEADER); } /** * Encodes a pointer to the next unclaimed memory and returns an encoder suitable to encode an * union at this location. */ public Encoder encoderForUnionPointer(int offset) { encodePointerToNextUnclaimedData(offset); Encoder result = new Encoder(mEncoderState); result.mEncoderState.claimMemory(16); return result; } /** * Encodes an array of {@link InterfaceRequest}. */ public void encode(InterfaceRequest[] v, int offset, int arrayNullability, int expectedLength) { if (v == null) { encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); return; } Encoder e = encoderForArray( BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset, expectedLength); for (int i = 0; i < v.length; ++i) { e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, BindingsHelper.isElementNullable(arrayNullability)); } } /** * Encodes an array of associated interfaces. Not yet supported. */ public void encode(AssociatedInterfaceNotSupported[] v, int offset, int arrayNullability, int expectedLength) {} /** * Encodes an array of associated interface requests. Not yet supported. */ public void encode(AssociatedInterfaceRequestNotSupported[] v, int offset, int arrayNullability, int expectedLength) {} /** * Encodes a null pointer iff the object is nullable, raises an exception * otherwise. */ public void encodeNullPointer(int offset, boolean nullable) { if (!nullable) { throw new SerializationException( "Trying to encode a null pointer for a non-nullable type."); } mEncoderState.byteBuffer.putLong(mBaseOffset + offset, 0); } /** * Encodes an invalid handle iff the object is nullable, raises an exception otherwise. */ public void encodeInvalidHandle(int offset, boolean nullable) { if (!nullable) { throw new SerializationException( "Trying to encode an invalid handle for a non-nullable type."); } mEncoderState.byteBuffer.putInt(mBaseOffset + offset, -1); } /** * Claim the given amount of memory at the end of the buffer, resizing it if needed. */ void claimMemory(int size) { mEncoderState.claimMemory(BindingsHelper.align(size)); } private void encodePointerToNextUnclaimedData(int offset) { encode((long) mEncoderState.dataEnd - (mBaseOffset + offset), offset); } private Encoder encoderForArray( int elementSizeInByte, int length, int offset, int expectedLength) { if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH && expectedLength != length) { throw new SerializationException("Trying to encode a fixed array of incorrect length."); } return encoderForArrayByTotalSize(length * elementSizeInByte, length, offset); } private Encoder encoderForArrayByTotalSize(int byteSize, int length, int offset) { encodePointerToNextUnclaimedData(offset); return getEncoderAtDataOffset( new DataHeader(DataHeader.HEADER_SIZE + byteSize, length)); } private void encodeByteArray(byte[] bytes, int length, int offset) { encoderForArrayByTotalSize(bytes.length, length, offset).append(bytes); } private void append(byte[] v) { mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); mEncoderState.byteBuffer.put(v); } private void append(short[] v) { mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); mEncoderState.byteBuffer.asShortBuffer().put(v); } private void append(int[] v) { mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); mEncoderState.byteBuffer.asIntBuffer().put(v); } private void append(float[] v) { mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); mEncoderState.byteBuffer.asFloatBuffer().put(v); } private void append(double[] v) { mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); mEncoderState.byteBuffer.asDoubleBuffer().put(v); } private void append(long[] v) { mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); mEncoderState.byteBuffer.asLongBuffer().put(v); } }