/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.os; import static android.system.OsConstants.MAP_SHARED; import static android.system.OsConstants.PROT_READ; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.system.ErrnoException; import android.system.Os; import android.util.Log; import com.android.internal.util.Preconditions; import java.io.FileDescriptor; import java.nio.ByteBuffer; import java.nio.DirectByteBuffer; import java.util.ArrayList; import java.util.List; /** * Provides utilities for dealing with HidlMemory. * * @hide */ public final class HidlMemoryUtil { static private final String TAG = "HidlMemoryUtil"; private HidlMemoryUtil() { } /** * Copies a byte-array into a new Ashmem region and return it as HidlMemory. * The returned instance owns the underlying file descriptors, and the client should generally * call close on it when no longer in use (or otherwise, when the object gets destroyed it will * be closed). * * @param input The input byte array. * @return A HidlMemory instance, containing a copy of the input. */ public static @NonNull HidlMemory byteArrayToHidlMemory(@NonNull byte[] input) { return byteArrayToHidlMemory(input, null); } /** * Copies a byte-array into a new Ashmem region and return it as HidlMemory. * The returned instance owns the underlying file descriptors, and the client should generally * call close on it when no longer in use (or otherwise, when the object gets destroyed it will * be closed). * * @param input The input byte array. * @param name An optional name for the ashmem region. * @return A HidlMemory instance, containing a copy of the input. */ public static @NonNull HidlMemory byteArrayToHidlMemory(@NonNull byte[] input, @Nullable String name) { Preconditions.checkNotNull(input); if (input.length == 0) { return new HidlMemory("ashmem", 0, null); } try (SharedMemory shmem = SharedMemory.create(name != null ? name : "", input.length)) { ByteBuffer buffer = shmem.mapReadWrite(); buffer.put(input); shmem.unmap(buffer); return sharedMemoryToHidlMemory(shmem); } catch (ErrnoException e) { throw new RuntimeException(e); } } /** * Copies a byte list into a new Ashmem region and return it as HidlMemory. * The returned instance owns the underlying file descriptors, and the client should generally * call close on it when no longer in use (or otherwise, when the object gets destroyed it will * be closed). * * @param input The input byte list. * @return A HidlMemory instance, containing a copy of the input. */ public static @NonNull HidlMemory byteListToHidlMemory(@NonNull List input) { return byteListToHidlMemory(input, null); } /** * Copies a byte list into a new Ashmem region and return it as HidlMemory. * The returned instance owns the underlying file descriptors, and the client should generally * call close on it when no longer in use (or otherwise, when the object gets destroyed it will * be closed). * * @param input The input byte list. * @param name An optional name for the ashmem region. * @return A HidlMemory instance, containing a copy of the input. */ public static @NonNull HidlMemory byteListToHidlMemory(@NonNull List input, @Nullable String name) { Preconditions.checkNotNull(input); if (input.isEmpty()) { return new HidlMemory("ashmem", 0, null); } try (SharedMemory shmem = SharedMemory.create(name != null ? name : "", input.size())) { ByteBuffer buffer = shmem.mapReadWrite(); for (Byte b : input) { buffer.put(b); } shmem.unmap(buffer); return sharedMemoryToHidlMemory(shmem); } catch (ErrnoException e) { throw new RuntimeException(e); } } /** * Copies all data from a HidlMemory instance into a byte array. * * @param mem The HidlMemory instance. Must be of name "ashmem" and of size that doesn't exceed * {@link Integer#MAX_VALUE}. * @return A byte array, containing a copy of the input. */ public static @NonNull byte[] hidlMemoryToByteArray(@NonNull HidlMemory mem) { Preconditions.checkNotNull(mem); Preconditions.checkArgumentInRange(mem.getSize(), 0L, (long) Integer.MAX_VALUE, "Memory size"); Preconditions.checkArgument(mem.getSize() == 0 || mem.getName().equals("ashmem"), "Unsupported memory type: %s", mem.getName()); if (mem.getSize() == 0) { return new byte[0]; } ByteBuffer buffer = getBuffer(mem); byte[] result = new byte[buffer.remaining()]; buffer.get(result); return result; } /** * Copies all data from a HidlMemory instance into a byte list. * * @param mem The HidlMemory instance. Must be of name "ashmem" and of size that doesn't exceed * {@link Integer#MAX_VALUE}. * @return A byte list, containing a copy of the input. */ @SuppressLint("ConcreteCollection") public static @NonNull ArrayList hidlMemoryToByteList(@NonNull HidlMemory mem) { Preconditions.checkNotNull(mem); Preconditions.checkArgumentInRange(mem.getSize(), 0L, (long) Integer.MAX_VALUE, "Memory size"); Preconditions.checkArgument(mem.getSize() == 0 || mem.getName().equals("ashmem"), "Unsupported memory type: %s", mem.getName()); if (mem.getSize() == 0) { return new ArrayList<>(); } ByteBuffer buffer = getBuffer(mem); ArrayList result = new ArrayList<>(buffer.remaining()); while (buffer.hasRemaining()) { result.add(buffer.get()); } return result; } /** * Converts a SharedMemory to a HidlMemory without copying. * * @param shmem The shared memory object. Null means "empty" and will still result in a non-null * return value. * @return The HidlMemory instance. */ @NonNull public static HidlMemory sharedMemoryToHidlMemory(@Nullable SharedMemory shmem) { if (shmem == null) { return new HidlMemory("ashmem", 0, null); } return fileDescriptorToHidlMemory(shmem.getFileDescriptor(), shmem.getSize()); } /** * Converts a FileDescriptor to a HidlMemory without copying. * * @param fd The FileDescriptor object. Null is allowed if size is 0 and will still result in * a non-null return value. * @param size The size of the memory buffer. * @return The HidlMemory instance. */ @NonNull public static HidlMemory fileDescriptorToHidlMemory(@Nullable FileDescriptor fd, int size) { Preconditions.checkArgument(fd != null || size == 0); if (fd == null) { return new HidlMemory("ashmem", 0, null); } try { NativeHandle handle = new NativeHandle(Os.dup(fd), true); return new HidlMemory("ashmem", size, handle); } catch (ErrnoException e) { throw new RuntimeException(e); } } private static ByteBuffer getBuffer(@NonNull HidlMemory mem) { try { final int size = (int) mem.getSize(); if (size == 0) { return ByteBuffer.wrap(new byte[0]); } NativeHandle handle = mem.getHandle(); final long address = Os.mmap(0, size, PROT_READ, MAP_SHARED, handle.getFileDescriptor(), 0); return new DirectByteBuffer(size, address, handle.getFileDescriptor(), () -> { try { Os.munmap(address, size); } catch (ErrnoException e) { Log.wtf(TAG, e); } }, true); } catch (ErrnoException e) { throw new RuntimeException(e); } } }