197 lines
6.8 KiB
Java
197 lines
6.8 KiB
Java
/*
|
|
* Copyright (C) 2021 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.bluetooth;
|
|
|
|
import android.os.UserHandle;
|
|
import android.util.Log;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
|
|
/** @hide */
|
|
public final class BluetoothUtils {
|
|
private static final String TAG = "BluetoothUtils";
|
|
|
|
/** This utility class cannot be instantiated */
|
|
private BluetoothUtils() {}
|
|
|
|
/** Match with UserHandl.NULL but accessible inside bluetooth package */
|
|
public static final UserHandle USER_HANDLE_NULL = UserHandle.of(-10000);
|
|
|
|
/** Class for Length-Value-Entry array parsing */
|
|
public static class TypeValueEntry {
|
|
private final int mType;
|
|
private final byte[] mValue;
|
|
|
|
TypeValueEntry(int type, byte[] value) {
|
|
mType = type;
|
|
mValue = value;
|
|
}
|
|
|
|
public int getType() {
|
|
return mType;
|
|
}
|
|
|
|
public byte[] getValue() {
|
|
return mValue;
|
|
}
|
|
}
|
|
|
|
// Helper method to extract bytes from byte array.
|
|
private static byte[] extractBytes(byte[] rawBytes, int start, int length) {
|
|
int remainingLength = rawBytes.length - start;
|
|
if (remainingLength < length) {
|
|
Log.w(
|
|
TAG,
|
|
"extractBytes() remaining length "
|
|
+ remainingLength
|
|
+ " is less than copying length "
|
|
+ length
|
|
+ ", array length is "
|
|
+ rawBytes.length
|
|
+ " start is "
|
|
+ start);
|
|
return null;
|
|
}
|
|
byte[] bytes = new byte[length];
|
|
System.arraycopy(rawBytes, start, bytes, 0, length);
|
|
return bytes;
|
|
}
|
|
|
|
/**
|
|
* Parse Length Value Entry from raw bytes
|
|
*
|
|
* <p>The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
|
|
*
|
|
* @param rawBytes raw bytes of Length-Value-Entry array
|
|
* @hide
|
|
*/
|
|
@SuppressWarnings("MixedMutabilityReturnType") // TODO(b/314811467)
|
|
public static List<TypeValueEntry> parseLengthTypeValueBytes(byte[] rawBytes) {
|
|
if (rawBytes == null) {
|
|
return Collections.emptyList();
|
|
}
|
|
if (rawBytes.length == 0) {
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
int currentPos = 0;
|
|
List<TypeValueEntry> result = new ArrayList<>();
|
|
while (currentPos < rawBytes.length) {
|
|
// length is unsigned int.
|
|
int length = rawBytes[currentPos] & 0xFF;
|
|
if (length == 0) {
|
|
break;
|
|
}
|
|
currentPos++;
|
|
if (currentPos >= rawBytes.length) {
|
|
Log.w(
|
|
TAG,
|
|
"parseLtv() no type and value after length, rawBytes length = "
|
|
+ rawBytes.length
|
|
+ ", currentPost = "
|
|
+ currentPos);
|
|
break;
|
|
}
|
|
// Note the length includes the length of the field type itself.
|
|
int dataLength = length - 1;
|
|
// fieldType is unsigned int.
|
|
int type = rawBytes[currentPos] & 0xFF;
|
|
currentPos++;
|
|
if (currentPos >= rawBytes.length) {
|
|
Log.w(
|
|
TAG,
|
|
"parseLtv() no value after length, rawBytes length = "
|
|
+ rawBytes.length
|
|
+ ", currentPost = "
|
|
+ currentPos);
|
|
break;
|
|
}
|
|
byte[] value = extractBytes(rawBytes, currentPos, dataLength);
|
|
if (value == null) {
|
|
Log.w(TAG, "failed to extract bytes, currentPost = " + currentPos);
|
|
break;
|
|
}
|
|
result.add(new TypeValueEntry(type, value));
|
|
currentPos += dataLength;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Serialize type value entries to bytes
|
|
*
|
|
* @param typeValueEntries type value entries
|
|
* @return serialized type value entries on success, null on failure
|
|
*/
|
|
public static byte[] serializeTypeValue(List<TypeValueEntry> typeValueEntries) {
|
|
// Calculate length
|
|
int length = 0;
|
|
for (TypeValueEntry entry : typeValueEntries) {
|
|
// 1 for length and 1 for type
|
|
length += 2;
|
|
if ((entry.getType() - (entry.getType() & 0xFF)) != 0) {
|
|
Log.w(
|
|
TAG,
|
|
"serializeTypeValue() type "
|
|
+ entry.getType()
|
|
+ " is out of range of 0-0xFF");
|
|
return null;
|
|
}
|
|
if (entry.getValue() == null) {
|
|
Log.w(TAG, "serializeTypeValue() value is null");
|
|
return null;
|
|
}
|
|
int lengthValue = entry.getValue().length + 1;
|
|
if ((lengthValue - (lengthValue & 0xFF)) != 0) {
|
|
Log.w(
|
|
TAG,
|
|
"serializeTypeValue() entry length "
|
|
+ entry.getValue().length
|
|
+ " is not in range of 0 to 254");
|
|
return null;
|
|
}
|
|
length += entry.getValue().length;
|
|
}
|
|
byte[] result = new byte[length];
|
|
int currentPos = 0;
|
|
for (TypeValueEntry entry : typeValueEntries) {
|
|
result[currentPos] = (byte) ((entry.getValue().length + 1) & 0xFF);
|
|
currentPos++;
|
|
result[currentPos] = (byte) (entry.getType() & 0xFF);
|
|
currentPos++;
|
|
System.arraycopy(entry.getValue(), 0, result, currentPos, entry.getValue().length);
|
|
currentPos += entry.getValue().length;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Convert an address to an obfuscate one for logging purpose
|
|
*
|
|
* @param address Mac address to be log
|
|
* @return Loggable mac address
|
|
*/
|
|
public static String toAnonymizedAddress(String address) {
|
|
if (address == null || address.length() != 17) {
|
|
return null;
|
|
}
|
|
return "XX:XX:XX:XX" + address.substring(11);
|
|
}
|
|
}
|