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.os.Parcel; 26 import android.os.Parcelable; 27 import android.os.SharedMemory; 28 import android.system.ErrnoException; 29 import android.util.Log; 30 import android.util.Slog; 31 32 import com.android.internal.annotations.GuardedBy; 33 34 import java.io.Closeable; 35 import java.nio.ByteBuffer; 36 37 /** 38 * Base class to allow passing {@code Parcelable} over binder directly or through shared memory if 39 * payload size is too big. 40 * 41 * <p>Child class should inherit this to use this or use {@link LargeParcelable} class. 42 * 43 * <p>Parcelized data will have following elements 44 * <ul> 45 * <li>@Nullable Parcelable 46 * <li>@Nullable SharedMemory which include serialized Parcelable if non-null. This will be set 47 * only when the previous Parcelable is null or this also can be null for no data case. 48 * </ul> 49 * 50 * @hide 51 */ 52 public abstract class LargeParcelableBase implements Parcelable, Closeable { 53 /** Payload size bigger than this value will be passed over shared memory. */ 54 public static final int MAX_DIRECT_PAYLOAD_SIZE = 4096; 55 private static final String TAG = LargeParcelable.class.getSimpleName(); 56 57 private static final boolean DBG_PAYLOAD = Log.isLoggable(TAG, Log.DEBUG); 58 private static final int DBG_DUMP_LENGTH = 16; 59 60 private static final int NULL_PAYLOAD = 0; 61 private static final int NONNULL_PAYLOAD = 1; 62 private static final int FD_HEADER = 0; 63 64 private final Object mLock = new Object(); 65 @GuardedBy("mLock") 66 private @Nullable SharedMemory mSharedMemory; 67 68 /** 69 * Serialize (=write Parcelable into given Parcel) a {@code Parcelable} child class wants to 70 * pass over binder call. 71 */ serialize(@onNull Parcel dest, int flags)72 protected abstract void serialize(@NonNull Parcel dest, int flags); 73 74 /** 75 * Serialize null payload to the given {@code Parcel}. For {@code Parcelable}, this can be 76 * simply {@code dest.writeParcelable(null)} but non-Parcelable should have other way to 77 * mark that there is no payload. 78 */ serializeNullPayload(@onNull Parcel dest)79 protected abstract void serializeNullPayload(@NonNull Parcel dest); 80 81 /** 82 * Read a {@code Parcelable} from the given {@code Parcel}. 83 */ deserialize(@onNull Parcel src)84 protected abstract void deserialize(@NonNull Parcel src); 85 LargeParcelableBase()86 public LargeParcelableBase() { 87 } 88 LargeParcelableBase(Parcel in)89 public LargeParcelableBase(Parcel in) { 90 // Make this compatible with stable AIDL 91 // payload size + Parcelable / payload + 1:has shared memory + 0 + file 92 // 0:no shared memory 93 // 0 + file makes it compatible with ParcelFileDescrpitor 94 // file contains: 95 // file size + Parcelable / payload + 0 96 int startPosition = in.dataPosition(); 97 int totalPayloadSize = in.readInt(); 98 deserialize(in); 99 int sharedMemoryPosition = in.dataPosition(); 100 boolean hasSharedMemory = (in.readInt() != NULL_PAYLOAD); 101 if (hasSharedMemory) { 102 int fdHeader = in.readInt(); 103 if (fdHeader != FD_HEADER) { 104 throw new IllegalArgumentException( 105 "Invalid data, wrong fdHeader, expected 0 while got " + fdHeader); 106 } 107 SharedMemory memory = SharedMemory.CREATOR.createFromParcel(in); 108 deserializeSharedMemoryAndClose(memory); 109 } 110 in.setDataPosition(startPosition + totalPayloadSize); 111 if (DBG_PAYLOAD) { 112 Slog.d(TAG, "Read, start:" + startPosition + " totalPayloadSize:" + totalPayloadSize 113 + " sharedMemoryPosition:" + sharedMemoryPosition 114 + " hasSharedMemory:" + hasSharedMemory + " dataAvail:" + in.dataAvail()); 115 } 116 } 117 118 @Override writeToParcel(@onNull Parcel dest, int flags)119 public void writeToParcel(@NonNull Parcel dest, int flags) { 120 int startPosition = dest.dataPosition(); 121 SharedMemory sharedMemory; 122 synchronized (mLock) { 123 sharedMemory = mSharedMemory; 124 } 125 int totalPayloadSize = 0; 126 if (sharedMemory != null) { 127 // optimized path for resending the same Parcelable multiple times with already 128 // created shared memory 129 totalPayloadSize = serializeMemoryFdOrPayloadToParcel(dest, flags, sharedMemory); 130 if (DBG_PAYLOAD) { 131 Slog.d(TAG, "Write, reusing shared memory, start:" + startPosition 132 + " totalPayloadSize:" + totalPayloadSize); 133 } 134 return; 135 } 136 137 // dataParcel is the parcel that would be serialized to the shared memory file. 138 Parcel dataParcel = Parcel.obtain(); 139 totalPayloadSize = serializeMemoryFdOrPayloadToParcel(dataParcel, flags, null); 140 141 boolean noSharedMemory = totalPayloadSize <= MAX_DIRECT_PAYLOAD_SIZE; 142 boolean hasNonNullPayload = true; 143 if (noSharedMemory) { 144 if (DBG_PAYLOAD) { 145 Slog.d(TAG, "not using shared memory"); 146 } 147 dest.appendFrom(dataParcel, 0, totalPayloadSize); 148 dataParcel.recycle(); 149 } else { 150 if (DBG_PAYLOAD) { 151 Slog.d(TAG, "using shared memory"); 152 } 153 sharedMemory = serializeParcelToSharedMemory(dataParcel); 154 dataParcel.recycle(); 155 synchronized (mLock) { 156 // If it is already set, let sharedMemory go and GV will close it later. 157 // This is ok as this kind of race should not happen often. 158 if (mSharedMemory != null) { 159 mSharedMemory = sharedMemory; 160 } 161 } 162 163 totalPayloadSize = serializeMemoryFdOrPayloadToParcel(dest, flags, sharedMemory); 164 } 165 if (DBG_PAYLOAD) { 166 Slog.d(TAG, "Write, start:" + startPosition + " totalPayloadSize:" + totalPayloadSize 167 + " hasNonNullPayload:" + hasNonNullPayload 168 + " hasSharedMemory:" + !noSharedMemory + " dataSize:" + dest.dataSize()); 169 } 170 } 171 updatePayloadSize(Parcel dest, int startPosition)172 private int updatePayloadSize(Parcel dest, int startPosition) { 173 int lastPosition = dest.dataPosition(); 174 int totalPayloadSize = lastPosition - startPosition; 175 dest.setDataPosition(startPosition); 176 dest.writeInt(totalPayloadSize); 177 dest.setDataPosition(lastPosition); 178 dest.setDataSize(lastPosition); 179 return totalPayloadSize; 180 } 181 182 // Write shared memory in compatible way with ParcelFileDescriptor writeSharedMemoryCompatibleToParcel(Parcel dest, SharedMemory memory, int flags)183 private void writeSharedMemoryCompatibleToParcel(Parcel dest, SharedMemory memory, int flags) { 184 // dest.writeParcelable() adds class type which makes it incompatible with C++. 185 if (memory == null) { 186 dest.writeInt(NULL_PAYLOAD); 187 return; 188 } 189 // non-null case 190 dest.writeInt(NONNULL_PAYLOAD); 191 dest.writeInt(FD_HEADER); // additional header for ParcelFileDescriptor 192 memory.writeToParcel(dest, flags); 193 } 194 195 @Override 196 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) describeContents()197 public int describeContents() { 198 return 0; 199 } 200 201 /** 202 * {@inheritDoc} 203 * 204 * <p>Close the underlying shared memory for this. This can be called multiple times safely. 205 * When this is not called explicitly, it will be closed when this instance is GCed. 206 * Calling this can be useful when many instances are created frequently. 207 * 208 * <p>If underlying payload is changed, the client should call this before sending it over 209 * binder as sending it over binder can keep shared memory generated from the previous binder 210 * call. 211 */ 212 @Override close()213 public void close() { 214 SharedMemory sharedMemory = null; 215 synchronized (mLock) { 216 sharedMemory = mSharedMemory; 217 mSharedMemory = null; 218 } 219 if (sharedMemory != null) { 220 sharedMemory.close(); 221 } 222 } 223 serializeParcelToSharedMemory(Parcel p)224 protected static SharedMemory serializeParcelToSharedMemory(Parcel p) { 225 SharedMemory memory = null; 226 ByteBuffer buffer = null; 227 int size = p.dataSize(); 228 try { 229 memory = SharedMemory.create(LargeParcelableBase.class.getSimpleName(), size); 230 buffer = memory.mapReadWrite(); 231 byte[] data = p.marshall(); 232 buffer.put(data, 0, size); 233 if (DBG_PAYLOAD) { 234 int dumpSize = Math.min(DBG_DUMP_LENGTH, data.length); 235 StringBuilder bd = new StringBuilder(); 236 bd.append("marshalled:"); 237 for (int i = 0; i < dumpSize; i++) { 238 bd.append(data[i]); 239 if (i != dumpSize - 1) { 240 bd.append(','); 241 } 242 } 243 bd.append("=memory:"); 244 for (int i = 0; i < dumpSize; i++) { 245 bd.append(buffer.get(i)); 246 if (i != dumpSize - 1) { 247 bd.append(','); 248 } 249 } 250 Slog.d(TAG, bd.toString()); 251 } 252 if (!memory.setProtect(PROT_READ)) { 253 memory.close(); 254 throw new SecurityException("Failed to set read-only protection on shared memory"); 255 } 256 } catch (ErrnoException e) { 257 throw new IllegalArgumentException("Failed to use shared memory", e); 258 } catch (Exception e) { 259 throw new IllegalArgumentException("failed to serialize", e); 260 } finally { 261 if (buffer != null) { 262 SharedMemory.unmap(buffer); 263 } 264 } 265 266 return memory; 267 } 268 copyFromSharedMemory(SharedMemory memory)269 protected static Parcel copyFromSharedMemory(SharedMemory memory) { 270 ByteBuffer buffer = null; 271 Parcel in = Parcel.obtain(); 272 try { 273 buffer = memory.mapReadOnly(); 274 // TODO(b/188781089) find way to avoid this additional copy 275 byte[] payload = new byte[buffer.limit()]; 276 buffer.get(payload); 277 in.unmarshall(payload, 0, payload.length); 278 in.setDataPosition(0); 279 if (DBG_PAYLOAD) { 280 int dumpSize = Math.min(DBG_DUMP_LENGTH, payload.length); 281 StringBuilder bd = new StringBuilder(); 282 bd.append("unmarshalled:"); 283 int parcelStartPosition = in.dataPosition(); 284 byte[] fromParcel = in.marshall(); 285 for (int i = 0; i < dumpSize; i++) { 286 bd.append(fromParcel[i]); 287 if (i != dumpSize - 1) bd.append(','); 288 } 289 bd.append("=startPosition:"); 290 bd.append(parcelStartPosition); 291 bd.append("=memory:"); 292 for (int i = 0; i < dumpSize; i++) { 293 bd.append(buffer.get(i)); 294 if (i != dumpSize - 1) bd.append(','); 295 } 296 bd.append("=interim_payload:"); 297 for (int i = 0; i < dumpSize; i++) { 298 bd.append(payload[i]); 299 if (i != dumpSize - 1) bd.append(','); 300 } 301 Slog.d(TAG, bd.toString()); 302 in.setDataPosition(parcelStartPosition); 303 } 304 } catch (ErrnoException e) { 305 throw new IllegalArgumentException("cannot create Parcelable from SharedMemory", e); 306 } catch (Exception e) { 307 throw new IllegalArgumentException("failed to deserialize", e); 308 } finally { 309 if (buffer != null) { 310 SharedMemory.unmap(buffer); 311 } 312 } 313 return in; 314 } 315 deserializeSharedMemoryAndClose(SharedMemory memory)316 private void deserializeSharedMemoryAndClose(SharedMemory memory) { 317 // The shared memory file contains a serialized largeParcelable. 318 // size + payload + 0 (no shared memory). 319 Parcel in = null; 320 try { 321 in = copyFromSharedMemory(memory); 322 // Even if we don't need the file size, we have to read it from the parcel to advance 323 // the data position. 324 int fileSize = in.readInt(); 325 if (DBG_PAYLOAD) { 326 Slog.d(TAG, "file size in shared memory file: " + fileSize); 327 } 328 deserialize(in); 329 // There is an additional 0 in the parcel, but we ignore that. 330 } finally { 331 memory.close(); 332 if (in != null) { 333 in.recycle(); 334 } 335 } 336 } 337 338 // If sharedMemory is not null, serialize null payload and shared memory to parcel. 339 // Otherwise, serialize the actual payload to parcel. serializeMemoryFdOrPayloadToParcel( Parcel dest, int flags, @Nullable SharedMemory sharedMemory)340 private int serializeMemoryFdOrPayloadToParcel( 341 Parcel dest, int flags, @Nullable SharedMemory sharedMemory) { 342 int startPosition = dest.dataPosition(); 343 dest.writeInt(0); // payload size 344 345 if (sharedMemory != null) { 346 serializeNullPayload(dest); 347 writeSharedMemoryCompatibleToParcel(dest, sharedMemory, flags); 348 } else { 349 serialize(dest, flags); 350 writeSharedMemoryCompatibleToParcel(dest, null, flags); 351 } 352 353 return updatePayloadSize(dest, startPosition); 354 } 355 } 356