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 */ 116 @VisibleForTesting makeBLEMessage(byte[] payload, OperationType operation, boolean isPayloadEncrypted)117 static BLEMessage makeBLEMessage(byte[] payload, OperationType operation, 118 boolean isPayloadEncrypted) { 119 return BLEMessage.newBuilder() 120 .setVersion(PROTOCOL_VERSION) 121 .setOperation(operation) 122 .setPacketNumber(1) 123 .setTotalPackets(1) 124 .setIsPayloadEncrypted(isPayloadEncrypted) 125 .setPayload(ByteString.copyFrom(payload)) 126 .build(); 127 } 128 129 /** 130 * Split given data if necessary to fit within the given {@code maxSize} 131 * 132 * @param payload The payload to potentially split across multiple {@link 133 * com.android.car.trust.BLEStream.BLEMessage}s 134 * @param operation The operation this message represents 135 * @param maxSize The maximum size of each chunk 136 * @return An array of {@link com.android.car.trust.BLEStream.BLEMessage}s 137 */ makeBLEMessages(byte[] payload, OperationType operation, int maxSize, boolean isPayloadEncrypted)138 public static List<BLEMessage> makeBLEMessages(byte[] payload, OperationType operation, 139 int maxSize, boolean isPayloadEncrypted) { 140 List<BLEMessage> bleMessages = new ArrayList(); 141 int payloadSize = payload.length; 142 int maxPayloadSize = 143 maxSize - getProtoHeaderSize(operation, payloadSize, isPayloadEncrypted); 144 if (payloadSize <= maxPayloadSize) { 145 bleMessages.add(makeBLEMessage(payload, operation, isPayloadEncrypted)); 146 return bleMessages; 147 } 148 int totalPackets = (int) Math.ceil((double) payloadSize / maxPayloadSize); 149 int start = 0; 150 int end = maxPayloadSize; 151 for (int i = 0; i < totalPackets; i++) { 152 bleMessages.add(BLEMessage.newBuilder() 153 .setVersion(PROTOCOL_VERSION) 154 .setOperation(operation) 155 .setPacketNumber(i + 1) 156 .setTotalPackets(totalPackets) 157 .setIsPayloadEncrypted(isPayloadEncrypted) 158 .setPayload(ByteString.copyFrom(Arrays.copyOfRange(payload, start, end))) 159 .build()); 160 start = end; 161 end = Math.min(start + maxPayloadSize, payloadSize); 162 } 163 return bleMessages; 164 } 165 166 /** 167 * Returns the header size for the proto in bytes. This method assumes that the proto 168 * contain a payload. 169 */ 170 @VisibleForTesting getProtoHeaderSize(OperationType operation, int payloadSize, boolean isPayloadEncrypted)171 static int getProtoHeaderSize(OperationType operation, int payloadSize, 172 boolean isPayloadEncrypted) { 173 int isPayloadEncryptedFieldSize = isPayloadEncrypted 174 ? BOOLEAN_FIELD_ENCODING_SIZE + FIELD_NUMBER_ENCODING_SIZE 175 : 0; 176 int operationSize = getEncodedSize(operation.getNumber()) + FIELD_NUMBER_ENCODING_SIZE; 177 178 // The payload size is a varint. 179 int payloadEncodingSize = FIELD_NUMBER_ENCODING_SIZE + getEncodedSize(payloadSize); 180 181 return CONSTANT_HEADER_FIELD_SIZE + operationSize + isPayloadEncryptedFieldSize 182 + payloadEncodingSize; 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