1 /* 2 * Copyright 2024 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 com.android.server.ranging.cs; 18 19 import android.ranging.ble.cs.BleCsRangingCapabilities; 20 21 import com.android.server.ranging.RangingTechnology; 22 import com.android.server.ranging.RangingUtils.Conversions; 23 import com.android.server.ranging.cs.CsOobConfig.CsSecurityType; 24 import com.android.server.ranging.oob.TechnologyHeader; 25 26 import com.google.auto.value.AutoValue; 27 import com.google.common.collect.ImmutableList; 28 29 import java.nio.ByteBuffer; 30 import java.util.Arrays; 31 32 /** Capability data for CS sent as part of CapabilityResponseMessage. */ 33 @AutoValue 34 public abstract class CsOobCapabilities { 35 36 /** Size in bytes of all properties when serialized. */ 37 private static final int EXPECTED_SIZE_BYTES = 9; 38 39 // Size in bytes of properties for serialization/deserialization. 40 private static final int SECURITY_TYPE_SIZE = 1; 41 private static final int BLUETOOTH_ADDRESS_SIZE = 6; 42 43 private static final int SECURITY_TYPE_SHIFT = 0; 44 45 /** Returns the size of the object in bytes when serialized. */ getSize()46 public static int getSize() { 47 return EXPECTED_SIZE_BYTES; 48 } 49 50 /** 51 * Parses the given byte array and returns {@link CsOobCapabilities} object. Throws {@link 52 * IllegalArgumentException} on invalid input. 53 */ parseBytes(byte[] capabilitiesBytes)54 public static CsOobCapabilities parseBytes(byte[] capabilitiesBytes) { 55 TechnologyHeader header = TechnologyHeader.parseBytes(capabilitiesBytes); 56 57 if (capabilitiesBytes.length < EXPECTED_SIZE_BYTES) { 58 throw new IllegalArgumentException( 59 String.format( 60 "CsOobCapabilities size is %d, expected at least %d", 61 capabilitiesBytes.length, EXPECTED_SIZE_BYTES)); 62 } 63 64 if (capabilitiesBytes.length < header.getSize()) { 65 throw new IllegalArgumentException( 66 String.format( 67 "CsOobCapabilities header size field is %d, but the size of the array" 68 + " is" 69 + " %d", 70 header.getSize(), capabilitiesBytes.length)); 71 } 72 73 if (header.getRangingTechnology() != RangingTechnology.CS) { 74 throw new IllegalArgumentException( 75 String.format( 76 "CsOobCapabilities header technology field is %s, expected %s", 77 header.getRangingTechnology(), RangingTechnology.CS)); 78 } 79 80 int parseCursor = header.getHeaderSize(); 81 82 // Supported security type 83 ImmutableList<CsSecurityType> securityTypes = 84 Conversions.byteArrayToIntList( 85 Arrays.copyOfRange( 86 capabilitiesBytes, parseCursor, 87 parseCursor + SECURITY_TYPE_SIZE), 88 SECURITY_TYPE_SHIFT) 89 .stream() 90 .map(CsSecurityType::fromValue) 91 .collect(ImmutableList.toImmutableList()); 92 parseCursor += SECURITY_TYPE_SIZE; 93 94 // CS Address 95 String bluetoothAddress = 96 Conversions.macAddressToString( 97 Arrays.copyOfRange( 98 capabilitiesBytes, parseCursor, 99 parseCursor + BLUETOOTH_ADDRESS_SIZE)); 100 parseCursor += BLUETOOTH_ADDRESS_SIZE; 101 102 return CsOobCapabilities.builder() 103 .setSupportedSecurityTypes(securityTypes) 104 .setBluetoothAddress(bluetoothAddress) 105 .build(); 106 } 107 108 /** Serializes this {@link CsOobCapabilities} object to bytes. */ toBytes()109 public final byte[] toBytes() { 110 ByteBuffer byteBuffer = ByteBuffer.allocate(EXPECTED_SIZE_BYTES); 111 byteBuffer 112 .put(RangingTechnology.CS.toByte()) 113 .put((byte) EXPECTED_SIZE_BYTES) 114 .put( 115 Conversions.intListToByteArrayBitmap( 116 getSupportedSecurityTypes().stream() 117 .map(CsSecurityType::getValue) 118 .collect(ImmutableList.toImmutableList()), 119 SECURITY_TYPE_SIZE, 120 SECURITY_TYPE_SHIFT)) 121 .put(Conversions.macAddressToBytes(getBluetoothAddress())); 122 123 return byteBuffer.array(); 124 } 125 fromRangingCapabilities( BleCsRangingCapabilities capabilities )126 public static CsOobCapabilities fromRangingCapabilities( 127 BleCsRangingCapabilities capabilities 128 ) { 129 return CsOobCapabilities.builder() 130 .setBluetoothAddress(capabilities.getBluetoothAddress()) 131 .setSupportedSecurityTypes(capabilities.getSupportedSecurityLevels().stream() 132 .map(CsSecurityType.SECURITY_TYPES::get) 133 .collect(ImmutableList.toImmutableList())) 134 .build(); 135 } 136 137 /** Returns the security type for CS. */ getSupportedSecurityTypes()138 public abstract ImmutableList<CsSecurityType> getSupportedSecurityTypes(); 139 140 /** Returns the Bluetooth address of the device. */ getBluetoothAddress()141 public abstract String getBluetoothAddress(); 142 143 /** Returns a builder for {@link CsOobCapabilities}. */ builder()144 public static Builder builder() { 145 return new AutoValue_CsOobCapabilities.Builder(); 146 } 147 148 /** Builder for {@link CsOobCapabilities}. */ 149 @AutoValue.Builder 150 public abstract static class Builder { 151 setSupportedSecurityTypes( ImmutableList<CsSecurityType> securityTypes)152 public abstract Builder setSupportedSecurityTypes( 153 ImmutableList<CsSecurityType> securityTypes); 154 setBluetoothAddress(String bluetoothAddress)155 public abstract Builder setBluetoothAddress(String bluetoothAddress); 156 autoBuild()157 public abstract CsOobCapabilities autoBuild(); 158 build()159 public CsOobCapabilities build() { 160 CsOobCapabilities csCapabilities = autoBuild(); 161 // Validate Bluetooth Address, will throw if invalid. 162 var unused = Conversions.macAddressToBytes(csCapabilities.getBluetoothAddress()); 163 return csCapabilities; 164 } 165 } 166 }