• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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