1 /* 2 * Copyright 2020 The gRPC Authors 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 io.grpc.binder.internal; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import java.io.ByteArrayInputStream; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import javax.annotation.Nullable; 28 29 /** 30 * An inputstream to serialize a single Android Parcelable object for gRPC calls, with support for 31 * serializing to a native Android Parcel, and for when a Parcelable is sent in-process. 32 * 33 * <p><b>Important:</b> It's not actually possible to marshall a parcelable to raw bytes without 34 * losing data, since a parcelable may contain file descriptors. While this class <i>does</i> 35 * support marshalling into bytes, this is only supported for the purposes of debugging/logging, and 36 * we intentionally don't support unmarshalling back to a parcelable. 37 * 38 * <p>This class really just wraps a Parcelable instance and masquerardes as an inputstream. See 39 * {@code ProtoLiteUtils} for a similar example of this pattern. 40 * 41 * <p>An instance of this class maybe be created from two sources. 42 * 43 * <ul> 44 * <li>To wrap a Parcelable instance we plan to send. 45 * <li>To wrap a Parcelable instance we've just received (and read from a Parcel). 46 * </ul> 47 * 48 * <p>In the first case, we expect to serialize to a {@link Parcel}, with a call to {@link 49 * #writeToParcel}. 50 * 51 * <p>In the second case, we only expect the Parcelable to be fetched (and not re-serialized). 52 * 53 * <p>For in-process gRPC calls, the same InputStream used to send the Parcelable (the first case), 54 * will also be used to parse the parcelable from the stream, in which case we shortcut serializing 55 * internally (possibly skipping it entirely if the instance is immutable). 56 */ 57 final class ParcelableInputStream<P extends Parcelable> extends InputStream { 58 @Nullable private final Parcelable.Creator<P> creator; 59 private final boolean safeToReturnValue; 60 private final P value; 61 62 @Nullable InputStream delegateStream; 63 64 @Nullable P sharableValue; 65 ParcelableInputStream( @ullable Parcelable.Creator<P> creator, P value, boolean safeToReturnValue)66 ParcelableInputStream( 67 @Nullable Parcelable.Creator<P> creator, P value, boolean safeToReturnValue) { 68 this.creator = creator; 69 this.value = value; 70 this.safeToReturnValue = safeToReturnValue; 71 // If we're not given a creator, the value must be safe to return unchanged. 72 checkArgument(creator != null || safeToReturnValue); 73 } 74 75 /** 76 * Create a stream from a {@link Parcel} object. Note that this immediately reads the Parcelable 77 * object, allowing the Parcel to be recycled after calling this method. 78 */ 79 @SuppressWarnings("unchecked") readFromParcel( Parcel parcel, ClassLoader classLoader)80 static <P extends Parcelable> ParcelableInputStream<P> readFromParcel( 81 Parcel parcel, ClassLoader classLoader) { 82 P value = (P) parcel.readParcelable(classLoader); 83 return new ParcelableInputStream<>(null, value, true); 84 } 85 86 /** Create a stream for a Parcelable object. */ forInstance( P value, Parcelable.Creator<P> creator)87 static <P extends Parcelable> ParcelableInputStream<P> forInstance( 88 P value, Parcelable.Creator<P> creator) { 89 return new ParcelableInputStream<>(creator, value, false); 90 } 91 92 /** Create a stream for a Parcelable object, treating the object as immutable. */ forImmutableInstance( P value, Parcelable.Creator<P> creator)93 static <P extends Parcelable> ParcelableInputStream<P> forImmutableInstance( 94 P value, Parcelable.Creator<P> creator) { 95 return new ParcelableInputStream<>(creator, value, true); 96 } 97 getDelegateStream()98 private InputStream getDelegateStream() { 99 if (delegateStream == null) { 100 Parcel parcel = Parcel.obtain(); 101 parcel.writeParcelable(value, 0); 102 byte[] res = parcel.marshall(); 103 parcel.recycle(); 104 delegateStream = new ByteArrayInputStream(res); 105 } 106 return delegateStream; 107 } 108 109 @Override read()110 public int read() throws IOException { 111 return getDelegateStream().read(); 112 } 113 114 @Override read(byte[] b, int off, int len)115 public int read(byte[] b, int off, int len) throws IOException { 116 return getDelegateStream().read(b, off, len); 117 } 118 119 @Override skip(long n)120 public long skip(long n) throws IOException { 121 if (n <= 0) { 122 return 0; 123 } 124 return getDelegateStream().skip(n); 125 } 126 127 @Override available()128 public int available() throws IOException { 129 return getDelegateStream().available(); 130 } 131 132 @Override close()133 public void close() throws IOException { 134 if (delegateStream != null) { 135 delegateStream.close(); 136 } 137 } 138 139 @Override mark(int readLimit)140 public void mark(int readLimit) { 141 // If there's no delegate stream yet, the current position is 0. That's the same 142 // as the default mark position, so there's nothing to do. 143 if (delegateStream != null) { 144 delegateStream.mark(readLimit); 145 } 146 } 147 148 @Override reset()149 public void reset() throws IOException { 150 if (delegateStream != null) { 151 delegateStream.reset(); 152 } 153 } 154 155 @Override markSupported()156 public boolean markSupported() { 157 // We know our delegate (ByteArrayInputStream) supports mark/reset. 158 return true; 159 } 160 161 /** 162 * Write the {@link Parcelable} this stream wraps to the given {@link Parcel}. 163 * 164 * <p>This will retain any android-specific data (e.g. file descriptors) which can't simply be 165 * serialized to bytes. 166 * 167 * @return The number of bytes written to the parcel. 168 */ writeToParcel(Parcel parcel)169 int writeToParcel(Parcel parcel) { 170 int startPos = parcel.dataPosition(); 171 parcel.writeParcelable(value, value.describeContents()); 172 return parcel.dataPosition() - startPos; 173 } 174 175 /** 176 * Get the parcelable as if it had been serialized/de-serialized. 177 * 178 * <p>If the parcelable is immutable, or it was already de-serialized from a Parcel (I.e. this 179 * instance was created with #readFromParcel), the value will be returned directly. 180 */ getParcelable()181 P getParcelable() { 182 if (safeToReturnValue) { 183 // We can just return the value directly. 184 return value; 185 } else { 186 // We need to serialize/de-serialize to a parcel internally. 187 if (sharableValue == null) { 188 sharableValue = marshallUnmarshall(value, checkNotNull(creator)); 189 } 190 return sharableValue; 191 } 192 } 193 marshallUnmarshall( P value, Parcelable.Creator<P> creator)194 private static <P extends Parcelable> P marshallUnmarshall( 195 P value, Parcelable.Creator<P> creator) { 196 // Serialize/de-serialize the object directly instead of using Parcel.writeParcelable, 197 // since there's no need to write out the class name. 198 Parcel parcel = Parcel.obtain(); 199 value.writeToParcel(parcel, 0); 200 parcel.setDataPosition(0); 201 P result = creator.createFromParcel(parcel); 202 parcel.recycle(); 203 return result; 204 } 205 206 @Override toString()207 public String toString() { 208 return "ParcelableInputStream[V: " + value + "]"; 209 } 210 } 211