blob: 43d0da9ac0ab153a0e3f8345be13573963bf79a0 [file] [log] [blame]
William Escandeb86023d2021-12-16 16:07:55 +01001/*
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
17package android.bluetooth;
18
William Escandebf5812e2022-01-28 22:38:08 +010019import android.os.UserHandle;
Jack He0c6507d2022-03-09 22:16:10 -080020import android.util.Log;
William Escandebf5812e2022-01-28 22:38:08 +010021
William Escandeb86023d2021-12-16 16:07:55 +010022import java.time.Duration;
Jack He0c6507d2022-03-09 22:16:10 -080023import java.util.ArrayList;
24import java.util.Collections;
25import java.util.List;
William Escandeb86023d2021-12-16 16:07:55 +010026
27/**
28 * {@hide}
29 */
30public final class BluetoothUtils {
Jack He0c6507d2022-03-09 22:16:10 -080031 private static final String TAG = "BluetoothUtils";
32
William Escandeb86023d2021-12-16 16:07:55 +010033 /**
34 * This utility class cannot be instantiated
35 */
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 */
46 static Duration getSyncTimeout() {
47 return SYNC_CALLS_TIMEOUT;
48 }
William Escandebf5812e2022-01-28 22:38:08 +010049
50 /**
51 * Match with UserHandl.NULL but accessible inside bluetooth package
52 */
53 public static final UserHandle USER_HANDLE_NULL = UserHandle.of(-10000);
Jack He0c6507d2022-03-09 22:16:10 -080054
55 static class TypeValueEntry {
56 private final int mType;
57 private final byte[] mValue;
58
59 TypeValueEntry(int type, byte[] value) {
60 mType = type;
61 mValue = value;
62 }
63
64 public int getType() {
65 return mType;
66 }
67
68 public byte[] getValue() {
69 return mValue;
70 }
71 }
72
73 // Helper method to extract bytes from byte array.
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 */
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 */
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 }
William Escandee5975fe2022-06-21 16:52:06 -0700178
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 */
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 }
William Escandeb86023d2021-12-16 16:07:55 +0100190}