chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/CoreImpl.java

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.mojo.system.impl;

import android.os.ParcelFileDescriptor;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;

import org.chromium.mojo.system.Core;
import org.chromium.mojo.system.DataPipe;
import org.chromium.mojo.system.DataPipe.ConsumerHandle;
import org.chromium.mojo.system.DataPipe.ProducerHandle;
import org.chromium.mojo.system.Handle;
import org.chromium.mojo.system.MessagePipeHandle;
import org.chromium.mojo.system.MojoException;
import org.chromium.mojo.system.MojoResult;
import org.chromium.mojo.system.Pair;
import org.chromium.mojo.system.ResultAnd;
import org.chromium.mojo.system.RunLoop;
import org.chromium.mojo.system.SharedBufferHandle;
import org.chromium.mojo.system.SharedBufferHandle.DuplicateOptions;
import org.chromium.mojo.system.SharedBufferHandle.MapFlags;
import org.chromium.mojo.system.UntypedHandle;
import org.chromium.mojo.system.Watcher;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;

/** Implementation of {@link Core}. */
@JNINamespace("mojo::android")
public class CoreImpl implements Core {
    /** Discard flag for the |MojoReadData| operation. */
    private static final int MOJO_READ_DATA_FLAG_DISCARD = 1 << 1;

    /** the size of a handle, in bytes. */
    private static final int HANDLE_SIZE = 8;

    /** the size of a flag, in bytes. */
    private static final int FLAG_SIZE = 4;

    /** The mojo handle for an invalid handle. */
    static final long INVALID_HANDLE = 0;

    private static class LazyHolder {
        private static final Core INSTANCE = new CoreImpl();
    }

    /** The run loop for the current thread. */
    private final ThreadLocal<BaseRunLoop> mCurrentRunLoop = new ThreadLocal<BaseRunLoop>();

    /** The offset needed to get an aligned buffer. */
    private final int mByteBufferOffset;

    /**
     * @return the instance.
     */
    public static Core getInstance() {
        return LazyHolder.INSTANCE;
    }

    private CoreImpl() {
        // Fix for the ART runtime, before:
        // https://android.googlesource.com/platform/libcore/+/fb6c80875a8a8d0a9628562f89c250b6a962e824%5E!/
        // This assumes consistent allocation.
        mByteBufferOffset =
                CoreImplJni.get()
                        .getNativeBufferOffset(CoreImpl.this, ByteBuffer.allocateDirect(8), 8);
    }

    /**
     * @see Core#getTimeTicksNow()
     */
    @Override
    public long getTimeTicksNow() {
        return CoreImplJni.get().getTimeTicksNow(CoreImpl.this);
    }

    /**
     * @see Core#createMessagePipe(MessagePipeHandle.CreateOptions)
     */
    @Override
    public Pair<MessagePipeHandle, MessagePipeHandle> createMessagePipe(
            MessagePipeHandle.CreateOptions options) {
        ByteBuffer optionsBuffer = null;
        if (options != null) {
            optionsBuffer = allocateDirectBuffer(8);
            optionsBuffer.putInt(0, 8);
            optionsBuffer.putInt(4, options.getFlags().getFlags());
        }
        ResultAnd<RawHandlePair> result =
                CoreImplJni.get().createMessagePipe(CoreImpl.this, optionsBuffer);
        if (result.getMojoResult() != MojoResult.OK) {
            throw new MojoException(result.getMojoResult());
        }
        return Pair.<MessagePipeHandle, MessagePipeHandle>create(
                new MessagePipeHandleImpl(this, result.getValue().first),
                new MessagePipeHandleImpl(this, result.getValue().second));
    }

