• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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