blob: 24249a3c8cdf0863c1f390f4a584d7e48bf8f345 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.connecteddevice.ble;
import static com.android.car.connecteddevice.util.SafeLog.loge;
import androidx.annotation.VisibleForTesting;
import com.android.car.connecteddevice.BleStreamProtos.BlePacketProto.BlePacket;
import com.android.car.protobuf.ByteString;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Factory for creating {@link BlePacket} protos.
*/
class BlePacketFactory {
private static final String TAG = "BlePacketFactory";
/**
* The size in bytes of a {@code fixed32} field in the proto.
*/
private static final int FIXED_32_SIZE = 4;
/**
* The bytes needed to encode the field number in the proto.
*
* <p>Since the {@link BlePacket} only has 4 fields, it will only take 1 additional byte to
* encode.
*/
private static final int FIELD_NUMBER_ENCODING_SIZE = 1;
/**
* The size in bytes of field {@code packet_number}. The proto field is a {@code fixed32}.
*/
private static final int PACKET_NUMBER_ENCODING_SIZE =
FIXED_32_SIZE + FIELD_NUMBER_ENCODING_SIZE;
/**
* Split given data if necessary to fit within the given {@code maxSize}.
*
* @param payload The payload to potentially split across multiple {@link BlePacket}s.
* @param messageId The unique id for identifying message.
* @param maxSize The maximum size of each chunk.
* @return A list of {@link BlePacket}s.
* @throws BlePacketFactoryException if an error occurred during the splitting of data.
*/
static List<BlePacket> makeBlePackets(byte[] payload, int messageId, int maxSize)
throws BlePacketFactoryException {
List<BlePacket> blePackets = new ArrayList<>();
int payloadSize = payload.length;
int totalPackets = getTotalPacketNumber(messageId, payloadSize, maxSize);
int maxPayloadSize = maxSize
- getPacketHeaderSize(totalPackets, messageId, Math.min(payloadSize, maxSize));
int start = 0;
int end = Math.min(payloadSize, maxPayloadSize);
for (int packetNum = 1; packetNum <= totalPackets; packetNum++) {
blePackets.add(BlePacket.newBuilder()
.setPacketNumber(packetNum)
.setTotalPackets(totalPackets)
.setMessageId(messageId)
.setPayload(ByteString.copyFrom(Arrays.copyOfRange(payload, start, end)))
.build());
start = end;
end = Math.min(start + maxPayloadSize, payloadSize);
}
return blePackets;
}
/**
* Compute the header size for the {@link BlePacket} proto in bytes. This method assumes that
* the proto contains a payload.
*/
@VisibleForTesting
static int getPacketHeaderSize(int totalPackets, int messageId, int payloadSize) {
return FIXED_32_SIZE + FIELD_NUMBER_ENCODING_SIZE
+ getEncodedSize(totalPackets) + FIELD_NUMBER_ENCODING_SIZE
+ getEncodedSize(messageId) + FIELD_NUMBER_ENCODING_SIZE
+ getEncodedSize(payloadSize) + FIELD_NUMBER_ENCODING_SIZE;
}
/**
* Compute the total packets required to encode a payload of the given size.
*/
@VisibleForTesting
static int getTotalPacketNumber(int messageId, int payloadSize, int maxSize)
throws BlePacketFactoryException {
int headerSizeWithoutTotalPackets = FIXED_32_SIZE + FIELD_NUMBER_ENCODING_SIZE
+ getEncodedSize(messageId) + FIELD_NUMBER_ENCODING_SIZE
+ getEncodedSize(Math.min(payloadSize, maxSize)) + FIELD_NUMBER_ENCODING_SIZE;
for (int value = 1; value <= PACKET_NUMBER_ENCODING_SIZE; value++) {
int packetHeaderSize = headerSizeWithoutTotalPackets + value
+ FIELD_NUMBER_ENCODING_SIZE;
int maxPayloadSize = maxSize - packetHeaderSize;
if (maxPayloadSize < 0) {
throw new BlePacketFactoryException("Packet header size too large.");
}
int totalPackets = (int) Math.ceil(payloadSize / (double) maxPayloadSize);
if (getEncodedSize(totalPackets) == value) {
return totalPackets;
}
}
loge(TAG, "Cannot get valid total packet number for message: messageId: "
+ messageId + ", payloadSize: " + payloadSize + ", maxSize: " + maxSize);
throw new BlePacketFactoryException("No valid total packet number.");
}
/**
* This method implements Protocol Buffers encoding algorithm.
*
* <p>Computes the number of bytes that would be needed to store a 32-bit variant.
*
* @param value the data that need to be encoded
* @return the size of the encoded data
* @see <a href="https://developers.google.com/protocol-buffers/docs/encoding#varints">
* Protocol Buffers Encoding</a>
*/
private static int getEncodedSize(int value) {
if (value < 0) {
return 10;
}
if ((value & (~0 << 7)) == 0) {
return 1;
}
if ((value & (~0 << 14)) == 0) {
return 2;
}
if ((value & (~0 << 21)) == 0) {
return 3;
}
if ((value & (~0 << 28)) == 0) {
return 4;
}
return 5;
}
private BlePacketFactory() {}
}