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 package com.android.car.connecteddevice.ble; 17 18 import static com.android.car.connecteddevice.util.SafeLog.loge; 19 20 import com.android.car.connecteddevice.BleStreamProtos.BlePacketProto.BlePacket; 21 import com.android.car.protobuf.ByteString; 22 import com.android.internal.annotations.VisibleForTesting; 23 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 import java.util.List; 27 28 /** 29 * Factory for creating {@link BlePacket} protos. 30 */ 31 class BlePacketFactory { 32 private static final String TAG = "BlePacketFactory"; 33 34 /** 35 * The size in bytes of a {@code fixed32} field in the proto. 36 */ 37 private static final int FIXED_32_SIZE = 4; 38 39 /** 40 * The bytes needed to encode the field number in the proto. 41 * 42 * <p>Since the {@link BlePacket} only has 4 fields, it will only take 1 additional byte to 43 * encode. 44 */ 45 private static final int FIELD_NUMBER_ENCODING_SIZE = 1; 46 47 /** 48 * The size in bytes of field {@code packet_number}. The proto field is a {@code fixed32}. 49 */ 50 private static final int PACKET_NUMBER_ENCODING_SIZE = 51 FIXED_32_SIZE + FIELD_NUMBER_ENCODING_SIZE; 52 53 /** 54 * Split given data if necessary to fit within the given {@code maxSize}. 55 * 56 * @param payload The payload to potentially split across multiple {@link BlePacket}s. 57 * @param messageId The unique id for identifying message. 58 * @param maxSize The maximum size of each chunk. 59 * @return A list of {@link BlePacket}s. 60 * @throws BlePacketFactoryException if an error occurred during the splitting of data. 61 */ makeBlePackets(byte[] payload, int messageId, int maxSize)62 static List<BlePacket> makeBlePackets(byte[] payload, int messageId, int maxSize) 63 throws BlePacketFactoryException { 64 List<BlePacket> blePackets = new ArrayList<>(); 65 int payloadSize = payload.length; 66 int totalPackets = getTotalPacketNumber(messageId, payloadSize, maxSize); 67 int maxPayloadSize = maxSize 68 - getPacketHeaderSize(totalPackets, messageId, Math.min(payloadSize, maxSize)); 69 70 int start = 0; 71 int end = Math.min(payloadSize, maxPayloadSize); 72 for (int packetNum = 1; packetNum <= totalPackets; packetNum++) { 73 blePackets.add(BlePacket.newBuilder() 74 .setPacketNumber(packetNum) 75 .setTotalPackets(totalPackets) 76 .setMessageId(messageId) 77 .setPayload(ByteString.copyFrom(Arrays.copyOfRange(payload, start, end))) 78 .build()); 79 start = end; 80 end = Math.min(start + maxPayloadSize, payloadSize); 81 } 82 return blePackets; 83 } 84 85 /** 86 * Compute the header size for the {@link BlePacket} proto in bytes. This method assumes that 87 * the proto contains a payload. 88 */ 89 @VisibleForTesting getPacketHeaderSize(int totalPackets, int messageId, int payloadSize)90 static int getPacketHeaderSize(int totalPackets, int messageId, int payloadSize) { 91 return FIXED_32_SIZE + FIELD_NUMBER_ENCODING_SIZE 92 + getEncodedSize(totalPackets) + FIELD_NUMBER_ENCODING_SIZE 93 + getEncodedSize(messageId) + FIELD_NUMBER_ENCODING_SIZE 94 + getEncodedSize(payloadSize) + FIELD_NUMBER_ENCODING_SIZE; 95 } 96 97 /** 98 * Compute the total packets required to encode a payload of the given size. 99 */ 100 @VisibleForTesting getTotalPacketNumber(int messageId, int payloadSize, int maxSize)101 static int getTotalPacketNumber(int messageId, int payloadSize, int maxSize) 102 throws BlePacketFactoryException { 103 int headerSizeWithoutTotalPackets = FIXED_32_SIZE + FIELD_NUMBER_ENCODING_SIZE 104 + getEncodedSize(messageId) + FIELD_NUMBER_ENCODING_SIZE 105 + getEncodedSize(Math.min(payloadSize, maxSize)) + FIELD_NUMBER_ENCODING_SIZE; 106 107 for (int value = 1; value <= PACKET_NUMBER_ENCODING_SIZE; value++) { 108 int packetHeaderSize = headerSizeWithoutTotalPackets + value 109 + FIELD_NUMBER_ENCODING_SIZE; 110 int maxPayloadSize = maxSize - packetHeaderSize; 111 if (maxPayloadSize < 0) { 112 throw new BlePacketFactoryException("Packet header size too large."); 113 } 114 int totalPackets = (int) Math.ceil(payloadSize / (double) maxPayloadSize); 115 if (getEncodedSize(totalPackets) == value) { 116 return totalPackets; 117 } 118 } 119 120 loge(TAG, "Cannot get valid total packet number for message: messageId: " 121 + messageId + ", payloadSize: " + payloadSize + ", maxSize: " + maxSize); 122 throw new BlePacketFactoryException("No valid total packet number."); 123 } 124 125 /** 126 * This method implements Protocol Buffers encoding algorithm. 127 * 128 * <p>Computes the number of bytes that would be needed to store a 32-bit variant. 129 * 130 * @param value the data that need to be encoded 131 * @return the size of the encoded data 132 * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding#varints"> 133 * Protocol Buffers Encoding</a> 134 */ getEncodedSize(int value)135 private static int getEncodedSize(int value) { 136 if (value < 0) { 137 return 10; 138 } 139 if ((value & (~0 << 7)) == 0) { 140 return 1; 141 } 142 if ((value & (~0 << 14)) == 0) { 143 return 2; 144 } 145 if ((value & (~0 << 21)) == 0) { 146 return 3; 147 } 148 if ((value & (~0 << 28)) == 0) { 149 return 4; 150 } 151 return 5; 152 } 153 BlePacketFactory()154 private BlePacketFactory() {} 155 } 156