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 android.os.Build.VERSION; 20 21 import com.android.internal.annotations.VisibleForTesting; 22 import com.android.libraries.testing.deviceshadower.internal.utils.MacAddressGenerator; 23 24 import java.io.FileDescriptor; 25 import java.io.IOException; 26 import java.nio.ByteBuffer; 27 import java.nio.ByteOrder; 28 import java.util.Map; 29 import java.util.concurrent.BlockingQueue; 30 import java.util.concurrent.ConcurrentHashMap; 31 import java.util.concurrent.CountDownLatch; 32 import java.util.concurrent.LinkedBlockingQueue; 33 34 /** 35 * Encapsulate page scan operations -- handle connection establishment between Bluetooth devices. 36 */ 37 public class PageScanHandler { 38 39 private static final ConnectionRequest REQUEST_SERVER_SOCKET_CLOSE = new ConnectionRequest(); 40 41 private static PageScanHandler sInstance = null; 42 getInstance()43 public static synchronized PageScanHandler getInstance() { 44 if (sInstance == null) { 45 sInstance = new PageScanHandler(); 46 } 47 return sInstance; 48 } 49 reset()50 public static synchronized void reset() { 51 sInstance = null; 52 } 53 54 // use FileDescriptor to identify incoming data before socket is connected. 55 private final Map<FileDescriptor, BlockingQueue<Integer>> mIncomingDataMap; 56 // map a server socket fd to a connection request queue 57 private final Map<FileDescriptor, BlockingQueue<ConnectionRequest>> mConnectionRequests; 58 // map a fd on client side to a fd of BluetoothSocket(not BluetoothServerSocket) on server side 59 private final Map<FileDescriptor, FileDescriptor> mClientServerFdMap; 60 // map a client fd to a connection request so the client socket can finish the pending 61 // connection 62 private final Map<FileDescriptor, ConnectionRequest> mPendingConnections; 63 PageScanHandler()64 private PageScanHandler() { 65 mIncomingDataMap = new ConcurrentHashMap<>(); 66 mConnectionRequests = new ConcurrentHashMap<>(); 67 mClientServerFdMap = new ConcurrentHashMap<>(); 68 mPendingConnections = new ConcurrentHashMap<>(); 69 } 70 postConnectionRequest(FileDescriptor serverSocketFd, ConnectionRequest request)71 public void postConnectionRequest(FileDescriptor serverSocketFd, ConnectionRequest request) 72 throws InterruptedException { 73 // used by the returning socket on server-side 74 FileDescriptor fd = FileDescriptorFactory.getInstance() 75 .createFileDescriptor(request.mServerAddress); 76 mClientServerFdMap.put(request.mClientFd, fd); 77 BlockingQueue<ConnectionRequest> requests = mConnectionRequests.get(serverSocketFd); 78 requests.put(request); 79 mPendingConnections.put(request.mClientFd, request); 80 } 81 addServerSocket(FileDescriptor serverSocketFd)82 public void addServerSocket(FileDescriptor serverSocketFd) { 83 mConnectionRequests.put(serverSocketFd, new LinkedBlockingQueue<ConnectionRequest>()); 84 } 85 getServerFd(FileDescriptor clientFd)86 public FileDescriptor getServerFd(FileDescriptor clientFd) { 87 return mClientServerFdMap.get(clientFd); 88 } 89 90 // TODO(b/79994182): see go/objecttostring-lsc 91 @SuppressWarnings("ObjectToString") processNextConnectionRequest(FileDescriptor serverSocketFd)92 public FileDescriptor processNextConnectionRequest(FileDescriptor serverSocketFd) 93 throws IOException, InterruptedException { 94 ConnectionRequest request = mConnectionRequests.get(serverSocketFd).take(); 95 if (request == REQUEST_SERVER_SOCKET_CLOSE) { 96 // TODO(b/79994182): FileDescriptor does not implement toString() in serverSocketFd 97 throw new IOException("Server socket is closed. fd: " + serverSocketFd); 98 } 99 writeInitialConnectionInfo(serverSocketFd, request.mClientAddress, request.mPort); 100 return request.mClientFd; 101 } 102 waitForConnectionEstablished(FileDescriptor clientFd)103 public void waitForConnectionEstablished(FileDescriptor clientFd) throws InterruptedException { 104 ConnectionRequest request = mPendingConnections.get(clientFd); 105 if (request != null) { 106 request.mCountDownLatch.await(); 107 } 108 } 109 finishPendingConnection(FileDescriptor clientFd)110 public void finishPendingConnection(FileDescriptor clientFd) { 111 ConnectionRequest request = mPendingConnections.get(clientFd); 112 if (request != null) { 113 request.mCountDownLatch.countDown(); 114 } 115 } 116 cancelServerSocket(FileDescriptor serverSocketFd)117 public void cancelServerSocket(FileDescriptor serverSocketFd) throws InterruptedException { 118 mConnectionRequests.get(serverSocketFd).put(REQUEST_SERVER_SOCKET_CLOSE); 119 } 120 writeInitialConnectionInfo(FileDescriptor fd, String address, int port)121 public void writeInitialConnectionInfo(FileDescriptor fd, String address, int port) 122 throws InterruptedException { 123 for (byte b : initialConnectionInfo(address, port)) { 124 write(fd, Integer.valueOf(b)); 125 } 126 } 127 writePort(FileDescriptor fd, int port)128 public void writePort(FileDescriptor fd, int port) throws InterruptedException { 129 byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(port).array(); 130 for (byte b : bytes) { 131 write(fd, Integer.valueOf(b)); 132 } 133 } 134 write(FileDescriptor fd, int data)135 public void write(FileDescriptor fd, int data) throws InterruptedException { 136 BlockingQueue<Integer> incomingData = mIncomingDataMap.get(fd); 137 if (incomingData == null) { 138 synchronized (mIncomingDataMap) { 139 incomingData = mIncomingDataMap.get(fd); 140 if (incomingData == null) { 141 incomingData = new LinkedBlockingQueue<Integer>(); 142 mIncomingDataMap.put(fd, incomingData); 143 } 144 } 145 } 146 incomingData.put(data); 147 } 148 read(FileDescriptor fd)149 public int read(FileDescriptor fd) throws InterruptedException { 150 return mIncomingDataMap.get(fd).take(); 151 } 152 153 /** 154 * A connection request from a {@link android.bluetooth.BluetoothSocket}. 155 */ 156 @VisibleForTesting 157 public static class ConnectionRequest { 158 159 final FileDescriptor mClientFd; 160 final String mClientAddress; 161 final String mServerAddress; 162 final int mPort; 163 final CountDownLatch mCountDownLatch; // block server socket until connection established 164 ConnectionRequest(FileDescriptor fd, String clientAddress, String serverAddress, int port)165 public ConnectionRequest(FileDescriptor fd, String clientAddress, String serverAddress, 166 int port) { 167 mClientFd = fd; 168 this.mClientAddress = clientAddress; 169 this.mServerAddress = serverAddress; 170 this.mPort = port; 171 mCountDownLatch = new CountDownLatch(1); 172 } 173 ConnectionRequest()174 private ConnectionRequest() { 175 mClientFd = null; 176 mClientAddress = null; 177 mServerAddress = null; 178 mPort = -1; 179 mCountDownLatch = new CountDownLatch(0); 180 } 181 } 182 initialConnectionInfo(String addr, int port)183 private static byte[] initialConnectionInfo(String addr, int port) { 184 byte[] mac = MacAddressGenerator.convertStringMacAddress(addr); 185 int channel = port; 186 int status = 0; 187 188 if (VERSION.SDK_INT < 23) { 189 byte[] signal = new byte[16]; 190 short signalSize = 16; 191 ByteBuffer buffer = ByteBuffer.wrap(signal); 192 buffer.order(ByteOrder.LITTLE_ENDIAN) 193 .putShort(signalSize) 194 .put(mac) 195 .putInt(channel) 196 .putInt(status); 197 return buffer.array(); 198 } else { 199 byte[] signal = new byte[20]; 200 short signalSize = 20; 201 short maxTxPacketSize = 10000; 202 short maxRxPacketSize = 10000; 203 ByteBuffer buffer = ByteBuffer.wrap(signal); 204 buffer.order(ByteOrder.LITTLE_ENDIAN) 205 .putShort(signalSize) 206 .put(mac) 207 .putInt(channel) 208 .putInt(status) 209 .putShort(maxTxPacketSize) 210 .putShort(maxRxPacketSize); 211 return buffer.array(); 212 } 213 } 214 } 215