    /**
     * @see Core#createDataPipe(DataPipe.CreateOptions)
     */
    @Override
    public Pair<ProducerHandle, ConsumerHandle> createDataPipe(DataPipe.CreateOptions options) {
        ByteBuffer optionsBuffer = null;
        if (options != null) {
            optionsBuffer = allocateDirectBuffer(16);
            optionsBuffer.putInt(0, 16);
            optionsBuffer.putInt(4, options.getFlags().getFlags());
            optionsBuffer.putInt(8, options.getElementNumBytes());
            optionsBuffer.putInt(12, options.getCapacityNumBytes());
        }
        ResultAnd<RawHandlePair> result =
                CoreImplJni.get().createDataPipe(CoreImpl.this, optionsBuffer);
        if (result.getMojoResult() != MojoResult.OK) {
            throw new MojoException(result.getMojoResult());
        }
        return Pair.<ProducerHandle, ConsumerHandle>create(
                new DataPipeProducerHandleImpl(this, result.getValue().first),
                new DataPipeConsumerHandleImpl(this, result.getValue().second));
    }

    /**
     * @see Core#createSharedBuffer(SharedBufferHandle.CreateOptions, long)
     */
    @Override
    public SharedBufferHandle createSharedBuffer(
            SharedBufferHandle.CreateOptions options, long numBytes) {
        ByteBuffer optionsBuffer = null;
        if (options != null) {
            optionsBuffer = allocateDirectBuffer(8);
            optionsBuffer.putInt(0, 8);
            optionsBuffer.putInt(4, options.getFlags().getFlags());
        }
        ResultAnd<Long> result =
                CoreImplJni.get().createSharedBuffer(CoreImpl.this, optionsBuffer, numBytes);
        if (result.getMojoResult() != MojoResult.OK) {
            throw new MojoException(result.getMojoResult());
        }
        return new SharedBufferHandleImpl(this, result.getValue());
    }

    /**
     * @see org.chromium.mojo.system.Core#acquireNativeHandle(long)
     */
    @Override
    public UntypedHandle acquireNativeHandle(long handle) {
        return new UntypedHandleImpl(this, handle);
    }

    /**
     * @see org.chromium.mojo.system.Core#wrapFileDescriptor(ParcelFileDescriptor)
     */
    @Override
    public UntypedHandle wrapFileDescriptor(ParcelFileDescriptor fd) {
        long releasedHandle = CoreImplJni.get().createPlatformHandle(fd.detachFd());
        return acquireNativeHandle(releasedHandle);
    }

    /**
     * @see Core#getWatcher()
     */
    @Override
    public Watcher getWatcher() {
        return new WatcherImpl();
    }

    /**
     * @see Core#createDefaultRunLoop()
     */
    @Override
    public RunLoop createDefaultRunLoop() {
        if (mCurrentRunLoop.get() != null) {
            throw new MojoException(MojoResult.FAILED_PRECONDITION);
        }
        BaseRunLoop runLoop = new BaseRunLoop(this);
        mCurrentRunLoop.set(runLoop);
        return runLoop;
    }

    /**
     * @see Core#getCurrentRunLoop()
     */
    @Override
    public RunLoop getCurrentRunLoop() {
        return mCurrentRunLoop.get();
    }

    /** Remove the current run loop. */
    void clearCurrentRunLoop() {
        mCurrentRunLoop.remove();
    }

    int closeWithResult(long mojoHandle) {
        return CoreImplJni.get().close(CoreImpl.this, mojoHandle);
    }

    void close(long mojoHandle) {
        int mojoResult = CoreImplJni.get().close(CoreImpl.this, mojoHandle);
        if (mojoResult != MojoResult.OK) {
            throw new MojoException(mojoResult);
        }
    }

    HandleSignalsState queryHandleSignalsState(long mojoHandle) {
        ByteBuffer buffer = allocateDirectBuffer(8);
        int result = CoreImplJni.get().queryHandleSignalsState(CoreImpl.this, mojoHandle, buffer);
        if (result != MojoResult.OK) throw new MojoException(result);
        return new HandleSignalsState(
                new HandleSignals(buffer.getInt(0)), new HandleSignals(buffer.getInt(4)));
    }

