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