• 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 android.os.Parcel;
20 import android.os.Parcelable;
21 import android.util.AndroidRuntimeException;
22 import io.grpc.Attributes;
23 import io.grpc.InternalMetadata;
24 import io.grpc.Metadata;
25 import io.grpc.Status;
26 import io.grpc.StatusException;
27 import io.grpc.binder.InboundParcelablePolicy;
28 import io.grpc.internal.GrpcUtil;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import javax.annotation.Nullable;
32 
33 /**
34  * Helper class for reading & writing metadata to parcels.
35  *
36  * <p>Metadata is written to a parcel as a single int for the number of name/value pairs, followed
37  * by the following pattern for each pair.
38  *
39  * <ol>
40  *   <li>name length (int)
41  *   <li>name (byte[])
42  *   <li>value length OR sentinel (int)
43  *   <li>value (byte[] OR Parcelable)
44  * </ol>
45  *
46  * The sentinel int at the start of a value may indicate bad metadata. When this happens, no more
47  * data follows the sentinel.
48  */
49 public final class MetadataHelper {
50 
51   /** The generic metadata marshaller we use for reading parcelables from the transport. */
52   private static final Metadata.BinaryStreamMarshaller<Parcelable> TRANSPORT_INBOUND_MARSHALLER =
53     new ParcelableMetadataMarshaller<>(null, true);
54 
55   /** Indicates the following value is a parcelable. */
56   private static final int PARCELABLE_SENTINEL = -1;
57 
MetadataHelper()58   private MetadataHelper() {}
59 
60   /**
61    * Write a Metadata instance to a Parcel.
62    *
63    * @param parcel The {@link Parcel} to write to.
64    * @param metadata The {@link Metadata} to write.
65    */
writeMetadata(Parcel parcel, @Nullable Metadata metadata)66   public static void writeMetadata(Parcel parcel, @Nullable Metadata metadata)
67       throws StatusException, IOException {
68     int n = metadata != null ? InternalMetadata.headerCount(metadata) : 0;
69     if (n == 0) {
70       parcel.writeInt(0);
71       return;
72     }
73     Object[] serialized = InternalMetadata.serializePartial(metadata);
74     parcel.writeInt(n);
75     for (int i = 0; i < n; i++) {
76       byte[] name = (byte[]) serialized[i * 2];
77       parcel.writeInt(name.length);
78       parcel.writeByteArray(name);
79       Object value = serialized[i * 2 + 1];
80       if (value instanceof byte[]) {
81         byte[] valueBytes = (byte[]) value;
82         parcel.writeInt(valueBytes.length);
83         parcel.writeByteArray(valueBytes);
84       } else if (value instanceof ParcelableInputStream) {
85         parcel.writeInt(PARCELABLE_SENTINEL);
86         ((ParcelableInputStream) value).writeToParcel(parcel);
87       } else {
88         // An InputStream which wasn't created by ParcelableUtils, which means there's another use
89         // of Metadata.BinaryStreamMarshaller. Just read the bytes.
90         //
91         // We know that BlockPool will give us a buffer at least as large as the max space for all
92         // names and values so it'll certainly be large enough (and the limit is only 8k so this
93         // is fine).
94         byte[] buffer = BlockPool.acquireBlock();
95         try {
96           InputStream stream = (InputStream) value;
97           int total = 0;
98           while (total < buffer.length) {
99             int read = stream.read(buffer, total, buffer.length - total);
100             if (read == -1) {
101               break;
102             }
103             total += read;
104           }
105           if (total == buffer.length) {
106             throw Status.RESOURCE_EXHAUSTED.withDescription("Metadata value too large").asException();
107           }
108           parcel.writeInt(total);
109           if (total > 0) {
110             parcel.writeByteArray(buffer, 0, total);
111           }
112         } finally {
113           BlockPool.releaseBlock(buffer);
114         }
115       }
116     }
117   }
118 
119   /**
120    * Read a Metadata instance from a Parcel.
121    *
122    * @param parcel The {@link Parcel} to read from.
123    */
readMetadata(Parcel parcel, Attributes attributes)124   public static Metadata readMetadata(Parcel parcel, Attributes attributes) throws StatusException {
125     int n = parcel.readInt();
126     if (n == 0) {
127       return new Metadata();
128     }
129     // For enforcing the header-size limit. Doesn't include parcelable data.
130     int bytesRead = 0;
131     // For enforcing the maximum allowed parcelable data (see InboundParcelablePolicy).
132     int parcelableBytesRead = 0;
133     Object[] serialized = new Object[n * 2];
134     for (int i = 0; i < n; i++) {
135       int numNameBytes = parcel.readInt();
136       bytesRead += 4;
137       byte[] name = readBytesChecked(parcel, numNameBytes, bytesRead);
138       bytesRead += numNameBytes;
139       serialized[i * 2] = name;
140       int numValueBytes = parcel.readInt();
141       bytesRead += 4;
142       if (numValueBytes == PARCELABLE_SENTINEL) {
143         InboundParcelablePolicy policy = attributes.get(BinderTransport.INBOUND_PARCELABLE_POLICY);
144         if (!policy.shouldAcceptParcelableMetadataValues()) {
145           throw Status.PERMISSION_DENIED
146               .withDescription("Parcelable metadata values not allowed")
147               .asException();
148         }
149         int parcelableStartPos = parcel.dataPosition();
150         try {
151           Parcelable value = parcel.readParcelable(MetadataHelper.class.getClassLoader());
152           if (value == null) {
153             throw Status.INTERNAL.withDescription("Read null parcelable in metadata").asException();
154           }
155           serialized[i * 2 + 1] = InternalMetadata.parsedValue(TRANSPORT_INBOUND_MARSHALLER, value);
156         } catch (AndroidRuntimeException are) {
157           throw Status.INTERNAL
158               .withCause(are)
159               .withDescription("Failure reading parcelable in metadata")
160               .asException();
161         }
162         int parcelableSize = parcel.dataPosition() - parcelableStartPos;
163         parcelableBytesRead += parcelableSize;
164         if (parcelableBytesRead > policy.getMaxParcelableMetadataSize()) {
165           throw Status.RESOURCE_EXHAUSTED
166               .withDescription(
167                   "Inbound Parcelables too large according to policy (see InboundParcelablePolicy)")
168               .asException();
169         }
170       } else if (numValueBytes < 0) {
171         throw Status.INTERNAL.withDescription("Unrecognized metadata sentinel").asException();
172       } else {
173         byte[] value = readBytesChecked(parcel, numValueBytes, bytesRead);
174         bytesRead += numValueBytes;
175         serialized[i * 2 + 1] = value;
176       }
177     }
178     return InternalMetadata.newMetadataWithParsedValues(n, serialized);
179   }
180 
181   /** Read a byte array checking that we're not reading too much. */
readBytesChecked( Parcel parcel, int numBytes, int bytesRead)182   private static byte[] readBytesChecked(
183       Parcel parcel,
184       int numBytes,
185       int bytesRead) throws StatusException {
186     if (bytesRead + numBytes > GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE) {
187       throw Status.RESOURCE_EXHAUSTED.withDescription("Metadata too large").asException();
188     }
189     byte[] res = new byte[numBytes];
190     if (numBytes > 0) {
191       parcel.readByteArray(res);
192     }
193     return res;
194   }
195 
196   /** A marshaller for passing parcelables in gRPC {@link Metadata} */
197   public static final class ParcelableMetadataMarshaller<P extends Parcelable>
198       implements Metadata.BinaryStreamMarshaller<P> {
199 
200     @Nullable private final Parcelable.Creator<P> creator;
201     private final boolean immutableType;
202 
ParcelableMetadataMarshaller(@ullable Parcelable.Creator<P> creator, boolean immutableType)203     public ParcelableMetadataMarshaller(@Nullable Parcelable.Creator<P> creator, boolean immutableType) {
204       this.creator = creator;
205       this.immutableType = immutableType;
206     }
207 
208     @Override
toStream(P value)209     public InputStream toStream(P value) {
210       return new ParcelableInputStream<>(creator, value, immutableType);
211     }
212 
213     @Override
214     @SuppressWarnings("unchecked")
parseStream(InputStream stream)215     public P parseStream(InputStream stream) {
216       if (stream instanceof ParcelableInputStream) {
217         return ((ParcelableInputStream<P>) stream).getParcelable();
218       } else {
219         throw new UnsupportedOperationException(
220             "Can't unmarshall a parcelable from a regular byte stream");
221       }
222     }
223   }
224 }
225