1 /* 2 * Copyright 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 com.android.libraries.testing.deviceshadower.internal.bluetooth.connection; 18 19 import com.android.libraries.testing.deviceshadower.internal.utils.Logger; 20 21 import com.google.common.base.Preconditions; 22 import com.google.common.collect.Sets; 23 24 import java.io.FileDescriptor; 25 import java.util.Iterator; 26 import java.util.Map; 27 import java.util.Set; 28 import java.util.UUID; 29 import java.util.concurrent.ConcurrentHashMap; 30 31 /** 32 * Encapsulates SDP operations including creating service record and allocating channel. 33 * <p>Listen on port and connect on port are not supported. </p> 34 */ 35 public class SdpHandler { 36 37 // intended to use "RfcommDelegate" 38 private static final Logger LOGGER = Logger.create("RfcommDelegate"); 39 40 private final Object mLock; 41 private final String mAddress; 42 private final Map<UUID, ServiceRecord> mServiceRecords; 43 private final Map<FileDescriptor, UUID> mFdUuidMap; 44 private final Set<Integer> mAvailablePortPool; 45 private final Set<Integer> mInUsePortPool; 46 SdpHandler(String address)47 public SdpHandler(String address) { 48 mLock = new Object(); 49 this.mAddress = address; 50 mServiceRecords = new ConcurrentHashMap<>(); 51 mFdUuidMap = new ConcurrentHashMap<>(); 52 mAvailablePortPool = Sets.newConcurrentHashSet(); 53 mInUsePortPool = Sets.newConcurrentHashSet(); 54 // 1 to 30 are valid RFCOMM port 55 for (int i = 1; i <= 30; i++) { 56 mAvailablePortPool.add(i); 57 } 58 } 59 createServiceRecord(UUID uuid, String serviceName)60 public ServiceRecord createServiceRecord(UUID uuid, String serviceName) { 61 Preconditions.checkNotNull(uuid); 62 LOGGER.d(String.format("Address %s: createServiceRecord with uuid %s", mAddress, uuid)); 63 if (isUuidRegistered(uuid) || !checkChannelAvailability()) { 64 return null; 65 } 66 synchronized (mLock) { 67 // ensure uuid is not registered and there's available channel 68 if (isUuidRegistered(uuid) || !checkChannelAvailability()) { 69 return null; 70 } 71 Iterator<Integer> available = mAvailablePortPool.iterator(); 72 int port = available.next(); 73 mAvailablePortPool.remove(port); 74 mInUsePortPool.add(port); 75 ServiceRecord record = new ServiceRecord(mAddress, serviceName, port); 76 mServiceRecords.put(uuid, record); 77 mFdUuidMap.put(record.mServerSocketFd, uuid); 78 PageScanHandler.getInstance().addServerSocket(record.mServerSocketFd); 79 return record; 80 } 81 } 82 removeServiceRecord(UUID uuid)83 public void removeServiceRecord(UUID uuid) { 84 Preconditions.checkNotNull(uuid); 85 LOGGER.d(String.format("Address %s: removeServiceRecord with uuid %s", mAddress, uuid)); 86 if (!isUuidRegistered(uuid)) { 87 return; 88 } 89 synchronized (mLock) { 90 if (!isUuidRegistered(uuid)) { 91 return; 92 } 93 ServiceRecord record = mServiceRecords.get(uuid); 94 mServiceRecords.remove(uuid); 95 mInUsePortPool.remove(record.mPort); 96 mAvailablePortPool.add(record.mPort); 97 mFdUuidMap.remove(record.mServerSocketFd); 98 } 99 } 100 lookupChannel(UUID uuid)101 public ServiceRecord lookupChannel(UUID uuid) { 102 ServiceRecord record = mServiceRecords.get(uuid); 103 if (record == null) { 104 LOGGER.e(String.format("Address %s: uuid %s not registered.", mAddress, uuid)); 105 } 106 return record; 107 } 108 getUuid(FileDescriptor serverSocketFd)109 public UUID getUuid(FileDescriptor serverSocketFd) { 110 return mFdUuidMap.get(serverSocketFd); 111 } 112 isUuidRegistered(UUID uuid)113 private boolean isUuidRegistered(UUID uuid) { 114 if (mServiceRecords.containsKey(uuid)) { 115 LOGGER.d(String.format("Address %s: Uuid %s in use.", mAddress, uuid)); 116 return true; 117 } 118 LOGGER.d(String.format("Address %s: Uuid %s not registered.", mAddress, uuid)); 119 return false; 120 } 121 checkChannelAvailability()122 private boolean checkChannelAvailability() { 123 if (mAvailablePortPool.isEmpty()) { 124 LOGGER.e(String.format("Address %s: No available channel.", mAddress)); 125 return false; 126 } 127 return true; 128 } 129 130 static class ServiceRecord { 131 132 final FileDescriptor mServerSocketFd; 133 final String mServiceName; 134 final int mPort; 135 ServiceRecord(String address, String serviceName, int port)136 ServiceRecord(String address, String serviceName, int port) { 137 mServerSocketFd = FileDescriptorFactory.getInstance().createFileDescriptor(address); 138 this.mServiceName = serviceName; 139 this.mPort = port; 140 } 141 } 142 } 143