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