1 /* 2 * Copyright (C) 2023 The Android Open Source Project 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 com.android.adservices.service.common.bhttp; 18 19 import static com.android.adservices.service.common.bhttp.BinaryHttpMessage.EMPTY_CONTENT; 20 21 import android.annotation.NonNull; 22 23 import com.android.adservices.LoggerFactory; 24 import com.android.internal.annotations.VisibleForTesting; 25 26 import com.google.auto.value.AutoValue; 27 28 import java.nio.ByteBuffer; 29 import java.util.Arrays; 30 import java.util.Objects; 31 32 /** Deserialize the message from a binary representation according to the framing indicator. */ 33 @AutoValue 34 public class BinaryHttpMessageDeserializer { 35 36 /** 37 * Deserialize the message from a binary representation according to the framing indicator. 38 * 39 * @throws IllegalArgumentException when message was truncated in a wrong place or message is 40 * malformed. 41 * @see <a 42 * href="https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-padding-and-truncation">Binary 43 * HTTP Padding And Truncation</a> 44 */ 45 @NonNull deserialize(@onNull final byte[] data)46 public BinaryHttpMessage deserialize(@NonNull final byte[] data) { 47 final BinaryHttpByteArrayReader reader = new BinaryHttpByteArrayReader(data); 48 switch (reader.getFramingIndicatorByte()) { 49 case FramingIndicator.FRAMING_INDICATOR_REQUEST_OF_KNOWN_LENGTH: 50 case FramingIndicator.FRAMING_INDICATOR_RESPONSE_OF_KNOWN_LENGTH: 51 return deserializeKnownLengthMessage(reader); 52 default: 53 throw new IllegalArgumentException( 54 String.format( 55 "Invalid framing indicator %d", reader.getFramingIndicatorByte())); 56 } 57 } 58 59 @NonNull 60 @VisibleForTesting deserializeKnownLengthRequestControlData( @onNull final BinaryHttpByteArrayReader reader)61 static ControlData deserializeKnownLengthRequestControlData( 62 @NonNull final BinaryHttpByteArrayReader reader) { 63 return RequestControlData.builder() 64 .setMethod(new String(reader.readNextKnownLengthData().getData())) 65 .setScheme(new String(reader.readNextKnownLengthData().getData())) 66 .setAuthority(new String(reader.readNextKnownLengthData().getData())) 67 .setPath(new String(reader.readNextKnownLengthData().getData())) 68 .build(); 69 } 70 71 @NonNull 72 @VisibleForTesting deserializeKnownLengthResponseControlData( @onNull final BinaryHttpByteArrayReader reader)73 static ControlData deserializeKnownLengthResponseControlData( 74 @NonNull final BinaryHttpByteArrayReader reader) { 75 ResponseControlData.Builder builder = ResponseControlData.builder(); 76 77 int statusCode; 78 while (HttpStatusCodeUtil.isInformativeStatusCode( 79 statusCode = (int) reader.readNextRfc9000Int())) { 80 builder.addInformativeResponse( 81 InformativeResponse.builder() 82 .setInformativeStatusCode(statusCode) 83 .setHeaderFields(deserializeKnownLengthFields(reader)) 84 .build()); 85 } 86 87 builder.setFinalStatusCode(statusCode); 88 return builder.build(); 89 } 90 91 @NonNull 92 @VisibleForTesting deserializeKnownLengthMessage( @onNull final BinaryHttpByteArrayReader reader)93 static BinaryHttpMessage deserializeKnownLengthMessage( 94 @NonNull final BinaryHttpByteArrayReader reader) { 95 final byte framingIndicator = reader.getFramingIndicatorByte(); 96 // For response, the informative responses are included in the control data to simplifying 97 // the code. 98 final ControlData controlData = deserializeKnownLengthControlData(framingIndicator, reader); 99 final Fields headerFields = deserializeKnownLengthFields(reader); 100 byte[] content = EMPTY_CONTENT; 101 if (reader.hasRemainingBytes()) { 102 content = reader.readNextKnownLengthData().getData(); 103 } 104 // We don't have trailer support for now. Trailer will be treated as part of padding. 105 int paddingLength = reader.remainingLength(); 106 return BinaryHttpMessage.builder() 107 .setFramingIndicator(framingIndicator) 108 .setControlData(controlData) 109 .setHeaderFields(headerFields) 110 .setContent(content) 111 .setPaddingLength(paddingLength) 112 .build(); 113 } 114 115 @NonNull deserializeKnownLengthControlData( final byte framingIndicator, @NonNull final BinaryHttpByteArrayReader reader)116 private static ControlData deserializeKnownLengthControlData( 117 final byte framingIndicator, @NonNull final BinaryHttpByteArrayReader reader) { 118 switch (framingIndicator) { 119 case FramingIndicator.FRAMING_INDICATOR_REQUEST_OF_KNOWN_LENGTH: 120 return deserializeKnownLengthRequestControlData(reader); 121 case FramingIndicator.FRAMING_INDICATOR_RESPONSE_OF_KNOWN_LENGTH: 122 return deserializeKnownLengthResponseControlData(reader); 123 default: 124 throw new IllegalArgumentException( 125 String.format( 126 "Invalid framing indicator %d", reader.getFramingIndicatorByte())); 127 } 128 } 129 130 @NonNull deserializeKnownLengthFields(@onNull final BinaryHttpByteArrayReader reader)131 static Fields deserializeKnownLengthFields(@NonNull final BinaryHttpByteArrayReader reader) { 132 Fields.Builder builder = Fields.builder(); 133 BinaryHttpMessageDeserializer.BinaryHttpByteArrayReader subReader = 134 reader.readNextKnownLengthData(); 135 while (subReader.hasRemainingBytes()) { 136 builder.appendField( 137 new String(subReader.readNextKnownLengthData().getData()), 138 new String(subReader.readNextKnownLengthData().getData())); 139 } 140 return builder.build(); 141 } 142 143 /** Helper class of separating the block of data in a binary HTTP message. */ 144 @VisibleForTesting 145 static class BinaryHttpByteArrayReader { 146 private static final LoggerFactory.Logger LOGGER = LoggerFactory.getFledgeLogger(); 147 @NonNull private final byte[] mData; 148 private final int mDataBlockEndIndex; 149 private int mNextIndex; 150 151 /** 152 * Initial the reader taking a Binary HTTP message byte array. 153 * 154 * <p>The reader starts at index 1 since the first byte of the array is the framing 155 * indicator. 156 */ BinaryHttpByteArrayReader(@onNull final byte[] data)157 BinaryHttpByteArrayReader(@NonNull final byte[] data) { 158 this(data, 1, data.length); 159 } 160 BinaryHttpByteArrayReader( @onNull final byte[] data, final int nextIndex, final int dataBlockEndIndex)161 private BinaryHttpByteArrayReader( 162 @NonNull final byte[] data, final int nextIndex, final int dataBlockEndIndex) { 163 Objects.requireNonNull(data); 164 mData = data; 165 mDataBlockEndIndex = dataBlockEndIndex; 166 mNextIndex = nextIndex; 167 } 168 169 /** 170 * Returns the next section of known length data. 171 * 172 * <p>First read the length indicator, then return the Reader for it's corresponding data 173 * block. 174 * 175 * <p>Major use case is in the field reading. Fields are represents in [total field length] 176 * [[name length][name][value length][value]]*n. We need to return a sub reader for the data 177 * of fields. 178 */ 179 @NonNull readNextKnownLengthData()180 BinaryHttpByteArrayReader readNextKnownLengthData() { 181 int start = mNextIndex; 182 int dataLength; 183 try { 184 dataLength = (int) readNextRfc9000Int(); 185 } catch (IllegalArgumentException e) { 186 LOGGER.e( 187 "No sufficient bytes for data length at index %d, out of boundary at %d", 188 start, mDataBlockEndIndex); 189 throw new IllegalArgumentException("No sufficient bytes can be read.", e); 190 } 191 int newDataBlockEndIndex = mNextIndex + dataLength; 192 if (newDataBlockEndIndex > mDataBlockEndIndex) { 193 LOGGER.e( 194 "No sufficient bytes for data at %d, actual end %d, requested end %d", 195 mNextIndex, mDataBlockEndIndex, newDataBlockEndIndex); 196 throw new IllegalArgumentException("No sufficient bytes can be read."); 197 } 198 BinaryHttpByteArrayReader subReader = 199 new BinaryHttpByteArrayReader(mData, mNextIndex, newDataBlockEndIndex); 200 mNextIndex += dataLength; 201 return subReader; 202 } 203 204 @NonNull getData()205 byte[] getData() { 206 return Arrays.copyOfRange(mData, mNextIndex, mDataBlockEndIndex); 207 } 208 hasRemainingBytes()209 boolean hasRemainingBytes() { 210 return mNextIndex < mDataBlockEndIndex; 211 } 212 remainingLength()213 int remainingLength() { 214 return mDataBlockEndIndex - mNextIndex; 215 } 216 getFramingIndicatorByte()217 byte getFramingIndicatorByte() { 218 return mData[0]; 219 } 220 221 /** 222 * Returns next FRC 9000 integer. 223 * 224 * @see <a 225 * href="https://datatracker.ietf.org/doc/html/rfc9000#name-variable-length-integer-enc">FRC 226 * 9000 Variable Length Integer</a> 227 */ readNextRfc9000Int()228 long readNextRfc9000Int() { 229 throwForNotEnoughBytesForInt(1); 230 long result; 231 // The highest 2 bit of starting byte indicates the length of the integer 232 // representation: 233 // 11 indicates 8 bytes; 234 // 10 indicates 4 bytes; 235 // 01 indicates 2 bytes; 236 // 00 indicates 1 byte. 237 switch (mData[mNextIndex] & 0b11000000) { 238 case 0b11000000: 239 // Leading 0b11...... is 8 byte encoding 240 throwForNotEnoughBytesForInt(8); 241 result = 242 ByteBuffer.allocate(8) 243 .put((byte) (mData[mNextIndex] & 0b00111111)) 244 .put(mData, mNextIndex + 1, 7) 245 .getLong(0); 246 mNextIndex += 8; 247 return result; 248 case 0b10000000: 249 // Leading 0b10...... is 4 byte encoding 250 throwForNotEnoughBytesForInt(4); 251 result = 252 ByteBuffer.allocate(8) 253 .put(new byte[] {0, 0, 0, 0}) 254 .put((byte) (mData[mNextIndex] & 0b00111111)) 255 .put(mData, mNextIndex + 1, 3) 256 .getLong(0); 257 mNextIndex += 4; 258 return result; 259 case 0b01000000: 260 // Leading 0b01...... is 2 byte encoding 261 throwForNotEnoughBytesForInt(2); 262 result = 263 ByteBuffer.allocate(8) 264 .put(new byte[] {0, 0, 0, 0, 0, 0}) 265 .put((byte) (mData[mNextIndex] & 0b00111111)) 266 .put(mData[mNextIndex + 1]) 267 .getLong(0); 268 mNextIndex += 2; 269 return result; 270 case 0b00000000: 271 default: 272 // Leading 0b00...... is 1 byte encoding 273 result = mData[mNextIndex++]; 274 return result; 275 } 276 } 277 throwForNotEnoughBytesForInt(final int requiredLength)278 private void throwForNotEnoughBytesForInt(final int requiredLength) { 279 int remainingLength = remainingLength(); 280 if (remainingLength < requiredLength) { 281 LOGGER.e( 282 "Not enough data to be read as FRC 9000 integer, first byte at %d of " 283 + "(%s), needed length %d, remaining length %d.", 284 mNextIndex, 285 remainingLength > 0 286 ? Integer.toString(mData[mNextIndex]) 287 : "already out of bound", 288 requiredLength, 289 remainingLength); 290 throw new IllegalArgumentException( 291 "Not enough data to be read as FRC 9000 integer."); 292 } 293 } 294 } 295 } 296