• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 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.internal;
18 
19 import static com.google.common.base.Charsets.US_ASCII;
20 
21 import com.google.common.io.BaseEncoding;
22 import io.grpc.InternalMetadata;
23 import io.grpc.Metadata;
24 import java.util.Arrays;
25 import java.util.logging.Logger;
26 
27 /**
28  * Utility functions for transport layer framing.
29  *
30  * <p>Within a given transport frame we reserve the first byte to indicate the type of compression
31  * used for the contents of the transport frame.
32  */
33 public final class TransportFrameUtil {
34 
35   private static final Logger logger = Logger.getLogger(TransportFrameUtil.class.getName());
36 
37   private static final byte[] binaryHeaderSuffixBytes =
38       Metadata.BINARY_HEADER_SUFFIX.getBytes(US_ASCII);
39 
40   /**
41    * Transform the given headers to a format where only spec-compliant ASCII characters are allowed.
42    * Binary header values are encoded by Base64 in the result.  It is safe to modify the returned
43    * array, but not to modify any of the underlying byte arrays.
44    *
45    * @return the interleaved keys and values.
46    */
47   @SuppressWarnings("BetaApi") // BaseEncoding is stable in Guava 20.0
toHttp2Headers(Metadata headers)48   public static byte[][] toHttp2Headers(Metadata headers) {
49     byte[][] serializedHeaders = InternalMetadata.serialize(headers);
50     // TODO(carl-mastrangelo): eventually remove this once all callers are updated.
51     if (serializedHeaders == null) {
52       return new byte[][]{};
53     }
54     int k = 0;
55     for (int i = 0; i < serializedHeaders.length; i += 2) {
56       byte[] key = serializedHeaders[i];
57       byte[] value = serializedHeaders[i + 1];
58       if (endsWith(key, binaryHeaderSuffixBytes)) {
59         // Binary header.
60         serializedHeaders[k] = key;
61         serializedHeaders[k + 1] = BaseEncoding.base64().encode(value).getBytes(US_ASCII);
62         k += 2;
63       } else {
64         // Non-binary header.
65         // Filter out headers that contain non-spec-compliant ASCII characters.
66         // TODO(zhangkun83): only do such check in development mode since it's expensive
67         if (isSpecCompliantAscii(value)) {
68           serializedHeaders[k] = key;
69           serializedHeaders[k + 1] = value;
70           k += 2;
71         } else {
72           String keyString = new String(key, US_ASCII);
73           logger.warning("Metadata key=" + keyString + ", value=" + Arrays.toString(value)
74               + " contains invalid ASCII characters");
75         }
76       }
77     }
78     // Fast path, everything worked out fine.
79     if (k == serializedHeaders.length) {
80       return serializedHeaders;
81     }
82     return Arrays.copyOfRange(serializedHeaders, 0, k);
83   }
84 
85   /**
86    * Transform HTTP/2-compliant headers to the raw serialized format which can be deserialized by
87    * metadata marshallers. It decodes the Base64-encoded binary headers.  This function modifies
88    * the headers in place.  By modifying the input array.
89    *
90    * @param http2Headers the interleaved keys and values of HTTP/2-compliant headers
91    * @return the interleaved keys and values in the raw serialized format
92    */
93   @SuppressWarnings("BetaApi") // BaseEncoding is stable in Guava 20.0
toRawSerializedHeaders(byte[][] http2Headers)94   public static byte[][] toRawSerializedHeaders(byte[][] http2Headers) {
95     for (int i = 0; i < http2Headers.length; i += 2) {
96       byte[] key = http2Headers[i];
97       byte[] value = http2Headers[i + 1];
98       http2Headers[i] = key;
99       if (endsWith(key, binaryHeaderSuffixBytes)) {
100         // Binary header
101         http2Headers[i + 1] = BaseEncoding.base64().decode(new String(value, US_ASCII));
102       } else {
103         // Non-binary header
104         // Nothing to do, the value is already in the right place.
105       }
106     }
107     return http2Headers;
108   }
109 
110   /**
111    * Returns {@code true} if {@code subject} ends with {@code suffix}.
112    */
endsWith(byte[] subject, byte[] suffix)113   private static boolean endsWith(byte[] subject, byte[] suffix) {
114     int start = subject.length - suffix.length;
115     if (start < 0) {
116       return false;
117     }
118     for (int i = start; i < subject.length; i++) {
119       if (subject[i] != suffix[i - start]) {
120         return false;
121       }
122     }
123     return true;
124   }
125 
126   /**
127    * Returns {@code true} if {@code subject} contains only bytes that are spec-compliant ASCII
128    * characters and space.
129    */
isSpecCompliantAscii(byte[] subject)130   private static boolean isSpecCompliantAscii(byte[] subject) {
131     for (byte b : subject) {
132       if (b < 32 || b > 126) {
133         return false;
134       }
135     }
136     return true;
137   }
138 
TransportFrameUtil()139   private TransportFrameUtil() {}
140 }
141