    /**
     * @see MessagePipeHandle#writeMessage(ByteBuffer, List, MessagePipeHandle.WriteFlags)
     */
    void writeMessage(
            MessagePipeHandleImpl pipeHandle,
            ByteBuffer bytes,
            List<? extends Handle> handles,
            MessagePipeHandle.WriteFlags flags) {
        ByteBuffer handlesBuffer = null;
        if (handles != null && !handles.isEmpty()) {
            handlesBuffer = allocateDirectBuffer(handles.size() * HANDLE_SIZE);
            for (Handle handle : handles) {
                // NOTE: Handles are closed by native code regardless of whether writeMessage()
                // succeeds below, so we unconditionally release them here.
                handlesBuffer.putLong(handle.releaseNativeHandle());
            }
            handlesBuffer.position(0);
        }
        int mojoResult =
                CoreImplJni.get()
                        .writeMessage(
                                CoreImpl.this,
                                pipeHandle.getMojoHandle(),
                                bytes,
                                bytes == null ? 0 : bytes.limit(),
                                handlesBuffer,
                                flags.getFlags());
        if (mojoResult != MojoResult.OK) {
            throw new MojoException(mojoResult);
        }
    }

    /**
     * @see MessagePipeHandle#readMessage(MessagePipeHandle.ReadFlags)
     */
    ResultAnd<MessagePipeHandle.ReadMessageResult> readMessage(
            MessagePipeHandleImpl handle, MessagePipeHandle.ReadFlags flags) {
        ResultAnd<MessagePipeHandle.ReadMessageResult> result =
                CoreImplJni.get()
                        .readMessage(CoreImpl.this, handle.getMojoHandle(), flags.getFlags());
        if (result.getMojoResult() != MojoResult.OK
                && result.getMojoResult() != MojoResult.SHOULD_WAIT) {
            throw new MojoException(result.getMojoResult());
        }

        MessagePipeHandle.ReadMessageResult readResult = result.getValue();
        long[] rawHandles = readResult.mRawHandles;
        if (rawHandles != null && rawHandles.length != 0) {
            readResult.mHandles = new ArrayList<UntypedHandle>(rawHandles.length);
            for (long rawHandle : rawHandles) {
                readResult.mHandles.add(new UntypedHandleImpl(this, rawHandle));
            }
        } else {
            readResult.mHandles = new ArrayList<UntypedHandle>(0);
        }

        return result;
    }

    /**
     * @see ConsumerHandle#discardData(int, DataPipe.ReadFlags)
     */
    int discardData(DataPipeConsumerHandleImpl handle, int numBytes, DataPipe.ReadFlags flags) {
        ResultAnd<Integer> result =
                CoreImplJni.get()
                        .readData(
                                CoreImpl.this,
                                handle.getMojoHandle(),
                                null,
                                numBytes,
                                flags.getFlags() | MOJO_READ_DATA_FLAG_DISCARD);
        if (result.getMojoResult() != MojoResult.OK) {
            throw new MojoException(result.getMojoResult());
        }
        return result.getValue();
    }

    /**
     * @see ConsumerHandle#readData(ByteBuffer, DataPipe.ReadFlags)
     */
    ResultAnd<Integer> readData(
            DataPipeConsumerHandleImpl handle, ByteBuffer elements, DataPipe.ReadFlags flags) {
        ResultAnd<Integer> result =
                CoreImplJni.get()
                        .readData(
                                CoreImpl.this,
                                handle.getMojoHandle(),
                                elements,
                                elements == null ? 0 : elements.capacity(),
                                flags.getFlags());
        if (result.getMojoResult() != MojoResult.OK
                && result.getMojoResult() != MojoResult.SHOULD_WAIT) {
            throw new MojoException(result.getMojoResult());
        }
        if (result.getMojoResult() == MojoResult.OK) {
            if (elements != null) {
                elements.limit(result.getValue());
            }
        }
        return result;
    }

    /**
     * @see ConsumerHandle#beginReadData(int, DataPipe.ReadFlags)
     */
    ByteBuffer beginReadData(
            DataPipeConsumerHandleImpl handle, int numBytes, DataPipe.ReadFlags flags) {
        ResultAnd<ByteBuffer> result =
                CoreImplJni.get()
                        .beginReadData(
                                CoreImpl.this, handle.getMojoHandle(), numBytes, flags.getFlags());
        if (result.getMojoResult() != MojoResult.OK) {
            throw new MojoException(result.getMojoResult());
        }
        return result.getValue().asReadOnlyBuffer();
    }

