/* * Copyright (C) 2021 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 com.android.car.internal; import static android.system.OsConstants.PROT_READ; import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.os.SharedMemory; import android.system.ErrnoException; import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import java.io.Closeable; import java.nio.ByteBuffer; /** * Base class to allow passing {@code Parcelable} over binder directly or through shared memory if * payload size is too big. * *
Child class should inherit this to use this or use {@link LargeParcelable} class. * *
Parcelized data will have following elements *
Close the underlying shared memory for this. This can be called multiple times safely. * When this is not called explicitly, it will be closed when this instance is GCed. * Calling this can be useful when many instances are created frequently. * *
If underlying payload is changed, the client should call this before sending it over * binder as sending it over binder can keep shared memory generated from the previous binder * call. */ @Override public void close() { SharedMemory sharedMemory = null; synchronized (mLock) { sharedMemory = mSharedMemory; mSharedMemory = null; } if (sharedMemory != null) { sharedMemory.close(); } } protected static SharedMemory serializeParcelToSharedMemory(Parcel p) { SharedMemory memory = null; ByteBuffer buffer = null; int size = p.dataSize(); try { memory = SharedMemory.create(LargeParcelableBase.class.getSimpleName(), size); buffer = memory.mapReadWrite(); byte[] data = p.marshall(); buffer.put(data, 0, size); if (DBG_PAYLOAD) { int dumpSize = Math.min(DBG_DUMP_LENGTH, data.length); StringBuilder bd = new StringBuilder(); bd.append("marshalled:"); for (int i = 0; i < dumpSize; i++) { bd.append(data[i]); if (i != dumpSize - 1) { bd.append(','); } } bd.append("=memory:"); for (int i = 0; i < dumpSize; i++) { bd.append(buffer.get(i)); if (i != dumpSize - 1) { bd.append(','); } } Slog.d(TAG, bd.toString()); } if (!memory.setProtect(PROT_READ)) { memory.close(); throw new SecurityException("Failed to set read-only protection on shared memory"); } } catch (ErrnoException e) { throw new IllegalArgumentException("Failed to use shared memory", e); } catch (Exception e) { throw new IllegalArgumentException("failed to serialize", e); } finally { if (buffer != null) { SharedMemory.unmap(buffer); } } return memory; } protected static Parcel copyFromSharedMemory(SharedMemory memory) { ByteBuffer buffer = null; Parcel in = Parcel.obtain(); try { buffer = memory.mapReadOnly(); // TODO(b/188781089) find way to avoid this additional copy byte[] payload = new byte[buffer.limit()]; buffer.get(payload); in.unmarshall(payload, 0, payload.length); in.setDataPosition(0); if (DBG_PAYLOAD) { int dumpSize = Math.min(DBG_DUMP_LENGTH, payload.length); StringBuilder bd = new StringBuilder(); bd.append("unmarshalled:"); int parcelStartPosition = in.dataPosition(); byte[] fromParcel = in.marshall(); for (int i = 0; i < dumpSize; i++) { bd.append(fromParcel[i]); if (i != dumpSize - 1) bd.append(','); } bd.append("=startPosition:"); bd.append(parcelStartPosition); bd.append("=memory:"); for (int i = 0; i < dumpSize; i++) { bd.append(buffer.get(i)); if (i != dumpSize - 1) bd.append(','); } bd.append("=interim_payload:"); for (int i = 0; i < dumpSize; i++) { bd.append(payload[i]); if (i != dumpSize - 1) bd.append(','); } Slog.d(TAG, bd.toString()); in.setDataPosition(parcelStartPosition); } } catch (ErrnoException e) { throw new IllegalArgumentException("cannot create Parcelable from SharedMemory", e); } catch (Exception e) { throw new IllegalArgumentException("failed to deserialize", e); } finally { if (buffer != null) { SharedMemory.unmap(buffer); } } return in; } private void deserializeSharedMemoryAndClose(SharedMemory memory) { // The shared memory file contains a serialized largeParcelable. // size + payload + 0 (no shared memory). Parcel in = null; try { in = copyFromSharedMemory(memory); // Even if we don't need the file size, we have to read it from the parcel to advance // the data position. int fileSize = in.readInt(); if (DBG_PAYLOAD) { Slog.d(TAG, "file size in shared memory file: " + fileSize); } deserialize(in); // There is an additional 0 in the parcel, but we ignore that. } finally { memory.close(); if (in != null) { in.recycle(); } } } // If sharedMemory is not null, serialize null payload and shared memory to parcel. // Otherwise, serialize the actual payload to parcel. private int serializeMemoryFdOrPayloadToParcel( Parcel dest, int flags, @Nullable SharedMemory sharedMemory) { int startPosition = dest.dataPosition(); dest.writeInt(0); // payload size if (sharedMemory != null) { serializeNullPayload(dest); writeSharedMemoryCompatibleToParcel(dest, sharedMemory, flags); } else { serialize(dest, flags); writeSharedMemoryCompatibleToParcel(dest, null, flags); } return updatePayloadSize(dest, startPosition); } }