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