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