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.Frc9000VariableLengthIntegerUtil.toFrc9000Int;
20 
21 import android.annotation.NonNull;
22 
23 import com.android.internal.util.Preconditions;
24 
25 import com.google.auto.value.AutoValue;
26 
27 import java.util.Arrays;
28 import java.util.Objects;
29 
30 /**
31  * Binary Representation of HTTP Messages.
32  *
33  * @see <a href="https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html">Binary
34  *     HTTP</a>
35  */
36 @AutoValue
37 public abstract class BinaryHttpMessage extends BinaryHttpSerializableComponent {
38     private static final int FRAMING_INDICATOR_LENGTH_IN_BYTES = 1;
39     static final byte[] EMPTY_CONTENT = new byte[0];
40     private static final int FRAMING_INDICATOR_SECTION_COUNT = 1;
41     private static final int CONTENT_SECTION_COUNT = 2;
42 
43     /** Returns the framing indicator of the message. */
44     @FramingIndicator
getFramingIndicator()45     public abstract byte getFramingIndicator();
46 
47     @NonNull
getControlData()48     abstract ControlData getControlData();
49 
50     /** Returns the header fields of the message. */
51     @NonNull
getHeaderFields()52     public abstract Fields getHeaderFields();
53 
54     /** Returns the content of the message. */
55     @NonNull
56     @SuppressWarnings("mutable")
getContent()57     public abstract byte[] getContent();
58 
59     /**
60      * By definition, content and trailer can be omitted without zero length indicator. The length
61      * of padding does not have a strict definition, we define the length as following:
62      *
63      * <ol>
64      *   <li>As we do not support trailer, we treat it as padding part for now.
65      *   <li>If content has zero length, we calculate padding after the length field.
66      *   <li>If length is omitted in the bytes, we return 0 for padding length.
67      *       <ol/>
68      *
69      * @return the length of padding.
70      */
getPaddingLength()71     public abstract int getPaddingLength();
72 
73     /** Returns if the message represents a request. */
isRequest()74     public boolean isRequest() {
75         return FramingIndicator.FRAMING_INDICATOR_REQUEST_OF_KNOWN_LENGTH == getFramingIndicator();
76     }
77 
78     /** Returns if the message represents a response. */
isResponse()79     public boolean isResponse() {
80         return FramingIndicator.FRAMING_INDICATOR_RESPONSE_OF_KNOWN_LENGTH == getFramingIndicator();
81     }
82 
83     /** Returns request control data if the message is a request message. */
84     @NonNull
getRequestControlData()85     public RequestControlData getRequestControlData() {
86         if (isRequest()) {
87             return (RequestControlData) getControlData();
88         }
89         throw new IllegalArgumentException("Message does not represent a Request.");
90     }
91 
92     /** Returns response control data if the message is a response message. */
93     @NonNull
getResponseControlData()94     public ResponseControlData getResponseControlData() {
95         if (isResponse()) {
96             return (ResponseControlData) getControlData();
97         }
98         throw new IllegalArgumentException("Message does not represent a Response.");
99     }
100 
101     /** Serialize the message to a binary representation according to the framing indicator. */
102     @NonNull
serialize()103     public byte[] serialize() {
104         switch (getFramingIndicator()) {
105             case FramingIndicator.FRAMING_INDICATOR_REQUEST_OF_KNOWN_LENGTH:
106             case FramingIndicator.FRAMING_INDICATOR_RESPONSE_OF_KNOWN_LENGTH:
107                 return combineWithPadding(knownLengthSerialize(), getPaddingLength());
108             default:
109                 throw new IllegalArgumentException("Invalid framing indicator.");
110         }
111     }
112 
113     /** Get a builder for known length request. */
114     @NonNull
knownLengthRequestBuilder( @onNull final RequestControlData requestControlData)115     public static Builder knownLengthRequestBuilder(
116             @NonNull final RequestControlData requestControlData) {
117         return builder()
118                 .setFramingIndicator(FramingIndicator.FRAMING_INDICATOR_REQUEST_OF_KNOWN_LENGTH)
119                 .setControlData(requestControlData);
120     }
121 
122     /** Get a builder for known length response. */
123     @NonNull
knownLengthResponseBuilder( @onNull final ResponseControlData responseControlData)124     public static Builder knownLengthResponseBuilder(
125             @NonNull final ResponseControlData responseControlData) {
126         return builder()
127                 .setFramingIndicator(FramingIndicator.FRAMING_INDICATOR_RESPONSE_OF_KNOWN_LENGTH)
128                 .setControlData(responseControlData);
129     }
130 
131     @NonNull
builder()132     static Builder builder() {
133         return new AutoValue_BinaryHttpMessage.Builder()
134                 .setContent(EMPTY_CONTENT)
135                 .setPaddingLength(0);
136     }
137 
138     @Override
getKnownLengthSerializedSectionsCount()139     int getKnownLengthSerializedSectionsCount() {
140         return FRAMING_INDICATOR_SECTION_COUNT
141                 + getControlData().getKnownLengthSerializedSectionsCount()
142                 + getHeaderFields().getKnownLengthSerializedSectionsCount()
143                 + CONTENT_SECTION_COUNT;
144     }
145 
146     /**
147      * {@inheritDoc}
148      *
149      * @return [framing indicator][control data sections]*n[header fields sections]*n[content
150      *     length][content]
151      * @see RequestControlData#knownLengthSerialize()
152      * @see ResponseControlData#knownLengthSerialize()
153      * @see Fields#knownLengthSerialize()
154      */
155     @Override
knownLengthSerialize()156     byte[][] knownLengthSerialize() {
157         byte[] framingIndicator = new byte[] {getFramingIndicator()};
158         // For response, the informative responses are included in the control data to simplifying
159         // the code.
160         byte[][] controlData = getControlData().knownLengthSerialize();
161         byte[][] headerFields = getHeaderFields().knownLengthSerialize();
162         byte[][] content = new byte[][] {toFrc9000Int(getContent().length), getContent()};
163         // We don't have trailer support for now.
164 
165         int totalSections =
166                 framingIndicator.length + controlData.length + headerFields.length + content.length;
167         byte[][] result = new byte[totalSections][];
168         result[0] = framingIndicator;
169         int position = FRAMING_INDICATOR_LENGTH_IN_BYTES;
170         System.arraycopy(controlData, 0, result, position, controlData.length);
171         position += controlData.length;
172         System.arraycopy(headerFields, 0, result, position, headerFields.length);
173         position += headerFields.length;
174         System.arraycopy(content, 0, result, position, content.length);
175         return result;
176     }
177 
178     @NonNull
combineWithPadding(@onNull final byte[][] sections, final int paddingLength)179     private byte[] combineWithPadding(@NonNull final byte[][] sections, final int paddingLength) {
180         int pointer = 0;
181         int totalSize = Arrays.stream(sections).mapToInt(s -> s.length).sum() + paddingLength;
182         byte[] result = new byte[totalSize];
183         for (byte[] section : sections) {
184             for (byte b : section) {
185                 result[pointer++] = b;
186             }
187         }
188         return result;
189     }
190 
191     /** Padding length is ignored as it does not have any info. */
192     @Override
equals(Object o)193     public final boolean equals(Object o) {
194         if (this == o) return true;
195         if (!(o instanceof BinaryHttpMessage)) return false;
196         BinaryHttpMessage that = (BinaryHttpMessage) o;
197         return getFramingIndicator() == that.getFramingIndicator()
198                 && getControlData().equals(that.getControlData())
199                 && getHeaderFields().equals(that.getHeaderFields())
200                 && Arrays.equals(getContent(), that.getContent());
201     }
202 
203     @Override
hashCode()204     public final int hashCode() {
205         return Objects.hash(
206                 getFramingIndicator(),
207                 getControlData(),
208                 getHeaderFields(),
209                 Arrays.hashCode(getContent()));
210     }
211 
212     /** Builder for {@link BinaryHttpMessage}. */
213     @AutoValue.Builder
214     public abstract static class Builder {
215         /** Set framing indicator for the message. */
216         @NonNull
setFramingIndicator(byte framingIndicator)217         abstract Builder setFramingIndicator(byte framingIndicator);
218         /** Set control data for the message. */
219         @NonNull
setControlData(@onNull ControlData controlData)220         abstract Builder setControlData(@NonNull ControlData controlData);
221 
222         /** Set header fields for the message. */
223         @NonNull
setHeaderFields(@onNull Fields headerFields)224         public abstract Builder setHeaderFields(@NonNull Fields headerFields);
225 
226         /** Sets message content. */
227         @NonNull
setContent(@onNull byte[] content)228         public abstract Builder setContent(@NonNull byte[] content);
229 
230         /** Sets padding length of the message. */
231         @NonNull
setPaddingLength(int paddingLength)232         public abstract Builder setPaddingLength(int paddingLength);
233 
getPaddingLength()234         abstract int getPaddingLength();
235 
236         @NonNull
autoBuild()237         abstract BinaryHttpMessage autoBuild();
238 
239         /** Returns the message built. */
240         @NonNull
build()241         public BinaryHttpMessage build() {
242             BinaryHttpMessage binaryHttpMessage = autoBuild();
243             Preconditions.checkArgumentNonnegative(getPaddingLength());
244             return binaryHttpMessage;
245         }
246     }
247 }
248