    /**
     * @see ConsumerHandle#endReadData(int)
     */
    void endReadData(DataPipeConsumerHandleImpl handle, int numBytesRead) {
        int result =
                CoreImplJni.get().endReadData(CoreImpl.this, handle.getMojoHandle(), numBytesRead);
        if (result != MojoResult.OK) {
            throw new MojoException(result);
        }
    }

    /**
     * @see ProducerHandle#writeData(ByteBuffer, DataPipe.WriteFlags)
     */
    ResultAnd<Integer> writeData(
            DataPipeProducerHandleImpl handle, ByteBuffer elements, DataPipe.WriteFlags flags) {
        return CoreImplJni.get()
                .writeData(
                        CoreImpl.this,
                        handle.getMojoHandle(),
                        elements,
                        elements.limit(),
                        flags.getFlags());
    }

    /**
     * @see ProducerHandle#beginWriteData(int, DataPipe.WriteFlags)
     */
    ByteBuffer beginWriteData(
            DataPipeProducerHandleImpl handle, int numBytes, DataPipe.WriteFlags flags) {
        ResultAnd<ByteBuffer> result =
                CoreImplJni.get()
                        .beginWriteData(
                                CoreImpl.this, handle.getMojoHandle(), numBytes, flags.getFlags());
        if (result.getMojoResult() != MojoResult.OK) {
            throw new MojoException(result.getMojoResult());
        }
        return result.getValue();
    }

    /**
     * @see ProducerHandle#endWriteData(int)
     */
    void endWriteData(DataPipeProducerHandleImpl handle, int numBytesWritten) {
        int result =
                CoreImplJni.get()
                        .endWriteData(CoreImpl.this, handle.getMojoHandle(), numBytesWritten);
        if (result != MojoResult.OK) {
            throw new MojoException(result);
        }
    }

    /**
     * @see SharedBufferHandle#duplicate(DuplicateOptions)
     */
    SharedBufferHandle duplicate(SharedBufferHandleImpl handle, DuplicateOptions options) {
        ByteBuffer optionsBuffer = null;
        if (options != null) {
            optionsBuffer = allocateDirectBuffer(8);
            optionsBuffer.putInt(0, 8);
            optionsBuffer.putInt(4, options.getFlags().getFlags());
        }
        ResultAnd<Long> result =
                CoreImplJni.get().duplicate(CoreImpl.this, handle.getMojoHandle(), optionsBuffer);
        if (result.getMojoResult() != MojoResult.OK) {
            throw new MojoException(result.getMojoResult());
        }
        return new SharedBufferHandleImpl(this, result.getValue());
    }

    /**
     * @see SharedBufferHandle#map(long, long, MapFlags)
     */
    ByteBuffer map(SharedBufferHandleImpl handle, long offset, long numBytes, MapFlags flags) {
        ResultAnd<ByteBuffer> result =
                CoreImplJni.get()
                        .map(
                                CoreImpl.this,
                                handle.getMojoHandle(),
                                offset,
                                numBytes,
                                flags.getFlags());
        if (result.getMojoResult() != MojoResult.OK) {
            throw new MojoException(result.getMojoResult());
        }
        return result.getValue();
    }

    /**
     * @see SharedBufferHandle#unmap(ByteBuffer)
     */
    void unmap(ByteBuffer buffer) {
        int result = CoreImplJni.get().unmap(CoreImpl.this, buffer);
        if (result != MojoResult.OK) {
            throw new MojoException(result);
        }
    }

    /**
     * @return the mojo handle associated to the given handle, considering invalid handles.
     */
    private long getMojoHandle(Handle handle) {
        if (handle.isValid()) {
            return ((HandleBase) handle).getMojoHandle();
        }
        return 0;
    }

    private static boolean isUnrecoverableError(int code) {
        switch (code) {
            case MojoResult.OK:
            case MojoResult.DEADLINE_EXCEEDED:
            case MojoResult.CANCELLED:
            case MojoResult.FAILED_PRECONDITION:
                return false;
            default:
                return true;
        }
    }

    private static int filterMojoResultForWait(int code) {
        if (isUnrecoverableError(code)) {
            throw new MojoException(code);
        }
        return code;
    }

