1 /*
2  * Copyright (C) 2021 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 android.bluetooth;
18 
19 import android.os.UserHandle;
20 import android.util.Log;
21 
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 
26 /** @hide */
27 public final class BluetoothUtils {
28     private static final String TAG = "BluetoothUtils";
29 
30     /** This utility class cannot be instantiated */
BluetoothUtils()31     private BluetoothUtils() {}
32 
33     /** Match with UserHandl.NULL but accessible inside bluetooth package */
34     public static final UserHandle USER_HANDLE_NULL = UserHandle.of(-10000);
35 
36     /** Class for Length-Value-Entry array parsing */
37     public static class TypeValueEntry {
38         private final int mType;
39         private final byte[] mValue;
40 
TypeValueEntry(int type, byte[] value)41         TypeValueEntry(int type, byte[] value) {
42             mType = type;
43             mValue = value;
44         }
45 
getType()46         public int getType() {
47             return mType;
48         }
49 
getValue()50         public byte[] getValue() {
51             return mValue;
52         }
53     }
54 
55     // Helper method to extract bytes from byte array.
extractBytes(byte[] rawBytes, int start, int length)56     private static byte[] extractBytes(byte[] rawBytes, int start, int length) {
57         int remainingLength = rawBytes.length - start;
58         if (remainingLength < length) {
59             Log.w(
60                     TAG,
61                     "extractBytes() remaining length "
62                             + remainingLength
63                             + " is less than copying length "
64                             + length
65                             + ", array length is "
66                             + rawBytes.length
67                             + " start is "
68                             + start);
69             return null;
70         }
71         byte[] bytes = new byte[length];
72         System.arraycopy(rawBytes, start, bytes, 0, length);
73         return bytes;
74     }
75 
76     /**
77      * Parse Length Value Entry from raw bytes
78      *
79      * <p>The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
80      *
81      * @param rawBytes raw bytes of Length-Value-Entry array
82      * @hide
83      */
84     @SuppressWarnings("MixedMutabilityReturnType") // TODO(b/314811467)
parseLengthTypeValueBytes(byte[] rawBytes)85     public static List<TypeValueEntry> parseLengthTypeValueBytes(byte[] rawBytes) {
86         if (rawBytes == null) {
87             return Collections.emptyList();
88         }
89         if (rawBytes.length == 0) {
90             return Collections.emptyList();
91         }
92 
93         int currentPos = 0;
94         List<TypeValueEntry> result = new ArrayList<>();
95         while (currentPos < rawBytes.length) {
96             // length is unsigned int.
97             int length = rawBytes[currentPos] & 0xFF;
98             if (length == 0) {
99                 break;
100             }
101             currentPos++;
102             if (currentPos >= rawBytes.length) {
103                 Log.w(
104                         TAG,
105                         "parseLtv() no type and value after length, rawBytes length = "
106                                 + rawBytes.length
107                                 + ", currentPost = "
108                                 + currentPos);
109                 break;
110             }
111             // Note the length includes the length of the field type itself.
112             int dataLength = length - 1;
113             // fieldType is unsigned int.
114             int type = rawBytes[currentPos] & 0xFF;
115             currentPos++;
116             if (currentPos >= rawBytes.length) {
117                 Log.w(
118                         TAG,
119                         "parseLtv() no value after length, rawBytes length = "
120                                 + rawBytes.length
121                                 + ", currentPost = "
122                                 + currentPos);
123                 break;
124             }
125             byte[] value = extractBytes(rawBytes, currentPos, dataLength);
126             if (value == null) {
127                 Log.w(TAG, "failed to extract bytes, currentPost = " + currentPos);
128                 break;
129             }
130             result.add(new TypeValueEntry(type, value));
131             currentPos += dataLength;
132         }
133         return result;
134     }
135 
136     /**
137      * Serialize type value entries to bytes
138      *
139      * @param typeValueEntries type value entries
140      * @return serialized type value entries on success, null on failure
141      */
serializeTypeValue(List<TypeValueEntry> typeValueEntries)142     public static byte[] serializeTypeValue(List<TypeValueEntry> typeValueEntries) {
143         // Calculate length
144         int length = 0;
145         for (TypeValueEntry entry : typeValueEntries) {
146             // 1 for length and 1 for type
147             length += 2;
148             if ((entry.getType() - (entry.getType() & 0xFF)) != 0) {
149                 Log.w(
150                         TAG,
151                         "serializeTypeValue() type "
152                                 + entry.getType()
153                                 + " is out of range of 0-0xFF");
154                 return null;
155             }
156             if (entry.getValue() == null) {
157                 Log.w(TAG, "serializeTypeValue() value is null");
158                 return null;
159             }
160             int lengthValue = entry.getValue().length + 1;
161             if ((lengthValue - (lengthValue & 0xFF)) != 0) {
162                 Log.w(
163                         TAG,
164                         "serializeTypeValue() entry length "
165                                 + entry.getValue().length
166                                 + " is not in range of 0 to 254");
167                 return null;
168             }
169             length += entry.getValue().length;
170         }
171         byte[] result = new byte[length];
172         int currentPos = 0;
173         for (TypeValueEntry entry : typeValueEntries) {
174             result[currentPos] = (byte) ((entry.getValue().length + 1) & 0xFF);
175             currentPos++;
176             result[currentPos] = (byte) (entry.getType() & 0xFF);
177             currentPos++;
178             System.arraycopy(entry.getValue(), 0, result, currentPos, entry.getValue().length);
179             currentPos += entry.getValue().length;
180         }
181         return result;
182     }
183 
184     /**
185      * Convert an address to an obfuscate one for logging purpose
186      *
187      * @param address Mac address to be log
188      * @return Loggable mac address
189      */
toAnonymizedAddress(String address)190     public static String toAnonymizedAddress(String address) {
191         if (address == null || address.length() != 17) {
192             return null;
193         }
194         return "XX:XX:XX:XX" + address.substring(11);
195     }
196 }
197