1 /******************************************************************************
2  *
3  *  Copyright 2018 The Android Open Source Project
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at:
8  *
9  *  http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  ******************************************************************************/
18 
19 #include "connection_manager.h"
20 
21 #include <base/functional/bind.h>
22 #include <base/functional/callback.h>
23 #include <base/location.h>
24 #include <bluetooth/log.h>
25 
26 #include <map>
27 #include <memory>
28 #include <set>
29 
30 #include "main/shim/acl_api.h"
31 #include "main/shim/le_scanning_manager.h"
32 #include "os/logging/log_adapter.h"
33 #include "osi/include/alarm.h"
34 #include "stack/btm/btm_dev.h"
35 #include "stack/include/advertise_data_parser.h"
36 #include "stack/include/bt_types.h"
37 #include "stack/include/btm_ble_api.h"
38 #include "stack/include/btm_client_interface.h"
39 #include "stack/include/btm_log_history.h"
40 #include "stack/include/main_thread.h"
41 #include "types/raw_address.h"
42 
43 #define DIRECT_CONNECT_TIMEOUT (30 * 1000) /* 30 seconds */
44 
45 using namespace bluetooth;
46 
47 constexpr char kBtmLogTag[] = "TA";
48 
49 struct closure_data {
50   base::OnceClosure user_task;
51   base::Location posted_from;
52 };
53 
alarm_closure_cb(void * p)54 static void alarm_closure_cb(void* p) {
55   closure_data* data = (closure_data*)p;
56   log::verbose("executing timer scheduled at {}", data->posted_from.ToString());
57   std::move(data->user_task).Run();
58   delete data;
59 }
60 
61 // Periodic alarms are not supported, because we clean up data in callback
alarm_set_closure(const base::Location & posted_from,alarm_t * alarm,uint64_t interval_ms,base::OnceClosure user_task)62 static void alarm_set_closure(const base::Location& posted_from, alarm_t* alarm,
63                               uint64_t interval_ms, base::OnceClosure user_task) {
64   closure_data* data = new closure_data;
65   data->posted_from = posted_from;
66   data->user_task = std::move(user_task);
67   log::verbose("scheduling timer {}", data->posted_from.ToString());
68   alarm_set_on_mloop(alarm, interval_ms, alarm_closure_cb, data);
69 }
70 
71 using unique_alarm_ptr = std::unique_ptr<alarm_t, decltype(&alarm_free)>;
72 
73 namespace connection_manager {
74 
75 struct tAPPS_CONNECTING {
76   // ids of clients doing background connection to given device
77   std::set<tAPP_ID> doing_bg_conn;
78   std::set<tAPP_ID> doing_targeted_announcements_conn;
79   bool is_in_accept_list;
80 
81   // Apps trying to do direct connection.
82   std::map<tAPP_ID, unique_alarm_ptr> doing_direct_conn;
83 };
84 
85 namespace {
86 // Maps address to apps trying to connect to it
87 std::map<RawAddress, tAPPS_CONNECTING> bgconn_dev;
88 
num_of_targeted_announcements_users(void)89 int num_of_targeted_announcements_users(void) {
90   return std::count_if(bgconn_dev.begin(), bgconn_dev.end(), [](const auto& pair) {
91     return !pair.second.is_in_accept_list && !pair.second.doing_targeted_announcements_conn.empty();
92   });
93 }
94 
is_anyone_interested_to_use_accept_list(const std::map<RawAddress,tAPPS_CONNECTING>::iterator it)95 bool is_anyone_interested_to_use_accept_list(
96         const std::map<RawAddress, tAPPS_CONNECTING>::iterator it) {
97   if (!it->second.doing_targeted_announcements_conn.empty()) {
98     return !it->second.doing_direct_conn.empty();
99   }
100   return !it->second.doing_bg_conn.empty() || !it->second.doing_direct_conn.empty();
101 }
102 
is_anyone_connecting(const std::map<RawAddress,tAPPS_CONNECTING>::iterator it)103 bool is_anyone_connecting(const std::map<RawAddress, tAPPS_CONNECTING>::iterator it) {
104   return !it->second.doing_bg_conn.empty() || !it->second.doing_direct_conn.empty() ||
105          !it->second.doing_targeted_announcements_conn.empty();
106 }
107 
108 }  // namespace
109 
110 /** background connection device from the list. Returns pointer to the device
111  * record, or nullptr if not found */
get_apps_connecting_to(const RawAddress & address)112 std::set<tAPP_ID> get_apps_connecting_to(const RawAddress& address) {
113   log::debug("address={}", address);
114   auto it = bgconn_dev.find(address);
115   return (it != bgconn_dev.end()) ? it->second.doing_bg_conn : std::set<tAPP_ID>();
116 }
117 
IsTargetedAnnouncement(const uint8_t * p_eir,uint16_t eir_len)118 static bool IsTargetedAnnouncement(const uint8_t* p_eir, uint16_t eir_len) {
119   const uint8_t* p_service_data = p_eir;
120   uint8_t service_data_len = 0;
121 
122   while ((p_service_data = AdvertiseDataParser::GetFieldByType(
123                   p_service_data + service_data_len,
124                   eir_len - (p_service_data - p_eir) - service_data_len,
125                   BTM_BLE_AD_TYPE_SERVICE_DATA_TYPE, &service_data_len))) {
126     uint16_t uuid;
127     uint8_t announcement_type;
128     const uint8_t* p_tmp = p_service_data;
129 
130     if (service_data_len < 3) {
131       continue;
132     }
133 
134     STREAM_TO_UINT16(uuid, p_tmp);
135     log::debug("Found UUID 0x{:04x}", uuid);
136 
137     if (uuid != 0x184E && uuid != 0x1853) {
138       continue;
139     }
140 
141     STREAM_TO_UINT8(announcement_type, p_tmp);
142     log::debug("Found announcement_type 0x{:02x}", announcement_type);
143     if (announcement_type == 0x01) {
144       return true;
145     }
146   }
147   return false;
148 }
149 
150 static void schedule_direct_connect_add(uint8_t app_id, const RawAddress& address);
151 
target_announcement_observe_results_cb(tBTM_INQ_RESULTS * p_inq,const uint8_t * p_eir,uint16_t eir_len)152 static void target_announcement_observe_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir,
153                                                    uint16_t eir_len) {
154   auto addr = p_inq->remote_bd_addr;
155   auto it = bgconn_dev.find(addr);
156   if (it == bgconn_dev.end() || it->second.doing_targeted_announcements_conn.empty()) {
157     return;
158   }
159 
160   if (!IsTargetedAnnouncement(p_eir, eir_len)) {
161     log::debug("Not a targeted announcement for device {}", addr);
162     return;
163   }
164 
165   log::info("Found targeted announcement for device {}", addr);
166 
167   if (it->second.is_in_accept_list) {
168     log::info("Device {} is already connecting", addr);
169     return;
170   }
171 
172   if (get_btm_client_interface().peer.BTM_GetHCIConnHandle(addr, BT_TRANSPORT_LE) != 0xFFFF) {
173     log::debug("Device {} already connected", addr);
174     return;
175   }
176 
177   BTM_LogHistory(kBtmLogTag, addr, "Found TA from");
178 
179   /* Take fist app_id and use it for direct_connect */
180   auto app_id = *(it->second.doing_targeted_announcements_conn.begin());
181 
182   /* If scan is ongoing lets stop it */
183   do_in_main_thread(base::BindOnce(schedule_direct_connect_add, app_id, addr));
184 }
185 
target_announcements_filtering_set(bool enable)186 static void target_announcements_filtering_set(bool enable) {
187   log::debug("enable {}", enable);
188   BTM_LogHistory(kBtmLogTag, RawAddress::kEmpty, (enable ? "Start filtering" : "Stop filtering"));
189 
190   /* Safe to call as if there is no support for filtering, this call will be
191    * ignored. */
192   bluetooth::shim::set_target_announcements_filter(enable);
193   BTM_BleTargetAnnouncementObserve(enable, target_announcement_observe_results_cb);
194 }
195 
196 /** Add a device to the background connection list for targeted announcements.
197  * Returns
198  *   true if device added to the list, or already in list,
199  *   false otherwise
200  */
background_connect_targeted_announcement_add(tAPP_ID app_id,const RawAddress & address)201 bool background_connect_targeted_announcement_add(tAPP_ID app_id, const RawAddress& address) {
202   log::info("app_id={}, address={}", static_cast<int>(app_id), address);
203 
204   bool disable_accept_list = false;
205 
206   auto it = bgconn_dev.find(address);
207   if (it != bgconn_dev.end()) {
208     // check if filtering already enabled
209     if (it->second.doing_targeted_announcements_conn.count(app_id)) {
210       log::info("app_id={}, already doing targeted announcement filtering to address={}",
211                 static_cast<int>(app_id), address);
212       return true;
213     }
214 
215     bool targeted_filtering_enabled = !it->second.doing_targeted_announcements_conn.empty();
216 
217     // Check if connecting
218     if (!it->second.doing_direct_conn.empty()) {
219       log::info("app_id={}, address={}, already in direct connection", static_cast<int>(app_id),
220                 address);
221 
222     } else if (!targeted_filtering_enabled && !it->second.doing_bg_conn.empty()) {
223       // device is already in the acceptlist so we would have to remove it
224       log::info("already doing background connection to address={}. Need to disable it.", address);
225       disable_accept_list = true;
226     }
227   }
228 
229   if (disable_accept_list) {
230     bluetooth::shim::ACL_IgnoreLeConnectionFrom(BTM_Sec_GetAddressWithType(address));
231     bgconn_dev[address].is_in_accept_list = false;
232   }
233 
234   bgconn_dev[address].doing_targeted_announcements_conn.insert(app_id);
235   if (bgconn_dev[address].doing_targeted_announcements_conn.size() == 1) {
236     BTM_LogHistory(kBtmLogTag, address, "Allow connection from");
237   }
238 
239   if (num_of_targeted_announcements_users() == 1) {
240     target_announcements_filtering_set(true);
241   }
242 
243   return true;
244 }
245 
246 /** Add a device from the background connection list.  Returns true if device
247  * added to the list, or already in list, false otherwise */
background_connect_add(uint8_t app_id,const RawAddress & address)248 bool background_connect_add(uint8_t app_id, const RawAddress& address) {
249   log::debug("app_id={}, address={}", static_cast<int>(app_id), address);
250   auto it = bgconn_dev.find(address);
251   bool in_acceptlist = false;
252   bool is_targeted_announcement_enabled = false;
253   if (it != bgconn_dev.end()) {
254     // device already in the acceptlist, just add interested app to the list
255     if (it->second.doing_bg_conn.count(app_id)) {
256       log::debug("app_id={}, already doing background connection to address={}",
257                  static_cast<int>(app_id), address);
258       return true;
259     }
260 
261     // Already in acceptlist ?
262     if (it->second.is_in_accept_list) {
263       log::debug("app_id={}, address={}, already in accept list", static_cast<int>(app_id),
264                  address);
265       in_acceptlist = true;
266     } else {
267       is_targeted_announcement_enabled = !it->second.doing_targeted_announcements_conn.empty();
268     }
269   }
270 
271   if (!in_acceptlist) {
272     // the device is not in the acceptlist
273     if (is_targeted_announcement_enabled) {
274       log::debug("Targeted announcement enabled, do not add to AcceptList");
275     } else {
276       if (!bluetooth::shim::ACL_AcceptLeConnectionFrom(BTM_Sec_GetAddressWithType(address),
277                                                        false)) {
278         log::warn("Failed to add device {} to accept list for app {}", address,
279                   static_cast<int>(app_id));
280         return false;
281       }
282       bgconn_dev[address].is_in_accept_list = true;
283     }
284   }
285 
286   // create entry for address, and insert app_id.
287   // new tAPPS_CONNECTING will be default constructed if not exist
288   bgconn_dev[address].doing_bg_conn.insert(app_id);
289   return true;
290 }
291 
292 /** Removes all registrations for connection for given device.
293  * Returns true if anything was removed, false otherwise */
remove_unconditional(const RawAddress & address)294 bool remove_unconditional(const RawAddress& address) {
295   log::debug("address={}", address);
296   auto it = bgconn_dev.find(address);
297   if (it == bgconn_dev.end()) {
298     log::warn("address {} is not found", address);
299     return false;
300   }
301 
302   bluetooth::shim::ACL_IgnoreLeConnectionFrom(BTM_Sec_GetAddressWithType(address));
303   bgconn_dev.erase(it);
304   return true;
305 }
306 
307 /** Remove device from the background connection device list or listening to
308  * advertising list.  Returns true if device was on the list and was
309  * successfully removed */
background_connect_remove(uint8_t app_id,const RawAddress & address)310 bool background_connect_remove(uint8_t app_id, const RawAddress& address) {
311   log::debug("app_id={}, address={}", static_cast<int>(app_id), address);
312   auto it = bgconn_dev.find(address);
313   if (it == bgconn_dev.end()) {
314     log::warn("address {} is not found", address);
315     return false;
316   }
317 
318   bool accept_list_enabled = it->second.is_in_accept_list;
319   auto num_of_targeted_announcements_before_remove =
320           it->second.doing_targeted_announcements_conn.size();
321 
322   bool removed_from_bg_conn = (it->second.doing_bg_conn.erase(app_id) > 0);
323   bool removed_from_ta = (it->second.doing_targeted_announcements_conn.erase(app_id) > 0);
324   if (!removed_from_bg_conn && !removed_from_ta) {
325     log::warn("Failed to remove background connection app {} for address {}",
326               static_cast<int>(app_id), address);
327     return false;
328   }
329 
330   if (removed_from_ta && it->second.doing_targeted_announcements_conn.size() == 0) {
331     BTM_LogHistory(kBtmLogTag, address, "Ignore connection from");
332   }
333 
334   if (is_anyone_connecting(it)) {
335     log::debug("some device is still connecting, app_id={}, address={}", static_cast<int>(app_id),
336                address);
337     /* Check which method should be used now.*/
338     if (!accept_list_enabled) {
339       /* Accept list was not used */
340       if (!it->second.doing_targeted_announcements_conn.empty()) {
341         /* Keep using filtering */
342         log::debug("Keep using target announcement filtering");
343       } else if (!it->second.doing_bg_conn.empty()) {
344         if (!bluetooth::shim::ACL_AcceptLeConnectionFrom(BTM_Sec_GetAddressWithType(address),
345                                                          false)) {
346           log::warn("Could not re add device to accept list");
347         } else {
348           bgconn_dev[address].is_in_accept_list = true;
349         }
350       }
351     }
352     return true;
353   }
354 
355   bgconn_dev.erase(it);
356 
357   // no more apps interested - remove from accept list and delete record
358   if (accept_list_enabled) {
359     bluetooth::shim::ACL_IgnoreLeConnectionFrom(BTM_Sec_GetAddressWithType(address));
360     return true;
361   }
362 
363   if ((num_of_targeted_announcements_before_remove > 0) &&
364       num_of_targeted_announcements_users() == 0) {
365     target_announcements_filtering_set(true);
366   }
367 
368   return true;
369 }
370 
is_background_connection(const RawAddress & address)371 bool is_background_connection(const RawAddress& address) {
372   auto it = bgconn_dev.find(address);
373   if (it == bgconn_dev.end()) {
374     return false;
375   }
376   return it->second.is_in_accept_list;
377 }
378 
379 /** deregister all related background connetion device. */
on_app_deregistered(uint8_t app_id)380 void on_app_deregistered(uint8_t app_id) {
381   log::debug("app_id={}", static_cast<int>(app_id));
382   auto it = bgconn_dev.begin();
383   auto end = bgconn_dev.end();
384   /* update the BG conn device list */
385   while (it != end) {
386     it->second.doing_bg_conn.erase(app_id);
387 
388     it->second.doing_direct_conn.erase(app_id);
389 
390     if (is_anyone_connecting(it)) {
391       it++;
392       continue;
393     }
394 
395     bluetooth::shim::ACL_IgnoreLeConnectionFrom(BTM_Sec_GetAddressWithType(it->first));
396     it = bgconn_dev.erase(it);
397   }
398 }
399 
remove_all_clients_with_pending_connections(const RawAddress & address)400 static void remove_all_clients_with_pending_connections(const RawAddress& address) {
401   log::debug("address={}", address);
402   auto it = bgconn_dev.find(address);
403   while (it != bgconn_dev.end() && !it->second.doing_direct_conn.empty()) {
404     uint8_t app_id = it->second.doing_direct_conn.begin()->first;
405     direct_connect_remove(app_id, address);
406     it = bgconn_dev.find(address);
407   }
408 }
409 
on_connection_complete(const RawAddress & address)410 void on_connection_complete(const RawAddress& address) {
411   log::info("Le connection completed to device:{}", address);
412 
413   remove_all_clients_with_pending_connections(address);
414 }
415 
on_connection_timed_out_from_shim(const RawAddress & address)416 void on_connection_timed_out_from_shim(const RawAddress& address) {
417   log::info("Connection failed {}", address);
418   on_connection_timed_out(0x00, address);
419 }
420 
421 /** Reset bg device list. If called after controller reset, set |after_reset|
422  * to true, as there is no need to wipe controller acceptlist in this case. */
reset(bool after_reset)423 void reset(bool after_reset) {
424   bgconn_dev.clear();
425   if (!after_reset) {
426     target_announcements_filtering_set(false);
427     bluetooth::shim::ACL_IgnoreAllLeConnections();
428   }
429 }
430 
wl_direct_connect_timeout_cb(uint8_t app_id,const RawAddress & address)431 static void wl_direct_connect_timeout_cb(uint8_t app_id, const RawAddress& address) {
432   log::debug("app_id={}, address={}", static_cast<int>(app_id), address);
433   on_connection_timed_out(app_id, address);
434 
435   // TODO: this would free the timer, from within the timer callback, which is
436   // bad.
437   direct_connect_remove(app_id, address, true);
438 }
439 
find_in_device_record(const RawAddress & bd_addr,tBLE_BD_ADDR * address_with_type)440 static void find_in_device_record(const RawAddress& bd_addr, tBLE_BD_ADDR* address_with_type) {
441   const tBTM_SEC_DEV_REC* p_dev_rec = btm_find_dev(bd_addr);
442   if (p_dev_rec == nullptr) {
443     return;
444   }
445 
446   if (p_dev_rec->device_type & BT_DEVICE_TYPE_BLE) {
447     if (p_dev_rec->ble.identity_address_with_type.bda.IsEmpty()) {
448       *address_with_type = {.type = p_dev_rec->ble.AddressType(), .bda = bd_addr};
449       return;
450     }
451     *address_with_type = p_dev_rec->ble.identity_address_with_type;
452     return;
453   }
454   *address_with_type = {.type = BLE_ADDR_PUBLIC, .bda = bd_addr};
455   return;
456 }
457 
create_le_connection(uint8_t,const RawAddress & bd_addr,tBLE_ADDR_TYPE addr_type)458 bool create_le_connection(uint8_t /* id */, const RawAddress& bd_addr, tBLE_ADDR_TYPE addr_type) {
459   tBLE_BD_ADDR address_with_type{
460           .type = addr_type,
461           .bda = bd_addr,
462   };
463 
464   find_in_device_record(bd_addr, &address_with_type);
465 
466   log::debug("Creating le direct connection to:{} type:{} (initial type: {})", address_with_type,
467              AddressTypeText(address_with_type.type), AddressTypeText(addr_type));
468 
469   if (address_with_type.type == BLE_ADDR_ANONYMOUS) {
470     log::warn(
471             "Creating le direct connection to:{}, address type 'anonymous' is "
472             "invalid",
473             address_with_type);
474     return false;
475   }
476 
477   bluetooth::shim::ACL_AcceptLeConnectionFrom(address_with_type,
478                                               /* is_direct */ true);
479   return true;
480 }
481 
482 /** Add a device to the direct connection list. Returns true if device
483  * added to the list, false otherwise */
direct_connect_add(uint8_t app_id,const RawAddress & address)484 bool direct_connect_add(uint8_t app_id, const RawAddress& address) {
485   log::debug("app_id={}, address={}", static_cast<int>(app_id), address);
486   bool in_acceptlist = false;
487   auto it = bgconn_dev.find(address);
488   if (it != bgconn_dev.end()) {
489     // app already trying to connect to this particular device
490     if (it->second.doing_direct_conn.count(app_id)) {
491       log::info("direct connect attempt from app_id=0x{:x} already in progress", app_id);
492       return false;
493     }
494 
495     // are we already in the acceptlist ?
496     if (it->second.is_in_accept_list) {
497       log::warn("Background connection attempt already in progress app_id={:x}", app_id);
498       in_acceptlist = true;
499     }
500   }
501 
502   if (!in_acceptlist) {
503     if (!bluetooth::shim::ACL_AcceptLeConnectionFrom(BTM_Sec_GetAddressWithType(address), true)) {
504       // if we can't add to acceptlist, turn parameters back to slow.
505       log::warn("Unable to add le device to acceptlist");
506       return false;
507     }
508     bgconn_dev[address].is_in_accept_list = true;
509   }
510 
511   // Setup a timer
512   alarm_t* timeout = alarm_new("wl_conn_params_30s");
513   alarm_set_closure(FROM_HERE, timeout, DIRECT_CONNECT_TIMEOUT,
514                     base::BindOnce(&wl_direct_connect_timeout_cb, app_id, address));
515 
516   bgconn_dev[address].doing_direct_conn.emplace(app_id, unique_alarm_ptr(timeout, &alarm_free));
517 
518   return true;
519 }
520 
schedule_direct_connect_add(uint8_t app_id,const RawAddress & address)521 static void schedule_direct_connect_add(uint8_t app_id, const RawAddress& address) {
522   direct_connect_add(app_id, address);
523 }
524 
direct_connect_remove(uint8_t app_id,const RawAddress & address,bool connection_timeout)525 bool direct_connect_remove(uint8_t app_id, const RawAddress& address, bool connection_timeout) {
526   log::debug("app_id={}, address={}", static_cast<int>(app_id), address);
527   auto it = bgconn_dev.find(address);
528   if (it == bgconn_dev.end()) {
529     log::warn("Unable to find background connection to remove peer:{}", address);
530     return false;
531   }
532 
533   auto app_it = it->second.doing_direct_conn.find(app_id);
534   if (app_it == it->second.doing_direct_conn.end()) {
535     log::warn("Unable to find direct connection to remove peer:{}", address);
536     return false;
537   }
538 
539   /* Let see if the device was connected due to Target Announcements.*/
540   bool is_targeted_announcement_enabled = !it->second.doing_targeted_announcements_conn.empty();
541 
542   // this will free the alarm
543   it->second.doing_direct_conn.erase(app_it);
544 
545   if (is_anyone_interested_to_use_accept_list(it)) {
546     if (connection_timeout) {
547       /* In such case we need to add device back to allow list because,
548        * when connection timeout out, the lower layer removes device from
549        * the allow list.
550        */
551       if (!bluetooth::shim::ACL_AcceptLeConnectionFrom(BTM_Sec_GetAddressWithType(address),
552                                                        false)) {
553         log::warn("Failed to re-add device {} to accept list after connection timeout", address);
554       }
555     }
556     return true;
557   }
558 
559   // no more apps interested - remove from acceptlist
560   bluetooth::shim::ACL_IgnoreLeConnectionFrom(BTM_Sec_GetAddressWithType(address));
561 
562   if (!is_targeted_announcement_enabled) {
563     bgconn_dev.erase(it);
564   } else {
565     it->second.is_in_accept_list = false;
566   }
567 
568   return true;
569 }
570 
dump(int fd)571 void dump(int fd) {
572   dprintf(fd, "\nconnection_manager state:\n");
573   if (bgconn_dev.empty()) {
574     dprintf(fd, "\tno Low Energy connection attempts\n");
575     return;
576   }
577 
578   dprintf(fd, "\tdevices attempting connection: %d", (int)bgconn_dev.size());
579   for (const auto& entry : bgconn_dev) {
580     // TODO: confirm whether we need to replace this
581     dprintf(fd, "\n\t * %s:\t\tin_accept_list: %s\t cap_targeted_announcements: %s",
582             ADDRESS_TO_LOGGABLE_CSTR(entry.first),
583             entry.second.is_in_accept_list ? "true" : "false",
584             entry.second.doing_targeted_announcements_conn.empty() ? "false" : "true");
585 
586     if (!entry.second.doing_direct_conn.empty()) {
587       dprintf(fd, "\n\t\tapps doing direct connect: ");
588       for (const auto& id : entry.second.doing_direct_conn) {
589         dprintf(fd, "%d, ", id.first);
590       }
591     }
592 
593     if (!entry.second.doing_bg_conn.empty()) {
594       dprintf(fd, "\n\t\tapps doing background connect: ");
595       for (const auto& id : entry.second.doing_bg_conn) {
596         dprintf(fd, "%d, ", id);
597       }
598     }
599   }
600   dprintf(fd, "\n");
601 }
602 
603 }  // namespace connection_manager
604