1 /*
2  * Copyright (C) 2019 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.car.trust;
18 
19 import android.util.Log;
20 
21 import com.android.car.BLEStreamProtos.BLEMessageProto.BLEMessage;
22 import com.android.car.BLEStreamProtos.BLEOperationProto.OperationType;
23 import com.android.car.protobuf.ByteString;
24 import com.android.internal.annotations.VisibleForTesting;
25 
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.List;
29 
30 /**
31  * Methods for creating {@link BLEStream} protos
32  */
33 class BLEMessageV1Factory {
34 
35     private static final String TAG = "BLEMessageFactory";
36 
37     /**
38      * The size in bytes of a {@code fixed32} field in the proto.
39      * See this <a href="https://developers.google.com/protocol-buffers/docs/encoding">site</a> for
40      * more details.
41      */
42     private static final int FIXED_32_SIZE = 4;
43 
44     // The size needed to encode a boolean proto field
45     private static final int BOOLEAN_FIELD_ENCODING_SIZE = 1;
46 
47     /**
48      * The bytes needed to encode the field number in the proto.
49      *
50      * <p>Since the v1 proto only has 6 fields, it will only take 1 additional byte to encode.
51      */
52     private static final int FIELD_NUMBER_ENCODING_SIZE = 1;
53     /**
54      * Current version of the proto.
55      */
56     private static final int PROTOCOL_VERSION = 1;
57     /**
58      * The size of the version field in the proto.
59      *
60      * <p>The version field is a {@code variant} and thus, its size can vary between 1-5 bytes.
61      * Since the version is {@code 1}, it will only take 1 byte to encode + 1 byte for the field
62      * number.
63      */
64     private static final int VERSION_SIZE = getEncodedSize(PROTOCOL_VERSION)
65             + FIELD_NUMBER_ENCODING_SIZE;
66     /**
67      * The size of fields in the header that do not change depending on their value.
68      *
69      * <p>The fixed fields are:
70      *
71      * <ol>
72      * <li>Version (1 byte + 1 byte for field)
73      * <li>Packet number (4 bytes + 1 byte for field)
74      * <li>Total packets (4 bytes + 1 byte for field)
75      * </ol>
76      *
77      * <p>Note, the version code is an {@code Int32Value} and thus, can vary, but it is always a set
78      * value at compile time. Thus, it can be calculated statically.
79      */
80     private static final int CONSTANT_HEADER_FIELD_SIZE = VERSION_SIZE
81             + (FIXED_32_SIZE + FIELD_NUMBER_ENCODING_SIZE)
82             + (FIXED_32_SIZE + FIELD_NUMBER_ENCODING_SIZE);
83 
BLEMessageV1Factory()84     private BLEMessageV1Factory() {}
85 
86     /**
87      * Creates an acknowledgement {@link BLEMessage}.
88      *
89      * <p>This type of proto should be used to let a client know that this device has received
90      * a partially completed {@code BLEMessage}.
91      *
92      * <p>Note, this type of message has an empty {@code payload} field.
93      *
94      * @return A {@code BLEMessage} with an {@code OperationType} of {@link OperationType.ACK}.
95      */
makeAcknowledgementMessage()96     static BLEMessage makeAcknowledgementMessage() {
97         return BLEMessage.newBuilder()
98                 .setVersion(PROTOCOL_VERSION)
99                 .setOperation(OperationType.ACK)
100                 .setPacketNumber(1)
101                 .setTotalPackets(1)
102                 .setIsPayloadEncrypted(false)
103                 .build();
104     }
105 
106     /**
107      * Method used to generate a single message, the packet number and total packets will set to 1
108      * by default
109      *
110      * @param payload   The data object to use as the
111      *                  {@link com.android.car.trust.BLEStream.BLEMessage}
112      *                  payload
113      * @param operation The operation this message represents
114      * @return The generated {@link com.android.car.trust.BLEStream.BLEMessage}
115      */
makeBLEMessage(byte[] payload, OperationType operation, boolean isPayloadEncrypted)116     private static BLEMessage makeBLEMessage(byte[] payload, OperationType operation,
117             boolean isPayloadEncrypted) {
118         return BLEMessage.newBuilder()
119                 .setVersion(PROTOCOL_VERSION)
120                 .setOperation(operation)
121                 .setPacketNumber(1)
122                 .setTotalPackets(1)
123                 .setIsPayloadEncrypted(isPayloadEncrypted)
124                 .setPayload(ByteString.copyFrom(payload))
125                 .build();
126     }
127 
128     /**
129      * Split given data if necessary to fit within the given {@code maxSize}
130      *
131      * @param payload   The payload to potentially split across multiple {@link
132      *                  com.android.car.trust.BLEStream.BLEMessage}s
133      * @param operation The operation this message represents
134      * @param maxSize   The maximum size of each chunk
135      * @return An array of {@link com.android.car.trust.BLEStream.BLEMessage}s
136      */
makeBLEMessages(byte[] payload, OperationType operation, int maxSize, boolean isPayloadEncrypted)137     public static List<BLEMessage> makeBLEMessages(byte[] payload, OperationType operation,
138             int maxSize, boolean isPayloadEncrypted) {
139         List<BLEMessage> bleMessages = new ArrayList();
140         int payloadSize = payload.length;
141         int maxPayloadSize =
142                 maxSize - getProtoHeaderSize(operation, payloadSize, isPayloadEncrypted);
143         if (payloadSize <= maxPayloadSize) {
144             bleMessages.add(makeBLEMessage(payload, operation, isPayloadEncrypted));
145             return bleMessages;
146         }
147         int totalPackets = (int) Math.ceil((double) payloadSize / maxPayloadSize);
148         int start = 0;
149         int end = maxPayloadSize;
150         for (int i = 0; i < totalPackets; i++) {
151             bleMessages.add(BLEMessage.newBuilder()
152                     .setVersion(PROTOCOL_VERSION)
153                     .setOperation(operation)
154                     .setPacketNumber(i + 1)
155                     .setTotalPackets(totalPackets)
156                     .setIsPayloadEncrypted(isPayloadEncrypted)
157                     .setPayload(ByteString.copyFrom(Arrays.copyOfRange(payload, start, end)))
158                     .build());
159             start = end;
160             end = Math.min(start + maxPayloadSize, payloadSize);
161         }
162         return bleMessages;
163     }
164 
165     /**
166      * Returns the header size for the proto in bytes. This method assumes that the proto
167      * contain a payload.
168      */
169     @VisibleForTesting
getProtoHeaderSize(OperationType operation, int payloadSize, boolean isPayloadEncrypted)170     static int getProtoHeaderSize(OperationType operation, int payloadSize,
171             boolean isPayloadEncrypted) {
172         int isPayloadEncryptedFieldSize = isPayloadEncrypted
173                 ? BOOLEAN_FIELD_ENCODING_SIZE + FIELD_NUMBER_ENCODING_SIZE
174                 : 0;
175         int operationSize = getEncodedSize(operation.getNumber()) + FIELD_NUMBER_ENCODING_SIZE;
176 
177         // The payload size is a varint.
178         int payloadEncodingSize = FIELD_NUMBER_ENCODING_SIZE + getEncodedSize(payloadSize);
179 
180         return CONSTANT_HEADER_FIELD_SIZE + operationSize + isPayloadEncryptedFieldSize
181                 + payloadEncodingSize;
182     }
183 
184     /**
185      * The methods in this section are taken from
186      * google3/third_party/swift/swift_protobuf/Sources/SwiftProtobuf/Variant.swift.
187      * It should be kept in sync as long as the proto version remains the same.
188      *
189      * <p>Computes the number of bytes that would be needed to store a 32-bit variant. Negative
190      * value is not considered because all proto values should be positive.
191      *
192      * @param value the data that need to be encoded
193      * @return the size of the encoded data
194      */
getEncodedSize(int value)195     private static int getEncodedSize(int value) {
196         if (value < 0) {
197             Log.e(TAG, "Get a negative value from proto");
198             return 10;
199         }
200         if ((value & (~0 << 7)) == 0) {
201             return 1;
202         }
203         if ((value & (~0 << 14)) == 0) {
204             return 2;
205         }
206         if ((value & (~0 << 21)) == 0) {
207             return 3;
208         }
209         if ((value & (~0 << 28)) == 0) {
210             return 4;
211         }
212         return 5;
213     }
214 }
215