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