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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.car.builtin.os.SharedMemoryHelper;
22 import android.os.Parcel;
23 import android.os.ParcelFileDescriptor;
24 import android.os.Parcelable;
25 import android.os.SharedMemory;
26 import android.util.Slog;
27 
28 import java.io.IOException;
29 import java.lang.ref.WeakReference;
30 import java.lang.reflect.Field;
31 import java.lang.reflect.Method;
32 import java.util.concurrent.Callable;
33 
34 /**
35  * Utility to pass any {@code Parcelable} through binder with automatic conversion into shared
36  * memory when data size is too big.
37  *
38  * <p> This class can work by itself but child class can be useful to use a custom class to
39  * interface with C++ world. For such usage, child class will only add its own {@code CREATOR} impl
40  * and a constructor taking {@code Parcel in}.
41  * <p>For stable AIDL, this class also provides two methods for serialization {@link
42  * #toLargeParcelable(Parcelable)} and deserialization
43  * {@link #reconstructStableAIDLParcelable(Parcelable, boolean)}. Plz check included test for the
44  * usage.
45  *
46  * @hide
47  */
48 public class LargeParcelable extends LargeParcelableBase {
49     /**
50      * Stable AIDL Parcelable should have this member with {@code ParcelFileDescriptor} to support
51      * bigger payload passing over shared memory.
52      */
53     public static final String STABLE_AIDL_SHARED_MEMORY_MEMBER = "sharedMemoryFd";
54     /**
55      * Stable AIDL Parcelable has {@code readFromParcel(Parcel)} public method.
56      */
57     public static final String STABLE_AIDL_PARCELABLE_READ_FROM_PARCEL = "readFromParcel";
58 
59     private static final String TAG = LargeParcelable.class.getSimpleName();
60     private static final boolean DBG_PAYLOAD = false;
61     private static final boolean DBG_STABLE_AIDL_CLASS = false;
62 
63     // cannot set this final even if it is set only once in constructor as set is done in
64     // deserialize call.
65     private @Nullable Parcelable mParcelable;
66 
67     // This is shared across thread. As this is per class, use volatile to avoid adding
68     // separate lock. If additional static volatile is added, a lock should be added.
69     private static volatile WeakReference<ClassLoader> sClassLoader = null;
70 
71     /**
72      * Sets {@code ClassLoader} for loading the {@Code Parcelable}. This should be done before
73      * getting binder call. Default classloader may not recognize the Parcelable passed and relevant
74      * classloader like package classloader should be set before getting any binder data.
75      */
setClassLoader(ClassLoader loader)76     public static void setClassLoader(ClassLoader loader) {
77         sClassLoader = new WeakReference<>(loader);
78     }
79 
LargeParcelable(Parcel in)80     public LargeParcelable(Parcel in) {
81         super(in);
82     }
83 
LargeParcelable(Parcelable parcelable)84     public LargeParcelable(Parcelable parcelable) {
85         mParcelable = parcelable;
86     }
87 
88     /**
89      * Returns {@code Parcelable} carried by this instance.
90      */
getParcelable()91     public Parcelable getParcelable() {
92         return mParcelable;
93     }
94 
95     @Override
serialize(@onNull Parcel dest, int flags)96     protected void serialize(@NonNull Parcel dest, int flags) {
97         int startPosition;
98         if (DBG_PAYLOAD) {
99             startPosition = dest.dataPosition();
100         }
101         dest.writeParcelable(mParcelable, flags);
102         if (DBG_PAYLOAD) {
103             Slog.d(TAG, "serialize-payload, start:" + startPosition
104                     + " size:" + (dest.dataPosition() - startPosition));
105         }
106     }
107 
108     @Override
serializeNullPayload(@onNull Parcel dest)109     protected void serializeNullPayload(@NonNull Parcel dest) {
110         int startPosition;
111         if (DBG_PAYLOAD) {
112             startPosition = dest.dataPosition();
113         }
114         dest.writeParcelable(null, 0);
115         if (DBG_PAYLOAD) {
116             Slog.d(TAG, "serializeNullPayload-payload, start:" + startPosition
117                     + " size:" + (dest.dataPosition() - startPosition));
118         }
119     }
120 
121     @Override
deserialize(@onNull Parcel src)122     protected void deserialize(@NonNull Parcel src) {
123         // default class loader does not work as it may not be in boot class path.
124         ClassLoader loader = (sClassLoader == null) ? null : sClassLoader.get();
125         int startPosition;
126         if (DBG_PAYLOAD) {
127             startPosition = src.dataPosition();
128         }
129         mParcelable = src.readParcelable(loader);
130         if (DBG_PAYLOAD) {
131             Slog.d(TAG, "deserialize-payload, start:" + startPosition
132                     + " size:" + (src.dataPosition() - startPosition)
133                     + " mParcelable:" + mParcelable);
134         }
135     }
136 
137     public static final @NonNull Parcelable.Creator<LargeParcelable> CREATOR =
138             new Parcelable.Creator<LargeParcelable>() {
139                 @Override
140                 public LargeParcelable[] newArray(int size) {
141                     return new LargeParcelable[size];
142                 }
143 
144                 @Override
145                 public LargeParcelable createFromParcel(@NonNull Parcel in) {
146                     return new LargeParcelable(in);
147                 }
148             };
149 
150     /**
151      * @see #toLargeParcelable(Parcelable, Callable<Parcelable>)
152      */
153     @Nullable
toLargeParcelable(@ullable Parcelable p)154     public static Parcelable toLargeParcelable(@Nullable Parcelable p) {
155         return toLargeParcelable(p, null);
156     }
157 
158     /**
159      * Prepare a {@code Parcelable} defined from Stable AIDL to be able to sent through binder.
160      *
161      * <p>The {@code Parcelable} should have a public member having name of
162      * {@link #STABLE_AIDL_SHARED_MEMORY_MEMBER} with {@code ParcelFileDescriptor} type.
163      *
164      * <p>If payload size is big, the input would be serialized to a shared memory file and a
165      * an empty {@code Parcelable} with only the file descriptor set would be returned. If the
166      * payload size is small enough to be sent across binder or the input already contains a shared
167      * memory file, the original input would be returned.
168      *
169      * @param p {@code Parcelable} the input to convert that might contain large data.
170      * @param constructEmptyParcelable a callable to create an empty Parcelable with the same type
171      *      as input. If this is null, the default initializer for the input type would be used.
172      * @return a {@code Parcelable} that could be sent through binder despite memory limitation.
173      */
174     @Nullable
toLargeParcelable( @ullable Parcelable p, @Nullable Callable<Parcelable> constructEmptyParcelable)175     public static Parcelable toLargeParcelable(
176             @Nullable Parcelable p, @Nullable Callable<Parcelable> constructEmptyParcelable) {
177         if (p == null) {
178             return null;
179         }
180         Class parcelableClass = p.getClass();
181         if (DBG_STABLE_AIDL_CLASS) {
182             Slog.d(TAG, "toLargeParcelable stable AIDL Parcelable:"
183                     + parcelableClass.getSimpleName());
184         }
185         Field field;
186         ParcelFileDescriptor sharedMemoryFd;
187         try {
188             field = parcelableClass.getField(STABLE_AIDL_SHARED_MEMORY_MEMBER);
189             sharedMemoryFd = (ParcelFileDescriptor) field.get(p);
190         } catch (Exception e) {
191             throw new IllegalArgumentException("Cannot access " + STABLE_AIDL_SHARED_MEMORY_MEMBER,
192                     e);
193         }
194         if (sharedMemoryFd != null) {
195             return p;
196         }
197         Parcel dataParcel = Parcel.obtain();
198         p.writeToParcel(dataParcel, 0);
199         int payloadSize = dataParcel.dataSize();
200         if (payloadSize <= LargeParcelableBase.MAX_DIRECT_PAYLOAD_SIZE) {
201             // direct path, no re-write to shared memory.
202             if (DBG_PAYLOAD) {
203                 Slog.d(TAG, "toLargeParcelable send directly, payload size:" + payloadSize);
204             }
205             return p;
206         }
207         SharedMemory memory = LargeParcelableBase.serializeParcelToSharedMemory(dataParcel);
208         dataParcel.recycle();
209 
210         try {
211             sharedMemoryFd = SharedMemoryHelper.createParcelFileDescriptor(memory);
212         } catch (IOException e) {
213             throw new IllegalArgumentException("unable to duplicate shared memory fd", e);
214         }
215 
216         Parcelable emptyPayload;
217         if (constructEmptyParcelable != null) {
218             try {
219                 emptyPayload = constructEmptyParcelable.call();
220             } catch (Exception e) {
221                 throw new IllegalArgumentException("Cannot use Parcelable constructor", e);
222             }
223         } else {
224             try {
225                 emptyPayload = (Parcelable) parcelableClass.newInstance();
226             } catch (Exception e) {
227                 throw new IllegalArgumentException("Cannot access Parcelable constructor", e);
228             }
229         }
230 
231         try {
232             field.set(emptyPayload, sharedMemoryFd);
233         } catch (Exception e) {
234             throw new IllegalArgumentException("Cannot access Parcelable member for FD", e);
235         }
236 
237         return emptyPayload;
238     }
239 
240     /**
241      * Reconstructs {@code Parcelable} defined from Stable AIDL. It should have a {@link
242      * ParcelFileDescriptor} member named {@link #STABLE_AIDL_SHARED_MEMORY_MEMBER} and will create
243      * a new {@code Parcelable} if the shared memory portion is not null. If there is no shared
244      * memory, it will return the original {@code Parcelable p} as it is.
245      *
246      * @param p                Original {@code Parcelable} containing the payload.
247      * @param keepSharedMemory Whether to keep created shared memory in the returned {@code
248      *                         Parcelable}. Set to {@code true} if this {@code Parcelable} is sent
249      *                         across binder repeatedly.
250      * @return a new {@code Parcelable} if payload read from shared memory or old one if payload
251      * is small enough.
252      */
reconstructStableAIDLParcelable(@ullable Parcelable p, boolean keepSharedMemory)253     public static @Nullable Parcelable reconstructStableAIDLParcelable(@Nullable Parcelable p,
254             boolean keepSharedMemory) {
255         if (p == null) {
256             return null;
257         }
258         Class parcelableClass = p.getClass();
259         if (DBG_STABLE_AIDL_CLASS) {
260             Slog.d(TAG, "reconstructStableAIDLParcelable stable AIDL Parcelable:"
261                     + parcelableClass.getSimpleName());
262         }
263         ParcelFileDescriptor sharedMemoryFd = null;
264         Field fieldSharedMemory;
265         try {
266             fieldSharedMemory = parcelableClass.getField(STABLE_AIDL_SHARED_MEMORY_MEMBER);
267             sharedMemoryFd = (ParcelFileDescriptor) fieldSharedMemory.get(p);
268         } catch (Exception e) {
269             throw new IllegalArgumentException("Cannot access " + STABLE_AIDL_SHARED_MEMORY_MEMBER,
270                     e);
271         }
272         if (sharedMemoryFd == null) {
273             if (DBG_PAYLOAD) {
274                 Slog.d(TAG, "reconstructStableAIDLParcelable null shared memory");
275             }
276             return p;
277         }
278         Parcel in = null;
279         Parcelable retParcelable;
280         try {
281             // SharedMemory.fromFileDescriptor take ownership, so we need to dupe to keep
282             // sharedMemoryFd the Parcelable valid.
283             SharedMemory memory = SharedMemory.fromFileDescriptor(sharedMemoryFd.dup());
284             in = LargeParcelableBase.copyFromSharedMemory(memory);
285             retParcelable = (Parcelable) parcelableClass.newInstance();
286             // runs retParcelable.readFromParcel(in)
287             Method readMethod = parcelableClass.getMethod(STABLE_AIDL_PARCELABLE_READ_FROM_PARCEL,
288                     new Class[]{Parcel.class});
289             readMethod.invoke(retParcelable, in);
290             if (keepSharedMemory) {
291                 fieldSharedMemory.set(retParcelable, sharedMemoryFd);
292             }
293             if (DBG_PAYLOAD) {
294                 Slog.d(TAG, "reconstructStableAIDLParcelable read shared memory, data size:"
295                         + in.dataPosition());
296             }
297         } catch (Exception e) {
298             throw new IllegalArgumentException("Cannot access Parcelable constructor/method", e);
299         } finally {
300             if (in != null) {
301                 in.recycle();
302             }
303         }
304         return retParcelable;
305     }
306 }
307