• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.internal;
18 
19 import static android.system.OsConstants.PROT_READ;
20 
21 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.car.builtin.os.SharedMemoryHelper;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.os.SharedMemory;
29 import android.system.ErrnoException;
30 import android.util.Log;
31 import android.util.Slog;
32 
33 import com.android.internal.annotations.GuardedBy;
34 
35 import java.io.Closeable;
36 import java.io.IOException;
37 import java.nio.ByteBuffer;
38 
39 /**
40  * Base class to allow passing {@code Parcelable} over binder directly or through shared memory if
41  * payload size is too big.
42  *
43  * <p>Child class should inherit this to use this or use {@link LargeParcelable} class.
44  *
45  * <p>Parcelized data will have following elements
46  * <ul>
47  * <li>@Nullable Parcelable
48  * <li>@Nullable SharedMemory which include serialized Parcelable if non-null. This will be set
49  * only when the previous Parcelable is null or this also can be null for no data case.
50  * </ul>
51  *
52  * <p>If the caller sends this class through binder, the caller must close this class after writing
53  * to parcel, unless this class is used as the return value for a binder call. If this is used as
54  * return value, the stored shared memory will be lost unless caller make a copy of the shared
55  * memory file descriptor.
56  *
57  * <p>If the caller receives this class through binder, the caller must close this after reading the
58  * data.
59  */
60 public abstract class LargeParcelableBase implements Parcelable, Closeable {
61     /** Payload size bigger than this value will be passed over shared memory. */
62     public static final int MAX_DIRECT_PAYLOAD_SIZE = 4096;
63     private static final String TAG = LargeParcelable.class.getSimpleName();
64 
65     private static final boolean DBG_PAYLOAD = Log.isLoggable(TAG, Log.DEBUG);
66     private static final int DBG_DUMP_LENGTH = 16;
67 
68     private static final int NULL_PAYLOAD = 0;
69     private static final int NONNULL_PAYLOAD = 1;
70     private static final int FD_HEADER = 0;
71 
72     private final Object mLock = new Object();
73     @GuardedBy("mLock")
74     private @Nullable SharedMemory mSharedMemory;
75 
76     /**
77      * Serialize (=write Parcelable into given Parcel) a {@code Parcelable} child class wants to
78      * pass over binder call.
79      */
serialize(@onNull Parcel dest, int flags)80     protected abstract void serialize(@NonNull Parcel dest, int flags);
81 
82     /**
83      * Serialize null payload to the given {@code Parcel}. For {@code Parcelable}, this can be
84      * simply {@code dest.writeParcelable(null)} but non-Parcelable should have other way to
85      * mark that there is no payload.
86      */
serializeNullPayload(@onNull Parcel dest)87     protected abstract void serializeNullPayload(@NonNull Parcel dest);
88 
89     /**
90      * Read a {@code Parcelable} from the given {@code Parcel}.
91      */
deserialize(@onNull Parcel src)92     protected abstract void deserialize(@NonNull Parcel src);
93 
LargeParcelableBase()94     public LargeParcelableBase() {
95     }
96 
LargeParcelableBase(Parcel in)97     public LargeParcelableBase(Parcel in) {
98         // Make this compatible with stable AIDL
99         // payload size + Parcelable / payload + 1:has shared memory + 0 + file
100         //                                       0:no shared memory
101         // 0 + file makes it compatible with ParcelFileDescrpitor
102         // file contains:
103         // file size + Parcelable / payload + 0
104         int startPosition = in.dataPosition();
105         int totalPayloadSize = in.readInt();
106         deserialize(in);
107         int sharedMemoryPosition = in.dataPosition();
108         boolean hasSharedMemory = (in.readInt() != NULL_PAYLOAD);
109         if (hasSharedMemory) {
110             int fdHeader = in.readInt();
111             if (fdHeader != FD_HEADER) {
112                 throw new IllegalArgumentException(
113                         "Invalid data, wrong fdHeader, expected 0 while got " + fdHeader);
114             }
115             try (SharedMemory memory = SharedMemory.CREATOR.createFromParcel(in)) {
116                 deserializeSharedMemory(memory);
117             }
118         }
119         in.setDataPosition(startPosition + totalPayloadSize);
120         if (DBG_PAYLOAD) {
121             Slog.d(TAG, "Read, start:" + startPosition + " totalPayloadSize:" + totalPayloadSize
122                     + " sharedMemoryPosition:" + sharedMemoryPosition
123                     + " hasSharedMemory:" + hasSharedMemory + " dataAvail:" + in.dataAvail());
124         }
125     }
126 
127     @Override
writeToParcel(@onNull Parcel dest, int flags)128     public void writeToParcel(@NonNull Parcel dest, int flags) {
129         int startPosition = dest.dataPosition();
130         SharedMemory storedSharedMemory;
131         synchronized (mLock) {
132             storedSharedMemory = mSharedMemory;
133         }
134         int totalPayloadSize = 0;
135         if (storedSharedMemory != null) {
136             // optimized path for resending the same Parcelable multiple times with already
137             // created shared memory
138             totalPayloadSize = serializeMemoryFdOrPayloadToParcel(dest, flags, storedSharedMemory);
139             if (DBG_PAYLOAD) {
140                 Slog.d(TAG, "Write, reusing shared memory, start:" + startPosition
141                         + " totalPayloadSize:" + totalPayloadSize);
142             }
143             if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
144                 // If we are writing this as return value, we must clear the stored shared memory
145                 // file otherwise the client does not know when to close it.
146                 storedSharedMemory.close();
147             }
148             return;
149         }
150 
151         // dataParcel is the parcel that would be serialized to the shared memory file.
152         Parcel dataParcel = Parcel.obtain();
153         totalPayloadSize = serializeMemoryFdOrPayloadToParcel(dataParcel, flags, null);
154 
155         boolean noSharedMemory = totalPayloadSize <= MAX_DIRECT_PAYLOAD_SIZE;
156         boolean hasNonNullPayload = true;
157         if (noSharedMemory) {
158             if (DBG_PAYLOAD) {
159                 Slog.d(TAG, "not using shared memory");
160             }
161             dest.appendFrom(dataParcel, 0, totalPayloadSize);
162             dataParcel.recycle();
163         } else {
164             if (DBG_PAYLOAD) {
165                 Slog.d(TAG, "using shared memory");
166             }
167             try (SharedMemory sharedMemory = serializeParcelToSharedMemory(dataParcel)) {
168                 totalPayloadSize = serializeMemoryFdOrPayloadToParcel(dest, flags, sharedMemory);
169 
170                 if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) == 0) {
171                     // Duplicate the file descriptor to store it.
172                     SharedMemory sharedMemoryCopy = SharedMemory.fromFileDescriptor(
173                             SharedMemoryHelper.createParcelFileDescriptor(sharedMemory));
174                     synchronized (mLock) {
175                         // If it is already set, replace the existing stored copy which should be
176                         // the same.
177                         if (mSharedMemory != null) {
178                             mSharedMemory.close();
179                         }
180                         mSharedMemory = sharedMemoryCopy;
181                     }
182                 }
183             } catch (IOException e) {
184                 Slog.e(TAG, "Failed to duplicate shared memory fd", e);
185             } finally {
186                 dataParcel.recycle();
187             }
188         }
189         if (DBG_PAYLOAD) {
190             Slog.d(TAG, "Write, start:" + startPosition + " totalPayloadSize:" + totalPayloadSize
191                     + " hasNonNullPayload:" + hasNonNullPayload
192                     + " hasSharedMemory:" + !noSharedMemory + " dataSize:" + dest.dataSize());
193         }
194     }
195 
updatePayloadSize(Parcel dest, int startPosition)196     private int updatePayloadSize(Parcel dest, int startPosition) {
197         int lastPosition = dest.dataPosition();
198         int totalPayloadSize = lastPosition - startPosition;
199         dest.setDataPosition(startPosition);
200         dest.writeInt(totalPayloadSize);
201         dest.setDataPosition(lastPosition);
202         dest.setDataSize(lastPosition);
203         return totalPayloadSize;
204     }
205 
206     // Write shared memory in compatible way with ParcelFileDescriptor
writeSharedMemoryCompatibleToParcel(Parcel dest, SharedMemory memory, int flags)207     private void writeSharedMemoryCompatibleToParcel(Parcel dest, SharedMemory memory, int flags) {
208         // dest.writeParcelable() adds class type which makes it incompatible with C++.
209         if (memory == null) {
210             dest.writeInt(NULL_PAYLOAD);
211             return;
212         }
213         // non-null case
214         dest.writeInt(NONNULL_PAYLOAD);
215         dest.writeInt(FD_HEADER); // additional header for ParcelFileDescriptor
216         // The file descriptor will be duped, so it is free to close the memory after this.
217         memory.writeToParcel(dest, flags);
218     }
219 
220     @Override
221     @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
describeContents()222     public int describeContents() {
223         return 0;
224     }
225 
226     @Override
finalize()227     protected void finalize() {
228         synchronized (mLock) {
229             if (mSharedMemory != null) {
230                 Slog.e(TAG, "LargeParcelableBase.close is not called before it is GCed");
231             }
232         }
233         close();
234     }
235 
236     /**
237      * {@inheritDoc}
238      *
239      * <p>Close the underlying shared memory for this. This can be called multiple times safely.
240      * When this is not called explicitly, it will be closed when this instance is GCed.
241      * Calling this can be useful when many instances are created frequently.
242      *
243      * <p>If underlying payload is changed, the client should call this before sending it over
244      * binder as sending it over binder can keep shared memory generated from the previous binder
245      * call.
246      */
247     @Override
close()248     public void close() {
249         SharedMemory sharedMemory = null;
250         synchronized (mLock) {
251             sharedMemory = mSharedMemory;
252             mSharedMemory = null;
253         }
254         if (sharedMemory != null) {
255             sharedMemory.close();
256         }
257     }
258 
serializeParcelToSharedMemory(Parcel p)259     protected static SharedMemory serializeParcelToSharedMemory(Parcel p) {
260         SharedMemory memory = null;
261         ByteBuffer buffer = null;
262         int size = p.dataSize();
263         try {
264             memory = SharedMemory.create(LargeParcelableBase.class.getSimpleName(), size);
265             buffer = memory.mapReadWrite();
266             byte[] data = p.marshall();
267             buffer.put(data, 0, size);
268             if (DBG_PAYLOAD) {
269                 int dumpSize = Math.min(DBG_DUMP_LENGTH, data.length);
270                 StringBuilder bd = new StringBuilder();
271                 bd.append("marshalled:");
272                 for (int i = 0; i < dumpSize; i++) {
273                     bd.append(data[i]);
274                     if (i != dumpSize - 1) {
275                         bd.append(',');
276                     }
277                 }
278                 bd.append("=memory:");
279                 for (int i = 0; i < dumpSize; i++) {
280                     bd.append(buffer.get(i));
281                     if (i != dumpSize - 1) {
282                         bd.append(',');
283                     }
284                 }
285                 Slog.d(TAG, bd.toString());
286             }
287             if (!memory.setProtect(PROT_READ)) {
288                 memory.close();
289                 throw new SecurityException("Failed to set read-only protection on shared memory");
290             }
291         } catch (ErrnoException e) {
292             memory.close();
293             throw new IllegalArgumentException("Failed to use shared memory", e);
294         } catch (Exception e) {
295             memory.close();
296             throw new IllegalArgumentException("failed to serialize", e);
297         } finally {
298             if (buffer != null) {
299                 SharedMemory.unmap(buffer);
300             }
301         }
302 
303         return memory;
304     }
305 
copyFromSharedMemory(SharedMemory memory)306     protected static Parcel copyFromSharedMemory(SharedMemory memory) {
307         ByteBuffer buffer = null;
308         Parcel in = Parcel.obtain();
309         try {
310             buffer = memory.mapReadOnly();
311             // TODO(b/188781089) find way to avoid this additional copy
312             byte[] payload = new byte[buffer.limit()];
313             buffer.get(payload);
314             in.unmarshall(payload, 0, payload.length);
315             in.setDataPosition(0);
316             if (DBG_PAYLOAD) {
317                 int dumpSize = Math.min(DBG_DUMP_LENGTH, payload.length);
318                 StringBuilder bd = new StringBuilder();
319                 bd.append("unmarshalled:");
320                 int parcelStartPosition = in.dataPosition();
321                 byte[] fromParcel = in.marshall();
322                 for (int i = 0; i < dumpSize; i++) {
323                     bd.append(fromParcel[i]);
324                     if (i != dumpSize - 1) bd.append(',');
325                 }
326                 bd.append("=startPosition:");
327                 bd.append(parcelStartPosition);
328                 bd.append("=memory:");
329                 for (int i = 0; i < dumpSize; i++) {
330                     bd.append(buffer.get(i));
331                     if (i != dumpSize - 1) bd.append(',');
332                 }
333                 bd.append("=interim_payload:");
334                 for (int i = 0; i < dumpSize; i++) {
335                     bd.append(payload[i]);
336                     if (i != dumpSize - 1) bd.append(',');
337                 }
338                 Slog.d(TAG, bd.toString());
339                 in.setDataPosition(parcelStartPosition);
340             }
341         } catch (ErrnoException e) {
342             throw new IllegalArgumentException("cannot create Parcelable from SharedMemory", e);
343         } catch (Exception e) {
344             throw new IllegalArgumentException("failed to deserialize", e);
345         } finally {
346             if (buffer != null) {
347                 SharedMemory.unmap(buffer);
348             }
349         }
350         return in;
351     }
352 
deserializeSharedMemory(SharedMemory memory)353     private void deserializeSharedMemory(SharedMemory memory) {
354         // The shared memory file contains a serialized largeParcelable.
355         // size + payload + 0 (no shared memory).
356         Parcel in = null;
357         try {
358             in = copyFromSharedMemory(memory);
359             // Even if we don't need the file size, we have to read it from the parcel to advance
360             // the data position.
361             int fileSize = in.readInt();
362             if (DBG_PAYLOAD) {
363                 Slog.d(TAG, "file size in shared memory file: " + fileSize);
364             }
365             deserialize(in);
366             // There is an additional 0 in the parcel, but we ignore that.
367         } finally {
368             if (in != null) {
369                 in.recycle();
370             }
371         }
372     }
373 
374     // If sharedMemory is not null, serialize null payload and shared memory to parcel.
375     // Otherwise, serialize the actual payload to parcel.
serializeMemoryFdOrPayloadToParcel( Parcel dest, int flags, @Nullable SharedMemory sharedMemory)376     private int serializeMemoryFdOrPayloadToParcel(
377             Parcel dest, int flags, @Nullable SharedMemory sharedMemory) {
378         int startPosition = dest.dataPosition();
379         dest.writeInt(0); // payload size
380 
381         if (sharedMemory != null) {
382             serializeNullPayload(dest);
383             writeSharedMemoryCompatibleToParcel(dest, sharedMemory, flags);
384         } else {
385             serialize(dest, flags);
386             writeSharedMemoryCompatibleToParcel(dest, null, flags);
387         }
388 
389         return updatePayloadSize(dest, startPosition);
390     }
391 }
392