• 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     static class TypeValueEntry {
56         private final int mType;
57         private final byte[] mValue;
58 
TypeValueEntry(int type, byte[] value)59         TypeValueEntry(int type, byte[] value) {
60             mType = type;
61             mValue = value;
62         }
63 
getType()64         public int getType() {
65             return mType;
66         }
67 
getValue()68         public byte[] getValue() {
69             return mValue;
70         }
71     }
72 
73     // Helper method to extract bytes from byte array.
extractBytes(byte[] rawBytes, int start, int length)74     private static byte[] extractBytes(byte[] rawBytes, int start, int length) {
75         int remainingLength = rawBytes.length - start;
76         if (remainingLength < length) {
77             Log.w(TAG, "extractBytes() remaining length " + remainingLength
78                     + " is less than copying length " + length + ", array length is "
79                     + rawBytes.length + " start is " + start);
80             return null;
81         }
82         byte[] bytes = new byte[length];
83         System.arraycopy(rawBytes, start, bytes, 0, length);
84         return bytes;
85     }
86 
87     /**
88      * Parse Length Value Entry from raw bytes
89      *
90      * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
91      *
92      * @param rawBytes raw bytes of Length-Value-Entry array
93      * @hide
94      */
parseLengthTypeValueBytes(byte[] rawBytes)95     public static List<TypeValueEntry> parseLengthTypeValueBytes(byte[] rawBytes) {
96         if (rawBytes == null) {
97             return Collections.emptyList();
98         }
99         if (rawBytes.length == 0) {
100             return Collections.emptyList();
101         }
102 
103         int currentPos = 0;
104         List<TypeValueEntry> result = new ArrayList<>();
105         while (currentPos < rawBytes.length) {
106             // length is unsigned int.
107             int length = rawBytes[currentPos] & 0xFF;
108             if (length == 0) {
109                 break;
110             }
111             currentPos++;
112             if (currentPos >= rawBytes.length) {
113                 Log.w(TAG, "parseLtv() no type and value after length, rawBytes length = "
114                         + rawBytes.length + ", currentPost = " + currentPos);
115                 break;
116             }
117             // Note the length includes the length of the field type itself.
118             int dataLength = length - 1;
119             // fieldType is unsigned int.
120             int type = rawBytes[currentPos] & 0xFF;
121             currentPos++;
122             if (currentPos >= rawBytes.length) {
123                 Log.w(TAG, "parseLtv() no value after length, rawBytes length = "
124                         + rawBytes.length + ", currentPost = " + currentPos);
125                 break;
126             }
127             byte[] value = extractBytes(rawBytes, currentPos, dataLength);
128             if (value == null) {
129                 Log.w(TAG, "failed to extract bytes, currentPost = " + currentPos);
130                 break;
131             }
132             result.add(new TypeValueEntry(type, value));
133             currentPos += dataLength;
134         }
135         return result;
136     }
137 
138     /**
139      * Serialize type value entries to bytes
140      * @param typeValueEntries type value entries
141      * @return serialized type value entries on success, null on failure
142      */
serializeTypeValue(List<TypeValueEntry> typeValueEntries)143     public static byte[] serializeTypeValue(List<TypeValueEntry> typeValueEntries) {
144         // Calculate length
145         int length = 0;
146         for (TypeValueEntry entry : typeValueEntries) {
147             // 1 for length and 1 for type
148             length += 2;
149             if ((entry.getType() - (entry.getType() & 0xFF)) != 0) {
150                 Log.w(TAG, "serializeTypeValue() type " + entry.getType()
151                         + " is out of range of 0-0xFF");
152                 return null;
153             }
154             if (entry.getValue() == null) {
155                 Log.w(TAG, "serializeTypeValue() value is null");
156                 return null;
157             }
158             int lengthValue = entry.getValue().length + 1;
159             if ((lengthValue - (lengthValue & 0xFF)) != 0) {
160                 Log.w(TAG, "serializeTypeValue() entry length "  + entry.getValue().length
161                         + " is not in range of 0 to 254");
162                 return null;
163             }
164             length += entry.getValue().length;
165         }
166         byte[] result = new byte[length];
167         int currentPos = 0;
168         for (TypeValueEntry entry : typeValueEntries) {
169             result[currentPos] = (byte) ((entry.getValue().length + 1) & 0xFF);
170             currentPos++;
171             result[currentPos] = (byte) (entry.getType() & 0xFF);
172             currentPos++;
173             System.arraycopy(entry.getValue(), 0, result, currentPos, entry.getValue().length);
174             currentPos += entry.getValue().length;
175         }
176         return result;
177     }
178 
179     /**
180      * Convert an address to an obfuscate one for logging purpose
181      * @param address Mac address to be log
182      * @return Loggable mac address
183      */
toAnonymizedAddress(String address)184     public static String toAnonymizedAddress(String address) {
185         if (address == null || address.length() != 17) {
186             return null;
187         }
188         return "XX:XX:XX" + address.substring(8);
189     }
190 }
191