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