• 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 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