    private ByteBuffer allocateDirectBuffer(int capacity) {
        ByteBuffer buffer = ByteBuffer.allocateDirect(capacity + mByteBufferOffset);
        if (mByteBufferOffset != 0) {
            buffer.position(mByteBufferOffset);
            buffer = buffer.slice();
        }
        return buffer.order(ByteOrder.nativeOrder());
    }

    @CalledByNative
    private static ResultAnd<ByteBuffer> newResultAndBuffer(int mojoResult, ByteBuffer buffer) {
        return new ResultAnd<>(mojoResult, buffer);
    }

    /**
     * Trivial alias for Pair<Long, Long>. This is needed because our jni generator is unable
     * to handle class that contains space.
     */
    static final class RawHandlePair extends Pair<Long, Long> {
        public RawHandlePair(Long first, Long second) {
            super(first, second);
        }
    }

    @CalledByNative
    private static ResultAnd<MessagePipeHandle.ReadMessageResult> newReadMessageResult(
            int mojoResult, byte[] data, long[] rawHandles) {
        MessagePipeHandle.ReadMessageResult result = new MessagePipeHandle.ReadMessageResult();
        if (mojoResult == MojoResult.OK) {
            result.mData = data;
            result.mRawHandles = rawHandles;
        }
        return new ResultAnd<>(mojoResult, result);
    }

    @CalledByNative
    private static ResultAnd<Integer> newResultAndInteger(int mojoResult, int numBytesRead) {
        return new ResultAnd<>(mojoResult, numBytesRead);
    }

    @CalledByNative
    private static ResultAnd<Long> newResultAndLong(int mojoResult, long value) {
        return new ResultAnd<>(mojoResult, value);
    }

    @CalledByNative
    private static ResultAnd<RawHandlePair> newNativeCreationResult(
            int mojoResult, long mojoHandle1, long mojoHandle2) {
        return new ResultAnd<>(mojoResult, new RawHandlePair(mojoHandle1, mojoHandle2));
    }

    @NativeMethods
    interface Natives {
        long getTimeTicksNow(CoreImpl caller);

        ResultAnd<RawHandlePair> createMessagePipe(CoreImpl caller, ByteBuffer optionsBuffer);

        ResultAnd<RawHandlePair> createDataPipe(CoreImpl caller, ByteBuffer optionsBuffer);

        ResultAnd<Long> createSharedBuffer(
                CoreImpl caller, ByteBuffer optionsBuffer, long numBytes);

        int close(CoreImpl caller, long mojoHandle);

        int queryHandleSignalsState(
                CoreImpl caller, long mojoHandle, ByteBuffer signalsStateBuffer);

        int writeMessage(
                CoreImpl caller,
                long mojoHandle,
                ByteBuffer bytes,
                int numBytes,
                ByteBuffer handlesBuffer,
                int flags);

        ResultAnd<MessagePipeHandle.ReadMessageResult> readMessage(
                CoreImpl caller, long mojoHandle, int flags);

        ResultAnd<Integer> readData(
                CoreImpl caller, long mojoHandle, ByteBuffer elements, int elementsSize, int flags);

        ResultAnd<ByteBuffer> beginReadData(
                CoreImpl caller, long mojoHandle, int numBytes, int flags);

        int endReadData(CoreImpl caller, long mojoHandle, int numBytesRead);

        ResultAnd<Integer> writeData(
                CoreImpl caller, long mojoHandle, ByteBuffer elements, int limit, int flags);

        ResultAnd<ByteBuffer> beginWriteData(
                CoreImpl caller, long mojoHandle, int numBytes, int flags);

        int endWriteData(CoreImpl caller, long mojoHandle, int numBytesWritten);

        ResultAnd<Long> duplicate(CoreImpl caller, long mojoHandle, ByteBuffer optionsBuffer);

        ResultAnd<ByteBuffer> map(
                CoreImpl caller, long mojoHandle, long offset, long numBytes, int flags);

        int unmap(CoreImpl caller, ByteBuffer buffer);

        int getNativeBufferOffset(CoreImpl caller, ByteBuffer buffer, int alignment);

        long createPlatformHandle(int fd);
    }
}