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